# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_core.ipynb.

# %% auto 0
__all__ = ['WRAPS_ASSIGN_ANNDOCS', 'percent', 'round_percent', 'percents', 'round_percents', 'ipercent', 'integer_percent',
           'ipercents', 'integer_percents', 'pints', 'spercents', 'sum_percents', 'apercents', 'accumulate_percents',
           'asums', 'dpercents', 'diff_percents', 'rpercents', 'perbounds', 'range_percents', 'idxstep', 'index_step',
           'idxperm', 'permute_indicies', 'index_split_by_percents', 'pidxs', 'npercents', 'npcts', 'norm_percents',
           'normalize_percents']

# %% ../nbs/00_core.ipynb 6
import random
from contextlib import redirect_stdout, redirect_stderr
from functools import wraps, partial
from itertools import takewhile, accumulate
from importlib import import_module

# %% ../nbs/00_core.ipynb 8
from types import FunctionType, ModuleType
from numbers import Number

# %% ../nbs/00_core.ipynb 11
#| export

# %% ../nbs/00_core.ipynb 13
#| export


# %% ../nbs/00_core.ipynb 15
#| export


# %% ../nbs/00_core.ipynb 17
#| export


# %% ../nbs/00_core.ipynb 19
from seeder import seedall

# %% ../nbs/00_core.ipynb 21
WRAPS_ASSIGN_ANNDOCS = ('__doc__', '__module__', '__annotations__')

# %% ../nbs/00_core.ipynb 23
def percent(p: float, ndigits: int = 3) -> float:
    '''Converts a percentage `p` to a decimal, rounding to `ndigits` decimal places.
    
    Parameters
    ----------
    p : float
        The percentage to convert.
        
    ndigits : int, default 3
        The number of decimal places to round to.
        
    Returns
    -------
    float
        The converted decimal percentage.
        
    See Also
    --------
    round_percent : Alias for percent
        
    '''
    return round(p if (p := abs(p)) < 1 else p / 100, ndigits)

@wraps(percent, assigned=WRAPS_ASSIGN_ANNDOCS)
def round_percent(p: float, ndigits: int = 3) -> float:
    '''Alias for `percent`.'''
    return percent(p, ndigits)

# %% ../nbs/00_core.ipynb 24
def percents(*pcts: float, ndigits: int = 3) -> tuple[float, ...]:
    '''Converts the percentages `pcts` to a decimal, rounding to `ndigits` decimal places.
    
    Parameters
    ----------
    pcts : *float
        The percentages to convert.
        
    ndigits : int, default 3
        The number of decimal places to round to.
        
    Returns
    -------
    tuple[float, ...]
        The converted decimal percentage.
        
    See Also
    --------
    round_percent : Alias for percent
        
    '''
    return tuple(filter(bool, (percent((p or 0), ndigits=ndigits) for p in  pcts)))

@wraps(percents, assigned=WRAPS_ASSIGN_ANNDOCS)
def round_percents(*pcts: float, ndigits: int = 3) -> tuple[float, ...]:
    '''Alias for `percent`.'''
    return percents(*pcts, ndigits=ndigits)

# %% ../nbs/00_core.ipynb 26
def ipercent(p: float = 1., total: int = 1, ndigits: int = 3) -> int:
    '''Calculates the integer part of `p` percent of `total`, 
    rounded to `ndigits` decimal places.
    
    Parameters
    ----------    
    p : float, default: 1.0
        The percentage to calculate.
        
    total : int, default: 1
        The total amount to calculate the percentage of.

    ndigits : int, default: 3
        The number of decimal places to round to.
        
    Returns
    -------
    int
        The integer part of the calculated percentage.
        
    See Also
    --------
    integer_percent : Alias for ipercent
    '''
    return int(total * percent(p, ndigits))

@wraps(ipercent, assigned=WRAPS_ASSIGN_ANNDOCS)
def integer_percent(*args, **kwargs):
    '''Alias for `ipercent`.'''
    return ipercent(*args, **kwargs)

