import re
import os
import struct
import logging
from math import log as logfm
from socket import inet_aton
from pyroute2 import config
from pyroute2.common import size_suffixes
from pyroute2.common import time_suffixes
from pyroute2.common import rate_suffixes
from pyroute2.common import basestring
from pyroute2.netlink import nla
from pyroute2.netlink import nla_string

log = logging.getLogger(__name__)

LINKLAYER_UNSPEC = 0
LINKLAYER_ETHERNET = 1
LINKLAYER_ATM = 2

ATM_CELL_SIZE = 53
ATM_CELL_PAYLOAD = 48

TCA_ACT_MAX_PRIO = 32
TIME_UNITS_PER_SEC = 1000000

try:
    with open('/proc/net/psched', 'r') as psched:
        [t2us,
         us2t,
         clock_res,
         wee] = [int(i, 16) for i in psched.read().split()]
    clock_factor = float(clock_res) / TIME_UNITS_PER_SEC
    tick_in_usec = float(t2us) / us2t * clock_factor
except IOError as e:
    if config.uname[0] == 'Linux':
        log.warning("tcmsg: %s", e)
        log.warning("the tc subsystem functionality is limited")
    clock_res = 0
    clock_factor = 1
    tick_in_usec = 1
    wee = 1000

_first_letter = re.compile('[^0-9]+')


def get_hz():
    if clock_res == 1000000:
        return wee
    else:
        return os.environ.get('HZ', 1000)


def get_by_suffix(value, default, func):
    if not isinstance(value, basestring):
        return value
    pos = _first_letter.search(value)
    if pos is None:
        suffix = default
    else:
        pos = pos.start()
        value, suffix = value[:pos], value[pos:]
    value = int(value)
    return func(value, suffix)


def get_size(size):
    return get_by_suffix(size, 'b',
                         lambda x, y: x * size_suffixes[y])


def get_time(lat):
    return get_by_suffix(lat, 'ms',
                         lambda x, y: (x * TIME_UNITS_PER_SEC) /
                         time_suffixes[y])


def get_rate(rate):
    return get_by_suffix(rate, 'bit',
                         lambda x, y: (x * rate_suffixes[y]) / 8)


def time2tick(time):
    # The code is ported from tc utility
    return int(time) * tick_in_usec


def calc_xmittime(rate, size):
    # The code is ported from tc utility
    return int(time2tick(TIME_UNITS_PER_SEC * (float(size) / rate)))


def percent2u32(pct):
    '''xlate a percentage to an uint32 value
    0% -> 0
    100% -> 2**32 - 1'''
    return int((2 ** 32 - 1) * pct / 100)


def red_eval_ewma(qmin, burst, avpkt):
    # The code is ported from tc utility
    wlog = 1
    W = 0.5
    a = float(burst) + 1 - float(qmin) / avpkt
    assert a >= 1

    while wlog < 32:
        wlog += 1
        W /= 2
        if (a <= (1 - pow(1 - W, burst)) / W):
            return wlog
    return -1


def red_eval_P(qmin, qmax, probability):
    # The code is ported from tc utility
    i = qmax - qmin
    assert i > 0

    probability /= i

    for i in range(32):
        if probability > 1:
            break
        probability *= 2

    return i


def red_eval_idle_damping(Wlog, avpkt, bps):
    # The code is ported from tc utility
    xmit_time = calc_xmittime(bps, avpkt)
    lW = -logfm(1.0 - 1.0 / (1 << Wlog)) / xmit_time
    maxtime = 31.0 / lW
    sbuf = []
    for clog in range(32):
        if (maxtime / (1 << clog) < 512):
            break
    if clog >= 32:
        return -1, sbuf
    for i in range(255):
        sbuf.append((i << clog) * lW)
        if sbuf[i] > 31:
            sbuf[i] = 31
    sbuf.append(31)
    return clog, sbuf


