"""Generators for random simplicial complexes."""

import random
from collections.abc import Sequence
from itertools import combinations

import networkx as nx

from toponetx.classes import SimplicialComplex
from toponetx.transform import graph_to_clique_complex

__all__ = [
    "linial_meshulam_complex",
    "multiparameter_linial_meshulam_complex",
    "random_clique_complex",
]


def linial_meshulam_complex(n: int, p: float, seed=None) -> SimplicialComplex:
    """Generate a random Linial-Meshulam simplicial complex [1]_.

    The random simplicial complex is generated by starting with a complete graph (i.e.,
    a complete 1-skeleton) and then adding 2-simplices with probability `p`.

    Parameters
    ----------
    n : int
        The number of nodes.
    p : float
        The probability of a 2-simplex being added.
    seed : int or random_state (optional)
        Indicator of random number generation state.

    Returns
    -------
    SimplicialComplex
        A random Linial-Meshulam simplicial complex.

    Notes
    -----
    This is equivalent to `multiparameter_linial_meshulam_complex` with edge
    probability one and 2-simplex probability `p`.

    References
    ----------
    .. [1] Nathan Linial and Roy Meshulam, "Homological Connectivity Of Random
           2-Complexes," Combinatorica 26, no. 4 (August 2006): 475-87,
           https://doi.org/10.1007/s00493-006-0027-9.
    """
    return multiparameter_linial_meshulam_complex(n, [1.0, p], seed)


def random_clique_complex(n: int, p: float, seed=None) -> SimplicialComplex:
    """Generate a random clique complex [1]_.

    In random clique complexes, a sample from an Erdős-Rényi graph is drawn and all
    cliques are identified with a face of a simplicial complex.

    Parameters
    ----------
    n : int
        The number of nodes.
    p : float
        The probability of an edge between two nodes.
    seed : int or random_state (optional)
        Indicator of random number generation state.

    Returns
    -------
    SimplicialComplex
        A random clique complex.

    References
    ----------
    .. [1] Matthew Kahle, "Topology of Random Clique Complexes,"
           Discrete Mathematics 309, no. 6 (April 2009): 1658-71,
           https://doi.org/10.1016/j.disc.2008.02.037.
    """
    graph = nx.gnp_random_graph(n, p, seed)
    return graph_to_clique_complex(graph)


def multiparameter_linial_meshulam_complex(
    n: int, ps: Sequence[float], seed: random.Random | None = None
) -> SimplicialComplex:
    """Generate a random multiparameter Linial-Meshulam complex.

    The random simplicial complex is generated inductively, starting with a set of `n`
    nodes. The probability for two nodes to be connected is given by `ps[0]`. Then, the
    probability for each `k`-clique to be identified by a `k-1`-simplex is given by
    `ps[k-2]`.

    Parameters
    ----------
    n : int
        The number of nodes.
    ps : sequence of floats
        The probabilities for each simplex rank. The first entry is the probability for
        two nodes to be connected. The second entry dictates the probability for a
        2-simplex to be added to the previous 1-skeleton etc.
    seed : int or random_state (optional)
        Indicator of random number generation state.

    Returns
    -------
    SimplicialComplex
        A random multiparameter Linial-Meshulam complex.

    Notes
    -----
    For `p[0] = 1.0` and `p[1] = p`, this recovers the standard random Linial-Meshulam
    complex provided by `random_linial_meshulam_complex`.
    """
    rng = nx.utils.create_py_random_state(seed)

    graph = nx.gnp_random_graph(n, ps[0], rng)
    SC = SimplicialComplex()

    for clique in nx.enumerate_all_cliques(graph):
        if len(clique) <= 2:
            SC.add_simplex(clique)
        elif len(clique) <= len(ps) + 1:
            # By random choice, we may have not taken all (k-1)-simplices of the
            # current k-clique. If that is the case, ignore this clique.
            simplices = SC.simplices
            if any(s not in simplices for s in combinations(clique, len(clique) - 1)):
                continue
            if rng.random() < ps[len(clique) - 2]:
                SC.add_simplex(clique)
        else:
            # `nx.enumerate_all_cliques` returns cliques in ascending order of size.
            # Abort calling the generator once we reach cliques for which we do not
            # have a probability.
            break

    return SC
