# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/02_grds.ipynb.

# %% auto 0
__all__ = ['isnan', 'isnanlike', 'checknan', 'isiter', 'isstrbytes', 'isnotiterstr', 'isorigin', 'istyping', 'istype',
           'trychecks', 'isallsame', 'aliascheck']

# %% ../nbs/02_grds.ipynb 6
import os, io, inspect
from math import isnan as pyisnan, nan as pynan
from operator import eq, is_
from contextlib import redirect_stderr

# %% ../nbs/02_grds.ipynb 8
from types import NoneType, FunctionType
from typing import (
    Self, Type, TypeVar, TypeGuard, TypeAlias, ParamSpec,
    Callable, Iterable, Optional, Protocol, _ProtocolMeta, 
    Any, Set, Dict, List, Tuple, Union, ClassVar, Generic, Literal,
    get_args, get_origin, runtime_checkable, overload, 
)

# %% ../nbs/02_grds.ipynb 10
#| export


# %% ../nbs/02_grds.ipynb 12
#| export


# %% ../nbs/02_grds.ipynb 14
from .cons import ALLATTRS
from ptyp.type import (
    T, P, Types, TypeLike, Guard,
    TGuard, IterT, IterFunc, NanGuard, IterGuard, IterTGuard,
    IterStr, AttrGuards
)

# %% ../nbs/02_grds.ipynb 17
def isnan(o: object, t: type | None = None) -> NanGuard:
    # if t is not None and not isnan(t, None): return False # NOTE: only apply isnan check if t is None
    try: return pyisnan(o)
    except: return False
    
def isnanlike(o: object, t: type) -> NanGuard:
    return all((isnan(t), isnan(o)))

def checknan(o: object, t: type) -> NanGuard:
    if isnan(t): return isnan(o)
    return False

# %% ../nbs/02_grds.ipynb 19
def isiter(o: object) -> IterGuard: 
    return isinstance(o, Iterable)

def isstrbytes(o: object) -> TypeGuard[Union[str, bytes]]: 
    return isinstance(o, (str, bytes))

def isnotiterstr(x) -> IterGuard: 
    return isiter(x) and not isstrbytes(x)

# %% ../nbs/02_grds.ipynb 21
def isorigin(t: T) -> TypeGuard[Union[Set, List, type, Type, Tuple, Iterable]]:
    types = (Set, List, type, Type, Tuple, Union, Literal, Iterable)
    return any((get_origin(t) is _ for _ in types))

def istyping(o: object, t: T) -> TypeGuard[T]:
    if not isorigin(t): return False
    try: return any(((o == (args := get_args(t))), o in args, o == t))
    except: return False
    
def istype(o: object, t: T, guards: IterFunc = tuple()) -> TGuard:
    funcs = (isinstance, istyping, eq, is_) + tuple(guards)
    with redirect_stderr(io.StringIO()):
        for comp in funcs:
            try:
                if comp(o, t): return True
            except: continue
    return False


# %% ../nbs/02_grds.ipynb 23
def trychecks(o: object, t: type) -> Guard:
    fns = (isinstance, issubclass, eq)
    if isnan(t): fns += (checknan, )
    for f in fns:
        try:
            if f(o, t): return True
        except: continue
    return False

# %% ../nbs/02_grds.ipynb 25
def isallsame(it: Iterable, dtype: T) -> IterTGuard:
    '''Checks if all elements in an iterable are of the same specified type.'''
    # not iterable, confirm type
    if not isiter(it): return trychecks(it, dtype)
    for el in it:
        # check if element is iterable and not a string (e.g. list, array, tuple, etc)
        if isnotiterstr(el): # if iterable, then recurse each element in the iterable
            try:                
                if not isallsame(el, dtype): return False 
            except TypeError: return False
            
        elif not trychecks(el, dtype): return False # otherwise check just this element
    return True

# %% ../nbs/02_grds.ipynb 27
def aliascheck(
    obj: T,           # object to check
    types: IterT, # types to check
    guards: IterFunc = tuple(),
    attrs: dict = dict(),
    hasattrs: IterStr = tuple(),  # attributes to check
    allattrs: IterStr = ALLATTRS, # attributes to check that values are all the same if object is iterable
    mapattrs: AttrGuards = dict(),
):
    '''Checks if an object matches a set of types and specific attributes.'''
    # print('aliascheck', obj, types, guards, attrs, hasattrs, allattrs, mapattrs)
    hasattrs = tuple(hasattrs) + tuple(k for k, v in attrs.items() if v not in allattrs)
    if not all(hasattr(obj, attr) for attr in hasattrs): return False
    
    
    found = any(istype(obj, t, guards) for t in types)
    # print(obj, found, list(istype(obj, t, guards) for t in types))
    if not found: return False
    
    for attr, val in attrs.items():        
        if getattr(obj, attr, None) == val:
            continue
        if attr in allattrs and isnotiterstr(obj) and isallsame(obj, val): 
            continue
        if attr in mapattrs and mapattrs[attr](obj, val):
            continue
    return True
