import functools
import importlib
import importlib.util
import importlib.machinery

from .const import Bound, inf
from .func import iterate, open, closed, openclosed, closedopen, empty, singleton
from .io import from_string, to_string, from_data, to_data
from .dict import IntervalDict


def partial(wrapped, *args, **kwargs):
    """
    Convenient helper that combines functools.update_wrapper and
    functools.partial. It has exactly the same signature than functools.partial.
    """
    return functools.update_wrapper(
        functools.partial(wrapped, *args, **kwargs), wrapped
    )


def create_api(interval, *, interval_dict=None, name=None):
    """
    Dynamically create a module whose API is similar to the one of portion, but
    configured to use given Interval class. Unless specified, a new IntervalDict
    subclass is automatically generated to use given Interval subclass.

    This feature is experimental, and may be changed even in minor or patch
    updates of portion.

    :param interval: a subclass of Interval.
    :param interval_dict: a subclass of IntervalDict.
    :param name: the name of the new module.
    """
    if name is None:
        module_name = "portion_" + interval.__name__
    else:
        module_name = name

    if interval_dict is None:
        interval_dict = type(
            interval.__name__ + "Dict",
            (IntervalDict,),
            dict(_klass=interval),
        )

    objects = {
        "inf": inf,
        "CLOSED": Bound.CLOSED,
        "OPEN": Bound.OPEN,
        "Interval": interval,
        "open": partial(open, klass=interval),
        "closed": partial(closed, klass=interval),
        "openclosed": partial(openclosed, klass=interval),
        "closedopen": partial(closedopen, klass=interval),
        "singleton": partial(singleton, klass=interval),
        "empty": partial(empty, klass=interval),
        "iterate": iterate,
        "from_string": partial(from_string, klass=interval),
        "to_string": to_string,
        "from_data": partial(from_data, klass=interval),
        "to_data": to_data,
        "IntervalDict": interval_dict,
    }

    module = importlib.util.module_from_spec(
        importlib.machinery.ModuleSpec(module_name, None)
    )

    module.__all__ = list(objects.keys())
    for name, obj in objects.items():
        setattr(module, name, obj)

    return module
