"""Utilities based on numpy arrays."""
import os
import random
import typing as t

import numpy as np
import numpy.typing as npt


def seed_python_numpy(seed: int) -> None:
    """Seed all randomness generated by the Python standard library and numpy.

    Args:
        seed (int): Seed to use for the random number generation.
    """
    random.seed(seed)
    np.random.seed(seed)
    os.environ["PYTHONHASHSEED"] = str(seed)


@t.overload
def rescale(
    x: float,
    old_min: float,
    old_max: float,
    new_min: float,
    new_max: float,
) -> float:
    ...


@t.overload
def rescale(
    x: npt.NDArray[t.Any],
    old_min: float | npt.NDArray[t.Any],
    old_max: float | npt.NDArray[t.Any],
    new_min: float | npt.NDArray[t.Any],
    new_max: float | npt.NDArray[t.Any],
) -> npt.NDArray[t.Any]:
    ...


@t.overload
def rescale(
    x: float | npt.NDArray[t.Any],
    old_min: npt.NDArray[t.Any],
    old_max: float | npt.NDArray[t.Any],
    new_min: float | npt.NDArray[t.Any],
    new_max: float | npt.NDArray[t.Any],
) -> npt.NDArray[t.Any]:
    ...


@t.overload
def rescale(
    x: float | npt.NDArray[t.Any],
    old_min: float | npt.NDArray[t.Any],
    old_max: npt.NDArray[t.Any],
    new_min: float | npt.NDArray[t.Any],
    new_max: float | npt.NDArray[t.Any],
) -> npt.NDArray[t.Any]:
    ...


@t.overload
def rescale(
    x: float | npt.NDArray[t.Any],
    old_min: float | npt.NDArray[t.Any],
    old_max: float | npt.NDArray[t.Any],
    new_min: npt.NDArray[t.Any],
    new_max: float | npt.NDArray[t.Any],
) -> npt.NDArray[t.Any]:
    ...


@t.overload
def rescale(
    x: float | npt.NDArray[t.Any],
    old_min: float | npt.NDArray[t.Any],
    old_max: float | npt.NDArray[t.Any],
    new_min: float | npt.NDArray[t.Any],
    new_max: npt.NDArray[t.Any],
) -> npt.NDArray[t.Any]:
    ...


def rescale(
    x: float | npt.NDArray[t.Any],
    old_min: float | npt.NDArray[t.Any],
    old_max: float | npt.NDArray[t.Any],
    new_min: float | npt.NDArray[t.Any],
    new_max: float | npt.NDArray[t.Any],
) -> float | npt.NDArray[t.Any]:
    """Rescale a value from one range to another.

    Args:
        x: The value to rescale.
        old_min: The minimum value of the old range.
        old_max: The maximum value of the old range.
        new_min: The minimum value of the new range.
        new_max: The maximum value of the new range.

    Returns:
        The rescaled value.

    NOTE:
        The returned value is a float if and only if all the arguments are floats.

    Example:
        ```python
        >>> import pytest
        >>> rescale(0.5, 0, 1, 0, 10)
        5.0
        >>> rescale(0.5, 0, 1, 10, 0)
        5.0
        >>> with pytest.raises(ValueError):
        ...     rescale(0.5, 0, 0, 0, 10)
        >>> with pytest.raises(ValueError):
        ...     rescale(0.5, 0, 1, 0, 0)

        ```
    """
    if np.allclose(old_min, old_max):
        raise ValueError("old_max must be different from old_min")
    if np.allclose(new_min, new_max):
        raise ValueError("new_max must be different from new_min")
    old_range = old_max - old_min
    new_range = new_max - new_min
    return (((x - old_min) * new_range) / old_range) + new_min
