# Copyright (c) The PyAMF Project.
# See LICENSE.txt for details.

"""
Provides XML support.

@since: 0.6
"""

#: list of supported third party packages that support the C{etree}
#: interface. At least enough for our needs anyway.
from miniamf.util import get_module

ETREE_MODULES = ['defusedxml.ElementTree']

#: A tuple of class/type objects that are used to represent XML objects.
types = None
#: A mapping of type -> module for all known xml types.
modules = {}
#: The module that will be used to create C{ElementTree} instances.
ET = None

__all__ = ['set_default_interface']


def set_default_interface(etree):
    """
    Sets the default interface that Mini-AMF will use to deal with XML entities
    (both objects and blobs).
    """
    global types, ET, modules

    t = _get_etree_type(etree)

    _types = set(types or [])
    _types.update([t])

    types = tuple(_types)

    modules[t] = etree

    old, ET = ET, etree

    return old


def find_libs():
    """
    Run through L{ETREE_MODULES} and find C{ElementTree} implementations so
    that any type can be encoded.

    We work through the C implementations first, then the pure Python versions.
    The downside to this is that B{all} libraries will be imported but I{only}
    one is ever used. The libs are small (relatively) and the flexibility that
    this gives seems to outweigh the cost. Time will tell.
    """

    types = []
    mapping = {}

    for mod in ETREE_MODULES:
        try:
            etree = get_module(mod)
        except ImportError:
            continue

        t = _get_etree_type(etree)

        types.append(t)
        mapping[t] = etree

    return tuple(types), mapping


def is_xml(obj):
    """
    Determines C{obj} is a valid XML type.

    If L{types} is not populated then L{find_libs} be called.
    """
    try:
        _bootstrap()
    except ImportError:
        return False

    return isinstance(obj, types)


def _get_type(e):
    """
    Returns the type associated with handling XML objects from this etree
    interface.
    """
    try:
        return e.__class__
    except AttributeError:
        return type(e)


def _get_etree_type(etree):
    """
    Returns the type associated with handling XML objects from this etree
    interface.
    """
    e = etree.fromstring('<foo/>')

    return _get_type(e)


def _no_et():
    raise ImportError(
        'Unable to find at least one compatible ElementTree library, use '
        'miniamf.set_default_etree to enable XML support'
    )


def _bootstrap():
    global types, modules

    if types is None:
        types, modules = find_libs()

    if ET is None:
        try:
            etree = modules[types[0]]
        except IndexError:
            _no_et()

        set_default_interface(etree)


def tostring(element, *args, **kwargs):
    """
    Helper func to provide easy access to the (possibly) moving target that is
    C{ET}.
    """
    _bootstrap()
    t = _get_type(element)

    etree = modules.get(t, None)

    if not etree:
        raise RuntimeError(
            'Unable to find the etree implementation related to %r '
            '(type %r)' % (element, t)
        )

    return etree.tostring(element, *args, **kwargs)


def fromstring(*args, **kwargs):
    """
    Helper func to provide easy access to the (possibly) moving target that is
    C{ET}.
    """
    _bootstrap()

    kwargs.setdefault('forbid_dtd', True)
    kwargs.setdefault('forbid_entities', True)

    return ET.fromstring(*args, **kwargs)
