"""
Functions to generate memory-variable test data that are used for research.
"""

import random
from typing import List, Optional, Tuple

from b_asic.process import PlainMemoryVariable
from b_asic.resources import ProcessCollection


def _insert_delays(
    inputorder: List[int], outputorder: List[int], min_lifetime: int, cyclic: bool
) -> Tuple[List[int], List[int]]:
    size = len(inputorder)
    maxdiff = min(outputorder[i] - inputorder[i] for i in range(size))
    outputorder = [o - maxdiff + min_lifetime for o in outputorder]
    maxdelay = max(outputorder[i] - inputorder[i] for i in range(size))
    if cyclic:
        if maxdelay >= size:
            inputorder = inputorder + [i + size for i in inputorder]
            outputorder = outputorder + [o + size for o in outputorder]
    return inputorder, outputorder


def generate_random_interleaver(
    size: int, min_lifetime: int = 0, cyclic: bool = True
) -> ProcessCollection:
    """
    Generate a ProcessCollection with memory variable corresponding to a random
    interleaver with length *size*.

    Parameters
    ----------
    size : int
        The size of the random interleaver sequence.
    min_lifetime : int, default: 0
        The minimum lifetime for a memory variable. Default is 0 meaning that at least
        one variable is passed from the input to the output directly,
    cyclic : bool, default: True
        If the interleaver should operate continuously in a cyclic manner. That is,
        start a new interleaving operation directly after the previous.

    Returns
    -------
    ProcessCollection

    """
    inputorders = list(range(size))
    outputorders = inputorders[:]
    random.shuffle(outputorders)
    inputorders, outputorders = _insert_delays(
        inputorders, outputorders, min_lifetime, cyclic
    )
    return ProcessCollection(
        {
            PlainMemoryVariable(inputorder, 0, {0: outputorders[i] - inputorder})
            for i, inputorder in enumerate(inputorders)
        },
        len(inputorders),
        cyclic,
    )


def generate_matrix_transposer(
    height: int,
    width: Optional[int] = None,
    min_lifetime: int = 0,
    cyclic: bool = True,
) -> ProcessCollection:
    r"""
    Generate a ProcessCollection with memory variable corresponding to transposing a
    matrix of size *height* :math:`\times` *width*. If *width* is not provided, a
    square matrix of size *height* :math:`\times` *height* is used.

    Parameters
    ----------
    height : int
        Matrix height.
    width : int, optional
        Matrix width. If not provided assumed to be equal to height, i.e., a square
        matrix.
    min_lifetime : int, default: 0
        The minimum lifetime for a memory variable. Default is 0 meaning that at
        least one variable is passed from the input to the output directly,
    cyclic : bool, default: True
        If the interleaver should operate continuously in a cyclic manner. That is,
        start a new interleaving operation directly after the previous.

    Returns
    -------
    ProcessCollection
    """
    if width is None:
        width = height

    inputorder = []
    for row in range(height):
        for col in range(width):
            inputorder.append(col + width * row)

    outputorder = []
    for row in range(width):
        for col in range(height):
            outputorder.append(col * width + row)

    inputorder, outputorder = _insert_delays(
        inputorder, outputorder, min_lifetime, cyclic
    )
    return ProcessCollection(
        {
            PlainMemoryVariable(
                inputorder[i],
                0,
                {0: outputorder[i] - inputorder[i]},
                name=f"{inputorder[i]}",
            )
            for i in range(len(inputorder))
        },
        len(inputorder),
        cyclic,
    )