# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/03_bflag.ipynb.

# %% auto 0
__all__ = ['bflag']

# %% ../nbs/03_bflag.ipynb 3
import inspect
from typing import Any, Self, Callable, Optional

# %% ../nbs/03_bflag.ipynb 4
from bflag.types import (BoolCall, BoolFunc)
from bflag.futil import (hasvargs, haskwargs, islambda, lambdastr, mergevargs, getdefs)
from bflag.butil import (isbooltype, boolcall, echobool, ascall, callbool, asbool)

# %% ../nbs/03_bflag.ipynb 6
class bflag(Callable):
    '''
    A class for handling boolean flags with optional callable functionality.

    The `bflag` class aims to normalize, convert, and evaluate boolean functions or values.
    It provides class methods and instance methods to ensure that the boolean function or value
    passed is in an executable and normalized form.

    Attributes
    ----------
    bool : bool, optional
        The boolean value if the flag represents a simple boolean value.

    func : Callable
        The function representing the flag's behavior.

    clsname -> str
        Returns the name of the class.

    Methods
    -------    
    boolcall(b: BoolCall) -> BoolCall
        Normalizes a Boolean callable to either bool or Callable.

    ascall(b: BoolCall) -> BoolFunc
        Converts a BoolCall to BoolFunc.

    asbool(v: Any, b: BoolCall, *args, **kwargs) -> bool
        Evaluates a BoolCall with the given value and optional arguments.

    __call__(val, *args, **kwargs) -> bool
        Makes the instance callable and returns a bool.

    Notes
    -----
    The pipeline of how `bflag` works is as follows:

    1. `boolcall(b: BoolCall) -> BoolCall`
        - Ensure that `b` is either an instance of a `bool` or a `Callable`.

    2. `ascall(b: BoolCall) -> BoolFunc`
        - Convert `b` to a `BoolFunc` i.e., a `Callable` that returns a `bool`.

    3. `callbool(v: Any, b: BoolCall, *args, **kwargs) -> bool`
        - Evaluate `b` with `v` and optional arguments.

    4. `asbool(v: Any, b: BoolCall, *args, **kwargs) -> bool`
        - Essentially an alias for `callbool` but with a more descriptive name
            and calls `ascall` on `b` to ensure that it is a `BoolFunc`.

    This means that `bflag` will ensure that `b` is a `BoolFunc` and then evaluate it with `v` and optional arguments.  

    Examples
    --------
    Create flags for positive, negative, and zero values:

    >>> posflag = bflag(lambda v: v > 0)
    >>> negflag = bflag(lambda v: v < 0)
    >>> zerflag = bflag(lambda v: v == 0)

    Evaluate flags:

    >>> posflag(1)
    True
    >>> posflag(-1)
    False

    >>> negflag(1)
    False
    >>> negflag(-1)
    True

    >>> zerflag(1)
    False
    >>> zerflag(0)
    True  
    '''

    @property
    def clsname(self) -> str:
        return type(self).__name__
    
    @classmethod
    def boolcall(cls, b: BoolCall = None) -> BoolCall:
        '''Class method to normalize a Boolean callable to either bool or Callable.
        
        Parameters
        ----------
        b : BoolCall, optional
            The value to normalize.
            
        Returns
        -------
        BoolCall
            The normalized value.
        '''
        return boolcall(b)
    
    @classmethod
    def ascall(cls, b: BoolCall = None) -> BoolFunc:
        '''Class method to convert a BoolCall to BoolFunc.
        
        Parameters
        ----------
        b : BoolCall, optional
            The value to convert.
            
        Returns
        -------
        BoolFunc
            The converted function.
        '''
        return ascall(b)
    
    @classmethod
    def asbool(cls, v: Any, b: BoolCall, *args, **kwargs) -> bool:
        '''Class method to evaluate a BoolCall with the given value and optional arguments.
        
        Parameters
        ----------
        v : Any
            The value to evaluate.
        b : BoolCall
            The boolean function or value to use for the evaluation.
        args : tuple, optional
            Additional arguments to pass to the function.
        kwargs : dict, optional
            Additional keyword arguments to pass to the function.
            
        Returns
        -------
        bool
            The result of the evaluation.
        '''        
        return asbool(v, b, *args, **kwargs)
    
    @property
    def sigstr(self) -> str:
        '''Gets the signature string of the function.'''
        if islambda(self.func):
            return lambdastr(self.func)
        return inspect.signature(self.func).__str__()

    @property
    def fncstr(self) -> str:
        '''Gets the function string which includes the name and the signature.'''
        fnname = self.func.__name__.replace('<lambda>', '')
        fncstr = f'{fnname}{self.sigstr}'
        return self.bool if self.bool is not None else fncstr
    
    def __new__(cls, b: BoolCall = None, name: Optional[str] = None, *args, **kwargs) -> Self:        
        obj = super().__new__(cls)
        obj.bool = b if isinstance(b, bool) else None
        obj.func = ascall(b)
        obj.name = name if name is not None else obj.clsname
        obj.varg = tuple(*args)
        obj.kwds = kwargs.copy()
        return obj

    def getdefs(self, val, *args, **kwargs):
        '''Gets the default arguments of the function.'''
        return getdefs(self.func, self.varg, self.kwds, val, *args, **kwargs)

    def __call__(self, val, *args, **kwargs) -> bool:
        '''Converts `val` to a `bool` utilizing the flag function.'''
        varg, kwds = self.getdefs(val, *args, **kwargs)
        return self.asbool(val, self.func, *varg, **kwds)
    
    def __str__(self) -> str:
        return f'{self.name}({self.fncstr})'
    
    def __repr__(self) -> str:
        return str(self)
