#!/usr/bin/env python3
"""
Minimal gettext .po -> .mo compiler (no system msgfmt needed).
Stages 7kaa translation catalogs into locale_build/<lang>/LC_MESSAGES/7kaa.mo
so the web build can be played in German (and the other shipped languages).

Each .po keeps its declared charset (de.po is ISO-8859-1, others UTF-8); the
header entry (msgid "") is preserved so gettext knows the encoding. 7kaa's
LocaleRes performs its own codeset conversion to its bitmap fonts.

Usage: python po2mo.py
"""
import os, struct, re

ROOT   = os.path.dirname(os.path.abspath(__file__))
PO_DIR = os.path.join(os.path.dirname(ROOT), "7kaa-2156", "po")
OUT    = os.path.join(ROOT, "locale_build")
LANGS  = ["de", "fr", "es", "ru", "pl", "pt_BR", "eo"]


def detect_charset(raw_bytes):
    m = re.search(rb"charset=([\w\-]+)", raw_bytes)
    if m:
        cs = m.group(1).decode('ascii', 'replace')
        try:
            "x".encode(cs); return cs
        except LookupError:
            pass
    return "ISO-8859-1"


def unescape(s):
    out, i = [], 0
    while i < len(s):
        c = s[i]
        if c == '\\' and i+1 < len(s):
            n = s[i+1]
            out.append({'n':'\n','t':'\t','r':'\r','"':'"','\\':'\\'}.get(n, n)); i += 2
        else:
            out.append(c); i += 1
    return ''.join(out)


def parse_po(path):
    raw = open(path, 'rb').read()
    cs = detect_charset(raw)
    text = raw.decode(cs, 'replace')
    entries = {}
    ctxt = None; msgid = None; msgstr = None; mode = None

    def flush():
        if msgid is not None and msgstr is not None:
            key = (ctxt + '\x04' + msgid) if ctxt else msgid
            entries[key] = msgstr

    for raw_line in text.splitlines():
        line = raw_line.strip()
        if not line or line.startswith('#'):
            continue
        if line.startswith('msgctxt '):
            flush(); ctxt = unescape(line[8:].strip().strip('"')); msgid=None; msgstr=None; mode='ctxt'
        elif line.startswith('msgid_plural'):
            mode = 'skip'
        elif line.startswith('msgid '):
            if mode in ('str','skip'): flush(); ctxt=None
            msgid = unescape(line[6:].strip().strip('"')); msgstr=None; mode='id'
        elif line.startswith('msgstr '):
            msgstr = unescape(line[7:].strip().strip('"')); mode='str'
        elif line.startswith('"'):
            seg = unescape(line.strip()[1:-1]) if len(line) >= 2 else ''
            if   mode=='ctxt': ctxt   = (ctxt   or '') + seg
            elif mode=='id':   msgid  = (msgid  or '') + seg
            elif mode=='str':  msgstr = (msgstr or '') + seg
    flush()
    return entries, cs


def write_mo(entries, charset, path):
    keys = sorted(entries.keys())
    ids  = [k.encode(charset, 'replace') for k in keys]
    strs = [entries[k].encode(charset, 'replace') for k in keys]
    n = len(keys)
    o_keys = 7*4
    o_vals = o_keys + n*8
    o_str  = o_vals + n*8           # hash table size 0
    out = bytearray()
    out += struct.pack('<IIIIIII', 0x950412de, 0, n, o_keys, o_vals, 0, o_str)
    blob = bytearray(); key_tbl = bytearray(); val_tbl = bytearray()
    for b in ids:
        key_tbl += struct.pack('<II', len(b), o_str + len(blob)); blob += b + b'\x00'
    for b in strs:
        val_tbl += struct.pack('<II', len(b), o_str + len(blob)); blob += b + b'\x00'
    out += key_tbl + val_tbl + blob
    os.makedirs(os.path.dirname(path), exist_ok=True)
    open(path, 'wb').write(out)


if __name__ == '__main__':
    n = 0
    for lang in LANGS:
        po = os.path.join(PO_DIR, lang + '.po')
        if not os.path.exists(po):
            print('skip:', lang); continue
        entries, cs = parse_po(po)
        mo = os.path.join(OUT, lang, 'LC_MESSAGES', '7kaa.mo')
        write_mo(entries, cs, mo)
        print(f'{lang}: {len(entries)} strings ({cs}) -> {mo}')
        n += 1
    print(f'done, {n} catalogs')
