from __future__ import annotations
from typing import Optional
from xml.etree.ElementTree import Element, tostring

from pymasep.common.exception import CreationException


class BaseObject:
    """
    Base class for all functional objects for pymasep that running into an environment.
    Any BaseObject can be created from template or xml. Name can be omitted if created with xml.

    :param environment: an Environment instance
    :param name: name of the BaseObject.
    :param xml_node: xml used to create the object.
    :param template: template of the base object. 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) -> None:
        self.environment = environment
        self.parent = parent
        if xml_node is None and template is None:
            raise CreationException('Need template or xml_node')
        self.name: Optional[str] = name
        self.template = template
        self.nature: Optional[str] = None
        if template is not None:
            self.nature = template.nature
        self._state: Optional[str] = None
        self.initializer = None

    @property
    def state(self) -> str:
        """
        state of the BaseObject. None if the object is just created.
        "init" if it misses something (input from user) to finish the creation. "run" is the BaseObject is operational.

        :return: the state of the BaseObject
        """
        return self._state

    @state.setter
    def state(self, s: str) -> None:
        """
        set the state of the BaseObject.
        :param s: the state (init, run)
        """
        self._state = s

    def id_str(self) -> str:
        """
        Create an str unique ID of the BaseObject. Depends on the content of the BaseObject.

        :return: the string unique ID
        """
        return self.name

    def get_fullname(self) -> str:
        """
        Return the full name of the base object including the parent name : greatparentname.parentname.name.

        :return: the full name
        """
        result = self.name
        if self.parent:
            parent_fullname = self.parent.get_fullname()
            parent_fullname = parent_fullname if parent_fullname else ''
            result = parent_fullname + '.' + self.name
        return result

    def to_xml(self) -> Element:
        """
        Transform the BaseObject to xml. See :ref:`serialization` for more information.

        :return: xml version of the BaseObject
        """
        result = Element(self.__class__.__name__)
        result.set('name', self.name)
        if self.nature is not None:
            result.set('nature', self.nature)
        if self.state is not None:
            result.set('state', self.state)
        if self.state != 'run':
            if self.initializer is not None:
                result.set('initializer', self.initializer.name)
        return result

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

        :param environment: the environment where the BaseObject is created.
        :param xml_node: the xml node containing .
        """
        self.name = xml_node.attrib['name'] if 'name' in xml_node.attrib else self.name
        if 'nature' in xml_node.attrib:
            self.nature = xml_node.attrib['nature']

        if 'template' in xml_node.attrib:
            bo_tmpl = self.environment.game.templates[xml_node.attrib['template']]
            self.nature = bo_tmpl.nature
            self.template = bo_tmpl

        if 'initializer' in xml_node.attrib:
            initializer = self.environment.game.initializers[xml_node.attrib['initializer']]
            self.initializer = initializer

        if 'state' in xml_node.attrib:
            self.state = xml_node.attrib['state']

    def __str__(self) -> str:
        """
        Transform the BaseObject to a string of it's xml representation. See :ref:`serialization` for more information.

        :return: A string containing the xml representation of the object.
        """
        return tostring(self.to_xml()).decode("utf-8")
