"""Simplified general utilities."""
import numpy as np
import warnings
from smpl_doc import doc
[docs]@doc.deprecated("1.0.3", "Use `s*n` instead.")
def times(s, n):
    """
    Concats string ``s`` ``n`` times.
    Examples
    --------
    >>> times("hi",5)
    'hihihihihi'
    .. deprecated:: 0.0.0
    """
    return s.join(["" for i in range(0, n + 1)]) 
[docs]def rename(old, new, warning=True):
    """
    Annotation to replace the name of a function argument.
    Examples
    --------
    >>> @rename("a","b")
    ... def f(b):
    ...     return b
    >>> f(1)
    1
    >>> f(a=1)
    1
    >>> f(b=1)
    1
    >>> f(b=2,a=1)
    2
    """
    def wrapper(target):
        def lammda(*args, **kwargs):
            if old in kwargs and new in kwargs:
                if warning:
                    warnings.warn(
                        f"Argument {old} and {new} are both set, {new} will be used.",
                        DeprecationWarning,
                    )
                del kwargs[old]
            elif old in kwargs:
                if warning:
                    warnings.warn(
                        f"Argument {old} is deprecated, use {new} instead.",
                        DeprecationWarning,
                    )
                kwargs[new] = kwargs[old]
                del kwargs[old]
            return target(*args, **kwargs)
        return lammda
    return wrapper 
# TODO split in to own package
[docs]def withify(prefix="with_", sufix="", override=False):
    """
    Decorator to add with_ methods to a class.
    Examples
    --------
    >>> from dataclasses import dataclass, field
    >>> from typing import Optional
    >>> @withify()
    ... @dataclass
    ... class A:
    ...     a : Optional[int] = field(default=0)
    ...     b : Optional[int] = field(default=0)
    ...     c : Optional[int] = field(default=0)
    >>> a = A(0,0,0)
    >>> a.with_a(1).with_b(2).with_c(3)
    A(a=1, b=2, c=3)
    """
    def _withify(cls):
        inst = cls()
        for k in inst.__annotations__.keys():
            fun = prefix + k + sufix
            ok = k
            if override or not hasattr(cls, fun):
                def tmp(self, value, k=ok):
                    """Set `value` and return self."""
                    self.__dict__[k] = value
                    return self
                tmp.__doc__ = f"Set {k} to `value` and return self."
                setattr(cls, fun, tmp)
        return cls
    return _withify 
[docs]def get(key, ddict, default=None):
    """
    Returns dict[key] if this exists else default.
    Examples
    --------
    >>> d = {'a' : 1 , 'b' : 2 , 'c' : 3}
    >>> get('a',d,5)
    1
    >>> get('x',d,5)
    5
    """
    if has(key, ddict):
        return ddict[key]
    else:
        return default 
[docs]def has(key, ddict):
    """
    Checks if the key is in the dict and not None.
    Examples
    --------
    >>> d = {'a' : 1 , 'b' : 2 , 'c' : 3}
    >>> has('a',d)
    True
    >>> has('x',d)
    False
    """
    return key in ddict and not ddict[key] is None 
[docs]def true(key, ddict):
    """
    Checks if the key is in the dict and not None and True.
    Examples
    --------
    >>> d = {'a' : True , 'b' : True , 'c' : False}
    >>> true('a', d)
    True
    >>> true('c', d)
    False
    >>> true('x', d)
    False
    """
    return has(key, ddict) and ddict[key] 
[docs]def find_nearest_index(array, value):
    """
    Returns the index of the element in ``array`` closest to ``value``
    Examples
    --------
    >>> find_nearest_index([1,7,6,2] , 1.9)
    3
    """
    array = np.asarray(array)
    idx = (np.abs(array - value)).argmin()
    return idx 
[docs]def find_nearest(array, value):
    """
    Return the element in ``array`` closest to ``value``
    Examples
    --------
    >>> find_nearest([1,7,6,2] , 1.9)
    2
    """
    return array[find_nearest_index(array, value)]