#!/usr/bin/env python3

# SPDX-License-Identifier: Multics or MIT-0
# Copyright (c) 2025 Jeffrey H. Johnson
# Copyright (c) 2025 The DPS8M Development Team
# scspell-id: 6257834a-9924-11f0-9736-80ee73e9b8e7

import sys

try:
    import fontforge
except ImportError:
    print("fontforge module is required, but could not be loaded.", flush=True)
    print("Try 'dnf install fontforge' or 'apt install python3-fontforge'.", flush=True)
    sys.exit(1)

import os
import re
import argparse
import time

SCALE = 20
Y_OFFSET = 380
STROKE_WIDTH = 15


def gct_name_to_font_name(gct_name):
    base_name = gct_name.replace("gct_", "").strip("_")
    parts = base_name.split("_")
    return "GCT_stroke" + "".join([p.capitalize() for p in parts])


def gct_name_to_family_name(gct_name):
    base_name = gct_name.replace("gct_", "").strip("_")
    parts = base_name.split("_")
    return "GCT Stroke" + " ".join([p.capitalize() for p in parts])


def convert_gct_to_sfd(input_path, output_path):
    font_name_base = os.path.basename(input_path)
    font_name = gct_name_to_font_name(font_name_base)
    family_name = gct_name_to_family_name(font_name_base)

    font = fontforge.font()
    font.fontname = font_name
    font.familyname = family_name
    font.fullname = family_name
    font.weight = "Regular"
    font.copyright = f"Converted from Multics {font_name_base}"
    font.em = 1000
    font.encoding = "UnicodeFull"

    name_map = {
        "bel": "bell",
        "excl_pt": "exclam",
        "dbl_quot": "quotedbl",
        "sharp": "numbersign",
        "dollar": "dollar",
        "percent": "percent",
        "amprsnd": "ampersand",
        "r_quote": "quoteright",
        "l_paren": "parenleft",
        "r_paren": "parenright",
        "star": "asterisk",
        "plus": "plus",
        "comma": "comma",
        "minus": "minus",
        "dot": "period",
        "slash": "slash",
        "zero": "zero",
        "one": "one",
        "two": "two",
        "three": "three",
        "four": "four",
        "five": "five",
        "six": "six",
        "seven": "seven",
        "eight": "eight",
        "nine": "nine",
        "colon": "colon",
        "semi": "semicolon",
        "lessthan": "less",
        "equal": "equal",
        "grthan": "greater",
        "ques_mrk": "question",
        "atsign": "at",
        "l_brack": "bracketleft",
        "backslsh": "backslash",
        "r_brack": "bracketright",
        "cirflex": "asciicircum",
        "l_quote": "quoteleft",
        "l_brace": "braceleft",
        "vert_bar": "bar",
        "r_brace": "braceright",
        "tilde": "asciitilde",
    }
    for i in range(26):
        name_map[chr(ord("A") + i)] = chr(ord("A") + i)
        name_map[chr(ord("a") + i)] = chr(ord("a") + i)

    with open(input_path, "r") as f:
        lines = f.readlines()

    current_glyph = None
    x, y = 0, 0
    in_glyph = False
    all_strokes = []
    current_stroke = []
    tmp_counter = 0

    def new_temp_glyph():
        nonlocal tmp_counter
        tmp_counter += 1
        return font.createChar(-1, f".tmp_{tmp_counter:06d}")

    for line in lines:
        line = line.strip()
        if not line or line.startswith("metric"):
            continue

        if line.endswith(":"):
            glyph_name = line[:-1]
            std_name = name_map.get(glyph_name, glyph_name)

            current_glyph = font.createChar(-1, std_name)
            print(f"Processing glyph: {glyph_name} -> {std_name}", flush=True)
            start_time = time.time()

            x, y = 0, 0
            in_glyph = True
            all_strokes = []
            current_stroke = []
            if current_glyph:
                current_glyph.clear()

        elif in_glyph:
            parts = re.split(r"\s+", line)
            command = parts[0]

            if command in ("shift", "end"):
                if current_stroke:
                    all_strokes.append(current_stroke)
                current_stroke = []

                if command == "shift":
                    try:
                        dx, dy = int(parts[1]), int(parts[2])
                        x += dx
                        y += dy
                        current_stroke.append((x, y))
                    except (ValueError, IndexError):
                        print(
                            f"Warning: Could not parse coordinates in {font_name_base} shift: {line}",
                            flush=True,
                        )
                else:
                    if current_glyph:
                        assembled = new_temp_glyph()

                        for stroke_points in all_strokes:
                            if len(stroke_points) < 2:
                                continue

                            tmp = new_temp_glyph()
                            contour = fontforge.contour()
                            scaled_points = [
                                (p[0] * SCALE, p[1] * SCALE + Y_OFFSET)
                                for p in stroke_points
                            ]
                            contour.moveTo(*scaled_points[0])
                            for pt in scaled_points[1:]:
                                contour.lineTo(*pt)
                            tmp.layers[1] += contour

                            tmp.correctDirection()
                            tmp.stroke("circular", STROKE_WIDTH)
                            tmp.correctDirection()
                            tmp.removeOverlap()
                            tmp.correctDirection()

                            assembled.layers[1] += tmp.layers[1]

                        current_glyph.layers[1] = assembled.layers[1]
                        current_glyph.width = x * SCALE
                        current_glyph.correctDirection()

                        end_time = time.time()
                        elapsed_ms = (end_time - start_time) * 1000
                        print(
                            f"Glyph '{std_name}' processing time: {elapsed_ms:.2f} ms",
                            flush=True,
                        )

                    in_glyph = False
                    current_glyph = None

            elif command == "vector":
                if not current_stroke:
                    current_stroke.append((x, y))
                try:
                    dx, dy = int(parts[1]), int(parts[2])
                    x += dx
                    y += dy
                    current_stroke.append((x, y))
                except (ValueError, IndexError):
                    print(
                        f"Warning: Could not parse coordinates in {font_name_base} vector: {line}",
                        flush=True,
                    )

    for g in list(font.glyphs()):
        if g.glyphname.startswith(".tmp_"):
            font.removeGlyph(g.glyphname)

    font.save(output_path)
    print(f"Font saved to {output_path}", flush=True)


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Convert GCT font to SFD.")
    parser.add_argument("input", help="Input GCT file path.")
    parser.add_argument("output", help="Output SFD file path.")
    args = parser.parse_args()

    convert_gct_to_sfd(args.input, args.output)
