from typing import Any, Dict, Optional

color_names = ("black", "red", "green", "yellow", "blue", "magenta", "cyan", "white")
foreground = {color_names[x]: "3%s" % x for x in range(8)}
background = {color_names[x]: "4%s" % x for x in range(8)}

RESET = "0"
opt_dict = {
    "bold": "1",
    "underscore": "4",
    "blink": "5",
    "reverse": "7",
    "conceal": "8",
}


def colorize(text: Optional[str] = "", opts: Optional[Any] = None, **kwargs: Dict[str, Any]):
    """
    Return your text, enclosed in ANSI graphics codes.

    Depends on the keyword arguments 'fg' and 'bg', and the contents of
    the opts tuple/list.

    Return the RESET code if no parameters are given.

    Valid colors:
        'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'

    Valid options:
        'bold'
        'underscore'
        'blink'
        'reverse'
        'conceal'
        'noreset' - string will not be auto-terminated with the RESET code

    Examples:
        colorize('hello', fg='red', bg='blue', opts=('blink',))
        colorize()
        colorize('goodbye', opts=('underscore',))
        print(colorize('first line', fg='red', opts=('noreset',)))
        print('this should be red too')
        print(colorize('and so should this'))
        print('this should not be red')
    """
    if not opts:
        opts = ()

    code_list = []
    if text == "" and len(opts) == 1 and opts[0] == "reset":
        return "\x1b[%sm" % RESET
    for k, v in kwargs.items():
        if k == "fg":
            code_list.append(foreground[v])
        elif k == "bg":
            code_list.append(background[v])
    for o in opts:
        if o in opt_dict:
            code_list.append(opt_dict[o])
    if "noreset" not in opts:
        text = "%s\x1b[%sm" % (text or "", RESET)
    return "%s%s" % (("\x1b[%sm" % ";".join(code_list)), text or "")


def make_style(opts: Optional[Any] = None, **kwargs: Dict[str, Any]):
    """
    Return a function with default parameters for colorize()

    Example:
        bold_red = make_style(opts=('bold',), fg='red')
        print(bold_red('hello'))
        KEYWORD = make_style(fg='yellow')
        COMMENT = make_style(fg='blue', opts=('bold',))
    """
    if not opts:
        opts = ()
    return lambda text: colorize(text, opts, **kwargs)


NOCOLOR_PALETTE = "nocolor"
DARK_PALETTE = "dark"
LIGHT_PALETTE = "light"

PALETTES = {
    NOCOLOR_PALETTE: {
        "ERROR": {},
        "SUCCESS": {},
        "WARNING": {},
        "NOTICE": {},
        "SQL_FIELD": {},
        "SQL_COLTYPE": {},
        "SQL_KEYWORD": {},
        "SQL_TABLE": {},
        "HTTP_INFO": {},
        "HTTP_SUCCESS": {},
        "HTTP_REDIRECT": {},
        "HTTP_NOT_MODIFIED": {},
        "HTTP_BAD_REQUEST": {},
        "HTTP_NOT_FOUND": {},
        "HTTP_SERVER_ERROR": {},
        "MIGRATE_HEADING": {},
        "MIGRATE_LABEL": {},
    },
    DARK_PALETTE: {
        "ERROR": {"fg": "red", "opts": ("bold",)},
        "SUCCESS": {"fg": "green", "opts": ("bold",)},
        "WARNING": {"fg": "yellow", "opts": ("bold",)},
        "NOTICE": {"fg": "red"},
        "SQL_FIELD": {"fg": "green", "opts": ("bold",)},
        "SQL_COLTYPE": {"fg": "green"},
        "SQL_KEYWORD": {"fg": "yellow"},
        "SQL_TABLE": {"opts": ("bold",)},
        "HTTP_INFO": {"opts": ("bold",)},
        "HTTP_SUCCESS": {},
        "HTTP_REDIRECT": {"fg": "green"},
        "HTTP_NOT_MODIFIED": {"fg": "cyan"},
        "HTTP_BAD_REQUEST": {"fg": "red", "opts": ("bold",)},
        "HTTP_NOT_FOUND": {"fg": "yellow"},
        "HTTP_SERVER_ERROR": {"fg": "magenta", "opts": ("bold",)},
        "MIGRATE_HEADING": {"fg": "cyan", "opts": ("bold",)},
        "MIGRATE_LABEL": {"opts": ("bold",)},
    },
    LIGHT_PALETTE: {
        "ERROR": {"fg": "red", "opts": ("bold",)},
        "SUCCESS": {"fg": "green", "opts": ("bold",)},
        "WARNING": {"fg": "yellow", "opts": ("bold",)},
        "NOTICE": {"fg": "red"},
        "SQL_FIELD": {"fg": "green", "opts": ("bold",)},
        "SQL_COLTYPE": {"fg": "green"},
        "SQL_KEYWORD": {"fg": "blue"},
        "SQL_TABLE": {"opts": ("bold",)},
        "HTTP_INFO": {"opts": ("bold",)},
        "HTTP_SUCCESS": {},
        "HTTP_REDIRECT": {"fg": "green", "opts": ("bold",)},
        "HTTP_NOT_MODIFIED": {"fg": "green"},
        "HTTP_BAD_REQUEST": {"fg": "red", "opts": ("bold",)},
        "HTTP_NOT_FOUND": {"fg": "red"},
        "HTTP_SERVER_ERROR": {"fg": "magenta", "opts": ("bold",)},
        "MIGRATE_HEADING": {"fg": "cyan", "opts": ("bold",)},
        "MIGRATE_LABEL": {"opts": ("bold",)},
    },
}
DEFAULT_PALETTE = DARK_PALETTE


