# particle_generation.py
"""Functions for generating primary particle radii."""

import logging

import numpy as np

logger = logging.getLogger(__name__)


def random_normal_custom() -> float:
    """
    Generates a normally distributed random number using the Ziggurat method
    as implemented in numpy, which is generally preferred over custom
    acceptance-rejection methods unless specific properties are needed.
    """
    # The custom Fortran implementation is an acceptance-rejection method.
    # For standard normal distribution, numpy's is highly optimized and recommended.
    return np.random.normal(0.0, 1.0)


def lognormal_pp_radii(
    rp_gstd: float, rp_g: float, n: int, seed: int | None = None
) -> np.ndarray:
    """Generate N random radii from a lognormal distribution.

    Parameters
    ----------
    rp_gstd : float
        Geometric standard deviation of the distribution (must be >= 1.0).
        If 1.0, generates monodisperse particles.
    rp_g : float
        Geometric mean radius of the distribution (must be > 0).
    n : int
        Number of radii to generate.
    seed : int | None, optional
        Random seed for reproducibility, by default None.

    Returns
    -------
    np.ndarray
        A 1D NumPy array of N generated radii.

    Raises
    ------
    ValueError
        If `rp_g` is not positive.

    Notes
    -----
    Uses `numpy.random.lognormal`. The underlying normal distribution's
    parameters are mu=log(rp_g) and sigma=log(rp_gstd).
    The original Fortran code included optional truncation at approximately
    +/- 2 geometric standard deviations; this is not enabled by default here.
    """
    if seed is not None:
        np.random.seed(seed)

    if rp_gstd < 1.0:
        logger.warning("Geometric standard deviation should be >= 1.0. Setting to 1.0.")
        rp_gstd = 1.0

    if rp_g <= 0:
        raise ValueError("Geometric mean radius (rp_g) must be positive.")

    if rp_gstd == 1.0:
        # Monodisperse case
        logger.info("Generating monodisperse particles.")
        return np.full(n, rp_g, dtype=float)
    else:
        # Polydisperse case using numpy's lognormal
        # The parameters for np.random.lognormal are mu and sigma of the *underlying* normal distribution.
        # mu = log(geometric_mean)
        # sigma = log(geometric_standard_deviation)
        mu = np.log(rp_g)
        sigma = np.log(rp_gstd)

        radii = np.random.lognormal(mean=mu, sigma=sigma, size=n)

        # --- Optional: Implement Fortran's Truncation/Rejection ---
        # The Fortran code truncates at rp_g / (rp_gstd**2) and rp_g * (rp_gstd**2)
        # This corresponds to approximately +/- 2 sigma in the underlying normal distribution.
        # np.random.lognormal directly generates from the distribution.
        # If strict truncation matching the Fortran code is needed:
        # min_val = rp_g / (rp_gstd**2)
        # max_val = rp_g * (rp_gstd**2)
        # radii_out = np.zeros(n, dtype=float)
        # generated_count = 0
        # while generated_count < n:
        #     # Generate candidates (can generate more than needed for efficiency)
        #     num_needed = n - generated_count
        #     candidates = np.random.lognormal(mean=mu, sigma=sigma, size=num_needed * 2) # Generate extras
        #     valid = candidates[(candidates >= min_val) & (candidates <= max_val)]
        #     num_valid = len(valid)
        #     take = min(num_valid, num_needed)
        #     if take > 0:
        #         radii_out[generated_count : generated_count + take] = valid[:take]
        #         generated_count += take
        # print(f"Generated polydisperse particles (truncated between {min_val:.2f} and {max_val:.2f}).")
        # return radii_out
        # --- End Optional Truncation ---

        logger.info(
            f"Generated polydisperse particles (mean={np.mean(radii):.2f}, std={np.std(radii):.2f})."
        )
        return radii
