from xml.dom import minidom
import copy
import enum

from .core_nodes import XmlDescriptor, XmlSimpleElement, ValueReference
from .utils import *
from lottie.nvector import NVector
from lottie.parsers.sif.ast_impl.base import SifAstNode, SifValue
from lottie.parsers.sif.sif.core import ObjectRegistry, TypeDescriptor, noop


class XmlAnimatable(XmlDescriptor):
    def __init__(self, name, typename, default=None, type_wrapper=noop):
        super().__init__(name)
        self.type = TypeDescriptor(typename, default, type_wrapper)

    def from_xml(self, obj, parent: minidom.Element, registry: ObjectRegistry, param: TypeDescriptor = None):
        cn = xml_first_element_child(parent, self.name)
        if cn:
            value = SifAstNode.from_dom(xml_first_element_child(cn), self.type_for(param), registry)
        else:
            value = self.default()

        setattr(obj, self.att_name, value)

    def to_xml(self, obj, parent: minidom.Element, dom: minidom.Document, type: TypeDescriptor = None):
        value = getattr(obj, self.att_name)
        if isinstance(value, SifValue) and isinstance(value.value, ValueReference):
            parent.setAttribute(self.name, ":" + value.value.id)
            return
        param = parent.appendChild(dom.createElement(self.name))
        param.appendChild(value.to_dom(dom, self.type_for(type)))
        return param

    def from_python(self, value):
        if not isinstance(value, SifAstNode):
            raise ValueError("%s isn't a valid value for %s" % (value, self.name))
        return value

    def default(self):
        return SifValue(copy.deepcopy(self.type.default_value))

    def type_for(self, param: TypeDescriptor):
        if param is not None and self.type.typename == "_recurse":
            return param
        return self.type


class XmlParam(XmlDescriptor):
    def __init__(self, name, typename, default=None, type_wrapper=noop, static=False):
        super().__init__(name)
        self.type = TypeDescriptor(typename, default, type_wrapper)
        self.static = static

    def _def(self):
        from ..api import Def
        return Def

    def from_xml(self, obj, parent: minidom.Element, registry: ObjectRegistry):
        for cn in xml_child_elements(parent, "param"):
            if cn.getAttribute("name") == self.name:
                use = cn.getAttribute("use")
                if use:
                    value = registry.get_object(use)
                else:
                    value_node = xml_first_element_child(cn)
                    if self.static:
                        value = self.type.value_from_xml_element(value_node, registry)
                    else:
                        value = SifAstNode.from_dom(value_node, self.type, registry)
                break
        else:
            value = self.default()

        setattr(obj, self.att_name, value)

    def to_xml(self, obj, parent: minidom.Element, dom: minidom.Document):
        param = parent.appendChild(dom.createElement("param"))
        param.setAttribute("name", self.name)
        value = getattr(obj, self.att_name)
        if isinstance(value, self._def()):
            param.setAttribute("use", ":" + value.id)
        else:
            if self.static:
                elem = self.type.value_to_xml_element(value, dom)
            else:
                elem = value.to_dom(dom, self.type)
            param.appendChild(elem)
        return param

    def from_python(self, value):
        if self.static:
            return self.type.type_wrapper(value)
        if not isinstance(value, (SifAstNode, self._def())):
            raise ValueError("%s isn't a valid value for %s" % (value, self.name))
        return value

    def default(self):
        if self.static:
            return copy.deepcopy(self.type.default_value)
        return SifValue(copy.deepcopy(self.type.default_value))


class XmlDynamicListParam(XmlDescriptor):
    _tag = "dynamic_list"

    def __init__(self, name, typename, att_name=None):
        super().__init__(name)
        self.type = TypeDescriptor(typename)
        if att_name is not None:
            self.att_name = att_name

    def to_xml(self, obj, parent: minidom.Element, dom: minidom.Document):
        param = parent.appendChild(dom.createElement("param"))
        param.setAttribute("name", self.name)
        dyl = param.appendChild(dom.createElement(self._tag))
        dyl.setAttribute("type", self.type.typename)
        values = getattr(obj, self.att_name)
        for val in values:
            entry = dyl.appendChild(dom.createElement("entry"))
            entry.appendChild(self._value_to_dom(val, dom))

    def _value_to_dom(self, val, dom: minidom.Document):
        return val.to_dom(dom, self.type)

    def from_xml(self, obj, parent: minidom.Element, registry: ObjectRegistry):
        values = []

        for cn in xml_child_elements(parent, "param"):
            if cn.getAttribute("name") == self.name:
                list = xml_first_element_child(cn)
                if list.getAttribute("type") != self.type.typename:
                    raise ValueError(
                        "Wrong type for %s: got %s instead of %s" %
                        (self.name, self.type.typename, list.getAttribute("type"))
                    )
                for entry in xml_child_elements(list, "entry"):
                    values.append(self._value_from_dom(xml_first_element_child(entry), registry))
                break

        setattr(obj, self.att_name, values)

    def _value_from_dom(self, element, registry):
        return SifAstNode.from_dom(element, self.type, registry)

    def from_python(self, value):
        return value

    def default(self):
        return []


class XmlStaticListParam(XmlDynamicListParam):
    _tag = "static_list"

    def _value_to_dom(self, val, dom: minidom.Document):
        return self.type.value_to_xml_element(val, dom)

    def _value_from_dom(self, element: minidom.Element, registry: ObjectRegistry):
        return self.type.value_from_xml_element(element, registry)
