#!/usr/bin/env python3
#
# Copyright © 2023 Egmont Koblinger
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library.  If not, see <https://www.gnu.org/licenses/>.

import gi
from gi.repository import GLib

def width(c):
  if c >= 0xD800 and c <= 0xDFFF:
    # chr() doesn't work on surrogates
    return 1

  if GLib.unichar_iszerowidth(chr(c)):
    return 0
  if GLib.unichar_iswide(chr(c)):
    return 2
  if GLib.unichar_iswide_cjk(chr(c)):
    return 3
  return 1

# list of (width_or_jump, value) tuples
maj_table = []
# list of ([list of maj values], [list of width values]) tuples
min_table = []

for maj in range(17 * 256):
  all_same = True
  ws = []
  for min in range(256):
    c = 256 * maj + min
    w = width(c)
    ws.append(w)
    if (w != ws[0]):
      all_same = False

  if all_same:
    # store the width straight away
    maj_table.append((False, w))
  else:
    # need to jump to a min_table
    for i in range(len(min_table)):
      if min_table[i][1] == ws:
        # identical min_table already exists, reuse it
        maj_table.append((True, i))
        min_table[i][0].append(maj)
        break
    else:
      # add new min_table
      maj_table.append((True, len(min_table)))
      min_table.append(([maj], ws))

assert len(min_table) <= 252, "min_table too large to be addressed by one byte"

glib_version = f'{GLib.MAJOR_VERSION}.{GLib.MINOR_VERSION}.{GLib.MICRO_VERSION}'
print(f'/* Generated by unicode-width-generate using GLib {glib_version}, do not edit! */')

print('''
#define MIN_TABLE(i) (i)
#define WIDTH(i) (252 + (i))

static uint8_t _vte_width_maj_table[] = {
''')

for maj in range(17 * 256):
  print(f'  /* U+{256 * maj:04X} */  ', end='')
  if maj_table[maj][0]:
    print(f'MIN_TABLE({maj_table[maj][1]}),')
  else:
    print(f'WIDTH({maj_table[maj][1]}),')

print('''
};

#undef MIN_TABLE
#undef WIDTH

#define WIDTHS(a, b, c, d) (((a) << 6) | ((b) << 4) | ((c) << 2) | (d))

static uint8_t _vte_width_min_table[][64] = {
''')

for (i, (majs, ws)) in enumerate(min_table):
  print(f'  /* [{i}] */  ' + '{')
  for (min, w) in enumerate(ws):
    if min % 4 == 0:
      chs = ', '.join(f'U+{256 * maj + min:04X}' for maj in majs)
      print(f'    /* {chs} */  WIDTHS( ', end='')
    print(w, end='')
    if min % 4 < 3:
      print(', ', end='')
    else:
      print(' ),')
  print('  },')

print('''
};

#undef WIDTHS

/* Returns the width of the given printable Unicode character.
 * For non-printable (control) characters the return value is undefined. */
static int (_vte_unichar_width)(gunichar c, int utf8_ambiguous_width)
{
        vte_assert_cmpuint(c, <=, 0x10FFFFU);

        /* TODO bump to 0x0300 when ambiguous width support is removed */
        if (c < 0x0080) [[likely]] {
                return 1;
        }
        uint8_t x = _vte_width_maj_table[c / 256];
        if (x >= 252) {
                x -= 252;
        } else {
                x = (_vte_width_min_table[x][c % 256 / 4] >> (6 - (c % 4) * 2)) & 0x03;
        }
        if (x == 3)
                x = utf8_ambiguous_width;
        return x;
}
''')