def parse_color_setting(config_string: str):
    """Parse a ESMERALD_COLORS environment variable to produce the system palette

    The general form of a palette definition is:

        "palette;role=fg;role=fg/bg;role=fg,option,option;role=fg/bg,option,option"

    where:
        palette is a named palette; one of 'light', 'dark', or 'nocolor'.
        role is a named style used by Esmerald
        fg is a foreground color.
        bg is a background color.
        option is a display options.

    Specifying a named palette is the same as manually specifying the individual
    definitions for each role. Any individual definitions following the palette
    definition will augment the base palette definition.

    Valid roles:
        'error', 'success', 'warning', 'notice', 'sql_field', 'sql_coltype',
        'sql_keyword', 'sql_table', 'http_info', 'http_success',
        'http_redirect', 'http_not_modified', 'http_bad_request',
        'http_not_found', 'http_server_error', 'migrate_heading',
        'migrate_label'

    Valid colors:
        'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'

    Valid options:
        'bold', 'underscore', 'blink', 'reverse', 'conceal', 'noreset'
    """
    if not config_string:
        return PALETTES[DEFAULT_PALETTE]

    # Split the color configuration into parts
    parts = config_string.lower().split(";")
    palette = PALETTES[NOCOLOR_PALETTE].copy()
    for part in parts:
        if part in PALETTES:
            # A default palette has been specified
            palette.update(PALETTES[part])
        elif "=" in part:
            # Process a palette defining string
            definition = {}

            # Break the definition into the role,
            # plus the list of specific instructions.
            # The role must be in upper case
            role, instructions = part.split("=")
            role = role.upper()

            styles = instructions.split(",")
            styles.reverse()

            # The first instruction can contain a slash
            # to break apart fg/bg.
            colors = styles.pop().split("/")
            colors.reverse()
            fg = colors.pop()
            if fg in color_names:
                definition["fg"] = fg
            if colors and colors[-1] in color_names:
                definition["bg"] = colors[-1]

            # All remaining instructions are options
            opts = tuple(s for s in styles if s in opt_dict)
            if opts:
                definition["opts"] = opts

            # The nocolor palette has all available roles.
            # Use that palette as the basis for determining
            # if the role is valid.
            if role in PALETTES[NOCOLOR_PALETTE] and definition:
                palette[role] = definition

    # If there are no colors specified, return the empty palette.
    if palette == PALETTES[NOCOLOR_PALETTE]:
        return None
    return palette
