from typing import Any
from serializers.base.deserialization import Deserializer
from serializers.base.serialization import Serializer
from types import NoneType
from ..base.constants import (PRIMITIVES, KEY, VALUE,
                         XmlRegularExpression as Expression)
import regex
import builtins


class XmlSerializer():

    def dumps(self, obj: Any) -> str:
        """
        Convert an object to string of 'XML' format.

        :param obj: Any python object.
        :type obj: Any

        :return: String of 'XML' format.
        :rtype: str
        """

        packed: dict = Serializer.pack(obj)
        return self._convert(packed)

    def loads(self, string: str):
        """
        Convert string of 'XML' format to object.

        :param string: String of 'XML' format.
        :type string: str

        :return: Python object.
        :rtype: Any
        """

        packed: dict = self._deconvert(string)
        return Deserializer.unpack(packed)

    def _convert(self, obj: dict) -> str:
        """
        Convert dictionary that represents object
        to a string of 'XML' format

        :param obj: Dictionary that represents object.
        :type obj: dict

        :return: String of 'XML' format.
        :rtype: str
        """

        if isinstance(obj, PRIMITIVES):
            if isinstance(obj, str):
                return XmlSerializer.create_xml_item(type(obj).__name__, XmlSerializer.to_special_xml(obj))
            else:
                return XmlSerializer.create_xml_item(type(obj).__name__, str(obj))

        if isinstance(obj, list):
            return XmlSerializer.create_xml_item(type(obj).__name__, "".join([self._convert(item) for item in obj]))

        if isinstance(obj, dict):
            return XmlSerializer.create_xml_item(type(obj).__name__, "".join(
                [f"{self._convert(key)}{self._convert(value)}" for key, value in obj.items()]))

    def _deconvert(self, string: str) -> dict:
        """
        Convert string of 'XML' format to
        dictionary that represents object.

        :param string: String of 'XML' format.
        :type string: str

        :return: Dictionary that represents object.
        :rtype: dict
        """

        string = string.strip()

        match = regex.fullmatch(Expression.ITEM.value, string)
        if not match:
            return

        key = match.group(KEY)
        value = match.group(VALUE)

        if key in map(lambda p: p.__name__, PRIMITIVES):
            if key == str.__name__:
                return XmlSerializer.from_special_xml(value)
            elif key == NoneType.__name__:
                return None
            elif key == bool.__name__:
                return True if value == 'True' else False
            else:
                return getattr(builtins, key)(value)

        if key == list.__name__:
            matches = regex.findall(Expression.ITEM.value, value)
            return [self._deconvert(match[0]) for match in matches]

        if key == dict.__name__:
            matches = regex.findall(Expression.ITEM.value, value)
            return {self._deconvert(matches[i][0]): self._deconvert(matches[i + 1][0])
                    for i in range(0, len(matches), 2)}

    @staticmethod
    def create_xml_item(tag_name, value):
        return f"<{tag_name}>{value}</{tag_name}>"

    @staticmethod
    def to_special_xml(string):
        return string.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;"). \
            replace('"', "&quot;").replace("'", "&apos;")

    @staticmethod
    def from_special_xml(string):
        return string.replace("&amp;", "&").replace("&lt;", "<").replace("&gt;", ">"). \
            replace("&quot;", '"').replace("&apos;", "'")