from math import ceil, log


def icdf_n_activations(
    a: float, time_bleach: float, time_off: float, eps: float = 1e-12
):
    """
    Given a probability threshold a in (0, 1],
    returns the smallest number of activations n such that P(N>=n)<a.
    """
    if a > 1.0 or a <= 0.0:
        raise ValueError("Expect a to be in (0, 1].")
    p = time_bleach / (time_off + time_bleach + eps)
    n = log(a) / log(p)
    return 1 + int(ceil(n))


def average_time_off(time_on: float) -> float:
    """
    Returns the average time spent in one off state.
    """
    return time_on


def average_time_on(time_off: float, time_bleach: float) -> float:
    """
    Returns the average time spent in one on state.
    """
    return time_off * time_bleach / (time_off + time_bleach)


def average_num_activations(time_off: float, time_bleach: float):
    """
    Returns the average number of activations.
    """
    return 1.0 + time_bleach / time_off


def average_lifespan_on(time_off: float, time_bleach: float) -> float:
    """
    Returns the total average time spent in ON states.
    """
    return average_time_on(time_off, time_bleach) * average_num_activations(
        time_off, time_bleach
    )


def average_frames_on(time_off: float, time_bleach: float) -> float:
    """
    Returns the average number of frames a fluorophore will be in on state
    """
    return average_time_on(time_off, time_bleach) * average_num_activations(
        time_off, time_bleach
    )
