#!/usr/bin/env python3
"""Dumps the Minecraft API surface PhotoSync depends on, for every bucket.

The adapters in :platform are written against exactly these signatures, and
docs/PORTING.md's breakpoint table is generated from this output. Run it after
adding a bucket to see, in one place, what that version changed:

    ./gradlew :platform:<version>:build      # once, so Loom caches the jar
    python3 tools/probe-api.py > /tmp/api.txt
"""

import glob
import os
import re
import subprocess
import sys

LOOM = os.path.expanduser("~/.gradle/caches/fabric-loom/minecraftMaven/net/minecraft")

# Class, then the members worth watching. A name missing from the output is
# itself the finding -- that is how the 26.x GuiGraphics removal showed up.
TARGETS = [
    (["net.minecraft.client.gui.GuiGraphics",
      "net.minecraft.client.gui.GuiGraphicsExtractor"],
     r"\b(fill|fillGradient|enableScissor|disableScissor|drawString|text|blit)\("),
    (["net.minecraft.client.gui.Font"], r"\b(width|lineHeight)\b"),
    (["net.minecraft.client.gui.components.events.GuiEventListener"],
     r"\b(mouseClicked|mouseReleased|mouseDragged|mouseScrolled|keyPressed|charTyped)\("),
    (["net.minecraft.client.gui.screens.Screen"],
     r"\b(render|extractRenderState|renderBackground|extractBackground|init|resize|removed"
     r"|isPauseScreen|shouldCloseOnEsc|tick)\("),
    (["net.minecraft.client.renderer.texture.DynamicTexture"], r"DynamicTexture\(|close\(|upload\(|getPixels\("),
    (["net.minecraft.client.renderer.texture.TextureManager"], r"\b(register|release|getTexture)\("),
    (["com.mojang.blaze3d.platform.NativeImage"], r"\b(read|writeToFile|setPixelRGBA|setPixel|close)\("),
    (["net.minecraft.client.Screenshot"], r"\b(grab|takeScreenshot|_grab)\("),
    # setScreen picks up setScreenAndShow too, which is the 26.x replacement.
    (["net.minecraft.client.Minecraft"], r"\b(stop|close|getInstance|setScreen|getWindow)\w*\("),
    (["net.minecraft.util.thread.BlockableEventLoop"], r"\b(execute|isSameThread)\("),
    (["net.minecraft.Util", "net.minecraft.util.Util"], r"\b(getPlatform|ioPool)\("),
]

VERSION_KEY = re.compile(r"minecraft-merged-(\d+(?:\.\d+)*)-")


def jars():
    found = {}
    for jar in glob.glob(os.path.join(LOOM, "minecraft-merged", "*", "*.jar")):
        if "sources" in jar:
            continue
        match = VERSION_KEY.search(os.path.basename(jar))
        if match:
            found.setdefault(match.group(1), jar)
    return sorted(found.items(), key=lambda item: [int(p) for p in item[0].split(".")])


def dump(jar, class_name, pattern):
    try:
        out = subprocess.run(["javap", "-cp", jar, "-p", class_name],
                             capture_output=True, text=True, timeout=120)
    except FileNotFoundError:
        sys.exit("javap not on PATH; run this with a JDK available")
    if out.returncode != 0:
        return None
    return [line.strip() for line in out.stdout.splitlines() if re.search(pattern, line)]


def main():
    for version, jar in jars():
        print("=" * 70)
        print(version)
        print("=" * 70)
        for class_names, pattern in TARGETS:
            for class_name in class_names:
                members = dump(jar, class_name, pattern)
                if members is None:
                    continue
                print(f"\n-- {class_name}")
                for member in members:
                    print(f"   {member}")
        print()


if __name__ == "__main__":
    main()
