import logging

from zeep.utils import qname_attr
from zeep.xsd.const import xsd_ns, xsi_ns
from zeep.xsd.types.base import Type
from zeep.xsd.valueobjects import AnyObject

logger = logging.getLogger(__name__)

__all__ = ['AnyType']


class AnyType(Type):
    _default_qname = xsd_ns('anyType')
    _attributes_unwrapped = []
    _element = None

    def __call__(self, value=None):
        return value or ''

    def render(self, parent, value, xsd_type=None, render_path=None):
        if isinstance(value, AnyObject):
            if value.xsd_type is None:
                parent.set(xsi_ns('nil'), 'true')
            else:
                value.xsd_type.render(parent, value.value, None, render_path)
                parent.set(xsi_ns('type'), value.xsd_type.qname)
        elif hasattr(value, '_xsd_elm'):
            value._xsd_elm.render(parent, value, render_path)
            parent.set(xsi_ns('type'), value._xsd_elm.qname)
        else:
            parent.text = self.xmlvalue(value)

    def parse_xmlelement(self, xmlelement, schema=None, allow_none=True,
                         context=None, schema_type=None):
        """Consume matching xmlelements and call parse() on each

        :param xmlelement: XML element objects
        :type xmlelement: lxml.etree._Element
        :param schema: The parent XML schema
        :type schema: zeep.xsd.Schema
        :param allow_none: Allow none
        :type allow_none: bool
        :param context: Optional parsing context (for inline schemas)
        :type context: zeep.xsd.context.XmlParserContext
        :param schema_type: The original type (not overriden via xsi:type)
        :type schema_type: zeep.xsd.types.base.Type
        :rtype: dict or None

        """
        xsi_type = qname_attr(xmlelement, xsi_ns('type'))
        xsi_nil = xmlelement.get(xsi_ns('nil'))
        children = list(xmlelement)

        # Handle xsi:nil attribute
        if xsi_nil == 'true':
            return None

        # Check if a xsi:type is defined and try to parse the xml according
        # to that type.
        if xsi_type and schema:
            xsd_type = schema.get_type(xsi_type, fail_silently=True)

            # If we were unable to resolve a type for the xsi:type (due to
            # buggy soap servers) then we just return the text or lxml element.
            if not xsd_type:
                logger.debug(
                    "Unable to resolve type for %r, returning raw data",
                    xsi_type.text)

                if xmlelement.text:
                    return xmlelement.text
                return children

            # If the xsd_type is xsd:anyType then we will recurs so ignore
            # that.
            if isinstance(xsd_type, self.__class__):
                return xmlelement.text or None

            return xsd_type.parse_xmlelement(
                xmlelement, schema, context=context)

        # If no xsi:type is set and the element has children then there is
        # not much we can do. Just return the children
        elif children:
            return children

        elif xmlelement.text is not None:
            return self.pythonvalue(xmlelement.text)

        return None

    def resolve(self):
        return self

    def xmlvalue(self, value):
        """Guess the xsd:type for the value and use corresponding serializer"""
        from zeep.xsd.types import builtins

        available_types = [
            builtins.String,
            builtins.Boolean,
            builtins.Decimal,
            builtins.Float,
            builtins.DateTime,
            builtins.Date,
            builtins.Time,
        ]
        for xsd_type in available_types:
            if isinstance(value, xsd_type.accepted_types):
                return xsd_type().xmlvalue(value)
        return str(value)

    def pythonvalue(self, value, schema=None):
        return value

    def signature(self, schema=None, standalone=True):
        return 'xsd:anyType'
