# cython: auto_pickle=False,embedsignature=True,always_allow_keywords=False
# -*- coding: utf-8 -*-
"""
Functions for finding factory objects to create internal objects
given their external form. This is usually derived from mime type
and possibly class name values in the external form. Factories
are registered in the component manager.

"""

# There are a *lot* of fixme (XXX and the like) in this file.
# Turn those off in general so we can see through the noise.
# pylint:disable=fixme

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import warnings

from zope import component
from zope import interface

from nti.externalization._base_interfaces import NotGiven
from nti.externalization.interfaces import IClassObjectFactory
from nti.externalization.interfaces import IExternalizedObjectFactoryFinder
from nti.externalization.interfaces import IFactory
from nti.externalization.interfaces import IMimeObjectFactory

from .legacy_factories import search_for_external_factory

from .._base_interfaces import get_standard_external_fields


StandardExternalFields = get_standard_external_fields()
component_queryAdapter = component.queryAdapter
component_queryUtility = component.queryUtility

logger = __import__('logging').getLogger(__name__)

__all__ = [
    'default_externalized_object_factory_finder',
    'default_externalized_object_factory_finder_factory',
    'find_factory_for_class_name',
    'find_factory_for',
]

def _search_for_mime_factory(externalized_object, mime_type):
    if not mime_type:
        return None

    factory = component_queryAdapter(externalized_object,
                                     IMimeObjectFactory,
                                     mime_type)
    if factory is not None:
        return factory

    # What about a named utility?
    factory = component_queryUtility(IMimeObjectFactory,
                                     mime_type)

    if factory is not None:
        return factory

    # Is there a default?
    factory = IMimeObjectFactory(externalized_object, None)

    return factory

def _search_for_class_factory(externalized_object, class_name):
    if not class_name:
        return None

    factory = component_queryAdapter(externalized_object,
                                     IClassObjectFactory,
                                     class_name)

    if factory is not None:
        return factory

    return find_factory_for_class_name(class_name)

def _find_factory_for_mime_or_class(externalized_object):
    # We use specialized interfaces instead of plain IFactory to make it clear
    # that these are being created from external data

    try:
        mime_type = externalized_object[StandardExternalFields.MIMETYPE]
    except TypeError:
        # Not subscriptable. We won't be able to work for
        # this object
        return None
    except KeyError:
        # sad trombone. Not present.
        pass
    else:
        factory = _search_for_mime_factory(externalized_object, mime_type)
        if factory is not None:
            return factory

    # Fallback to class
    try:
        class_name = externalized_object[StandardExternalFields.CLASS]
    except KeyError:
        # very sad trombone
        return None

    return _search_for_class_factory(externalized_object, class_name)


class _DefaultExternalizedObjectFactoryFinder(object):
    # This is an IFactory, declared below.
    # (Cython cdef classes cannot have decorators)
    __slots__ = ()

    def find_factory(self, externalized_object):
        return _find_factory_for_mime_or_class(externalized_object)

    # We are callable for BWC and because that's what an IFactory is
    def __call__(self, externalized_object):
        return self.find_factory(externalized_object)

interface.classImplements(_DefaultExternalizedObjectFactoryFinder,
                          IFactory)

default_externalized_object_factory_finder = _DefaultExternalizedObjectFactoryFinder()


@interface.implementer(IExternalizedObjectFactoryFinder)
def default_externalized_object_factory_finder_factory(unused_externalized_object):
    return default_externalized_object_factory_finder


def find_factory_for_class_name(class_name):
    factory = component_queryUtility(IClassObjectFactory, class_name)
    if factory is None:
        factory = search_for_external_factory(class_name)
    # Did we chop off an extra 's'?
    if factory is None and class_name and class_name.endswith('s'):
        factory = search_for_external_factory(class_name + 's')
    return factory


def find_factory_for(externalized_object, registry=NotGiven):
    """
    find_factory_for(externalized_object) -> factory

    Given a
    :class:`~nti.externalization.interfaces.IExternalizedObject`,
    locate and return a factory to produce a Python object to hold its
    contents.

    If there is a
    :class:`~nti.externalization.interfaces.IExternalizedObjectFactoryFinder`
    adapter registered for the externalized object, we return the
    results of its ``find_factory`` method. Note that since
    externalized objects are typically simple lists or dicts, such
    adapters have the capability to hijack all factory finding,
    probably unintentionally.

    Otherwise, we examine the contents of the object itself to find a
    registered factory based on MIME type (preferably) or class name.

    .. versionchanged:: 1.0a10
       The ``registry`` argument is deprecated and ignored.
    """
    if registry is not NotGiven: # pragma: no cover
        warnings.warn(
            "The registry argument is deprecated and ignored",
            FutureWarning
        )

    factory_finder = IExternalizedObjectFactoryFinder(externalized_object, None)

    if factory_finder is not None:
        return factory_finder.find_factory(externalized_object) # pylint:disable=too-many-function-args

    # We do it this way instead of using
    # ``default_externalized_object_factory_finder`` as the default in
    # queryAdapter so cython can optimize the call.
    return _find_factory_for_mime_or_class(externalized_object)


from nti.externalization._compat import import_c_accel # pylint:disable=wrong-import-position,wrong-import-order
import_c_accel(globals(), 'nti.externalization.internalization._factories')
