import re
import abc
from typing import List, Tuple
from sccd.statechart.dynamic.statechart_instance import InternalEvent, Instance, StatechartInstance

# TODO: Clean this mess up. Look at all object management operations and see how they can be improved.
class ObjectManager(Instance):
    __slots__ = ["cd", "output_callback", "schedule_callback", "instances"]

    _regex_pattern = re.compile("^([a-zA-Z_]\w*)(?:\[(\d+)\])?$")

    def __init__(self, cd, output_callback, schedule_callback, cancel_callback):
        self.cd = cd
        self.output_callback = output_callback
        self.schedule_callback = schedule_callback
        self.cancel_callback = cancel_callback


        i = StatechartInstance(cd.get_default_class(), self, self.output_callback, self.schedule_callback, self.cancel_callback)
        
        # set of all instances in the runtime
        # we need to maintain this set in order to do broadcasts
        self.instances = [i]

    def _create(self, class_name) -> StatechartInstance:
        statechart_model = self.cd.classes[class_name]
        i = StatechartInstance(statechart_model, self, self.output_callback, self.schedule_callback, self.cancel_callback)
        self.instances.append(i)
        return i

    def initialize(self):
        pass

    # Implementation of super class: Instance
    def big_step(self, input_events: List[InternalEvent]):
        pass
        # output = []
        # for e in input_events:
        #     try:
        #         o = ObjectManager._handlers[e.name](self, timestamp, e.parameters)
        #         if isinstance(o, OutputEvent):
        #             output.append(o)
        #         elif isinstance(o, list):
        #             output.extend(o)
        #     except KeyError:
        #         pass
        # return output

    # def _assoc_ref(self, input_string) -> List[Tuple[str,int]]:
    #     if len(input_string) == 0:
    #         raise AssociationReferenceException("Empty association reference.")
    #     path_string =  input_string.split("/")
    #     result = []
    #     for piece in path_string:
    #         match = ObjectManager._regex_pattern.match(piece)
    #         if match:
    #             name = match.group(1)
    #             index = match.group(2)
    #             if index is None:
    #                 index = -1
    #             result.append((name,int(index)))
    #         else:
    #             raise AssociationReferenceException("Invalid entry in association reference. Input string: " + input_string)
    #     return result
            
    # def _handle_broadcast(self, timestamp, parameters) -> OutputEvent:
    #     if len(parameters) != 2:
    #         raise ParameterException ("The broadcast event needs 2 parameters (source of event and event name).")
    #     return OutputEvent(parameters[1], InstancesTarget(self.instances))

    # def _handle_create(self, timestamp, parameters) -> List[OutputEvent]:
    #     if len(parameters) < 2:
    #         raise ParameterException ("The create event needs at least 2 parameters.")

    #     source = parameters[0]
    #     association_name = parameters[1]
        
    #     traversal_list = self._assoc_ref(association_name)
    #     instances = self._get_instances(source, traversal_list)
        
    #     association = source.associations[association_name]
    #     # association = self.instances_map[source].getAssociation(association_name)
    #     if association.allowedToAdd():
    #         ''' allow subclasses to be instantiated '''
    #         class_name = association.to_class if len(parameters) == 2 else parameters[2]
    #         instance = self._create(class_name)
    #         # new_instance = self.model.classes[class_name](parameters[3:])
    #         if not instance:
    #             raise ParameterException("Creating instance: no such class: " + class_name)
    #         output_events = instance.initialize(timestamp)
    #         try:
    #             index = association.addInstance(instance)
    #         except AssociationException as exception:
    #             raise RuntimeException("Error adding instance to association '" + association_name + "': " + str(exception))
    #         p = instance.associations.get("parent")
    #         if p:
    #             p.addInstance(source)
    #         return output_events.append(OutputEvent(Event("instance_created", None, [association_name+"["+str(index)+"]"]), InstancesTarget([source])))
    #     else:
    #         return OutputEvent(Event("instance_creation_error", None, [association_name]), InstancesTarget([source]))

    # def _handle_delete(self, timestamp, parameters) -> OutputEvent:
    #     if len(parameters) < 2:
    #         raise ParameterException ("The delete event needs at least 2 parameters.")
    #     else:
    #         source = parameters[0]
    #         association_name = parameters[1]
            
    #         traversal_list = self._assoc_ref(association_name)
    #         instances = self._get_instances(source, traversal_list)
    #         # association = self.instances_map[source].getAssociation(traversal_list[0][0])
    #         association = source.associations[traversal_list[0][0]]
            
    #         for i in instances:
    #             try:
    #                 for assoc_name in i["instance"].associations:
    #                     if assoc_name != 'parent':
    #                         traversal_list = self._assoc_ref(assoc_name)
    #                         instances = self._get_instances(i["instance"], traversal_list)
    #                         if len(instances) > 0:
    #                             raise RuntimeException("Error removing instance from association %s, still %i children left connected with association %s" % (association_name, len(instances), assoc_name))
    #                 # del i["instance"].controller.input_ports[i["instance"].narrow_cast_port]
    #                 association.removeInstance(i["instance"])
    #                 self.instances.remove(i["instance"])
    #             except AssociationException as exception:
    #                 raise RuntimeException("Error removing instance from association '" + association_name + "': " + str(exception))
    #             i["instance"].user_defined_destructor()
    #             i["instance"].stop()
            
    #         return OutputEvent(Event("instance_deleted", parameters = [parameters[1]]), InstancesTarget([source]))
                
    # def _handle_associate(self, timestamp, parameters) -> OutputEvent:
    #     if len(parameters) != 3:
    #         raise ParameterException ("The associate_instance event needs 3 parameters.")
    #     else:
    #         source = parameters[0]
    #         to_copy_list = self._get_instances(source, self._assoc_ref(parameters[1]))
    #         if len(to_copy_list) != 1:
    #             raise AssociationReferenceException ("Invalid source association reference.")
    #         wrapped_to_copy_instance = to_copy_list[0]["instance"]
    #         dest_list = self._assoc_ref(parameters[2])
    #         if len(dest_list) == 0:
    #             raise AssociationReferenceException ("Invalid destination association reference.")
    #         last = dest_list.pop()
    #         if last[1] != -1:
    #             raise AssociationReferenceException ("Last association name in association reference should not be accompanied by an index.")
                
    #         added_links = []
    #         for i in self._get_instances(source, dest_list):
    #             association = i["instance"].associations[last[0]]
    #             if association.allowedToAdd():
    #                 index = association.addInstance(wrapped_to_copy_instance)
    #                 added_links.append(i["path"] + ("" if i["path"] == "" else "/") + last[0] + "[" + str(index) + "]")
            
    #         return OutputEvent(Event("instance_associated", parameters = [added_links]), InstancesTarget([source]))
                
    # def _handle_disassociate(self, timestamp, parameters) -> OutputEvent:
    #     if len(parameters) < 2:
    #         raise ParameterException ("The disassociate_instance event needs at least 2 parameters.")
    #     else:
    #         source = parameters[0]
    #         association_name = parameters[1]
    #         if not isinstance(association_name, list):
    #             association_name = [association_name]
    #         deleted_links = []
            
    #         for a_n in association_name:
    #             traversal_list = self._assoc_ref(a_n)
    #             instances = self._get_instances(source, traversal_list)
                
    #             for i in instances:
    #                 try:
    #                     index = i['ref'].associations[i['assoc_name']].removeInstance(i["instance"])
    #                     deleted_links.append(a_n +  "[" + str(index) + "]")
    #                 except AssociationException as exception:
    #                     raise RuntimeException("Error disassociating '" + a_n + "': " + str(exception))
            
    #         return OutputEvent(Event("instance_disassociated", parameters = [deleted_links]), InstancesTarget([source]))
        
    # def _handle_narrowcast(self, timestamp, parameters) -> OutputEvent:
    #     if len(parameters) != 3:
    #         raise ParameterException ("The narrow_cast event needs 3 parameters.")
    #     source, targets, cast_event = parameters
        
    #     if not isinstance(targets, list):
    #         targets = [targets]

    #     all_instances = []
    #     for target in targets:
    #         traversal_list = self._assoc_ref(target)
    #         instances = self._get_instances(source, traversal_list)
    #         all_instances.extend(instances)
    #     return OutputEvent(cast_event, instances)
        
    # def _get_instances(self, source, traversal_list):
    #     print("_get_instances(source=",source,"traversal_list=",traversal_list)
    #     currents = [{
    #         "instance": source,
    #         "ref": None,
    #         "assoc_name": None,
    #         "assoc_index": None,
    #         "path": ""
    #     }]
    #     # currents = [source]
    #     for (name, index) in traversal_list:
    #         nexts = []
    #         for current in currents:
    #             association = current["instance"].associations[name]
    #             if (index >= 0 ):
    #                 try:
    #                     nexts.append({
    #                         "instance": association.instances[index],
    #                         "ref": current["instance"],
    #                         "assoc_name": name,
    #                         "assoc_index": index,
    #                         "path": current["path"] + ("" if current["path"] == "" else "/") + name + "[" + str(index) + "]"
    #                     })
    #                 except KeyError:
    #                     # Entry was removed, so ignore this request
    #                     pass
    #             elif (index == -1):
    #                 for i in association.instances:
    #                     nexts.append({
    #                         "instance": association.instances[i],
    #                         "ref": current["instance"],
    #                         "assoc_name": name,
    #                         "assoc_index": index,
    #                         "path": current["path"] + ("" if current["path"] == "" else "/") + name + "[" + str(index) + "]"
    #                     })
    #                 #nexts.extend( association.instances.values() )
    #             else:
    #                 raise AssociationReferenceException("Incorrect index in association reference.")
    #         currents = nexts
    #     return currents

    # _handlers = {
    #     # "narrow_cast": _handle_narrowcast,
    #     "broad_cast": _handle_broadcast,
    #     # "create_instance": _handle_create,
    #     # "associate_instance": _handle_associate,
    #     # "disassociate_instance": _handle_disassociate,
    #     # "delete_instance": _handle_delete
    # }
