"""Apple II nominal display colours, represented by 4-bit dot sequences.

These are the "asymptotic" colours as displayed in e.g. continuous runs of
pixels.  The effective colours that are actually displayed are not discrete,
due to NTSC artifacting being a continuous process.
"""

from typing import Tuple, Type

import enum
import functools


class NominalColours(enum.Enum):
    pass


class HGRColours(NominalColours):
    """Map from 4-bit dot representation to DHGR pixel colours.

    Dots are in memory bit order (MSB -> LSB), which is opposite to screen
    order (LSB -> MSB is ordered left-to-right on the screen)

    Note that these are right-rotated from the HGR mapping, because of a
    1-tick phase difference in the colour reference signal for DHGR vs HGR
    """
    BLACK = 0b0000
    MAGENTA = 0b0001
    BROWN = 0b1000
    ORANGE = 0b1001  # HGR colour
    DARK_GREEN = 0b0100
    GREY1 = 0b0101
    GREEN = 0b1100  # HGR colour
    YELLOW = 0b1101
    DARK_BLUE = 0b0010
    VIOLET = 0b0011  # HGR colour
    GREY2 = 0b1010
    PINK = 0b1011
    MED_BLUE = 0b0110  # HGR colour
    LIGHT_BLUE = 0b0111
    AQUA = 0b1110
    WHITE = 0b1111


class DHGRColours(NominalColours):
    """Map from 4-bit dot representation to DHGR pixel colours.

    Dots are in memory bit order (MSB -> LSB), which is opposite to screen
    order (LSB -> MSB is ordered left-to-right on the screen)

    Note that these are right-rotated from the HGR mapping, because of a
    1-tick phase difference in the colour reference signal for DHGR vs HGR
    """

    # representation.
    BLACK = 0b0000
    MAGENTA = 0b1000
    BROWN = 0b0100
    ORANGE = 0b1100  # HGR colour
    DARK_GREEN = 0b0010
    GREY1 = 0b1010
    GREEN = 0b0110  # HGR colour
    YELLOW = 0b1110
    DARK_BLUE = 0b0001
    VIOLET = 0b1001  # HGR colour
    GREY2 = 0b0101
    PINK = 0b1101
    MED_BLUE = 0b0011  # HGR colour
    LIGHT_BLUE = 0b1011
    AQUA = 0b0111
    WHITE = 0b1111


def ror(int4: int, howmany: int) -> int:
    """Rotate-right an int4 some number of times."""
    res = int4
    for _ in range(howmany):
        res = _ror(res)

    return res


def _ror(int4: int) -> int:
    return ((int4 & 0b1110) >> 1) ^ ((int4 & 0b0001) << 3)


def rol(int4: int, howmany: int) -> int:
    """Rotate-left an int4 some number of times."""
    res = int4
    for _ in range(howmany):
        res = _rol(res)

    return res


def _rol(int4: int) -> int:
    return ((int4 & 0b0111) << 1) ^ ((int4 & 0b1000) >> 3)


@functools.lru_cache(None)
def dots_to_nominal_colour_pixels(
        num_bits: int,
        dots: int,
        colours: Type[NominalColours],
        init_phase: int = 1  # Such that phase = 0 at start of body
) -> Tuple[NominalColours]:
    """Sequence of num_bits nominal colour pixels via sliding 4-bit window.

    Includes the 3-bit header that represents the trailing 3 bits of the
    previous tuple body.  e.g. for DHGR, storing a byte in aux even columns
    will also influence the colours of the previous main odd column.

    This naively models (approximates) the NTSC colour artifacting.

    TODO: Use a more careful analogue colour composition model to produce
    effective pixel colours.

    TODO: DHGR vs HGR colour differences can be modeled by changing init_phase
    """
    res = []

    shifted = dots
    phase = init_phase

    for i in range(num_bits):
        colour = rol(shifted & 0b1111, phase)
        res.append(colours(colour))

        shifted >>= 1
        phase += 1
        if phase == 4:
            phase = 0

    return tuple(res)


@functools.lru_cache(None)
def dots_to_nominal_colour_pixel_values(
        num_bits: int,
        dots: int,
        colours: Type[NominalColours],
        init_phase: int = 1  # Such that phase = 0 at start of body
) -> Tuple[int]:
    """"Sequence of num_bits nominal colour values via sliding 4-bit window."""

    return tuple(p.value for p in dots_to_nominal_colour_pixels(
        num_bits, dots, colours, init_phase
    ))

