# AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbs/01t_utils.misc.ipynb.

# %% auto 0
__all__ = ['flatten', 'sort_quadrilateral', 'kwargs_grid', 'safe_zip', 'skip_error', 'Cache', 'transparent_cmap', 'partial',
           'random_local_seed', 'random_color']

# %% ../../nbs/01t_utils.misc.ipynb 2
from fastcore.all import *
import numbers
import random
import numpy as np
import polvo as pv

from contextlib import contextmanager
from PIL import Image

# %% ../../nbs/01t_utils.misc.ipynb 3
def flatten(x):
    "From https://stackoverflow.com/a/2158532/6772672"
    if isinstance(x, Iterable) and not isinstance(x, (str, bytes)): 
        return [a for i in x for a in flatten(i)]
    else: return [x]

# %% ../../nbs/01t_utils.misc.ipynb 5
def sort_quadrilateral(points):
    ysorted_idxs = np.argsort(points[:, 1])
    top_idxs, bottom_idxs = ysorted_idxs[:2], ysorted_idxs[2:]
    xsorted_top_idxs = top_idxs[np.argsort(points[top_idxs][:, 0])]
    xreversesorted_bottom_idxs = bottom_idxs[np.argsort(points[bottom_idxs][:, 0])][::-1]
    # Combine the sorted points in the order: [top-left, top-right, bottom-right, bottom-left]
    sorted_points = np.vstack((points[xsorted_top_idxs], points[xreversesorted_bottom_idxs]))
    sorted_idxs = np.concatenate((xsorted_top_idxs, xreversesorted_bottom_idxs))
    return sorted_points, sorted_idxs

# %% ../../nbs/01t_utils.misc.ipynb 8
def kwargs_grid(**kwargs):
    "Returns a generator with all combinations of kwargs"
    return (dict(zip(kwargs.keys(), v)) for v in itertools.product(*kwargs.values()))

# %% ../../nbs/01t_utils.misc.ipynb 10
@functools.wraps(zip)
def safe_zip(*args, **kwargs):
    if len(set(map(len, args))) not in (0, 1):
        raise ValueError(f'All elements should have the same size, but got {[len(x) for x in args]}')
    return zip(*args, **kwargs)

# %% ../../nbs/01t_utils.misc.ipynb 13
class skip_error:
    def __init__(self, fn, log=True):
        'Returns the error instead of raising it.'
        store_attr()
        
    def __call__(self, *args, **kwargs):
        try: return self.fn(*args, **kwargs)
        except Exception as e: 
            if self.log: print(f'{e} {(args, kwargs)}')
            return e

# %% ../../nbs/01t_utils.misc.ipynb 15
class Cache:
    def __init__(self, func, save_dir=None, debug=False):
        self.func = func
        self.save_dir = Path(save_dir or Path.home()/'.cache/polvo')
        self.debug = debug
        
    def get(self, name, use_cache=True, **func_kwargs):
        path = self.save_dir/name
        try: 
            if use_cache: 
                o = pv.open_pickle(path)
                if self.debug: print('Using cache.')
                return o
        except FileNotFoundError: 
            pass
        
        if self.debug: print('Not using cache.')
        x = self.func(**func_kwargs)
        pv.mkdir(path.parent, exist_ok=True)
        pv.save_pickle(x, path)
        return x
    
    def list_cached(self, **kwargs):
        return pv.get_files(self.save_dir, **kwargs)

# %% ../../nbs/01t_utils.misc.ipynb 18
def transparent_cmap():
    from matplotlib.colors import LinearSegmentedColormap
    colors = [(0, 0, 0, 0) for _ in range(256)]
    return LinearSegmentedColormap.from_list('transparent_cmap', colors, N=256)

# %% ../../nbs/01t_utils.misc.ipynb 23
class partial:
    """Wraps functools.partial, same functionality.
    Modifies the original partial `__repr__` and `__str__`.
    """

    def __init__(self, func, *args, **kwargs):
        self._partial = functools.partial(func, *args, **kwargs)

    def __call__(self, *args, **kwargs): return self._partial(*args, **kwargs)

    def __str__(self):
        name = self._partial.func.__name__
        partial_str = str(self._partial)
        return re.sub(r"<.+>", name, partial_str)

    def __repr__(self): return str(self)

# %% ../../nbs/01t_utils.misc.ipynb 24
@contextmanager
def random_local_seed(seed):
    state = random.getstate()
    random.seed(seed)
    try: yield
    finally: random.setstate(state)

# %% ../../nbs/01t_utils.misc.ipynb 25
def random_color(): return tuple(((np.random.random(3)*0.6 + 0.4)*255).astype(int))
