# Generate character width table based on:
#    glibc                   localedata/charmaps/UTF-8
#    unicode-org/icu-data    charset/data/ucm/*.ucm
#
# The glibc's charmap file seems to contain some errors originating from the unicode.org database.
# However, we use it as-is because many apps place text according to the result of wcwidth(3).

import gzip
import pathlib
import re
import sys

def usage():
    print('Usage: gen-charwidth.py <glibc-version> <glibc-i18n-dir> <icu-data-dir>', file=sys.stderr)

# char_width
#   0 --> zero width (invisible on its own)
#   1 --> narrow
#   2 --> wide
#   3 --> narrow in non-CJK mode, wide in CJK mode
char_width = [1 for _ in range(0x110000)]
glibc_version = None

def read_glibc_charmap(glibc_i18n_dir):
    """Set base character width based on the glibc's charmap file."""
    global char_width

    base = pathlib.Path(glibc_i18n_dir)
    charmap_file = base / 'charmaps' / 'UTF-8.gz'
    if charmap_file.exists():
        fin = gzip.open(charmap_file, 'rt')
    else:
        charmap_file = base / 'charmaps' / 'UTF-8'
        fin = open(charmap_file, 'r')

    with fin:
        for line in fin:
            line = line.strip()
            if line == 'WIDTH':
                break
        for line in fin:
            line = line.strip()
            if line == 'END WIDTH':
                break
            m = re.match(r'<U([0-9A-F]+)>(?:.*<U([0-9A-F]+)>)?\s+(\d+)', line)
            if m:
                r1 = int(m.group(1), 16)
                if m.group(2) is None:
                    r2 = r1
                else:
                    r2 =  int(m.group(2), 16)
                w = int(m.group(3))
                for c in range(r1, r2 + 1):
                    char_width[c] = w

def read_icu_data_ucms(icu_data_dir):
    """Set special case (narrow in non-CJK mode, wide in CJK mode)
    base on the CP932/CP936/CP949/CP950 conversion table.
    """
    global char_width
    base = pathlib.Path(icu_data_dir)
    for ucm in [
        'windows-932-2000.ucm',
        'windows-936-2000.ucm',
        'windows-949-2000.ucm',
        'windows-950_hkscs-2001.ucm',
        'windows-950-2000.ucm',
    ]:
        with open(base / 'charset' / 'data' / 'ucm' / ucm, 'r') as fin:
            for line in fin:
                line = line.strip()
                if line == 'CHARMAP':
                    break
            for line in fin:
                line = line.strip()
                if line == 'END CHARMAP':
                    break
                # extract characters that are mapped to 2 bytes as "exact 1-1 roundtrip mapping" in MBCS.
                m = re.match(r'<U([0-9A-F]+)>\s+\\x([0-9A-F]+)\\x([0-9A-F]+)\s\|0', line)
                if m:
                    c = int(m.group(1), 16)
                    #b1 = int(m.group(2), 16)
                    #b2 = int(m.group(3), 16)
                    if c >= 0xE000 and c <= 0xF8FF: # Private Use Area
                        continue
                    if c >= 0xFF00 and c <= 0xFFEF: # Halfwidth and Fullwidth Forms
                        continue
                    if char_width[c] == 1:
                        char_width[c] = 3

def output_ranges(file):
    """Output character width table"""
    global char_width
    global glibc_version
    ranges = []
    cur_range = None
    for c, w in enumerate(char_width):
        if cur_range is not None:
            if w == cur_range[2]:
                cur_range[1] = c
                continue
        cur_range = [c, c, w]
        ranges.append(cur_range)

    with open(file, 'w', encoding='ASCII') as fout:
        print('# Generated by Core/Scripts/gen-charwidth.py', file=fout)
        print('#', file=fout)
        print('# Unicode character width table', file=fout)
        print(f'# Based on glibc-{glibc_version} and icu-data', file=fout)
        print('# WIDTH can be', file=fout)
        print('#  0 : zero width (invisible on its own)', file=fout)
        print('#  1 : narrow (default; omitted in this file)', file=fout)
        print('#  2 : wide', file=fout)
        print('#  3 : narrow in non-CJK mode, wide in CJK mode', file=fout)
        print('#', file=fout)
        print('# CODEPOINT     WIDTH', file=fout)
        print('# (SINGLE/RANGE)', file=fout)

        for r in ranges:
            if r[2] == 1:
                continue
            if r[0] == r[1]:
                print(f'U{r[0]:06X}         {r[2]}', file=fout)
            else:
                print(f'U{r[0]:06X} U{r[1]:06X} {r[2]}', file=fout)

if __name__ == '__main__':
    if len(sys.argv) < 4:
        usage()
        sys.exit(1)

    glibc_version = sys.argv[1]
    read_glibc_charmap(sys.argv[2])
    read_icu_data_ucms(sys.argv[3])

    output_ranges(pathlib.Path(__file__).parent.parent / 'charwidth')
