# Copyright (C) 2023 IBM Corp.
# SPDX-License-Identifier: Apache-2.0
"""
Utility functions.
(Not intended for external use.)
"""
import logging
from collections import deque
from copy import copy, deepcopy
from functools import cmp_to_key, lru_cache, reduce, total_ordering, wraps
from hashlib import sha256
from itertools import chain, combinations, count, dropwhile, repeat, starmap
from pathlib import Path
from re import compile
from more_itertools import (always_reversible, first, flatten, islice_extended,
                            last, peekable, sliding_window, unique_everseen)
islice = islice_extended
# -- Combinators -----------------------------------------------------------
[docs]
def identity(x):
    """The identity function, i.e., (𝜆 x ⇒ x)."""
    return x 
[docs]
def all_map(f, xs):
    """Tests whether `f` holds for all `x` in `xs`."""
    return all(map(f, xs)) 
[docs]
def any_map(f, xs):
    """Tests whether `f` holds for some `x` in `xs`."""
    return any(map(f, xs)) 
[docs]
def flip(f):
    """Returns the function (𝜆 x, y ⇒ f(y, x))."""
    @wraps(f)
    def g(x, y):
        return f(y, x)
    return g 
[docs]
def foldl(f, x, xs):
    """Left-folds `xs` with using `f` and starting value `x`."""
    return reduce(f, xs, x) 
[docs]
def foldl_args(f, x, *args):
    """Same as :func:`foldl` but applies to varargs."""
    return foldl(f, x, args) 
[docs]
def foldl1(f, xs):
    """Left-folds `xs` using `f` and no starting value."""
    return reduce(f, xs) 
[docs]
def foldl1_args(f, *args):
    """Same as :func:`foldl1` but applies to varargs."""
    return foldl1(f, args) 
[docs]
def foldl_infix(f, g, arg1, arg2, *args, **kwargs):
    """Left-fold variant used to apply infix operators.
    For example:
    .. code-block:: python
       foldl_infix(f, g, [1,2,3,4,5], **kwargs)
    is equivalent to:
    .. code-block:: python
       f(g(g(g(1, 2), 3), 4), 5, **kwargs)
    """
    if not args:
        return f(arg1, arg2, **kwargs)
    else:
        it = chain([arg1, arg2], islice(args, len(args) - 1))
        return f(foldl1(g, it), args[-1], **kwargs) 
[docs]
def unfoldl(f, x):
    """Left-unfolds `x` using unpacking function `f`.
    Equivalent to:
    .. code-block:: python
       def g(x):
          if f(x) is None:      # no more unpacking
             return [x]
          else:
             return g(x[0]) + [x[1]]
    Used to recover values which were left-folded.  For example:
    .. code-block:: python
       >>> f = (lambda t: t if isinstance(t, tuple) else None)
       >>> it = unfold(f, (((1, 2), 3), 4))
       >>> list(it)
       [1, 2, 3, 4]
    """
    return always_reversible(_unfoldl(f, x)) 
def _unfoldl(f, x):
    while True:
        t = f(x)
        if t is None:
            yield x
            break
        x, r = t
        yield r
[docs]
def foldr(f, x, xs):
    """Right-folds `xs` using `f` and starting value `x`."""
    #
    # This seems to be the fastest version.
    # See <tests/profile_util.py>.
    #
    for y in reversed(xs):
        x = f(y, x)
    return x 
[docs]
def foldr_args(f, x, *args):
    """Same as :func:`foldr` but applies to varargs."""
    return foldr(f, x, args) 
[docs]
def foldr1(f, xs):
    """Right-folds `xs` using `f` and no starting value."""
    #
    # The efficiency remarks regarding foldr() also apply here.
    # See <tests/profile_util.py>.
    #
    it = reversed(xs)
    x = next(it)
    for y in it:
        x = f(y, x)
    return x 
[docs]
def foldr1_args(f, *args):
    """Same as :func:`foldr1` but applies to varargs."""
    return foldr1(f, args) 
