
"""section.py

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

from __future__ import annotations

from string import ascii_letters
from string import digits

from exabgp.configuration.core.error import Error


class Section(Error):
    name = 'undefined'
    known = dict()  # command/section and code to handle it
    default = dict()  # command/section has a a defult value, use it if no data was provided
    action = {}  # how to handle this command ( append, add, assign, route )
    assign = {}  # configuration to class variable lookup for setattr

    def __init__(self, tokerniser, scope, error):
        Error.__init__(self)
        self.tokeniser = tokerniser
        self.scope = scope
        self.error = error
        self._names = []

    def clear(self):
        self._names = []

    @classmethod
    def register(cls, name, action, afi=''):
        def inner(function):
            identifier = (afi, name) if afi else name
            if identifier in cls.known:
                raise RuntimeError('more than one registration per command attempted')
            cls.known[identifier] = function
            cls.action[identifier] = action
            return function

        return inner

    def check_name(self, name):
        if any(False if c in ascii_letters + digits + '.-_' else True for c in name):
            self.throw(f'invalid character in name for {self.name} ')
        if name in self._names:
            self.throw(f'the name "{name}" already exists in {self.name}')
        self._names.append(name)

    def pre(self):
        return True

    def post(self):
        return True

    def parse(self, name, command):  # noqa: C901
        identifier = command if command in self.known else (self.name, command)
        if identifier not in self.known:
            options = ', '.join([str(_) for _ in self.known])
            return self.error.set(
                f'unknown command {command} options are {options}',
            )

        try:
            if command in self.default:
                insert = self.known[identifier](self.tokeniser.iterate, self.default[command])
            else:
                insert = self.known[identifier](self.tokeniser.iterate)

            action = self.action.get(identifier, '')

            if action == 'set-command':
                self.scope.set(command, insert)
            elif action == 'extend-name':
                self.scope.extend(name, insert)
            elif action == 'append-name':
                self.scope.append(name, insert)
            elif action == 'append-command':
                self.scope.append(command, insert)
            elif action == 'extend-command':
                self.scope.extend(command, insert)
            elif action == 'attribute-add':
                self.scope.attribute_add(name, insert)
            elif action == 'nlri-set':
                self.scope.nlri_assign(name, self.assign[command], insert)
            elif action == 'nlri-add':
                for adding in insert:
                    self.scope.nlri_add(name, command, adding)
            elif action == 'nlri-nexthop':
                self.scope.nlri_nexthop(name, insert)
            elif action == 'nexthop-and-attribute':
                ip, attribute = insert
                if ip:
                    self.scope.nlri_nexthop(name, ip)
                if attribute:
                    self.scope.attribute_add(name, attribute)
            elif action == 'append-route':
                self.scope.extend_routes(insert)
            elif action == 'nop':
                pass
            else:
                raise RuntimeError(f'name {name} command {command} has no action set')
            return True
        except ValueError as exc:
            return self.error.set(str(exc))

        return True
