import numpy as np
from typing import Union, Iterator, Callable
import numpy.typing as npt


class BpfPointsError(ValueError): ...

class BpfInversionError(ValueError): ...


class BpfInterface:
    def ntodx(self, N: int) -> float: ...
    def dxton(self, dx: float) -> int: ...
    def bounds(self) -> tuple[float, float]: ...

    @property
    def x0(self) -> float: ...


    @property
    def x1(self) -> float: ...

    def __add__(self, b: BpfInterface | float) -> BpfInterface: ...

    def __sub__(self, b: BpfInterface | float) -> BpfInterface: ...

    def __mul__(self, b: BpfInterface | float) -> BpfInterface: ...

    def __div__(self, b: BpfInterface | float) -> BpfInterface: ...

    def __truediv__(self, b: BpfInterface | float) -> BpfInterface: ...

    def __pow__(self, b: BpfInterface | float, module: float) -> BpfInterface: ...

    def __neg__(self: BpfInterface) -> BpfInterface: ...

    def __mod__(self, other: BpfInterface | float) -> BpfInterface: ...

    def __abs__(self) -> BpfInterface: ...

    def __or__(self, b: BpfInterface|float) -> BpfInterface: ...

    def __rshift__(self, b: BpfInterface|float) -> BpfInterface: ...

    def __lshift__(self, b: BpfInterface|float) -> BpfInterface: ...

    def __xor__(self, b: BpfInterface|float) -> BpfInterface: ...

    def _get_points_for_rendering(self, n: int = -1) -> tuple[np.ndarray, np.ndarray]: ...

    def render(self, xs: int | np.ndarray | list[float], interpolation: str = 'linear') -> np.ndarray: ...

    def sampled(self, dx: float, interpolation='linear') -> BpfInterface: ...

    def plot(self, kind: str = 'line', n: int = -1, show: bool = True, axes=None, **keys) -> None: ...

    def sample_between(self, x0: float, x1: float, dx: float, out: np.ndarray | float | None = None) -> np.ndarray: ...

    def sampled_between(self, x0: float, x1: float, dx: float, interpolation='linear') -> BpfInterface: ...

    def mapn_between(self, n: int, x0: float, x1: float, out: np.ndarray | None = None) -> np.ndarray: ...

    def map(self, xs: int | np.ndarray, out: np.ndarray | None = None) -> np.ndarray: ...

    def concat(self, other: BpfInterface)-> BpfInterface: ...

    def round(self) -> BpfInterface: ...

    def rand(self) -> BpfInterface: ...

    def cos(self) -> BpfInterface: ...

    def sin(self) -> BpfInterface: ...

    def ceil(self) -> BpfInterface: ...

    def expon(self) -> BpfInterface: ...

    def floor(self) -> BpfInterface: ...

    def tanh(self) -> BpfInterface: ...

    def abs(self) -> BpfInterface: ...

    def sqrt(self) -> BpfInterface: ...

    def acos(self) -> BpfInterface: ...

    def asin(self) -> BpfInterface: ...

    def tan(self) -> BpfInterface: ...

    def sinh(self) -> BpfInterface: ...

    def log10(self) -> BpfInterface: ...

    def log(self, base: float = 2.718281828459045) -> BpfInterface: ...

    def m2f(self) -> BpfInterface: ...

    def f2m(self) -> BpfInterface: ...
    def db2amp(self) -> BpfInterface: ...
    def amp2db(self) -> BpfInterface: ...
    def clip(self, y0: float = float('-inf'), y1: float = float('inf')) -> BpfInterface: ...
    def derivative(self) -> BpfInterface: ...
    def integrated(self) -> BpfInterface: ...
    def integrate(self) -> float: ...
    def integrate_between(self, x0: float, x1: float, N: int = 0) -> float: ...
    def mean(self) -> float: ...
    def zeros(self, h: float = 0.01, N: int = 0,
              x0: float = float('nan'), x1: float = float('nan'), maxzeros: int = 0) -> list[float]: ...
    def max(self, b: BpfInterface|float) -> BpfInterface: ...
    def min(self, b: BpfInterface|float) -> BpfInterface: ...
    def __call__(self, other: float) -> float: ...

    def keep_slope(self, epsilon: float = 0.) -> BpfInterface: ...

    def outbound(self, y0: float, y1: float) -> BpfInterface: ...

    def apply(self, func) -> BpfInterface: ...

    def preapply(self, func) -> BpfInterface: ...

    def periodic(self) -> BpfInterface: ...

    def stretched(self, rx: float, fixpoint: float = 0.) -> BpfInterface: ...

    def fit_between(self, x0: float, x1: float) -> BpfInterface: ...

    def shifted(self, dx: float) -> BpfInterface: ...

    def inverted(self) -> BpfInterface: ...

    @classmethod
    def fromseq(cls, *points: float|tuple[float, float], **kws) -> BpfInterface: ...

    def copy(self) -> BpfInterface: ...