def get_rate_parameters(kwarg):
    # rate and burst are required
    rate = get_rate(kwarg['rate'])
    burst = kwarg['burst']

    # if peak, mtu is required
    peak = get_rate(kwarg.get('peak', 0))
    mtu = kwarg.get('mtu', 0)
    if peak:
        assert mtu

    # limit OR latency is required
    limit = kwarg.get('limit', None)
    latency = get_time(kwarg.get('latency', None))
    assert limit is not None or latency is not None

    # calculate limit from latency
    if limit is None:
        rate_limit = rate * float(latency) /\
            TIME_UNITS_PER_SEC + burst
        if peak:
            peak_limit = peak * float(latency) /\
                TIME_UNITS_PER_SEC + mtu
            if rate_limit > peak_limit:
                rate_limit = peak_limit
        limit = rate_limit

    return {'rate': int(rate),
            'mtu': mtu,
            'buffer': calc_xmittime(rate, burst),
            'limit': int(limit)}


tc_flow_keys = {'src': 0x01,
                'dst': 0x02,
                'proto': 0x04,
                'proto-src': 0x08,
                'proto-dst': 0x10,
                'iif': 0x20,
                'priority': 0x40,
                'mark': 0x80,
                'nfct': 0x0100,
                'nfct-src': 0x0200,
                'nfct-dst': 0x0400,
                'nfct-proto-src': 0x0800,
                'nfct-proto-dst': 0x1000,
                'rt-classid': 0x2000,
                'sk-uid': 0x4000,
                'sk-gid': 0x8000,
                'vlan-tag': 0x010000,
                'rxhash': 0x020000,
                }


def get_tca_keys(kwarg, name):
    if name not in kwarg:
        raise ValueError('Missing attribute: {0}'.format(name))

    res = 0
    keys = kwarg[name]
    if name == 'hash':
        keys = keys.split(',')

    for key, value in tc_flow_keys.items():
        if key in keys:
            res |= value

    return res


tc_flow_modes = {'map': 0,
                 'hash': 1, }


def get_tca_mode(kwarg):
    if 'mode' not in kwarg:
        raise ValueError('Missing attribute: mode')

    for key, value in tc_flow_modes.items():
        if key == kwarg['mode']:
            return value

    raise ValueError('Unknown flow mode {0}'.format(kwarg['mode']))


def get_tca_ops(kwarg, attrs):
    xor_value = 0
    mask_value = 0
    addend_value = 0
    rshift_value = 0

    for elem in kwarg['ops']:
        op = elem['op']
        num = elem['num']

        if op == 'and':
            mask_value = num
            attrs.append(['TCA_FLOW_XOR', xor_value])
            attrs.append(['TCA_FLOW_MASK', mask_value])
        elif op == 'or':
            if mask_value == 0:
                mask_value = (~num + 1) & 0xFFFFFFFF
            xor_value = num
            attrs.append(['TCA_FLOW_XOR', xor_value])
            attrs.append(['TCA_FLOW_MASK', mask_value])
        elif op == 'xor':
            if mask_value == 0:
                mask_value = 0xFFFFFFFF
            xor_value = num
            attrs.append(['TCA_FLOW_XOR', xor_value])
            attrs.append(['TCA_FLOW_MASK', mask_value])
        elif op == 'rshift':
            rshift_value = num
            attrs.append(['TCA_FLOW_RSHIFT', rshift_value])
        elif op == 'addend':
            # Check if an IP was specified
            if type(num) == str and len(num.split('.')) == 4:
                if num.startswith('-'):
                    inverse = True
                else:
                    inverse = False
                ip = num.strip('-')

                # Convert IP to uint32
                ip = inet_aton(ip)
                ip = struct.unpack('>I', ip)[0]

                if inverse:
                    ip = (~ip + 1) & 0xFFFFFFFF
                addend_value = ip
            else:
                addend_value = num
            attrs.append(['TCA_FLOW_ADDEND', addend_value])


