from smnp.ast.node.block import Block
from smnp.ast.node.condition import IfElse
from smnp.ast.node.extend import Extend
from smnp.ast.node.function import FunctionDefinition
from smnp.ast.node.imports import Import
from smnp.ast.node.program import Program
from smnp.ast.node.ret import Return
from smnp.ast.node.throw import Throw
from smnp.error.runtime import RuntimeException
from smnp.type.model import Type


class Evaluator:


    @classmethod
    def evaluator(cls, node, environment):
        pass

    @classmethod
    def evaluate(cls, node, environment):
        result = cls.evaluator(node, environment)
        if result is None:
            return EvaluationResult.OK(Type.void())
        return EvaluationResult.OK(result)

    @staticmethod
    def forNodes(evaluator, *nodes):
        def nodeEvaluator(node, environment):
            if type(node) in nodes:
                return evaluator(node, environment)

            return EvaluationResult.FAIL()

        return nodeEvaluator

    @staticmethod
    def oneOf(*evaluators):
        def combinedEvaluator(node, environment):
            for evaluator in evaluators:
                result = evaluator(node, environment)
                if result.result:
                    return result

            return EvaluationResult.FAIL()

        return combinedEvaluator


class EvaluationResult():
    def __init__(self, result, value):
        if result and value is None:
            raise RuntimeError("Value musn't be None if result is set to True for EvaluationResult")
        if type(value) == EvaluationResult:
            raise RuntimeError(f"Nested EvaluationResult detected. Trying to create EvaluationResult with value = {value}")
        self.result = result
        self.value = value

    @staticmethod
    def FAIL():
        return EvaluationResult(False, None)

    @staticmethod
    def OK(value):
        return EvaluationResult(True, value)

    def __str__(self):
        return f"{'OK' if self.result else 'FAILED'}[{self.value}]"


def evaluate(node, environment):
    from smnp.runtime.evaluators.program import ProgramEvaluator
    from smnp.runtime.evaluators.expression import expressionEvaluator
    from smnp.runtime.evaluators.condition import IfElseStatementEvaluator
    from smnp.runtime.evaluators.block import BlockEvaluator
    from smnp.runtime.evaluators.imports import ImportEvaluator
    from smnp.runtime.evaluators.function import FunctionDefinitionEvaluator
    from smnp.runtime.evaluators.function import ReturnEvaluator
    from smnp.runtime.evaluators.extend import ExtendEvaluator
    from smnp.runtime.evaluators.throw import ThrowEvaluator

    result = Evaluator.oneOf(
        Evaluator.forNodes(ProgramEvaluator.evaluate, Program),
        Evaluator.forNodes(IfElseStatementEvaluator.evaluate, IfElse),
        Evaluator.forNodes(BlockEvaluator.evaluate, Block),
        Evaluator.forNodes(ImportEvaluator.evaluate, Import),
        Evaluator.forNodes(FunctionDefinitionEvaluator.evaluate, FunctionDefinition),
        Evaluator.forNodes(ReturnEvaluator.evaluate, Return),
        Evaluator.forNodes(ExtendEvaluator.evaluate, Extend),
        Evaluator.forNodes(ThrowEvaluator.evaluate, Throw),
        #Evaluator.forNodes(ImportEvaluator.evaluate, ImportNode),
        #Evaluator.forNodes(FunctionDefinitionEvaluator.evaluate, FunctionDefinitionNode),
        #Evaluator.forNodes(ExtendEvaluator.evaluate, ExtendNode),
        #Evaluator.forNodes(BlockEvaluator.evaluate, BlockNode),
        #Evaluator.forNodes(ReturnEvaluator.evaluate, ReturnNode),
        expressionEvaluator()
    )(node, environment)

    if not result.result:
        raise RuntimeException("Cannot evaluate program", node.pos)

    return result

