#!/usr/bin/env python3

import os
import subprocess
import sys

def parse_hex_byte(s):
    assert(s.endswith("h"))
    s = s[:-1]
    assert(0 <= int(s, 16) <= 255)
    return "0x" + s

def parse_hex_list(s):
    if s == "n/a":
        return None
    if s.startswith("("):
        assert(s.endswith(")h"))
        s = s[1:-2]
    l = [b.strip() for b in s.split(",")]
    l = [b[:-1] if b.endswith("h") else b for b in l]
    for b in l:
        assert(0 <= int(b, 16) <= 255)
    return "0x" + "".join(l)

def parse_pixel_param(l):
    l = l[2].split(" ")
    assert(len(l) >= 2 and l[1] == "Pixels")
    return int(l[0])

def parse_line_param(l):
    l = l[1].split(" ")
    assert(len(l) >= 2 and l[1] == "lines")
    return int(l[0])

def parse_timing(page):
    lines = [l.strip() for l in page.splitlines()]
    if len(lines) < 6:
        return None
    try:
        i = lines.index("Detailed Timing Parameters")
    except ValueError:
        return None
    raw_metadata = lines[1:i]
    raw_params = lines[i+1:]

    metadata = {}
    for l in raw_metadata:
        if not ": " in l:
            continue
        k, v = l.split(":", 1)
        metadata[k.strip()] = v.strip()
    assert("Resolution" in metadata and "EDID ID" in metadata)

    if "Proposed" in metadata and not "Adopted" in metadata:
        print("skipping proposed but not adopted timing: " + metadata["Resolution"],
              file=sys.stderr)
        return None

    params = {}
    for l in raw_params:
        if not " = " in l:
            continue
        tokens = [l.strip() for l in l.split("=")]
        params[tokens[0]] = tokens[1:]
    assert("Timing Name" in params)

    res = metadata["Resolution"]
    size, rest = res.split(" at ", 1)
    horiz_video, vert_video = size.split("x", 1)
    refresh_rate_hz, rest = rest.split(" Hz ", 1)

    horiz_video = int(horiz_video.strip())
    vert_video = int(vert_video.strip())
    refresh_rate_hz = float(refresh_rate_hz.strip())

    ids = {}
    for kv in metadata["EDID ID"].split(";"):
        k, v = kv.split(":")
        ids[k.strip()] = v.strip()
    assert("DMT ID" in ids)

    dmt_id = parse_hex_byte(ids["DMT ID"])
    edid_std_id = parse_hex_list(ids["Std. 2 Byte Code"])
    cvt_id = parse_hex_list(ids["CVT 3 Byte Code"])

    pixel_clock_mhz = float(params["Pixel Clock"][0].split(";")[0])
    horiz_blank = parse_pixel_param(params["Hor Blank Time"])
    horiz_front_porch = parse_pixel_param(params["// H Front Porch"])
    horiz_sync_pulse = parse_pixel_param(params["Hor Sync Time"])
    horiz_border = parse_pixel_param(params["// H Right Border"])
    assert(horiz_border == parse_pixel_param(params["// H Left Border"]))
    vert_blank = parse_line_param(params["Ver Blank Time"])
    vert_front_porch = parse_line_param(params["// V Front Porch"])
    vert_sync_pulse = parse_line_param(params["Ver Sync Time"])
    vert_border = parse_line_param(params["// V Bottom Border"])
    assert(vert_border == parse_line_param(params["// V Top Border"]))

    return {
        "dmt_id": dmt_id,
        "edid_std_id": 0 if edid_std_id is None else edid_std_id,
        "cvt_id": 0 if cvt_id is None else cvt_id,
        "horiz_video": horiz_video,
        "vert_video": vert_video,
        "refresh_rate_hz": refresh_rate_hz,
        "pixel_clock_hz": int(pixel_clock_mhz * 1000 * 1000),
        "horiz_blank": horiz_blank,
        "horiz_front_porch": horiz_front_porch,
        "horiz_sync_pulse": horiz_sync_pulse,
        "horiz_border": horiz_border,
        "vert_blank": vert_blank,
        "vert_front_porch": vert_front_porch,
        "vert_sync_pulse": vert_sync_pulse,
        "vert_border": vert_border,
    }

if len(sys.argv) != 2:
    print("usage: gen-dmt.py <DMT PDF>", file=sys.stderr)
    sys.exit(1)
in_path = sys.argv[1]
in_basename = os.path.basename(in_path)

tool_dir = os.path.dirname(os.path.realpath(__file__))
out_path = tool_dir + "/../dmt-table.c"

cmd = ["pdftotext", "-nodiag", "-layout", in_path, "-"]
page = ""
timings = []
for l in subprocess.check_output(cmd, text=True):
    if l.startswith("\f"):
        t = parse_timing(page)
        if t is not None:
            timings.append(t)
        page = l[1:]
    else:
        page += l

with open(out_path, "w+") as f:
    f.write("/* DO NOT EDIT! This file has been generated by gen-dmt.py from {}. */\n\n".format(in_basename))
    f.write('#include "dmt.h"\n\n')
    f.write("const struct di_dmt_timing _di_dmt_timings[] = {\n")
    for t in timings:
        f.write("\t{\n")
        for k, v in t.items():
            f.write("\t\t.{} = {},\n".format(k, v))
        f.write("\t},\n")
    f.write("};\n\n")
    f.write("const size_t _di_dmt_timings_len = {};\n".format(len(timings)))
