import re

from flask import request

from distribution import app


def get_adapter(name, default=None, is_public=False):
    """
    Resolves and returns an adapter given its name; if the initial
    lookup fails and a default was provided, it will be tried next.
    If that fails, None is returned.

    :param name: (string) the name of the adapter to lookup
    :param default: (string|None) name of a fallback adapter.
    :param is_public: (bool|False) restrict only public adapters.

    """
    from distribution import adapters

    adapter = getattr(adapters, name, None)

    if adapter is None and default not in [None, name]:
        adapter = getattr(adapters, default, None)

    if adapter and adapter.is_public != is_public:
        adapter = None
    return adapter


mimetype_re = re.compile(
    r'''application/              # Must start with "application/"
        (?:                       # followed by either
            vnd\.natgeo\+(\w+)    # "vnd.natgeo+rss" or "vnd.natgeo+json", etc.
            |                     # or
            vnd\.(api\+json)(?!;) # (exactly) "vnd.api+json"
        )''', re.VERBOSE)


def format_from_accept_header(value):
    """
    Looks up and returns the first recognizable format on an Accept
    header, or None.

    A value will match if it has the natgeo vendor mimetype format
    (e.g. "application/vnd.natgeo+rss") or JSON API's
    (e.g. "application/vnd.api+json"). The return value for the latter
    will be "jsonapi".

    :param value: (string) the Accept header contents.
    :return: the format that was found (string) or None.

    """
    matches = mimetype_re.search(value)
    if matches:
        return matches.group(1) or 'jsonapi'


def resolve_adapter(requested_adapter, default=None):
    """

    :param requested_adapter: (string or None) the name of the
    preferred adapter.

    :param default: an optional fallback adapter to use when the
    intended adapter can't be determined.

    :return: an adapter or None if none matches.

    """
    # If no adapter was given in the path,
    if requested_adapter is None:
        # ...we lookup in the querystring arguments
        requested_adapter = request.args.get('format')

        # ...then in the Accept header
        if requested_adapter is None and "accept" in request.headers:
            accept = request.headers['accept']
            requested_adapter = format_from_accept_header(accept)

        # ...and if that fails, we resort to the default.
        if requested_adapter is None:
            requested_adapter = default

    return get_adapter(requested_adapter)


def create_adapters(adapter_definitions, default_model=None):
    """
    Creates adapter instances out of a configuration dictionary.

    :param adapter_definitions: (dict) a dictionary-like object mapping
    new instance names (keys) with a dictionary of options. If the
    options have an "adapter" entry, it is taken as the model class to
    instantiate, otherwise the default_model is used.

    :param default_model: the default class to instantiate adapters
    from, when none is declared in the options dictionary.

    :return: A dictionary of all the successfully created instances.

    """
    from distribution.adapters import models

    new_adapters = {}
    for (adapter_name, options) in adapter_definitions.items():
        model_name = options.pop('adapter', default_model)
        try:
            model = getattr(models, model_name, None)
            new_adapters[adapter_name] = model(**options)
        except (AttributeError, TypeError) as e:
            app.logger.exception(e)

    return new_adapters
