import functools
from warnings import warn

import numpy as np


def dec_debug(debug):
    if debug > 0:
        return debug - 1
    if debug < 0:
        return debug + 1
    return False


def do_debug(debug):
    return debug == -1 or debug > 0


def adaptive(img):
    import skimage

    mi = np.min(img)
    img = np.asarray(256**2 * (img - mi) / (np.max(img) - mi), dtype=np.uint16)
    img = skimage.exposure.equalize_adapthist(img, clip_limit=0.03)
    return img


def smiley(
    width,
    height=None,
    ratio=0.9,
    eye=0.15,
    thick=0.1,
    eye_sep=0.35,
    eye_height=0.3,
    mouth_rad=0.5,
    mouth_thick=0.1,
):
    if height is None:
        height = width
    size = min(width, height)
    Y, X = np.mgrid[-height // 2 : height // 2, -width // 2 : width // 2]
    R = np.sqrt(X**2 + Y**2) / (size / 2)
    smiley = (ratio > R) * ((ratio - thick) < R)
    smiley += (
        np.sqrt(
            (X - eye_sep * ratio * size / 2) ** 2
            + (Y + eye_height * ratio * size / 2) ** 2
        )
        < ratio * eye * size / 2
    )
    smiley += (
        np.sqrt(
            (X + eye_sep * ratio * size / 2) ** 2
            + (Y + eye_height * ratio * size / 2) ** 2
        )
        < ratio * eye * size / 2
    )
    smiley += (
        (ratio * mouth_rad > R)
        * ((ratio * mouth_rad - ratio * mouth_thick) < R)
        * (Y > 0)
    )
    return smiley * 1.0


def aliased(cls):
    original_methods = cls.__dict__.copy()
    for name in original_methods:
        method = original_methods[name]
        if hasattr(method, "_aliases"):
            for alias in method._aliases:
                setattr(cls, *alias)
    return cls


class alias:
    def __init__(self, *aliases):
        self.aliases = aliases

    def __call__(self, func):
        if not hasattr(func, "_aliases"):
            func._aliases = []
        func._aliases += [(x, func) for x in self.aliases]
        import sys

        module = sys.modules[func.__module__]
        for alias in self.aliases:
            setattr(module, alias, func)
        return func


class deprecated:
    def __init__(self, alias):
        self.alias = alias

    def __call__(self, func):
        import sys

        msg = "the function {} is DEPRECATED. Please use {} instead".format(
            self.alias, func.__name__
        )
        module = sys.modules[func.__module__]

        @functools.wraps(func)
        def wrapper(*args, **kargs):
            from warnings import warn

            warn(msg, stacklevel=2)
            return func(*args, **kargs)

        setattr(module, self.alias, wrapper)
        if not hasattr(func, "_aliases"):
            func._aliases = []
        func._aliases += [(self.alias, wrapper)]
        return func


@alias("get_bam", "get_BAM")
def getBAM(x, x0, N=10, least_one=False):
    """
    get the profile of the GaAlAs layer from a BAM-L200 sample.

    Parameters
    ----------
    x: numpy.ndarray
        x-axis in nm
    x0: float
        offset of the x-axis in nm (ie. where the first edge of the BAM is located)
    N: int
        The number of headings grating (seems to be 13 on the SEM image in the doc, but 10 in reality?)
    least_one: bool
        if True each GaAlAs strip are at least 1px wide.
    """
    P = x * 0

    # Here are the certified known distances
    d = {
        "W1": 691,
        "W2": 691,
        "W3": 293,
        "W4": 294,
        "W5": 19.5,
        "W6": 195,
        "W7": 195,
        "W8": 38,
        "W9": 3.6,
        "W10": 14.2,
        "W11": 3.5,
        "W12": 96,
        "W13": 5,
        "W14": 1,
        "P1": 587,
        "P2": 389,
        "P3": 273,
        "P4": 193,
        "P5": 136,
        "P6": 97,
        "P7": 67.5,
        "P8": 48.5,
        "P9": 76.5,
        "P10": 57,
        "P11": 42,
        "P12": 31,
        "P13": 23,
        "P14": 17.5,
        "P15": 13.3,
        "P16": 9.4,
        "P17": 6.9,
        "P18": 4.6,
        "P19": 3,
        "P20": 2,
    }
    # The prelines
    lines = [(-147 * i, 80) for i in range(N, 0, -1)]

    # Estimated coordinates of the starting of lines
    pos = {
        "W1": 0,
        "P1": d["W1"] + d["W2"],
        "W5": 2683,
        "P9": 8760,
        "W8": 7300,
        "W9": 7800,
        "W10": 8250,
        "W11": 9500,
        "W12": 9840,
        "W14": 8100,
    }
    pos_rel = {
        "W5": {
            "P2": 397,
            "P3": 1380,
            "P4": 2140,
            "P5": 2730,
            "P6": 3200,
            "P7": 3535,
            "P8": 3730,
        },
        "P9": {"P10": 231, "P11": 402, "P12": 539, "P13": 642, "P14": 710},
        "W11": {"P15": 60, "P16": 104, "P17": 141, "P18": 162, "P19": 182, "P20": 191},
    }
    for rel in pos_rel:
        for key in pos_rel[rel]:
            pos[key] = pos[rel] + pos_rel[rel][key]

    for key in sorted(pos):
        width = [d[key], d[key] // 2][key[0] == "P"]
        lines.append((pos[key], width))
        if key[0] == "P":
            lines.append((pos[key] + d[key], width))

    for pos, width in lines:
        P[((x - x0) >= pos) * ((x - x0) <= pos + width)] = 1
        if least_one:
            P[np.argmin(abs(x - x0 - pos))] = 1  # At least 1 pixel set ?
    return P


def in_ipynb():
    try:
        # noinspection PyUnresolvedReferences
        cfg = get_ipython().config
        return "IPKernelApp" in cfg
    except NameError:
        return False


if in_ipynb():
    try:
        from tqdm.notebook import tqdm
    except:
        try:
            from tqdm import tqdm
        except:
            warn(
                "the library tqdm cannot be found. All progressbar will be disabled.",
                stacklevel=2,
            )

            def tqdm(x):
                return x

    PB = tqdm
else:
    try:
        from tqdm import tqdm
    except:
        warn(
            "the library tqdm cannot be found. All progressbar will be disabled.",
            stacklevel=2,
        )

        def tqdm(x):
            return x

    PB = tqdm