tc_actions = {'unspec': -1,     # TC_ACT_UNSPEC
              'ok': 0,          # TC_ACT_OK
              'reclassify': 1,  # TC_ACT_RECLASSIFY
              'shot': 2,        # TC_ACT_SHOT
              'drop': 2,        # TC_ACT_SHOT
              'pipe': 3,        # TC_ACT_PIPE
              'stolen': 4,      # TC_ACT_STOLEN
              'queued': 5,      # TC_ACT_QUEUED
              'repeat': 6,      # TC_ACT_REPEAT
              'redirect': 7,    # TC_ACT_REDIRECT
              }


class nla_plus_rtab(nla):
    class parms(nla):
        def adjust_size(self, size, mpu, linklayer):
            # The current code is ported from tc utility
            if size < mpu:
                size = mpu

            if linklayer == LINKLAYER_ATM:
                cells = size / ATM_CELL_PAYLOAD
                if size % ATM_CELL_PAYLOAD > 0:
                    cells += 1
                size = cells * ATM_CELL_SIZE

            return size

        def calc_rtab(self, kind):
            # The current code is ported from tc utility
            rtab = []
            mtu = self.get('mtu', 0) or 1600
            cell_log = self['%s_cell_log' % (kind)]
            mpu = self['%s_mpu' % (kind)]
            rate = self.get(kind, 'rate')

            # calculate cell_log
            if cell_log == 0:
                while (mtu >> cell_log) > 255:
                    cell_log += 1

            # fill up the table
            for i in range(256):
                size = self.adjust_size((i + 1) << cell_log,
                                        mpu,
                                        LINKLAYER_ETHERNET)
                rtab.append(calc_xmittime(rate, size))

            self['%s_cell_align' % (kind)] = -1
            self['%s_cell_log' % (kind)] = cell_log
            return rtab

        def encode(self):
            self.rtab = None
            self.ptab = None
            if self.get('rate', False):
                self.rtab = self.calc_rtab('rate')
            if self.get('peak', False):
                self.ptab = self.calc_rtab('peak')
            if self.get('ceil', False):
                self.ctab = self.calc_rtab('ceil')
            nla.encode(self)

    class rtab(nla_string):
        own_parent = True

        def encode(self):
            parms = self.parent.get_encoded('TCA_TBF_PARMS') or \
                self.parent.get_encoded('TCA_HTB_PARMS') or \
                self.parent.get_encoded('TCA_POLICE_TBF')
            if parms is not None:
                self.value = getattr(parms, self.__class__.__name__)
                self['value'] = struct.pack('I' * 256,
                                            *(int(x) for x in self.value))
            nla_string.encode(self)

        def decode(self):
            nla_string.decode(self)
            parms = self.parent.get_attr('TCA_TBF_PARMS') or \
                self.parent.get_attr('TCA_HTB_PARMS') or \
                self.parent.get_attr('TCA_POLICE_TBF')
            if parms is not None:
                rtab = struct.unpack('I' * (len(self['value']) / 4),
                                     self['value'])
                self.value = rtab
                setattr(parms, self.__class__.__name__, rtab)

    class ptab(rtab):
        pass

    class ctab(rtab):
        pass


class stats2(nla):
    nla_map = (('TCA_STATS_UNSPEC', 'none'),
               ('TCA_STATS_BASIC', 'basic'),
               ('TCA_STATS_RATE_EST', 'rate_est'),
               ('TCA_STATS_QUEUE', 'queue'),
               ('TCA_STATS_APP', 'stats_app'))

    class basic(nla):
        fields = (('bytes', 'Q'),
                  ('packets', 'I'))

    class rate_est(nla):
        fields = (('bps', 'I'),
                  ('pps', 'I'))

    class queue(nla):
        fields = (('qlen', 'I'),
                  ('backlog', 'I'),
                  ('drops', 'I'),
                  ('requeues', 'I'),
                  ('overlimits', 'I'))

    class stats_app(nla.hex):
        pass