# %% ../nbs/00_core.ipynb 28
def ipercents(
    *percents: float, 
    total: int = 1, 
    ndigits: int = 3, 
    useall: bool = True, 
    overflow: bool = False,
) -> tuple[int, ...]:
    '''Divides total into the corresponding integer values given the
    percents in `*percents` and ensures that all values are used
    
    Parameters
    ----------
    *percents : float
        Percentages to divide `total` into.
        
    total : int
        The total amount to divide according to `percents`.
        
    ndigits : int, default: 3
        The number of decimal places to round percentages to.
        
    useall : bool, default: True
        Whether to ensure that the sum of values is equal to `total`.

    overflow : bool, default: False
        Whether to allow overflowing the total amount. If `False`, the last value will be
        less than or equal to the total amount.
    
    Returns
    -------
    tuple[int, ...]
        The integer values corresponding to the percentages in `percents`.
        
    See Also
    --------
    pints : Alias for `ipercents`
    integer_percents : Alias for `ipercents`
    '''
    pints = [ipercent(pct, total=total, ndigits=ndigits) for pct in percents]    
    if not overflow and sum(pints) > total: pints.pop()
    if useall and sum(pints) < total: pints.append(total - sum(pints))
    return pints

@wraps(ipercents, assigned=WRAPS_ASSIGN_ANNDOCS)
def integer_percents(*args, **kwargs):
    '''Alias for `ipercents`.'''
    return ipercents(*args, **kwargs)

@wraps(ipercents, assigned=WRAPS_ASSIGN_ANNDOCS)
def pints(*args, **kwargs):
    '''Alias for `ipercents`.'''
    return ipercents(*args, **kwargs)

# %% ../nbs/00_core.ipynb 30
def spercents(*pcts: float, total: int = 1) -> tuple[float, ...]:
    sums = tuple(takewhile(lambda s: s <= total, accumulate(pcts)))
    return sums

@wraps(spercents)
def sum_percents(*pcts, total: int = 1) -> tuple[float, ...]:
    return spercents(*pcts, total=total)

# %% ../nbs/00_core.ipynb 31
def apercents(
    *percents: float, 
    total: int = 1, 
    ndigits: int = 3,
    useall: bool = True, 
    overflow: bool = False,
) -> tuple[int, ...]:
    '''Accumulate the integer values of the corresponding percentages for `total`.
    
    Parameters
    ----------
    total : int
        The total amount to divide according to `percents`.
        
    *percents : float
        Percentages to divide `total` into.
        
    ndigits : int, default: 3
        The number of decimal places to round percentages to.
        
    useall : bool, default: True
        Whether to ensure that the sum of values is equal to `total`.

    overflow : bool, default: False
        Whether to allow overflowing the total amount. If `False`, the last value will be
        less than or equal to the total amount.
    
    Returns
    -------
    tuple[tuple[int, int], ...]
        Tuples of start and end indices for each percentage of `total`.
        
    See Also
    --------
    accumulate_percents : Alias for `apercents`
    asums : Alias for `apercents`
    '''
    pints = ipercents(*percents, total=total, ndigits=ndigits, useall=useall, overflow=overflow)
    asums = sum_percents(*pints, total=total)
    # asums = takewhile(lambda s: s <= total, accumulate(pints))
    return tuple(asums)

@wraps(apercents, assigned=WRAPS_ASSIGN_ANNDOCS)
def accumulate_percents(*args, **kwargs):
    '''Alias for `apercents`.'''
    return apercents(*args, **kwargs)

@wraps(apercents, assigned=WRAPS_ASSIGN_ANNDOCS)
def asums(*args, **kwargs):
    '''Alias for `apercents`.'''
    return apercents(*args, **kwargs)

# %% ../nbs/00_core.ipynb 33
def dpercents(*pcts, sums: tuple[float, ...] | None = None, total: int = 1) -> tuple[float, ...]:
    if sums is None: sums = sum_percents(*pcts, total=total)
    diff = tuple((idx-off, idx) for off, idx in zip(pcts, asums))
    return diff

@wraps(dpercents)
def diff_percents(*pcts, sums: tuple[float, ...] | None = None, total: int = 1) -> tuple[float, ...]:
    return dpercents(*pcts, sums=sums, total=total)

# %% ../nbs/00_core.ipynb 34
def rpercents(
    *percents: float, 
    total: int = 1, 
    ndigits: int = 3,
    useall: bool = True, 
    overflow: bool = False,
) -> tuple[tuple[int, int], ...]:
    '''Generates tuples representing ranges based on percentages of `total`.
    
    Parameters
    ----------
    *percents : float
        Percentages to divide `total` into.
        
    total : int
        The total amount to divide according to `percents`.
        
    ndigits : int, default: 3
        The number of decimal places to round percentages to.
        
    Returns
    -------
    tuple[tuple[int, int], ...]
        Tuples of start and end indices for each percentage of `total`.
        
    See Also
    --------
    range_percents : Alias for `rpercents`
    perbounds : Alias for `rpercents`
    idxstep : Alias for `rpercents`
    index_step : Alias for `rpercents`
    '''
    pints = ipercents(*percents, total=total, ndigits=ndigits, useall=useall, overflow=overflow)
    asums = apercents(*percents, total=total, ndigits=ndigits, useall=useall, overflow=overflow)
    return list((idx-off, idx) for off, idx in zip(pints, asums))

