import unittest

from utils import run_file, get_constructor

set_inheritance = [
        "Which link in the metamodel is the inheritance link?",
        "Set inheritance link!",
    ]

do_instantiate_simple = [
            "new", "PetriNets", "abc",
            "instantiate", "Transition", "t1",
            "instantiate", "Place", "p1", "attr_add", "p1", "tokens", 5,
            "instantiate", "Place", "p2", "attr_add", "p2", "tokens", 0,
            "instantiate", "P2T", "p2t", "p1", "t1", "attr_add", "p2t", "weight", 2,
            "instantiate", "T2P", "t2p", "t1", "p2", "attr_add", "t2p", "weight", 1]

instantiate_node = ["Type to instantiate?",
                    "Name of new element?",
                    "Instantiation successful!"]

instantiate_edge = ["Type to instantiate?",
                    "Name of new element?",
                    "Source name?",
                    "Destination name?",
                    "Instantiation successful!"]

all_files = [   "pn_interface.alc",
                "primitives.alc",
                "object_operations.alc",
                "conformance_scd.alc",
                "library.alc",
                "metamodels.alc",
                "constructors.alc",
                "modelling.alc",
                "compilation_manager.alc",
            ]

greeting =          ["Welcome to the Model Management Interface, running live on the Modelverse!",
                     "Use 'help' command for a list of possible commands"]
new =               ["Metamodel to instantiate?",
                     "Name of model?"]
prompt =            ["Please give your command."]
loaded =            ["Model loaded, ready for commands!",
                     "Use 'help' command for a list of possible commands"] + prompt
load =              ["Model to load?"]
instantiate =       ["Type to instantiate?"]
instantiate_name =  ["Name of new element?"]
instantiate_ok =    ["Instantiation successful!"]
instantiate_source= ["Source name?"]
instantiate_destination = ["Destination name?"]

def list_menu(defined):
    defined.append(("PetriNets", "SimpleClassDiagrams"))
    defined.append(("SimpleClassDiagrams", "SimpleClassDiagrams"))
    defined.append(("LTM_bottom", "LTM_bottom"))
    return ["Found models:",
            set(["  %s : %s" % (m, mm) for m, mm in defined])]

def list_model(defined):
    return ["List of all elements:",
            set(["  %s : %s" % (m, mm) for m, mm in defined])]

def read_node(name, t, defs, attrs):
    return ["Element to read?",
            "Name: %s" % name, 
            "Type: %s" % t,
            "Defines attributes:"] + \
            ([set(["  %s : %s" % (m, mm) for m, mm in defs])] if defs else []) + \
            ["Attributes:"] + \
            ([set(['  "%s" : "%s" = %s' % (m, mm, v) for m, mm, v in attrs])] if attrs else [])

def read_edge(name, t, src, dst, defs, attrs):
    return ["Element to read?",
            "Name: %s" % name, 
            "Type: %s" % t,
            "Source: %s" % src,
            "Destination: %s" % dst,
            "Defines attributes:"] + \
            ([set(["  %s : %s" % (m, mm) for m, mm in defs])] if defs else []) + \
            ["Attributes:"] + \
            ([set(['  "%s" : "%s" = %s' % (m, mm, v) for m, mm, v in attrs])] if attrs else [])

delete =            ["Model to delete?", "Deleted!"]
rename =            ["Old name?", "New name?", "Rename complete!"]
attr_add =          ["Which model do you want to assign an attribute to?",
                     "Which attribute do you wish to assign?",
                     "Value of attribute?",
                     "Added attribute!"]
attr_del =          ["Which model do you want to remove an attribute of?",
                     "Which attribute do you want to delete?",
                     "Attribute deleted!",
                    ]

help_root =         ["Currently no model is loaded, so your operations are limited to:",
                     "  new    -- Create a new model and save it for future use"
                     "  load   -- Load a previously made model",
                     "  rename -- Rename a previously made model",
                     "  delete -- Delete a previously made model",
                     "  list   -- Show a list of all stored models",
                     "  help   -- Show a list of possible commands"]
verify_fail_weight= ["Natural number not larger than or equal to zero"]
verify_fail_tokens= ["Natural number not larger than or equal to zero"]
verify_fail_structure = ["Source of model edge not typed by source of type: p2t"]

init = greeting + prompt

did_instantiate_simple = init + \
                         new + \
                         loaded +\
                         instantiate_node + \
                         prompt + \
                         instantiate_node + \
                         prompt + \
                         attr_add + \
                         prompt + \
                         instantiate_node + \
                         prompt + \
                         attr_add + \
                         prompt + \
                         instantiate_edge + \
                         prompt + \
                         attr_add + \
                         prompt + \
                         instantiate_edge + \
                         prompt + \
                         attr_add + \
                         prompt

