#!/usr/bin/env python3
from pathlib import Path
import re
import sys


ROOT = Path(__file__).resolve().parents[1]
PPD_DIR = ROOT / "ppd"
TEMPLATE_DIR = PPD_DIR / "_templates"
MODEL_MAP = ROOT / "supported_models.tsv"
EXPECTED_FILTER = '*cupsFilter: "application/vnd.cups-postscript 0 ptps"\n'
MANUAL_COPIES = "*cupsManualCopies: True\n"
EVEN_DUPLEX = "*cupsEvenDuplex: true\n"


def ppd_name(model: str) -> str:
    return f"Pantum {model} Series PS.ppd"


def pc_file_name(model: str) -> str:
    compact = model.replace(" Plus", "P").replace(" ", "")
    return f"{compact[:8]}.PPD"


def rewrite_line(line: str, model: str) -> tuple[str, bool]:
    replacements = {
        "*PCFileName:": f'*PCFileName: "{pc_file_name(model)}"',
        "*Product:": f'*Product: "(Pantum {model} Series)"',
        "*ShortNickName:": f'*ShortNickName: "{model} Series PS"',
        "*ModelName:": f'*ModelName: "Pantum {model} Series PS"',
        "*NickName:": f'*NickName: "Pantum {model} Series PS"',
        "*1284DeviceID:": f'*1284DeviceID: "CMD:PS3;MFG:Pantum;MDL:{model} series;"',
    }

    for prefix, replacement in replacements.items():
        if line.startswith(prefix):
            return replacement + "\n", True
    return line, False


def fix_jcl_ui_blocks(lines: list[str]) -> list[str]:
    fixed = []
    i = 0

    while i < len(lines):
        line = lines[i]
        match = re.match(r"^\*OpenUI\s+(\*\S+)", line)
        if not match:
            fixed.append(line)
            i += 1
            continue

        option = match.group(1).split("/", 1)[0].rstrip(":")
        block = [line]
        i += 1

        while i < len(lines):
            block.append(lines[i])
            if lines[i].startswith("*CloseUI:") and option in lines[i]:
                i += 1
                break
            i += 1

        is_jcl = any(
            item.startswith("*OrderDependency:") and f" JCLSetup {option}" in item
            for item in block
        )
        if is_jcl:
            block[0] = block[0].replace("*OpenUI", "*JCLOpenUI", 1)
            block[-1] = block[-1].replace("*CloseUI:", "*JCLCloseUI:", 1)

        fixed.extend(block)

    return fixed


def parse_models() -> list[tuple[str, str, str, str]]:
    rows = []
    for raw in MODEL_MAP.read_text(encoding="utf-8").splitlines():
        if not raw or raw.startswith("#"):
            continue
        fields = raw.split("\t")
        if len(fields) != 4:
            raise ValueError(f"bad model row: {raw!r}")
        rows.append((fields[0], fields[1], fields[2], fields[3]))
    return rows


def generate_one(model: str, template_model: str) -> None:
    source = TEMPLATE_DIR / ppd_name(template_model)
    target = PPD_DIR / ppd_name(model)

    if not source.exists():
        raise FileNotFoundError(f"missing template PPD: {source}")

    lines = source.read_text(encoding="utf-8", errors="surrogateescape").splitlines(True)
    seen = set()
    out = []
    source_has_filter = any(line.startswith("*cupsFilter:") for line in lines)
    saw_filter = False
    saw_manual_copies = False
    saw_even_duplex = False
    injected_filter = False

    for line in lines:
        if line.startswith("*cupsFilter:"):
            if not saw_filter:
                out.append(EXPECTED_FILTER)
                out.append(MANUAL_COPIES)
                out.append(EVEN_DUPLEX)
                saw_filter = True
                saw_manual_copies = True
                saw_even_duplex = True
            continue

        if line.startswith("*cupsManualCopies:"):
            if not saw_manual_copies:
                out.append(MANUAL_COPIES)
                saw_manual_copies = True
            continue

        if line.startswith("*cupsEvenDuplex:"):
            if not saw_even_duplex:
                out.append(EVEN_DUPLEX)
                saw_even_duplex = True
            continue

        new_line, changed = rewrite_line(line, model)
        if changed:
            seen.add(line.split(":", 1)[0] + ":")
        out.append(new_line)

        if new_line.startswith("*NickName:") and not source_has_filter and not saw_filter:
            out.append(EXPECTED_FILTER)
            out.append(MANUAL_COPIES)
            out.append(EVEN_DUPLEX)
            saw_filter = True
            saw_manual_copies = True
            saw_even_duplex = True
            injected_filter = True

    required = {
        "*PCFileName:",
        "*Product:",
        "*ShortNickName:",
        "*ModelName:",
        "*NickName:",
        "*1284DeviceID:",
    }
    missing = sorted(required - seen)
    if missing:
        raise ValueError(f"{source} missing fields: {', '.join(missing)}")

    out = fix_jcl_ui_blocks(out)
    text = "".join(out)
    if EXPECTED_FILTER.strip() not in text:
        raise ValueError(f"{source} does not use the expected ptps PostScript filter")
    if MANUAL_COPIES.strip() not in text:
        raise ValueError(f"{source} does not enable CUPS manual copies")
    if EVEN_DUPLEX.strip() not in text:
        raise ValueError(f"{source} does not enable even duplex page padding")
    if "CMD:PS3" not in text:
        raise ValueError(f"{source} does not declare PS3 support")

    target.write_text(text, encoding="utf-8", errors="surrogateescape")
    if injected_filter:
        print(f"  injected {EXPECTED_FILTER.strip()} into {target.name}")


def main() -> int:
    PPD_DIR.mkdir(parents=True, exist_ok=True)
    count = 0
    for model, template_model, status, note in parse_models():
        generate_one(model, template_model)
        print(f"{model}\t{status}\t{template_model}\t{note}")
        count += 1
    print(f"generated {count} supported PPDs")
    return 0


if __name__ == "__main__":
    try:
        raise SystemExit(main())
    except Exception as exc:
        print(f"ERROR: {exc}", file=sys.stderr)
        raise SystemExit(1)
