#!/usr/bin/env python3
from __future__ import annotations

import argparse
import json
import struct
from pathlib import Path

IMAGE_BASE = 0x400000
PAIRED_YEAR_VALUE_TABLE_VA = 0x005F3980
PAIR_COUNT = 21
TERMINAL_SCALAR_VA = 0x005F3A24
DIRECT_MULTIPLIER_VAS = [0x005C8888, 0x005C9ED8, 0x005C8680]
COMPLEMENT_DIVISOR_VA = 0x005C8A20
COMPLEMENT_MULTIPLIER_VA = 0x005C8DA8
COMPLEMENT_BIAS_VA = 0x005C8618
COMPLEMENT_SCALE_VA = 0x005C9ED0
COMPLEMENT_FLOOR_VA = 0x005C88A0
COMPLEMENT_BUILD_106_MULTIPLIER_VA = 0x005C8878
COMPLEMENT_CAP_VA = 0x005C8988
SCALED_COMPANION_NUMERATOR_VA = 0x005C8A20
SCALED_COMPANION_MULTIPLIER_VA = 0x005C8680
SCALED_COMPANION_BIAS_VA = 0x005C88C8
SCALED_COMPANION_SCALE_VA = 0x005C9EC8


def read_u32_table(blob: bytes, va: int, count: int) -> list[int]:
    offset = va - IMAGE_BASE
    data = blob[offset : offset + count * 4]
    if len(data) != count * 4:
        raise ValueError(f"table at {va:#x} truncated")
    return [struct.unpack("<I", data[i : i + 4])[0] for i in range(0, len(data), 4)]


def read_f64(blob: bytes, va: int) -> float:
    offset = va - IMAGE_BASE
    data = blob[offset : offset + 8]
    if len(data) != 8:
        raise ValueError(f"f64 at {va:#x} truncated")
    return struct.unpack("<d", data)[0]


def build_artifact(exe_bytes: bytes) -> dict[str, object]:
    raw_pairs = read_u32_table(exe_bytes, PAIRED_YEAR_VALUE_TABLE_VA, PAIR_COUNT * 2)
    terminal_scalar_raw = read_u32_table(exe_bytes, TERMINAL_SCALAR_VA, 1)[0]
    entries = []
    for year, value in zip(raw_pairs[::2], raw_pairs[1::2]):
        entries.append({"year": year, "value": float(value)})
    return {
        "source_kind": "rt3.exe-static-table",
        "reader_family": "world_refresh_selected_year_bucket_scalar_band",
        "table_virtual_address": f"0x{PAIRED_YEAR_VALUE_TABLE_VA:08x}",
        "pair_count": PAIR_COUNT,
        "terminal_scalar_virtual_address": f"0x{TERMINAL_SCALAR_VA:08x}",
        "terminal_scalar_value": float(terminal_scalar_raw),
        "direct_lane_multipliers": [
            read_f64(exe_bytes, va) for va in DIRECT_MULTIPLIER_VAS
        ],
        "complement_formula": {
            "divisor": read_f64(exe_bytes, COMPLEMENT_DIVISOR_VA),
            "multiplier": read_f64(exe_bytes, COMPLEMENT_MULTIPLIER_VA),
            "bias": read_f64(exe_bytes, COMPLEMENT_BIAS_VA),
            "scale": read_f64(exe_bytes, COMPLEMENT_SCALE_VA),
            "floor": read_f64(exe_bytes, COMPLEMENT_FLOOR_VA),
            "build_106_multiplier": read_f64(exe_bytes, COMPLEMENT_BUILD_106_MULTIPLIER_VA),
            "cap": read_f64(exe_bytes, COMPLEMENT_CAP_VA),
        },
        "scaled_companion_formula": {
            "numerator": read_f64(exe_bytes, SCALED_COMPANION_NUMERATOR_VA),
            "multiplier": read_f64(exe_bytes, SCALED_COMPANION_MULTIPLIER_VA),
            "bias": read_f64(exe_bytes, SCALED_COMPANION_BIAS_VA),
            "scale": read_f64(exe_bytes, SCALED_COMPANION_SCALE_VA),
        },
        "entries": entries,
    }


def main() -> int:
    parser = argparse.ArgumentParser(
        description="Extract the selected-year bucket ladder used by RT3 world year refresh."
    )
    parser.add_argument("exe", type=Path, help="Path to RT3.exe")
    parser.add_argument("output", type=Path, help="Output JSON path")
    args = parser.parse_args()

    artifact = build_artifact(args.exe.read_bytes())
    args.output.write_text(json.dumps(artifact, indent=2) + "\n", encoding="utf-8")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
