from contextlib import contextmanager
from itertools import filterfalse
from typing import Dict, List, Optional, Union
from gersemi.ast_helpers import is_comment, is_one_of_keywords
from gersemi.keywords import KeywordMatcher
from .argument_aware_command_invocation_dumper import (
    ArgumentAwareCommandInvocationDumper,
)
from .section_aware_command_invocation_dumper import Sections


def create_signature_patch(signature, old_class):
    def get(key, default_value=None):
        return signature.get(key, [] if default_value is None else default_value)

    class Impl(old_class):
        front_positional_arguments = get("front_positional_arguments")
        back_positional_arguments = get("back_positional_arguments")
        options = get("options")
        one_value_keywords = get("one_value_keywords")
        multi_value_keywords = get("multi_value_keywords")
        sections = get("sections", {})
        keyword_formatters = get("keyword_formatters", {})
        keyword_preprocessors = get("keyword_preprocessors", {})

    return Impl


class MultipleSignatureCommandInvocationDumper(ArgumentAwareCommandInvocationDumper):
    signatures: Dict[Optional[KeywordMatcher], Dict[str, Union[List, Sections]]] = {}

    @contextmanager
    def _update_signature_characteristics(self, signature):
        if signature is None:
            yield
            return

        old_class = type(self)
        try:
            self.__class__ = create_signature_patch(signature, old_class)
            yield
        finally:
            self.__class__ = old_class

    def _get_signature_matcher(self, keyword):
        for item in self.signatures:
            if is_one_of_keywords([item], keyword):
                return item
        return None

    def format_command(self, tree):
        _, arguments = tree.children
        arguments = self._preprocess_arguments(arguments)
        arguments_only = filterfalse(is_comment, arguments.children)
        matcher = None
        for argument in arguments_only:
            matcher = self._get_signature_matcher(argument)
            if matcher is not None:
                break

        signature = self.signatures.get(matcher, None)
        with self._update_signature_characteristics(signature):
            return super().format_command(tree)
