#!/usr/bin/env python3
# -*- coding: utf-8 -*-

#   Copyright 2023 Brooks Su
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

"""Sets 256-color display attributes of character terminal.

There are three pairs of functions to set or restore colors according
to their literal name:

    set_text_color(color)
    reset_text_color()

    set_background_color(color)
    reset_background_color()

    set_color(text_color, background_color)
    reset_color()

Enum class Color defined constants of some basic colors and grayscales,
and more colors can be generated by function color256(r, g, b).

A simple sample as following:

    import color256
    from color256 import Color

    color256.set_color(Color.GOLD, Color.BLUE)
    print('Hello, color256!')
    color = color256.color256(4, 3, 1) # get a bronze color
    color256.set_text_color(color)
    ...
    color256.reset_color()

Reference: https://en.wikipedia.org/wiki/ANSI_escape_code
"""

import enum

_CSI_LEAD = '\033['

def color256(red: int, green: int, blue: int) -> int:
    """Returns a 256-color number according to the given RGB parameters.

    Args:
        All parameters' valid values must be in range of 0~5.

    Raises:
        ValueError: Argument value out of range.
    """
    if not (0 <= red <= 5 and 0 <= green <= 5 and 0 <= blue <= 5):
        raise ValueError('Argument value out of range.')
    return 16 + red * 36 + green * 6 + blue


class Color(enum.IntEnum):
    """Enumerates some common constants of colors.
    """
    BLACK = 0
    RED = 1
    GREEN = 2
    YELLOW = 3
    BLUE = 4
    MAGENTA = 5
    CYAN = 6
    WHITE = 7
    BRIGHT_BLACK = 8
    BRIGHT_RED = 9
    BRIGHT_GREEN = 10
    BRIGHT_YELLOW = 11
    BRIGHT_BLUE = 12
    BRIGHT_MAGENTA = 13
    BRIGHT_CYAN = 14
    BRIGHT_WHITE = 15

    GRAYSCALE_DARKEST = 232
    GRAYSCALE_DARK = 238
    GRAYSCALE_MIDDLE = 243
    GRAYSCALE_LIGHT = 249
    GRAYSCALE_LIGHTEST = 255
    GRAY = color256(3, 3, 3)
    SILVER = color256(4, 4, 4)

    TAN = color256(5, 4, 3)
    BRONZE = color256(4, 3, 1)
    COPPER = BRONZE
    COFFEE = color256(2, 1, 0)
    CHOCOLATE = color256(4, 2, 1)
    BROWN = color256(3, 1, 1)

    PINK = color256(5, 3, 3)
    HOT_PINK = color256(5, 2, 4)
    DEEP_PINK = color256(5, 0, 3)
    TOMATO = color256(5, 2, 2)
    INDIAN_RED = TOMATO
    MAROON = RED
    CRIMSON = color256(5, 0, 1)
    SCARLET = CRIMSON

    VIOLET = color256(3, 0, 5)
    BRIGHT_VIOLET = color256(5, 3, 5)
    ORCHID = color256(3, 1, 5)
    BRIGHT_ORCHID = color256(4, 2, 4)
    PLUM = color256(5, 4, 5)
    LAVENDER = color256(4, 4, 5)
    MEDIUM_PURPLE = color256(3, 2, 5)
    PURPLE = color256(3, 0, 3)

    INDIGO = color256(1, 0, 4)
    NAVY = color256(0, 0, 3)
    ROYAL_BLUE = color256(2, 3, 5)
    SKY_BLUE = color256(3, 5, 5)
    DEEP_SKY_BLUE = color256(0, 4, 5)
    AZURE = color256(4, 5, 5)

    TURQUOISE = color256(2, 5, 4)
    SPRING_GREEN = color256(2, 4, 3)
    SEA_GREEN = color256(1, 4, 4)
    DEEP_SEA_GREEN = color256(1, 3, 2)
    OLIVE = color256(3, 3, 0)

    BEIGE = color256(5, 5, 4)
    IVORY = BEIGE
    KHAKI = color256(5, 5, 3)
    DEEP_KHAKI = color256(4, 4, 2)
    GOLD = color256(5, 4, 0)
    ORANGE = color256(5, 3, 0)
    DEEP_ORANGE = color256(5, 2, 0)


def set_text_color(color: int):
    """Sets text(foreground) color of terminal.
    """
    print(f'{_CSI_LEAD}38;5;{color}m', end='')


def reset_text_color():
    """Restores text color to the default value of terminal.
    """
    print(f'{_CSI_LEAD}39m', end='')


def set_background_color(color: int):
    """Sets background color of terminal.
    """
    print(f'{_CSI_LEAD}48;5;{color}m', end='')


def reset_background_color():
    """Restores background color to the default value of terminal.
    """
    print(f'{_CSI_LEAD}49m', end='')


def set_color(text_color: int, background_color: int):
    """Sets text(foreground) color to 'text_color' and background color
    to 'background_color'.
    """
    print(f'{_CSI_LEAD}38;5;{text_color}m{_CSI_LEAD}48;5;{background_color}m',
          end='')


def reset_color():
    """Restores text and background color to the default value of terminal.
    """
    print(f'{_CSI_LEAD}39;49m', end='')


def _test_color256():
    def color_block(color, width):
        set_background_color(color)
        print(' ' * width, end='')
        reset_background_color()

    def text_color():
        for i in range(6):
            for char in 'Hello, color256!':
                set_text_color(ord(char) % 32 + 20 + i * 36)
                print(char, end='')
            print('')
        reset_text_color()

    def grayscale():
        for color in range(Color.GRAYSCALE_DARKEST,
                           Color.GRAYSCALE_LIGHTEST + 1):
            color_block(color, 3)
        print('')

    def rgb():
        for red in range(6):
            for green in range(6):
                for blue in range(6):
                    color_block(color256(red, green, blue), 2)
            print('')

    def constant_color():
        for color in sorted(Color):
            print(f'{color.name:18s}: {color.value:3d} ', end='')
            color_block(color, 12)
            print('')

    text_color()
    grayscale()
    rgb()
    constant_color()


if __name__ == '__main__':
    _test_color256()