@wraps(rpercents, assigned=WRAPS_ASSIGN_ANNDOCS)
def perbounds(*args, **kwargs):
    '''Alias for `rpercents`.'''
    return rpercents(*args, **kwargs)

@wraps(rpercents, assigned=WRAPS_ASSIGN_ANNDOCS)
def range_percents(*args, **kwargs):
    '''Alias for `rpercents`.'''
    return rpercents(*args, **kwargs)

@wraps(rpercents, assigned=WRAPS_ASSIGN_ANNDOCS)
def idxstep(*args, **kwargs):
    '''Alias for `rpercents`.'''
    return rpercents(*args, **kwargs)


@wraps(rpercents, assigned=WRAPS_ASSIGN_ANNDOCS)
def index_step(*args, **kwargs):
    '''Alias for `rpercents`.'''
    return rpercents(*args, **kwargs)

# %% ../nbs/00_core.ipynb 36
def idxperm(*percents: float, total: int = 1, seed: int = 3) -> tuple[tuple[Number, ...], ...]:
    '''Generates a permutation of indices divided into segments based on `percents` of `total`.
    
    Parameters
    ----------
    *percents : float
        The percentages to divide the total indices into.
        
    total : int
        The total number of indices to permute.
        
    seed : int, default 3
        The seed for the random number generator.
        
    Returns
    -------
    tuple[ints, ...]
        nums of indices for each segment based on `percents`.
        
    See Also
    --------
    idxstep : Generates a step of indices based on `percents` of `total`.
    permute_indicies : Alias for `idxperm`.
    index_split_by_percents : Alias for `idxperm`.
    pidxs : Alias for `idxperm`.
    '''
    idx = list(range(total))
    seedall(seed)
    random.shuffle(idx)
    return tuple(idx[a:b] for a, b in range_percents(*percents, total=total))

@wraps(idxperm, assigned=WRAPS_ASSIGN_ANNDOCS)
def permute_indicies(*percents: float, total: int = 1, seed: int = 3) -> tuple[tuple[Number, ...], ...]:
    '''Alias for `idxperm`.'''
    return idxperm(*percents, total=total, seed=seed)

@wraps(idxperm, assigned=WRAPS_ASSIGN_ANNDOCS)
def index_split_by_percents(*percents: float, total: int = 1, seed: int = 3) -> tuple[tuple[Number, ...], ...]:
    '''Alias for `idxperm`.'''
    return idxperm(*percents, total=total, seed=seed)


@wraps(idxperm, assigned=WRAPS_ASSIGN_ANNDOCS)
def pidxs(*percents: float, total: int = 1, seed: int = 3) -> tuple[tuple[Number, ...], ...]:
    '''Alias for `idxperm`.'''
    return idxperm(*percents, total=total, seed=seed)

# %% ../nbs/00_core.ipynb 38
def npercents(*pcts, ndigits: int = 3) -> tuple[float, ...]:
    ps = round_percents(*pcts, ndigits=ndigits)
    if sum(ps) > 1: ps.pop()        
    if sum(ps) <= 1: 
        diff = percent(1 - sum(ps))
        if len(ps) == len(pcts) or len(ps) == 1: 
            ps[-1] += diff
        else: 
            ps.append(diff)
    while len(ps) < len(pcts): ps.append(0)
    return ps
        

@wraps(npercents, assigned=WRAPS_ASSIGN_ANNDOCS)
def npcts(*percents: float) -> tuple[float, ...]:
    '''Alias for `npercents`.'''
    return npercents(*percents)

@wraps(npercents, assigned=WRAPS_ASSIGN_ANNDOCS)
def norm_percents(*percents: float) -> tuple[float, ...]:
    '''Alias for `npercents`.'''
    return npercents(*percents)

@wraps(npercents, assigned=WRAPS_ASSIGN_ANNDOCS)
def normalize_percents(*percents: float) -> tuple[float, ...]:
    '''Alias for `npercents`.'''
    return npercents(*percents)


# %% ../nbs/00_core.ipynb 40
#| export
