from __future__ import annotations

import json
from pathlib import Path
from typing import Dict, Iterable, List, Tuple

from fontTools.fontBuilder import FontBuilder
from fontTools.pens.ttGlyphPen import TTGlyphPen

from .dictionary_tool import load_dictionary
from .symbol_logic import RootSpec, coda_style, onset_vector, vowel_anchor


UNITS_PER_EM = 1000
ASCENT = 820
DESCENT = -180


def _draw_octagon(pen: TTGlyphPen, cx: int, cy: int, r: int) -> None:
    pts = [
        (cx, cy + r),
        (cx + int(r * 0.7), cy + int(r * 0.7)),
        (cx + r, cy),
        (cx + int(r * 0.7), cy - int(r * 0.7)),
        (cx, cy - r),
        (cx - int(r * 0.7), cy - int(r * 0.7)),
        (cx - r, cy),
        (cx - int(r * 0.7), cy + int(r * 0.7)),
    ]
    pen.moveTo(pts[0])
    for p in pts[1:]:
        pen.lineTo(p)
    pen.closePath()


def _draw_square(pen: TTGlyphPen, x0: int, y0: int, x1: int, y1: int) -> None:
    pen.moveTo((x0, y0))
    pen.lineTo((x1, y0))
    pen.lineTo((x1, y1))
    pen.lineTo((x0, y1))
    pen.closePath()


def _draw_diamond(pen: TTGlyphPen, cx: int, cy: int, rx: int, ry: int) -> None:
    pen.moveTo((cx, cy + ry))
    pen.lineTo((cx + rx, cy))
    pen.lineTo((cx, cy - ry))
    pen.lineTo((cx - rx, cy))
    pen.closePath()


def _draw_line_box(pen: TTGlyphPen, p0: Tuple[int, int], p1: Tuple[int, int], w: int = 30) -> None:
    x0, y0 = p0
    x1, y1 = p1
    dx, dy = x1 - x0, y1 - y0
    if dx == 0 and dy == 0:
        return
    if abs(dx) >= abs(dy):
        # Horizontal-ish segment.
        _draw_square(pen, min(x0, x1), y0 - w, max(x0, x1), y0 + w)
    else:
        # Vertical-ish segment.
        _draw_square(pen, x0 - w, min(y0, y1), x0 + w, max(y0, y1))


def _semantic_base(pen: TTGlyphPen, semantic_class: str) -> None:
    if semantic_class == "entity":
        _draw_square(pen, 260, 260, 740, 740)
    elif semantic_class == "action":
        _draw_line_box(pen, (280, 300), (720, 700))
        _draw_line_box(pen, (280, 700), (720, 300))
    elif semantic_class == "quality":
        _draw_diamond(pen, 500, 500, 250, 250)
    elif semantic_class == "relation":
        _draw_square(pen, 220, 460, 780, 540)
        _draw_square(pen, 300, 300, 380, 700)
        _draw_square(pen, 620, 300, 700, 700)
    else:
        _draw_octagon(pen, 500, 500, 240)


def _draw_onset_stem(pen: TTGlyphPen, onset: str) -> Tuple[int, int]:
    cx, cy = 500, 500
    dx, dy = onset_vector(onset)
    ex, ey = cx + dx, cy + dy
    _draw_line_box(pen, (cx, cy), (ex, ey), w=24)
    return ex, ey


def _draw_vowel_mark(pen: TTGlyphPen, vowel: str) -> None:
    x, y = vowel_anchor(vowel)
    _draw_diamond(pen, x, y, 45, 45)


def _draw_coda_mark(pen: TTGlyphPen, endpoint: Tuple[int, int], coda: str) -> None:
    style = coda_style(coda)
    x, y = endpoint
    if style == "none":
        return
    if style == "short_tick":
        _draw_square(pen, x - 20, y - 20, x + 80, y + 20)
    elif style == "angle_tick":
        _draw_line_box(pen, (x, y), (x + 90, y - 70), w=18)
    elif style == "double_tick":
        _draw_square(pen, x - 20, y - 50, x + 90, y - 10)
        _draw_square(pen, x - 20, y + 10, x + 90, y + 50)
    elif style == "bar":
        _draw_square(pen, x - 70, y - 16, x + 70, y + 16)
    elif style == "hook":
        _draw_square(pen, x - 20, y - 20, x + 20, y + 90)
        _draw_square(pen, x - 20, y + 50, x + 80, y + 90)
    elif style == "cross_tick":
        _draw_square(pen, x - 20, y - 20, x + 80, y + 20)
        _draw_square(pen, x + 20, y - 80, x + 60, y + 80)


def _glyph_for_root(spec: RootSpec):
    pen = TTGlyphPen(None)
    _semantic_base(pen, spec.semantic_class)
    endpoint = _draw_onset_stem(pen, spec.onset)
    _draw_vowel_mark(pen, spec.vowel)
    _draw_coda_mark(pen, endpoint, spec.coda)
    return pen.glyph()


def build_font(specs: Iterable[RootSpec], out_path: Path, family_name: str = "LogiGlyph") -> Path:
    specs = list(specs)
    glyph_order = [".notdef", "space"] + [f"uni{spec.codepoint:04X}" for spec in specs]
    cmap: Dict[int, str] = {32: "space"}
    glyf = {}
    hmtx = {}

    notdef_pen = TTGlyphPen(None)
    _draw_square(notdef_pen, 120, 120, 880, 880)
    _draw_square(notdef_pen, 240, 240, 760, 760)
    glyf[".notdef"] = notdef_pen.glyph()
    hmtx[".notdef"] = (1000, 0)

    space_pen = TTGlyphPen(None)
    glyf["space"] = space_pen.glyph()
    hmtx["space"] = (500, 0)

    glyph_map = []
    for spec in specs:
        name = f"uni{spec.codepoint:04X}"
        glyf[name] = _glyph_for_root(spec)
        hmtx[name] = (1000, 0)
        cmap[spec.codepoint] = name
        glyph_map.append(
            {
                "gloss": spec.gloss,
                "root": spec.root,
                "char": spec.char,
                "codepoint": f"U+{spec.codepoint:04X}",
                "symbol_id": spec.symbol_id,
            }
        )

    fb = FontBuilder(UNITS_PER_EM, isTTF=True)
    fb.setupGlyphOrder(glyph_order)
    fb.setupCharacterMap(cmap)
    fb.setupGlyf(glyf)
    fb.setupHorizontalMetrics(hmtx)
    fb.setupHorizontalHeader(ascent=ASCENT, descent=DESCENT)
    fb.setupNameTable(
        {
            "familyName": family_name,
            "styleName": "Regular",
            "fullName": f"{family_name} Regular",
            "psName": f"{family_name}-Regular",
            "uniqueFontIdentifier": f"{family_name}-Regular-001",
        }
    )
    fb.setupOS2(
        sTypoAscender=ASCENT,
        sTypoDescender=DESCENT,
        usWinAscent=ASCENT,
        usWinDescent=abs(DESCENT),
    )
    fb.setupPost()
    fb.setupMaxp()

    out_path.parent.mkdir(parents=True, exist_ok=True)
    fb.save(str(out_path))

    map_path = out_path.with_name("glyph_map.json")
    map_path.write_text(json.dumps(glyph_map, indent=2, ensure_ascii=False), encoding="utf-8")
    return out_path


def build_font_from_dictionary(dict_path: Path, out_path: Path, family_name: str = "LogiGlyph") -> Path:
    specs = load_dictionary(dict_path)
    return build_font(specs, out_path=out_path, family_name=family_name)

