from typing import Union, List

from .constants import SCALES
from .utils import note_to_midi, root_to_midi


def midi_scale(
    key: Union[str, int],
    scale: Union[List[int], str],
    min_note: int = 0,
    max_note: int = 127,
    **kwargs,
) -> List[int]:
    """
    Generate a scale of midi notes.

    Args:
        key: A valid root name from `midi_utils.constants.ROOTS`
             or a midi note between 0 and 11.
        scale: A valid scale name from `midi_utils.constants.SCALES`
               or a list of integers, eg [0, 4, 7]
        min_note: The lowest note in the scale;
                  A valid note name from `midi_utils.constants.NOTES`
                  or a midi note between 0 and 127
        max_note: The lowest note in the scale;
                  A valid note name from `midi_utils.constants.NOTES`
                  or a midi note between 0 and 127

    """
    if min_note > max_note:
        raise ValueError(
            'The min_note "%s" is larger than the max_note "%s"' % min_note, max_note
        )
    # convert args
    key = root_to_midi(key)
    min_note = note_to_midi(min_note)
    max_note = note_to_midi(max_note)

    # lookup scale by name
    if isinstance(scale, str):
        scale = scale.upper().replace(" ", "_").strip()
        if scale in SCALES:
            scale = SCALES[scale]
        else:
            raise ValueError(
                f'"{scale}" is not a valid scale name. Choose from: {", ".join(SCALES.keys())}'
            )

    # position it according to the key
    key_scale = [s + key for s in scale]

    # stretch from min note to max note
    return [
        x + (12 * j)
        for j in range(12)
        for x in key_scale
        if x + (12 * j) >= min_note and x + (12 * j) <= max_note
    ]


def map_to_midi_scale(x, scale: List[int], min_x: int, max_x: int, **kwargs) -> int:
    """
    Map an arbitrary value (`x`) to a provided midi `scale` given the `min_x` and `max_x` values.
    This is useful for generated data sonifications in a particular key.

    Args:
        x: An integer to map to a midi note
        scale: A scale generated by `midi_utils.midi_scale`
        min_x: The lowest possible value of `x`
        max_x: The highest possible value of `x`
    """
    scale_length = len(scale) - 1
    scale_idx = int(round(((x - min_x) * scale_length) / (max_x - min_x)))
    return scale[scale_idx]
