/*
 *   This file is a part of the SteamWar software.
 *
 *   Copyright (C) 2026 SteamWar.de-Serverteam
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Affero General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *   GNU Affero General Public License for more details.
 *
 *   You should have received a copy of the GNU Affero General Public License
 *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package de.steamwar;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.List;

/**
 * Command-line tool that produces a widened copy of a JAR for use as a
 * compile-time stub in IntelliJ / Gradle.
 *
 * Usage:
 *   java -jar jar-widener.jar <input.jar> <output.jar> [file.accesswidener ...]
 *
 * The output JAR is identical to the input JAR except that every class
 * targeted by the access widener entries has its access flags patched:
 *   accessible  → public
 *   extendable  → public + non-final
 *   mutable     → non-final field
 *
 * Intended for use as a Gradle task so IntelliJ sees the already-widened
 * class when you Ctrl+click NMS code, and javac compiles without complaints.
 */
public class Main {

    public static void main(String[] args) throws Exception {
        if (args.length < 2) {
            System.err.println("Usage: jar-widener <input.jar> <output.jar> [*.accesswidener ...]");
            System.exit(1);
        }

        Path inputJar  = Path.of(args[0]);
        Path outputJar = Path.of(args[1]);

        if (!Files.exists(inputJar)) {
            System.err.println("Input JAR not found: " + inputJar);
            System.exit(1);
        }

        // --- Collect all access widener entries ---
        List<AccessWidenerEntry> entries = new ArrayList<>();

        if (args.length > 2) {
            for (int i = 2; i < args.length; i++) {
                Path awFile = Path.of(args[i]);
                if (!Files.exists(awFile)) {
                    System.err.println("Warning: access widener file not found, skipping: " + awFile);
                    continue;
                }
                try (InputStream in = Files.newInputStream(awFile)) {
                    List<AccessWidenerEntry> parsed = AccessWidenerParser.parse(in);
                    System.out.println("Loaded " + parsed.size() + " entries from " + awFile.getFileName());
                    entries.addAll(parsed);
                }
            }
        }

        if (entries.isEmpty()) {
            System.out.println("No access widener entries found — copying JAR unchanged.");
            Files.createDirectories(outputJar.getParent());
            Files.copy(inputJar, outputJar, StandardCopyOption.REPLACE_EXISTING);
            return;
        }

        System.out.println("Widening " + inputJar.getFileName()
                + " with " + entries.size() + " total entr"
                + (entries.size() == 1 ? "y" : "ies") + "...");

        // --- Copy input → output, transforming .class files in place ---
        Files.createDirectories(outputJar.getParent());
        Files.copy(inputJar, outputJar, StandardCopyOption.REPLACE_EXISTING);

        ClassPatcher patcher = new ClassPatcher(entries);

        try (FileSystem fs = FileSystems.newFileSystem(outputJar)) {
            // Walk every .class entry in the JAR
            try (var stream = Files.walk(fs.getPath("/"))) {
                stream.filter(p -> p.toString().endsWith(".class"))
                        .forEach(classPath -> patchClass(fs, classPath, patcher));
            }
        }

        System.out.println("Done. Widened JAR written to " + outputJar);
    }

    private static void patchClass(FileSystem fs, Path classPath, ClassPatcher patcher) {
        // Derive internal class name from path  e.g. /net/minecraft/Foo.class → net/minecraft/Foo
        String internalName = classPath.toString()
                .replaceFirst("^/", "")
                .replace(".class", "");

        try {
            byte[] original = Files.readAllBytes(classPath);
            byte[] patched  = patcher.patch(internalName, original);

            if (patched != null) {
                Files.write(classPath, patched);
                System.out.println("  Widened: " + internalName);
            }
        } catch (IOException e) {
            System.err.println("  Warning: failed to patch " + internalName + ": " + e.getMessage());
        }
    }
}

