
"""operational/__init__.py

Created by Thomas Mangin on 2015-06-23.
Copyright (c) 2009-2017 Exa Networks. All rights reserved.
License: 3-clause BSD. (See the COPYRIGHT file)
"""

from __future__ import annotations

from exabgp.util.ip import isipv4

from exabgp.protocol.family import AFI
from exabgp.protocol.family import SAFI

from exabgp.bgp.message.open.routerid import RouterID
from exabgp.bgp.message.operational import MAX_ADVISORY
from exabgp.bgp.message.operational import Advisory
from exabgp.bgp.message.operational import Query
from exabgp.bgp.message.operational import Response

# Operational message advisory overhead (including quotes)
ADVISORY_QUOTE_OVERHEAD = 2  # Two quote characters surrounding advisory


def _operational(klass, parameters, tokeniser):
    def utf8(string):
        return string.encode('utf-8')

    def valid(_):
        return True

    # Maximum values for unsigned integer types
    MAX_U32 = 0xFFFFFFFF  # Maximum 32-bit unsigned integer
    MAX_U64 = 0xFFFFFFFFFFFFFFFF  # Maximum 64-bit unsigned integer

    def u32(_):
        return int(_) <= MAX_U32

    def u64(_):
        return int(_) <= MAX_U64

    def advisory(_):
        return len(_.encode('utf-8')) <= MAX_ADVISORY + ADVISORY_QUOTE_OVERHEAD  # the two quotes

    convert = {'afi': AFI.value, 'safi': SAFI.value, 'sequence': int, 'counter': int, 'advisory': utf8}

    validate = {
        'afi': AFI.value,
        'safi': SAFI.value,
        'sequence': u32,
        'counter': u64,
    }

    number = len(parameters) * 2
    tokens = []
    while len(tokens) != number:
        tokens.append(tokeniser())

    data = {}

    while tokens and parameters:
        command = tokens.pop(0).lower()
        value = tokens.pop(0)

        if command == 'router-id':
            if isipv4(value):
                data['routerid'] = RouterID(value)
            else:
                raise ValueError('invalid operational value for {}'.format(command))
            continue

        expected = parameters.pop(0)

        if command != expected:
            raise ValueError('invalid operational syntax, unknown argument {}'.format(command))
        if not validate.get(command, valid)(value):
            raise ValueError('invalid operational value for {}'.format(command))

        data[command] = convert[command](value)

    if tokens or parameters:
        raise ValueError('invalid advisory syntax, missing argument(s) {}'.format(', '.join(parameters)))

    if 'routerid' not in data:
        data['routerid'] = None

    return klass(**data)


_dispatch = {}


def register(name):
    def inner(function):
        _dispatch[name] = function
        return function

    return inner


@register('asm')
def asm(tokeniser):
    return _operational(Advisory.ASM, ['afi', 'safi', 'advisory'], tokeniser)


@register('adm')
def adm(tokeniser):
    return _operational(Advisory.ADM, ['afi', 'safi', 'advisory'], tokeniser)


@register('rpcq')
def rpcq(tokeniser):
    return _operational(Query.RPCQ, ['afi', 'safi', 'sequence'], tokeniser)


@register('rpcp')
def rpcp(tokeniser):
    return _operational(Response.RPCP, ['afi', 'safi', 'sequence', 'counter'], tokeniser)


@register('apcq')
def apcq(tokeniser):
    return _operational(Query.APCQ, ['afi', 'safi', 'sequence'], tokeniser)


@register('apcp')
def apcp(tokeniser):
    return _operational(Response.APCP, ['afi', 'safi', 'sequence', 'counter'], tokeniser)


@register('lpcq')
def lpcq(tokeniser):
    return _operational(Query.LPCQ, ['afi', 'safi', 'sequence'], tokeniser)


@register('lpcp')
def lpcp(tokeniser):
    return _operational(Response.LPCP, ['afi', 'safi', 'sequence', 'counter'], tokeniser)


def operational(what, tokeniser):
    return _dispatch.get(what, lambda _: None)(tokeniser)