[docs]
def foldr_infix(f, g, arg1, arg2, *args, **kwargs):
    """Right-fold variant used to apply infix operators.
    For example:
    .. code-block:: python
       foldr_infix(f, g, [1,2,3,4,5], **kwargs)
    is equivalent to:
    .. code-block:: python
       f(1, g(2, g(3, g(4, 5))), **kwargs)
    """
    if not args:
        return f(arg1, arg2, **kwargs)
    else:
        return f(arg1, foldr1_args(g, arg2, *args), **kwargs) 
[docs]
def unfoldr(f, x):
    """Right-unfolds `x` using unpacking function `f`.
    Equivalent to:
    .. code-block:: python
       def h(t):
          if f(t) is None:      # no more unpacking
             return [t]
          else:
             return [t[0]] + h(t[1])
    Used to recover values which were right-folded.  For example:
    .. code-block:: python
       >>> f = (lambda t: t if isinstance(t, tuple) else None)
       >>> it = unfold(f, (1, (2, (3, 4))))
       >>> list(it)
       [1, 2, 3, 4]
    """
    while True:
        t = f(x)
        if t is None:
            yield x
            break
        l, x = t
        yield l 
[docs]
def map_args(f, *args):
    """Same as :func:`map` but applies to varargs."""
    return map(f, args) 
[docs]
def match_first(f, xs, x=None):
    """Returns the first element of `xs` satisfying `f`; or `x`"""
    return foldr(lambda x, y: x if f(x) else y, x, xs) 
[docs]
def match_last(f, xs, x=None):  # last element satisfying f; or x
    """Returns the last element of `xs` satisfying `f`; or `x`."""
    return foldl(lambda x, y: y if f(y) else x, x, xs) 
[docs]
def sliding_pairs(it):
    """Returns a sliding window of width 2 over `it`."""
    return sliding_window(it, 2) 
[docs]
def sliding_pairs_args(x1, x2, *xs):
    """Same as :func:`sliding_pairs` but applies to varargs."""
    return sliding_pairs(chain([x1, x2], xs)) 
# -- Proxy -----------------------------------------------------------------
[docs]
class Proxy:
    """Proxy object.
    Forwards any access to the object obtained using `get_proxy`.
    """
    def __init__(self, get_proxy):
        get_proxy = get_proxy if callable(get_proxy) else lambda: get_proxy
        super().__setattr__('_get_proxy', get_proxy)
    def __call__(self, *args, **kwargs):
        return self._get_proxy()(*args, **kwargs)
    def __getattr__(self, k):
        return getattr(self._get_proxy(), k)
    def __setattr__(self, k, v):
        setattr(self._get_proxy(), k, v)
    def __delattr__(self, k):
        delattr(self._get_proxy(), k)
    def __str__(self):
        return str(self._get_proxy()) 
# -- Imports ---------------------------------------------------------------
[docs]
def get_package_data_dir(modname):
    """Returns package-data directory of `modname`."""
    import importlib
    return Path(importlib.util.find_spec(modname).origin).parent 
# -- Misc ------------------------------------------------------------------
[docs]
class Nil:
    """Class representing the absence of value."""
    pass 
[docs]
def camel2snake(
        name,
        re1=compile(r'(.)([A-Z][a-z]+)'),
        re2=compile(r'([a-z0-9])([A-Z])')):
    """Converts `name` from camel-case to snake-case."""
    return re2.sub(r'\1_\2', re1.sub(r'\1_\2', name)).lower() 
[docs]
def get_variant(name, re=compile(r'(.*?)(\d*)$')):
    """Returns the next numerical-suffixed variant of `name`."""
    pre, suf = re.match(name).groups()
    if suf:
        return pre + str(int(suf) + 1)
    else:
        return pre + '0' 
[docs]
def get_variant_not_in(name, avoid):
    """Same as :func:`get_variant` but skip variants in `avoid`."""
    var = name
    while var in avoid:
        var = get_variant(name)
    return var