"""
Code here taken from the one and only Hunter McGushion and his fantastic
library: https://github.com/HunterMcGushion/hyperparameter_hunter
"""

import wrapt


class Alias:
    def __init__(self, primary_name, aliases):
        """Convert uses of `aliases` to `primary_name` upon calling the decorated function/method

        Parameters
        ----------
        primary_name: String
            Preferred name for the parameter, the value of which will be set to the value of the
            used alias. If `primary_name` is already explicitly used on call in addition to any
            aliases, the value of `primary_name` will remain unchanged. It only assumes the value of
            an alias if the `primary_name` is not used
        aliases: List, string
            One or multiple string aliases for `primary_name`. If `primary_name` is not used on
            call, its value will be set to that of a random alias in `aliases`. Before calling the
            decorated callable, all `aliases` are removed from its kwargs

        Examples
        --------
        >>> class Foo():
        ...     @Alias("a", ["a2"])
        ...     def __init__(self, a, b=None):
        ...         print(a, b)
        >>> @Alias("a", ["a2"])
        ... @Alias("b", ["b2"])
        ... def bar(a, b=None):
        ...    print(a, b)
        >>> foo = Foo(a2="x", b="y")
        x y
        >>> bar(a2="x", b2="y")
        x y"""
        self.primary_name = primary_name
        self.aliases = aliases if isinstance(aliases, list) else [aliases]

    @wrapt.decorator
    def __call__(self, wrapped, instance, args, kwargs):
        for alias in set(self.aliases).intersection(kwargs):
            # Only set if no `primary_name` already. Remove `aliases`, leaving only `primary_name`
            kwargs.setdefault(self.primary_name, kwargs.pop(alias))
            # Record aliases used in `instance.__wd_aliases_used` or `wrapped.__wd_aliases_used`
            if instance:
                set_default_attr(instance, "__wd_aliases_used", {})[
                    self.primary_name
                ] = alias
            else:
                set_default_attr(wrapped, "__wd_aliases_used", {})[
                    self.primary_name
                ] = alias
        return wrapped(*args, **kwargs)


def set_default_attr(obj, name, value):
    """Set the `name` attribute of `obj` to `value` if the attribute does not already exist

    Parameters
    ----------
    obj: Object
        Object whose `name` attribute will be returned (after setting it to `value`, if necessary)
    name: String
        Name of the attribute to set to `value`, or to return
    value: Object
        Default value to give to `obj.name` if the attribute does not already exist

    Returns
    -------
    Object
        `obj.name` if it exists. Else, `value`

    Examples
    --------
    >>> foo = type("Foo", tuple(), {"my_attr": 32})
    >>> set_default_attr(foo, "my_attr", 99)
    32
    >>> set_default_attr(foo, "other_attr", 9000)
    9000
    >>> assert foo.my_attr == 32
    >>> assert foo.other_attr == 9000
    """
    try:
        return getattr(obj, name)
    except AttributeError:
        setattr(obj, name, value)
    return value