class BpfBase(BpfInterface):
    def __init__(self,
                 xs: Union[np.ndarray, list[float]],
                 ys: Union[np.ndarray, list[float]]
                 ): ...

    @property
    def descriptor(self) -> str: ...

    def mapn_between(self, n: int, xstart: float, xend: float, out: np.ndarray|None = None) -> np.ndarray: ...

    def points(self) -> tuple[np.ndarray, np.ndarray]: ...

    def clone_with_new_data(self, xs: np.ndarray, ys: np.ndarray) -> BpfInterface: ...

    def insertpoint(self, x: float, y: float) -> BpfInterface: ...

    def removepoint(self, x: float) -> BpfInterface: ...

    def segments(self) -> Iterator[tuple[float, float, float]]: ...

    @property
    def exp(self) -> float: ...


class Linear(BpfBase):

    def __init__(self,
                 xs: Union[np.ndarray, list[float]],
                 ys: Union[np.ndarray, list[float]]
                 ): ...


    def inverted(self) -> Linear: ...

    def flatpairs(self) -> np.ndarray: ...


class Smooth(BpfBase):
    def __init__(self,
                 xs: Union[np.ndarray, list[float]],
                 ys: Union[np.ndarray, list[float]]
                 ): ...


class Smoother(BpfBase):
    def __init__(self,
                 xs: Union[np.ndarray, list[float]],
                 ys: Union[np.ndarray, list[float]]
                 ): ...


class Halfcos(BpfBase):
    def __init__(self,
                 xs: Union[np.ndarray, list[float]],
                 ys: Union[np.ndarray, list[float]],
                 exp: float = 1.0, numiter: int =1): ...


class Halfcosm(Halfcos):
    def __init__(self,
                 xs: Union[np.ndarray, list[float]],
                 ys: Union[np.ndarray, list[float]],
                 exp: float = 1.0, numiter: int =1): ...


class Expon(BpfBase):
    def __init__(self,
                 xs: Union[np.ndarray, list[float]],
                 ys: Union[np.ndarray, list[float]],
                 exp: float = 1.0, numiter: int =1): ...


class Exponm(Expon):
    def __init__(self,
                 xs: Union[np.ndarray, list[float]],
                 ys: Union[np.ndarray, list[float]],
                 exp: float = 1.0, numiter: int =1): ...


class NoInterpol(BpfBase):
    def __init__(self,
                 xs: Union[np.ndarray, list[float]],
                 ys: Union[np.ndarray, list[float]]
                 ): ...


class Nearest(BpfBase):
    def __init__(self,
                 xs: Union[np.ndarray, list[float]],
                 ys: Union[np.ndarray, list[float]]
                 ): ...


class Sampled(BpfInterface):
    def __init__(self,
                 samples: np.ndarray,
                 dx: float,
                 x0: float = 0.,
                 interpolation: str = 'linear'): ...

    @property
    def ys(self) -> np.ndarray: ...


    @property
    def samplerate(self) -> float: ...

    @property
    def xs(self) -> np.ndarray: ...


    @property
    def dx(self) -> float: ...

    def set_interpolation(self, interpolation: str) -> Sampled: ...


    def flatpairs(self) -> np.ndarray: ...



class Spline(BpfInterface):
    def __init__(self,
                 xs: Union[np.ndarray, list[float]],
                 ys: Union[np.ndarray, list[float]]
                 ): ...


class USpline(BpfInterface):
    def __init__(self,
                 xs: Union[np.ndarray, list[float]],
                 ys: Union[np.ndarray, list[float]]
                 ): ...


class Slope(BpfInterface):

    @property
    def slope(self) -> float: ...

    @slope.setter
    def slope(self, value: float) -> None: ...

    @property
    def offset(self) -> float: ...

    @offset.setter
    def offset(self, value: float) -> None: ...



def _FunctionWrap(f: Callable[[float], float], bounds=(INFNEG, INF)) -> BpfInterface: ...


class _BpfCompose(BpfInterface): ...


class _BpfConcat(BpfInterface): ...


class _BpfBlend(BpfInterface): ...


class Multi(BpfInterface):
    def __init__(self, xs: npt.ArrayLike, ys: npt.ArrayLike, interpolations: list[str]) -> None: ...


class Const(BpfInterface):
    def __init__(self, value: float) -> None: ...


class _MultipleBpfs(BpfInterface):

    def __init__(self, bpfs: list[BpfInterface]) -> None: ...


class Max(_MultipleBpfs): ...

class Min(_MultipleBpfs): ...

class Stack(_MultipleBpfs): ...


def brentq(bpf: BpfInterface, x0: float, xa: float, xb: float, xtol=9.9999999999999998e-13,
           rtol=4.4408920985006262e-16, max_iter=100) -> tuple[float,int]: ...


def blend(a: BpfInterface, b: BpfInterface, mix=0.5) -> BpfInterface: ...



def bpf_zero_crossings(b: BpfInterface,
                       h: float = 0.01, N: int = 0,
                       x0: float = float('nan'),
                       x1: float = float('nan'),
                       maxzeros: int = 0
                       ) -> list[float]: ...
