import unittest
from modelverse_kernel.main import ModelverseKernel
from utils import execute_until_finished, MvSWrapper, get_inst, get_phase, get_returnvalue, get_phase_ref, read_primitive_interfaces, get_inst_ref, add_new_user
import math

class TestFactorial(unittest.TestCase):
    def setUp(self):
        self.mvs = MvSWrapper()
        self.root = self.mvs.execute("RR", [])[0]
        self.mvk = ModelverseKernel(self.root)
        self.primitives = read_primitive_interfaces(self.root, self.mvs)
        execute_until_finished(self.mvk, self.mvs, "load_primitives", [])

    def test_factorial_1(self):
        # Test for 'no recursion'
        self.helper_construct_and_invoke(1, math.factorial(1))

    def test_factorial_2(self):
        # Test for 'single recursion'
        self.helper_construct_and_invoke(2, math.factorial(2))

    def test_factorial_5(self):
        # Test for 'multiple recursion'
        self.helper_construct_and_invoke(5, math.factorial(5))

    def test_factorial_10(self):
        # Test for 'many recursion'
        self.helper_construct_and_invoke(10, math.factorial(10))

    def test_factorial_20(self):
        # Test for 'high numbers recursion'
        self.helper_construct_and_invoke(20, math.factorial(20))

    def helper_construct_and_invoke(self, param, result, fail=False):
        # Try to follow the same naming as in the dot file

        var_value_4 = self.mvs.execute("CN", [])[0]
        var_4 = self.mvs.execute("CNV", ["accumulation"])[0]
        signature_4 = self.mvs.execute("CN", [])[0]
        params_4 = self.mvs.execute("CN", [])[0]
        body_4 = self.mvs.execute("CN", [])[0]
        params_4_a = self.mvs.execute("CN", [])[0]
        invoke_params = self.mvs.execute("CN", [])[0]

        var_1 = self.primitives["integer_subtraction"]
        var_2 = self.primitives["integer_multiplication"]
        var_3 = self.primitives["integer_gt"]

        param_1a = self.mvs.execute("CN", [])[0]
        param_1b = self.mvs.execute("CN", [])[0]
        param_2a = self.mvs.execute("CN", [])[0]
        param_2b = self.mvs.execute("CN", [])[0]
        param_3a = self.mvs.execute("CN", [])[0]
        param_3b = self.mvs.execute("CN", [])[0]
        param_4a = self.mvs.execute("CN", [])[0]
        param_4b = self.mvs.execute("CN", [])[0]
        params_4_a = self.mvs.execute("CN", [])[0]

        value_1 = self.mvs.execute("CNV", [1])[0]
        value_2 = self.mvs.execute("CNV", [1])[0]
        value_3 = self.mvs.execute("CNV", [1])[0]
        value_4 = self.mvs.execute("CNV", [param])[0]
        value_a = self.mvs.execute("CNV", ["a"])[0]
        value_b = self.mvs.execute("CNV", ["b"])[0]
        value_c = self.mvs.execute("CNV", ["c"])[0]
        value_a2 = self.mvs.execute("CNV", ["a"])[0]
        value_b2 = self.mvs.execute("CNV", ["b"])[0]
        value_a3 = self.mvs.execute("CNV", ["a"])[0]
        value_b3 = self.mvs.execute("CNV", ["b"])[0]
        value_n = self.mvs.execute("CNV", ["n"])[0]
        value_n2 = self.mvs.execute("CNV", ["n"])[0]

        access_resolve_4 = self.mvs.execute("CNV", [{"value":"access"}])[0]
        access_resolve_7 = self.mvs.execute("CNV", [{"value":"access"}])[0]

        if_1 = self.mvs.execute("CNV", [{"value":"if"}])[0]
        call_1 = self.mvs.execute("CNV", [{"value":"call"}])[0]
        call_2 = self.mvs.execute("CNV", [{"value":"call"}])[0]
        call_3 = self.mvs.execute("CNV", [{"value":"call"}])[0]
        call_4 = self.mvs.execute("CNV", [{"value":"call"}])[0]
        invoke_factorial = self.mvs.execute("CNV", [{"value":"call"}])[0]
        resolve_2 = self.mvs.execute("CNV", [{"value":"resolve"}])[0]
        resolve_4 = self.mvs.execute("CNV", [{"value":"resolve"}])[0]
        resolve_6 = self.mvs.execute("CNV", [{"value":"resolve"}])[0]
        resolve_7 = self.mvs.execute("CNV", [{"value":"resolve"}])[0]
        resolve_10 = self.mvs.execute("CNV", [{"value":"resolve"}])[0]
        access_1 = self.mvs.execute("CNV", [{"value":"access"}])[0]
        access_2 = self.mvs.execute("CNV", [{"value":"access"}])[0]
        access_3 = self.mvs.execute("CNV", [{"value":"access"}])[0]
        return_1 = self.mvs.execute("CNV", [{"value":"return"}])[0]
        return_2 = self.mvs.execute("CNV", [{"value":"return"}])[0]
        const_1 = self.mvs.execute("CNV", [{"value":"constant"}])[0]
        const_2 = self.mvs.execute("CNV", [{"value":"constant"}])[0]
        const_3 = self.mvs.execute("CNV", [{"value":"constant"}])[0]
        const_4 = self.mvs.execute("CNV", [{"value":"constant"}])[0]
        const_5 = self.mvs.execute("CNV", [{"value":"constant"}])[0]

        # Rebind the invoke_factorial to the initial IP
        root = self.root
        _globals = self.mvs.execute("RD", [root, "__hierarchy"])[0]
        self.mvs.execute("DE", [self.mvs.execute("RDE", [_globals, "__IP"])[0]])
        self.mvs.execute("CD", [_globals, "__IP", invoke_factorial])

        t = self.mvs.execute("CE", [_globals, var_value_4])[0]
        self.mvs.execute("CE", [t, var_4])

        self.mvs.execute("CD", [var_value_4, "value", signature_4])
        self.mvs.execute("CD", [signature_4, "body", if_1])
        self.mvs.execute("CD", [signature_4, "params", params_4])
        self.mvs.execute("CD", [params_4, "n", params_4_a])

        self.mvs.execute("CD", [if_1, "cond", call_1])
        self.mvs.execute("CD", [if_1, "then", return_2])
        self.mvs.execute("CD", [if_1, "else", return_1])

        node_3 = self.mvs.execute("CNV", [{"value":"constant"}])[0]
        self.mvs.execute("CD", [call_1, "func", node_3])
        self.mvs.execute("CD", [node_3, "node", self.primitives["integer_gt"]])

        self.mvs.execute("CD", [call_1, "params", param_1a])
        self.mvs.execute("CD", [param_1a, "name", value_a])
        self.mvs.execute("CD", [param_1a, "value", access_1])
        self.mvs.execute("CD", [access_1, "var", resolve_2])
        self.mvs.execute("CD", [resolve_2, "var", params_4_a])
        self.mvs.execute("CD", [param_1a, "next_param", param_1b])
        self.mvs.execute("CD", [param_1b, "name", value_b])
        self.mvs.execute("CD", [param_1b, "value", const_1])
        self.mvs.execute("CD", [const_1, "node", value_1])
        self.mvs.execute("CD", [call_1, "last_param", param_1b])

        self.mvs.execute("CD", [return_1, "value", const_2])
        self.mvs.execute("CD", [const_2, "node", value_2])

        self.mvs.execute("CD", [return_2, "value", call_2])
        node_2 = self.mvs.execute("CNV", [{"value":"constant"}])[0]
        self.mvs.execute("CD", [call_2, "func", node_2])
        self.mvs.execute("CD", [node_2, "node", self.primitives["integer_multiplication"]])

        self.mvs.execute("CD", [call_2, "params", param_2a])
        self.mvs.execute("CD", [call_2, "last_param", param_2b])
        self.mvs.execute("CD", [param_2a, "name", value_a2])
        self.mvs.execute("CD", [param_2a, "value", access_2])
        self.mvs.execute("CD", [param_2a, "next_param", param_2b])
        self.mvs.execute("CD", [access_2, "var", resolve_10])
        self.mvs.execute("CD", [resolve_10, "var", params_4_a])
        self.mvs.execute("CD", [param_2b, "name", value_b2])
        self.mvs.execute("CD", [param_2b, "value", call_3])
        self.mvs.execute("CD", [call_3, "func", access_resolve_4])
        self.mvs.execute("CD", [access_resolve_4, "var", resolve_4])
        self.mvs.execute("CD", [resolve_4, "var", var_4])
        self.mvs.execute("CD", [call_3, "params", param_3a])
        self.mvs.execute("CD", [call_3, "last_param", param_3a])
        self.mvs.execute("CD", [param_3a, "name", value_n])
        self.mvs.execute("CD", [param_3a, "value", call_4])
        node_1 = self.mvs.execute("CNV", [{"value":"constant"}])[0]
        self.mvs.execute("CD", [call_4, "func", node_1])
        self.mvs.execute("CD", [node_1, "node", self.primitives["integer_subtraction"]])

        self.mvs.execute("CD", [call_4, "params", param_4a])
        self.mvs.execute("CD", [param_4a, "name", value_a3])
        self.mvs.execute("CD", [param_4a, "value", access_3])
        self.mvs.execute("CD", [access_3, "var", resolve_6])
        self.mvs.execute("CD", [resolve_6, "var", params_4_a])
        self.mvs.execute("CD", [param_4a, "next_param", param_4b])
        self.mvs.execute("CD", [param_4b, "name", value_b3])
        self.mvs.execute("CD", [param_4b, "value", const_3])
        self.mvs.execute("CD", [const_3, "node", value_3])
        self.mvs.execute("CD", [call_4, "last_param", param_4b])

        self.mvs.execute("CD", [invoke_factorial, "func", access_resolve_7])
        self.mvs.execute("CD", [access_resolve_7, "var", resolve_7])
        self.mvs.execute("CD", [resolve_7, "var", var_4])
        self.mvs.execute("CD", [invoke_factorial, "params", invoke_params])
        self.mvs.execute("CD", [invoke_factorial, "last_param", invoke_params])
        self.mvs.execute("CD", [invoke_params, "value", const_4])
        self.mvs.execute("CD", [invoke_params, "name", value_n2])
        self.mvs.execute("CD", [const_4, "node", value_4])
        self.mvs.execute("CD", [invoke_factorial, "next", const_5])
        self.mvs.execute("CD", [const_5, "node", value_c])

        execute_until_finished(self.mvk, self.mvs, "initialize_new_user", [])

        t = self.mvs.execute("CE", [self.mvs.execute("RD", [self.mvs.execute("RD", [root, "user_1"])[0], "globals"])[0] , var_value_4])[0]
        self.mvs.execute("CE", [t, var_4])

        ### Execute rules
        self.assertEquals(get_inst(self.root, self.mvs), "call")
        self.assertEquals(get_phase(self.root, self.mvs), "init")
        execute_until_finished(self.mvk, self.mvs)

        #i = 0
        while (get_inst_ref(self.root, self.mvs) != const_5) or (get_phase(self.root, self.mvs) != "init"):
            #print("EXEC (%s) -- (%s, %s)" % (i, get_inst(self.root, self.mvs), get_phase(self.root, self.mvs)))
            #i += 1
            #self.mvs.mvs.dump(0, "%i.dot" % i)
            execute_until_finished(self.mvk, self.mvs)

        if not fail:
            self.assertEquals(get_returnvalue(self.root, self.mvs), result)
