from smnp.ast.node import type as ast
from smnp.ast.node.none import NoneNode
from smnp.ast.node.type import TypesList
from smnp.error.runtime import RuntimeException
from smnp.function.signature import varargSignature, signature, optional
from smnp.runtime.evaluators.expression import expressionEvaluator, expressionEvaluatorWithMatcher
from smnp.runtime.tools.error import updatePos
from smnp.type.model import Type
from smnp.type.signature.matcher.list import listOfMatchers
from smnp.type.signature.matcher.map import mapOfMatchers
from smnp.type.signature.matcher.type import allTypes, oneOf, ofType


def evaluateDefaultArguments(node, environment):
    defaultValues = { arg.variable.value: expressionEvaluator(doAssert=True)(arg.optionalValue, environment).value for arg in node.children if type(arg.optionalValue) != NoneNode }

    return defaultValues


def argumentsNodeToMethodSignature(node, environment):
    try:
        sign = []
        vararg = None
        argumentsCount = len(node.children)
        checkPositionOfOptionalArguments(node)
        defaultArgs = {}
        for i, child in enumerate(node.children):
            matchers = {
                ast.Type: (lambda c: c.type, typeMatcher),
                NoneNode: (lambda c: c.type, lambda c: allTypes()),
                TypesList: (lambda c: c, multipleTypeMatcher)
            }
            evaluatedMatcher = matchers[type(child.type)][1](matchers[type(child.type)][0](child))

            if child.vararg:
                if i != argumentsCount - 1:
                    raise RuntimeException("Vararg must be the last argument in signature", child.pos)
                vararg = evaluatedMatcher
            else:
                if type(child.optionalValue) != NoneNode:
                    defaultArgs[child.variable.value] = expressionEvaluatorWithMatcher(
                        evaluatedMatcher,
                        exceptionProvider=lambda value: RuntimeException(
                            f"Value '{value.stringify()}' doesn't match declared type: {evaluatedMatcher.string}", child.optionalValue.pos)
                    )(child.optionalValue, environment)
                    evaluatedMatcher = optional(evaluatedMatcher)
                sign.append(evaluatedMatcher)

        return defaultArgs, (varargSignature(vararg, *sign, wrapVarargInValue=True) if vararg is not None else signature(*sign))
    except RuntimeException as e:
        raise updatePos(e, node)


def checkPositionOfOptionalArguments(node):
    firstOptional = next((i for i, v in enumerate(node.children) if type(v.optionalValue) != NoneNode), None) #next(filter(lambda arg: type(arg.optionalValue) != NoneNode, node.children), None)
    if firstOptional is not None:
        regularAfterOptional = next((i for i, v in enumerate(node.children[firstOptional:]) if type(v.optionalValue) == NoneNode), None)
        if regularAfterOptional is not None:
            raise RuntimeException(f"Optional arguments should be declared at the end of the arguments list", node.children[regularAfterOptional].pos)


def multipleTypeMatcher(typeNode):
    subSignature = []

    if len(typeNode.type.children) == 0:
        return allTypes()

    for child in typeNode.type.children:
        m = typeMatcher(child)
        subSignature.append(m)

    return oneOf(*subSignature)


def typeMatcher(typeNode):
    if type(typeNode.specifiers) == NoneNode:
        return ofType(typeNode.type.value)
    elif typeNode.type.value == Type.LIST and len(typeNode.specifiers) == 1:
        return listSpecifier(typeNode.specifiers[0])
    elif typeNode.type.value == Type.MAP and len(typeNode.specifiers) == 2:
        return mapSpecifier(typeNode.specifiers[0], typeNode.specifiers[1])

    raise RuntimeException("Unknown type", typeNode.pos)  # Todo: Improve pointing position


def listSpecifier(specifier):
    subSignature = []

    if len(specifier.children) == 0:
        subSignature.append(allTypes())

    for child in specifier.children:
        subSignature.append(typeMatcher(child))

    return listOfMatchers(*subSignature)


def mapSpecifier(keySpecifier, valueSpecifier):
    keySubSignature = []
    valueSubSignature = []

    if len(keySpecifier.children) == 0:
        keySubSignature.append(allTypes())

    if len(valueSpecifier.children) == 0:
        valueSubSignature.append(allTypes())

    for child in keySpecifier.children:
        keySubSignature.append(typeMatcher(child))

    for child in valueSpecifier.children:
        valueSubSignature.append(typeMatcher(child))

    return mapOfMatchers(keySubSignature, valueSubSignature)