"""
Constants related to character encodings.

Helpful links:

* ISO-8859: [www.mit.edu/people/kenta/two/iso8859.html](https://www.mit.edu/people/kenta/two/iso8859.html)

* UTF-8: [www.utf8-chartable.de/unicode-utf8-table.pl?utf8=dec](https://www.utf8-chartable.de/unicode-utf8-table.pl?utf8=dec)  # noqa: E501
"""

# Bytes (TODO: why is this here?)
NEWLINE_BYTE = b"\n"

# String constants
ENCODING = 'encoding'
ASCII = 'ascii'
UTF_8 = 'utf-8'
UTF_16 = 'utf-16'
UTF_32 = 'utf-32'
ISO_8859_1 = 'iso-8859-1'
WINDOWS_1252 = 'windows-1252'


# Byte order marks
BOMS = {
    b'\x2b\x2f\x76':     'UTF-7 BOM',
    b'\xef\xbb\xbf':     'UTF-8 BOM',
    b'\xfe\xff':         'UTF-16 BOM big-endian',
    b'\xff\xfe':         'UTF-16 BOM little-endian',
    b'\xff\xfe\x00\x00': 'UTF-32 BOM little-endian',
    b'\x00\x00\xfe\xff': 'UTF-32 BOM big-endian',
    b'\x0e\xfe\xff':     'SCSU BOM',
}


# ASCII characters that either print nothing, put the cursor in a weird place, or (worst of all) actively
# delete stuff you already printed
UNPRINTABLE_ASCII = {
    0: 'NUL',    # 'Null',
    1: 'SOH',    # 'StartHeading',
    2: 'STX',    # 'StartText',
    3: 'ETX',
    4: 'EOT',    # End of transmission
    5: 'ENQ',    # 'Enquiry',
    6: 'ACK',    # 'Acknowledgement',
    7: 'BEL',    # 'Bell',
    8: 'BS',     # 'BackSpace',
    # 9:  'HT'   # 'HorizontalTab',
    # 10: 'LF',  # 'LineFeed',
    11: 'VT',    # 'VerticalTab',
    12: 'FF',    # 'FormFeed', AKA 'NewPage'
    13: 'CR',    # 'CarriageReturn',
    14: 'SO',    # 'ShiftOut',
    15: 'SI',    # 'ShiftIn',
    16: 'DLE',   # 'DataLineEscape',
    17: 'DC1',   # DeviceControl1',
    18: 'DC2',   # 'DeviceControl2',
    19: 'DC3',   # 'DeviceControl3',
    20: 'DC4',   # 'DeviceControl4',
    21: 'NAK',   # NegativeAcknowledgement',
    22: 'SYN',   # 'SynchronousIdle',
    23: 'ETB',   # 'EndTransmitBlock',
    24: 'CAN',   # 'Cancel',
    25: 'EM',    # 'EndMedium',
    26: 'SUB',   # 'Substitute',
    27: 'ESC',   # 'Escape',
    28: 'FS',    # 'FileSeparator',
    29: 'GS',    # 'GroupSeparator',
    30: 'RS',    # 'RecordSeparator',
    31: 'US',    # 'UnitSeparator',
    127: 'DEL',  # Delete
}


def scrub_c1_control_chars(char_map: dict) -> None:
    """
    Fill in a `dict` with integer keys/values corresponding to where a given char encoding has no chars
    because this range is for C1 control chars (AKA the "undefined" part of most character maps).
    """
    for i in range(128, 160):
        char_map[i] = f"C1.CHAR{i}"


# ISO-8859-1 AKA "Latin-1". Basically ASCII but using more of 128-256    http://www.gammon.com.au/unicode/
UNPRINTABLE_ISO_8859_1 = UNPRINTABLE_ASCII.copy()
scrub_c1_control_chars(UNPRINTABLE_ISO_8859_1)

UNPRINTABLE_ISO_8859_1.update({
    129: 'HOP',
    141: 'RLF',
    160: 'NBSP',
    173: 'SHY',
})


# UTF-8 Makes no use of 128-256 on their own, only as continuation bytes.
# The C1 bytes can appear but only as continuations
# https://en.wikipedia.org/wiki/UTF-8
UNPRINTABLE_UTF_8 = UNPRINTABLE_ASCII.copy()

# C0, C1, FE, and FF, etc. *never* appear in UTF-8
UNPRINTABLE_UTF_8.update({
    192: 'C0',
    193: 'C1',
    245: 'F5',
    246: 'F6',
    247: 'F7',
    248: 'F8',
    249: 'F9',
    250: 'FA',
    251: 'FB',
    252: 'FC',
    253: 'FD',
    254: 'FE',
    255: 'FF',
})


# Win_1252 is a lot like other 256 char encodings but they colonized the C1 char DMZ in the middle
UNPRINTABLE_WIN_1252 = UNPRINTABLE_ASCII.copy()

UNPRINTABLE_WIN_1252.update({
    129: 'HOP',   # High Octet Preset
    141: 'RLF',   # Reverse Line Feed
    143: 'SS3',   # Single shift 3
    144: 'DCS',   # Device Control String
    147: 'STS',   # Set transmit state
    160: 'NBSP',  # Non-breaking space
})


# ISO-8859-7     #http://www.gammon.com.au/unicode/
UNPRINTABLE_ISO_8859_7 = UNPRINTABLE_ASCII.copy()
scrub_c1_control_chars(UNPRINTABLE_ISO_8859_7)

UNPRINTABLE_ISO_8859_7.update({
    174: 'AE',
    210: 'D2',
    255: 'FF'
})


# Keys are names of encodings we will attempt to decode with, values are dicts mapping the unprintable bytes
# in that encoding to appropriate string represenations of those unprintable bytes.
# Order matters here, as we will attempt the decoding in the order of the keys.
ENCODINGS_TO_ATTEMPT = {
    ASCII:        UNPRINTABLE_ASCII,
    UTF_8:        UNPRINTABLE_UTF_8,
    UTF_16:       None,
    UTF_32:       None,  # UTF-16 and 32 are handled differently
    ISO_8859_1:   UNPRINTABLE_ISO_8859_1,
    WINDOWS_1252: UNPRINTABLE_WIN_1252,
}

SINGLE_BYTE_ENCODINGS = [
    ASCII,
    ISO_8859_1,
    WINDOWS_1252,
]

# Keys are encodings that use multiple bytes to represent a single character, values are the possible offsets
# to attempt to use as the starting point for decoding in a given set of bytes.
WIDE_UTF_ENCODINGS = {
    UTF_16: [0, 1],
    UTF_32: [0, 1, 2, 3],
}


def encoding_offsets(encoding: str) -> list:
    """Get possible offsets for a given encoding. If the encoding is not in `WIDE_UTF_ENCODINGS`, return `[0]`."""
    return WIDE_UTF_ENCODINGS.get(encoding, [0])


def encoding_width(encoding: str) -> int:
    """Get the width of a character in bytes for a given encoding, which is the number of possible offsets."""
    return len(encoding_offsets(encoding))


def is_wide_utf(encoding: str) -> bool:
    """Check if the encoding is a wide UTF encoding (UTF-16 or UTF-32)."""
    return encoding in WIDE_UTF_ENCODINGS


# TODO: this is unused cruft (mostly Asian language encodings)
ENCODINGS = [
    'big5',
    'big5hkscs',
    'cp950',
    'gb2312',
    'gbk',
    'gb18030',
    'hz',
    'iso2022_jp_2',
    'utf-7',
    'utf-8',
    'utf-16',
]