def list_types(t):
    return ["List of types:"] + \
            [set(["  %s : %s" % (m, mm) for m, mm in t])]

modify =            ["Element to modify?", 
                     "Attribute to modify?",
                     "New value?",
                     "Modified!",]

class TestPetrinetInterface(unittest.TestCase):
    def test_po_pn_interface_manage(self):
        self.pn_interface_manage("PO")

    def test_co_pn_interface_manage(self):
        self.pn_interface_manage("CO")

    def pn_interface_manage(self, mode):
        self.assertTrue(run_file(all_files,
            ["list",
             "new", "PetriNets", "abc", "exit",
             "list",
             "new", "PetriNets", "def", "exit",
             "list",
             "delete", "def",
             "list",
             "rename", "abc", "a",
             "list",
             "delete", "a",
             "list",
             ],
            init + \
                list_menu([]) + prompt + \
                new + loaded + prompt + list_menu([("abc", "PetriNets")]) + prompt + \
                new + loaded + prompt + list_menu([("abc", "PetriNets"), ("def", "PetriNets")]) + prompt + \
                delete + prompt + list_menu([("abc", "PetriNets")]) + prompt + \
                rename + prompt + list_menu([("a", "PetriNets")]) + prompt + \
                delete + prompt + list_menu([]) + prompt,
            mode))

    def test_po_pn_interface_new_reload(self):
        self.pn_interface_new_reload("PO")

    def test_co_pn_interface_new_reload(self):
        self.pn_interface_new_reload("CO")

    def pn_interface_new_reload(self, mode):
        self.assertTrue(run_file(all_files,
            ["new", "PetriNets", "abc", "exit", "load", "abc"],
            init + new + loaded + prompt + load + loaded,
            mode))

    def test_po_pn_interface_instantiate_place(self):
        self.pn_interface_instantiate_place("PO")

    def test_co_pn_interface_instantiate_place(self):
        self.pn_interface_instantiate_place("CO")

    def pn_interface_instantiate_place(self, mode):
        self.assertTrue(run_file(all_files,
            ["new", "PetriNets", "abc",
            "instantiate", "Place", "p1",
            "attr_add", "p1", "tokens", 5,
            "list",
            "read", "p1",
            "instantiate", "Transition", "t1",
            "list",
            "read", "t1"],
            init + new + loaded + \
                instantiate_node + prompt + \
                attr_add + prompt + \
                list_model([("p1", "Place"), ("p1.tokens", "Natural")]) + prompt + \
                read_node("p1", "Place", [], [("tokens", "Natural", 5)]) + prompt + \
                instantiate_node + prompt + \
                list_model([("p1", "Place"), ("t1", "Transition"), ("p1.tokens", "Natural")]) + prompt + \
                read_node("t1", "Transition", [], []) + prompt,
            mode))

    def test_po_pn_interface_instantiate_arcs(self):
        self.pn_interface_instantiate_arcs("PO")

    def test_co_pn_interface_instantiate_arcs(self):
        self.pn_interface_instantiate_arcs("CO")

    def pn_interface_instantiate_arcs(self, mode):
        self.assertTrue(run_file(all_files,
            do_instantiate_simple + [
                    "read", "p1",
                    "read", "p2",
                    "read", "t1",
                    "read", "p2t",
                    "read", "t2p",
                ],
            did_instantiate_simple + \
                read_node("p1", "Place", [], [("tokens", "Natural", 5)]) + prompt + \
                read_node("p2", "Place", [], [("tokens", "Natural", 0)]) + prompt + \
                read_node("t1", "Transition", [], []) + prompt + \
                read_edge("p2t", "P2T", "p1", "t1", [], [("weight", "Natural", 2)]) + prompt + \
                read_edge("t2p", "T2P", "t1", "p2", [], [("weight", "Natural", 1)]) + prompt,
            mode))

    def test_po_pn_interface_verify_OK(self):
        self.pn_interface_verify_OK("PO")

    def test_co_pn_interface_verify_OK(self):
        self.pn_interface_verify_OK("CO")

    def pn_interface_verify_OK(self, mode):
        self.assertTrue(run_file(all_files,
            do_instantiate_simple + ["verify"],
            did_instantiate_simple + ["OK"], mode))

    def test_po_pn_interface_verify_fail_tokens(self):
        self.pn_interface_verify_fail_tokens("PO")

    def test_co_pn_interface_verify_fail_tokens(self):
        self.pn_interface_verify_fail_tokens("CO")

    def pn_interface_verify_fail_tokens(self, mode):
        self.assertTrue(run_file(all_files,
            do_instantiate_simple + ["modify", "p1", "tokens", -5, "verify"],
            did_instantiate_simple + modify + prompt + verify_fail_tokens + prompt, mode))

    def test_po_pn_interface_verify_fail_weight(self):
        self.pn_interface_verify_fail_weight("PO")

    def test_co_pn_interface_verify_fail_weight(self):
        self.pn_interface_verify_fail_weight("CO")

    def pn_interface_verify_fail_weight(self, mode):
        self.assertTrue(run_file(all_files,
            do_instantiate_simple + ["modify", "p2t", "weight", -2, "verify"],
            did_instantiate_simple + modify + prompt + verify_fail_weight + prompt, mode))

    def test_po_pn_interface_verify_fail_structure(self):
        self.pn_interface_verify_fail_structure("PO")

    def test_co_pn_interface_verify_fail_structure(self):
        self.pn_interface_verify_fail_structure("CO")

    def pn_interface_verify_fail_structure(self, mode):
        self.assertTrue(run_file(all_files,
            ["new", "PetriNets", "abc",
            "instantiate", "Transition", "t1",
            "instantiate", "Place", "p1", "attr_add", "p1", "tokens", 5,
            "instantiate", "P2T", "p2t", "t1", "p1", "attr_add", "p2t", "weight", 2, "verify"],
            init + new + loaded + \
                instantiate_node + prompt + \
                instantiate_node + prompt + attr_add + prompt + \
                instantiate_edge + prompt + attr_add + prompt + \
                verify_fail_structure,
            mode))

    def test_po_pn_interface_types(self):
        self.pn_interface_types("PO")

    def test_co_pn_interface_types(self):
        self.pn_interface_types("CO")

    def pn_interface_types(self, mode):
        self.assertTrue(run_file(all_files,
            ["new", "PetriNets", "abc", "types"],
            init + new + loaded + list_types([("Place", "Class"),
                                              ("Transition", "Class"),
                                              ("P2T", "Association"),
                                              ("T2P", "Association"),
                                              ("Natural", "Class"),
                                              ("Place_tokens", "Association"),
                                              ("Place_tokens.name", "String"),
                                              ("Place_tokens.target_lower_cardinality", "Natural"),
                                              ("Place_tokens.target_upper_cardinality", "Natural"),
                                              ("P2T_weight", "Association"),
                                              ("P2T_weight.name", "String"),
                                              ("P2T_weight.target_lower_cardinality", "Natural"),
                                              ("P2T_weight.target_upper_cardinality", "Natural"),
                                              ("T2P_weight", "Association"),
                                              ("T2P_weight.name", "String"),
                                              ("T2P_weight.target_lower_cardinality", "Natural"),
                                              ("T2P_weight.target_upper_cardinality", "Natural"),
                    ]) + prompt,
            mode))

    def test_po_pn_interface_modify_place(self):
        self.pn_interface_modify_place("PO")

    def test_co_pn_interface_modify_place(self):
        self.pn_interface_modify_place("CO")

    def pn_interface_modify_place(self, mode):
        self.assertTrue(run_file(all_files,
            ["new", "PetriNets", "abc",
                "instantiate", "Place", "p1", "attr_add", "p1", "tokens", 5,
                "read", "p1",
                "modify", "p1", "tokens", 1, "read", "p1"],
            init + new + loaded + \
                instantiate_node + prompt + attr_add + prompt + \
                read_node("p1", "Place", [], [("tokens", "Natural", 5)]) + prompt + \
                modify + prompt + \
                read_node("p1", "Place", [], [("tokens", "Natural", 1)]) + prompt,
            mode))

    def test_po_pn_interface_verify_fail_attr_lower_cardinality(self):
        self.pn_interface_verify_fail_attr_lower_cardinality("PO")

    def test_co_pn_interface_verify_fail_attr_lower_cardinality(self):
        self.pn_interface_verify_fail_attr_lower_cardinality("CO")

    def pn_interface_verify_fail_attr_lower_cardinality(self, mode):
        self.assertTrue(run_file(all_files,
            do_instantiate_simple + ["instantiate", "Place", "p999", "verify"],
            did_instantiate_simple + instantiate_node + prompt + ["Lower cardinality violation for outgoing edge of type Place_tokens at p999"] + prompt,
            mode))

    def test_po_pn_interface_verify_fail_attr_upper_cardinality(self):
        self.pn_interface_verify_fail_attr_upper_cardinality("PO")

    def test_co_pn_interface_verify_fail_attr_upper_cardinality(self):
        self.pn_interface_verify_fail_attr_upper_cardinality("CO")

    def pn_interface_verify_fail_attr_upper_cardinality(self, mode):
        self.assertTrue(run_file(all_files,
            do_instantiate_simple + ["attr_add", "p1", "tokens", 5, "verify"],
            did_instantiate_simple + attr_add + prompt + ["Upper cardinality violation for outgoing edge of type Place_tokens at p1"] + prompt,
            mode))

    def test_po_pn_interface_verify_natural(self):
        self.pn_interface_verify_natural("PO")

    def test_co_pn_interface_verify_natural(self):
        self.pn_interface_verify_natural("CO")

    def pn_interface_verify_natural(self, mode):
        self.assertTrue(run_file(all_files,
            ["new", "PetriNets", "abc",
            "instantiate", "Place", "p1",
            "attr_add", "p1", "tokens", -5,
            "attr_del", "p1", "tokens",
            "attr_add", "p1", "tokens", 4,
            "verify"],
            init + new + loaded + \
            instantiate_node + prompt + \
            attr_add + prompt + \
            attr_del + prompt + \
            attr_add + prompt + \
            ["OK"] + prompt,
            mode))

    def test_po_pn_interface_verify_PN_OK(self):
        self.pn_interface_verify_PN_OK("PO")

    def test_co_pn_interface_verify_PN_OK(self):
        self.pn_interface_verify_PN_OK("CO")

    def pn_interface_verify_PN_OK(self, mode):
        self.assertTrue(run_file(all_files,
            ["load", "PetriNets", "verify"],
            init + load + loaded + ["OK"], mode))

    def test_po_rpgame(self):
        self.rpgame("PO")

    def test_co_rpgame(self):
        self.rpgame("CO")

    def rpgame(self, mode):

        constraint_code = \
            """
include "primitives.alh"
include "object_operations.alh"

Element function constraint(model : Element, name : String):
\tElement associations
\tElement back_associations
\tElement association
\tString destination
\tassociations = allOutgoingAssociationInstances(model, name, "tile_left")
\twhile (0 < list_len(associations)):
\t\tassociation = set_pop(associations)
\t\tdestination = readAssociationDestination(model, association)
\t\tback_associations = allOutgoingAssociationInstances(model, destination, "tile_right")
\t\tif (list_len(back_associations) < 1):
\t\t\treturn "Left link does not have a right link back"!
\t\telse:
\t\t\tassociation = set_pop(back_associations)
\t\t\tdestination = readAssociationDestination(model, association)
\t\t\tif (destination != name):
\t\t\t\treturn "Right link does not have a left link back to the same tile"!
\tassociations = allOutgoingAssociationInstances(model, name, "tile_right")
\twhile (0 < list_len(associations)):
\t\tassociation = set_pop(associations)
\t\tdestination = readAssociationDestination(model, association)
\t\tback_associations = allOutgoingAssociationInstances(model, destination, "tile_left")
\t\tif (list_len(back_associations) < 1):
\t\t\treturn "Right link does not have a left link back"!
\t\telse:
\t\t\tassociation = set_pop(back_associations)
\t\t\tdestination = readAssociationDestination(model, association)
\t\t\tif (destination != name):
\t\t\t\treturn "Right link does not have a left link back to the same tile"!
\tassociations = allOutgoingAssociationInstances(model, name, "tile_top")
\twhile (0 < list_len(associations)):
\t\tassociation = set_pop(associations)
\t\tdestination = readAssociationDestination(model, association)
\t\tback_associations = allOutgoingAssociationInstances(model, destination, "tile_bottom")
\t\tif (list_len(back_associations) < 1):
\t\t\treturn "Top link does not have a bottom link back"!
\t\telse:
\t\t\tassociation = set_pop(back_associations)
\t\t\tdestination = readAssociationDestination(model, association)
\t\t\tif (destination != name):
\t\t\t\treturn "Top link does not have a bottom link back to the same tile"!
\tassociations = allOutgoingAssociationInstances(model, name, "tile_bottom")
\twhile (0 < list_len(associations)):
\t\tassociation = set_pop(associations)
\t\tdestination = readAssociationDestination(model, association)
\t\tback_associations = allOutgoingAssociationInstances(model, destination, "tile_top")
\t\tif (list_len(back_associations) < 1):
\t\t\treturn "Bottom link does not have a top link back"!
\t\telse:
\t\t\tassociation = set_pop(back_associations)
\t\t\tdestination = readAssociationDestination(model, association)
\t\t\tif (destination != name):
\t\t\t\treturn "Bottom link does not have a top link back to the same tile"!
\treturn "OK"!
            """

        constructors = get_constructor(constraint_code)
        print(constructors)

        self.assertTrue(run_file(all_files,
            ["new", "SimpleClassDiagrams", "RPGame",
                "set_inheritance", "Inheritance",
                "instantiate", "Class", "Scene",
                "instantiate", "Class", "Tile",
                "instantiate", "Class", "Item",
                "instantiate", "Class", "Goal",
                "instantiate", "Class", "Character",
                "instantiate", "Class", "Hero",
                "instantiate", "Association", "scene_has_tiles", "Scene", "Tile",
                "instantiate", "Association", "tile_left", "Tile", "Tile",
                "instantiate", "Association", "tile_right", "Tile", "Tile",
                "instantiate", "Association", "tile_top", "Tile", "Tile",
                "instantiate", "Association", "tile_bottom", "Tile", "Tile",
                "instantiate", "Association", "character_on", "Character", "Tile",
                "instantiate", "Association", "item_on", "Item", "Tile",
                "instantiate", "Inheritance", "hero_is_character", "Hero", "Character",
                "instantiate", "Inheritance", "goal_is_item", "Goal", "Item",
                "attr_add", "Scene", "lower_cardinality", 1,
                "attr_add", "Scene", "upper_cardinality", 1,
                "attr_add", "Goal", "lower_cardinality", 1,
                "attr_add", "scene_has_tiles", "source_lower_cardinality", 1,
                "attr_add", "scene_has_tiles", "source_upper_cardinality", 1,
                "attr_add", "scene_has_tiles", "target_lower_cardinality", 1,
                "attr_add", "item_on", "target_lower_cardinality", 1,
                "attr_add", "item_on", "target_upper_cardinality", 1,
                "attr_add", "item_on", "source_upper_cardinality", 1,
                "attr_add", "character_on", "target_lower_cardinality", 1,
                "attr_add", "character_on", "target_upper_cardinality", 1,
                "attr_add", "character_on", "source_upper_cardinality", 1,
                "attr_add", "tile_left", "source_upper_cardinality", 1,
                "attr_add", "tile_left", "target_upper_cardinality", 1,
                "attr_add", "tile_right", "source_upper_cardinality", 1,
                "attr_add", "tile_right", "target_upper_cardinality", 1,
                "attr_add", "tile_top", "source_upper_cardinality", 1,
                "attr_add", "tile_top", "target_upper_cardinality", 1,
                "attr_add", "tile_bottom", "source_upper_cardinality", 1,
                "attr_add", "tile_bottom", "target_upper_cardinality", 1,
                "constrain", "Tile",
            ] + constructors + ["verify"] + ["exit"] + [
                "new", "RPGame", "my_game",
                "instantiate", "Scene", "scene",
                "instantiate", "Hero", "Link",
                "instantiate", "Goal", "goal",
                "instantiate", "Tile", "tile_00",
                "instantiate", "Tile", "tile_01",
                "instantiate", "Tile", "tile_10",
                "instantiate", "Tile", "tile_11",
                "instantiate", "scene_has_tiles", "", "scene", "tile_00",
                "instantiate", "scene_has_tiles", "", "scene", "tile_01",
                "instantiate", "scene_has_tiles", "", "scene", "tile_10",
                "instantiate", "scene_has_tiles", "", "scene", "tile_11",
                "instantiate", "character_on", "",  "Link", "tile_00",
                "instantiate", "item_on", "", "goal", "tile_11",
                "instantiate", "tile_left", "", "tile_01", "tile_00",
                "instantiate", "tile_left", "", "tile_11", "tile_10",
                "instantiate", "tile_right", "", "tile_00", "tile_01",
                "instantiate", "tile_right", "", "tile_10", "tile_11",
                "instantiate", "tile_top", "", "tile_10", "tile_00",
                "instantiate", "tile_top", "", "tile_11", "tile_01",
                "instantiate", "tile_bottom", "", "tile_00", "tile_10",
                "instantiate", "tile_bottom", "", "tile_01", "tile_11",
                "verify",
            ],
            init + new + loaded + \
                set_inheritance + prompt + \
                (instantiate_node + prompt) * 6 + \
                (instantiate_edge + prompt) * 9 + \
                (attr_add + prompt) * 20 + \
                ["Element to constrain (empty for global)?",
                 "Give input to function constructors for LOCAL constraint!",
                 "Added constraint to model!"] + \
                prompt + \
                ["OK"] + \
                prompt + prompt + new + loaded + \
                (instantiate_node + prompt) * 7 + \
                (instantiate_edge + prompt) * 14 + \
                ["OK"],
            mode))
