from typing import Optional, Set
from xml.etree.ElementTree import Element

from pymasep.common.base_object import BaseObject
from pymasep.common.agent import Agent


class State(BaseObject):
    """
    State of the environment

    :param environment: an Environment instance
    :param name: name of the Agent.
    :param xml_node: xml used to create the state.
    :param template: template of the state. Needed if no xml_node.
    :param parent: parent object (see objects hierarchy)

    :raise pymasep.common.exception.CreationException: if no xml or no template is present
    """

    def __init__(self, environment,
                 name: Optional[str] = None,
                 xml_node: Optional[Element] = None,
                 template=None,
                 parent: Optional[BaseObject] = None):
        super().__init__(environment=environment, name=name, xml_node=xml_node, template=template, parent=parent)
        self.agents: Set[Agent] = set()
        self.objects = dict()
        if template is not None:
            for sub_template in template.subclass_templates:
                obj = self.environment.create_object(name=sub_template.name_base_object,
                                                     template=sub_template,
                                                     parent=self)
                self.add_object(obj)
        self.is_final: bool = False
        self.step: int = 0

    @BaseObject.state.getter
    def state(self) -> str:
        result = 'run'
        for o in self.objects.values():
            if o.state == 'init':
                result = 'init'
        return result

    def id_str(self) -> str:
        """
        Create an str unique ID of the State.
        Depends on the content of the base object and the id_str of all objects in the state

        :return: the string unique ID
        """
        ordered_objects = sorted(self.objects.keys())
        id_objects = ''
        for o in ordered_objects:
            id_objects += self.objects[o].id_str()
        return super().id_str() + id_objects

    def __eq__(self, other):
        """
        Test the equality of 2 states. Two states are equals if they have the same type and their id_str() are equals.
        Note : in some extreme cases, two different states can have the same id_str() but should not be equals.
        This case may occur for example with charac name Name and Name0 with Name='00' and Name0='0'. But you have to
        have a twisted mind to model the state this way. Please don't :) The code will remain simpler :)
        """
        if type(other) is type(self):
            return self.id_str() == other.id_str()
        return False

    def add_object(self, obj) -> None:
        """
        Add an object into the state. The object is added in self.agents if the object is an Agent

        :param obj: object to add into state.
        """
        obj.parent = self
        self.objects[obj.get_fullname()] = obj
        if isinstance(obj, Agent):
            self.agents.add(obj)

    def to_xml(self) -> Element:
        """
        Transform the State to xml. See :ref:`serialization` for more information.
        :return  xml version of the State
        """
        result = super().to_xml()
        result.set('is_final', str(self.is_final))
        result.set('env_step', str(self.step))
        for obj in self.objects.values():
            obj_element = obj.to_xml()
            result.append(obj_element)
        return result

    def from_xml(self, environment, xml_node: Element) -> None:
        """
        Transform a xml to a State. The instance must be created before (with __init__(), passing the xml_node).
        See :ref:`serialization` for more information.

        :param environment: the environment where the Agent is created.
        :param xml_node: the xml node containing .

        :raise  pymasep.common.exception.CreationException: SubElement must be present if no initializer not present.
                                  At creation, Initializer must be present for all characteristics
                                  if initializer is not present for ObjectState
        """
        super().from_xml(environment=environment, xml_node=xml_node)
        if 'is_final' in xml_node.attrib:
            self.is_final = (xml_node.attrib['is_final'] == 'True')
        else:
            self.is_final = False
        if 'env_step' in xml_node.attrib:
            self.step = int(xml_node.attrib['env_step'])
        if 'template' in xml_node.attrib:
            state_tmpl = self.environment.game.templates[xml_node.attrib['template']]
            for object_tmpl in state_tmpl.subclass_templates:
                new_object = self.environment.create_object(name=object_tmpl.name_base_object,
                                                            template=object_tmpl,
                                                            parent=self)
                self.add_object(new_object)
        for object_xml in xml_node:
            obj = environment.create_object(name=None, xml_node=object_xml, parent=self)
            self.add_object(obj)
