
"""
Functions to generate random walks on networks, which can be interpreted as symbolic sequences.
"""


import numpy as np
from ..optionalNJIT import optional_njit
from ..warningHandling import warnUser



def UnbiasedRandomWalk( numNodes: int, lPath: int ) -> np.ndarray :

    """
    Generate a symbolic time series using an unbiased random walk on a network.
    
    Parameters
    ----------
    numNodes : int
        Number of nodes in the network, hence of symbols.
    lPath : float
        Length of the time series (path) to be generated.

    Returns
    -------
    np.ndarray
        Generated time series.
    """

    if numNodes < 1:
        raise ValueError( "numNodes must be greater than 0" )
    if lPath < 1:
        raise ValueError( "lPath must be greater than 0" )

    Path = [ ]

    for i in range( lPath ):
        Path.append( np.random.randint( 0, numNodes ) )

    return np.array( Path, dtype = int )



def BiasedRandomWalk( numNodes, lPath, alpha ) -> np.ndarray :

    """
    Generate a symbolic time series using an biased random walk on a network.
    The biases are generated by the links' weights. Additionally, the parameter
    :math:`\\alpha` allows to introduce a random reset.
    
    Parameters
    ----------
    numNodes : int
        Number of nodes in the network, hence of symbols.
    lPath : float
        Length of the time series (path) to be generated.
    alpha : float
        Probability of jumping to a random node. For :math:`\\alpha = 1` an unbiased walk is recovered.

    Returns
    -------
    np.ndarray
        Generated time series.
    """
    if numNodes < 1:
        raise ValueError( "numNodes must be greater than 0" )
    if lPath < 1:
        raise ValueError( "lPath must be greater than 0" )
    if alpha < 0.0 or alpha > 1.0:
        warnUser( "TimeSeriesGeneration.SymbolicRandomWalk.BiasedRandomWalk", \
                  "alpha should be between 0.0 and 1.0" )

    W = np.random.uniform( 0, 1, size = ( numNodes, numNodes ) )
    for n1 in range( numNodes ):
        W[ n1, n1 ] = 0.
        for n2 in range( n1 + 1, numNodes ):
            W[ n1, n2 ] = 1. - W[ n2, n1 ]

    for n1 in range( numNodes ):
        W[ n1, : ] = W[ n1, : ] / np.sum( W[ n1, : ] )

    Path = [ ]

    Path.append( np.random.randint( 0, numNodes ) )
    for i in range( 1, lPath ):
        n1 = Path[ -1 ]
        p = ( 1. - alpha ) * W[ n1, : ] + alpha
        p /= np.sum( p )
        n2 = np.random.choice( numNodes, p = p )
        Path.append( n2 )

    return np.array( Path, dtype = int )
