package com.example.addon;

import net.minecraft.client.network.ServerAddress;

import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class launcher {
    private static String get_classpaths(String libdir){
        return String.join(":", new String[]{
            libdir + "/org/lwjgl/lwjgl-freetype-natives-linux/3.3.3/lwjgl-freetype-natives-linux-3.3.3.jar",
            libdir + "/org/lwjgl/lwjgl-freetype/3.3.3/lwjgl-freetype-3.3.3.jar",
            libdir + "/org/lwjgl/lwjgl-glfw-natives-linux/3.3.3/lwjgl-glfw-natives-linux-3.3.3.jar",
            libdir + "/org/lwjgl/lwjgl-glfw/3.3.3/lwjgl-glfw-3.3.3.jar",
            libdir + "/org/lwjgl/lwjgl-jemalloc-natives-linux/3.3.3/lwjgl-jemalloc-natives-linux-3.3.3.jar",
            libdir + "/org/lwjgl/lwjgl-jemalloc/3.3.3/lwjgl-jemalloc-3.3.3.jar",
            libdir + "/org/lwjgl/lwjgl-natives-linux/3.3.3/lwjgl-natives-linux-3.3.3.jar",
            libdir + "/org/lwjgl/lwjgl-openal-natives-linux/3.3.3/lwjgl-openal-natives-linux-3.3.3.jar",
            libdir + "/org/lwjgl/lwjgl-openal/3.3.3/lwjgl-openal-3.3.3.jar",
            libdir + "/org/lwjgl/lwjgl-opengl-natives-linux/3.3.3/lwjgl-opengl-natives-linux-3.3.3.jar",
            libdir + "/org/lwjgl/lwjgl-opengl/3.3.3/lwjgl-opengl-3.3.3.jar",
            libdir + "/org/lwjgl/lwjgl-stb-natives-linux/3.3.3/lwjgl-stb-natives-linux-3.3.3.jar",
            libdir + "/org/lwjgl/lwjgl-stb/3.3.3/lwjgl-stb-3.3.3.jar",
            libdir + "/org/lwjgl/lwjgl-tinyfd-natives-linux/3.3.3/lwjgl-tinyfd-natives-linux-3.3.3.jar",
            libdir + "/org/lwjgl/lwjgl-tinyfd/3.3.3/lwjgl-tinyfd-3.3.3.jar",
            libdir + "/org/lwjgl/lwjgl/3.3.3/lwjgl-3.3.3.jar",
            libdir + "/com/github/oshi/oshi-core/6.4.10/oshi-core-6.4.10.jar",
            libdir + "/com/google/code/gson/gson/2.10.1/gson-2.10.1.jar",
            libdir + "/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar",
            libdir + "/com/google/guava/guava/32.1.2-jre/guava-32.1.2-jre.jar",
            libdir + "/com/ibm/icu/icu4j/73.2/icu4j-73.2.jar",
            libdir + "/com/mojang/authlib/6.0.54/authlib-6.0.54.jar",
            libdir + "/com/mojang/blocklist/1.0.10/blocklist-1.0.10.jar",
            libdir + "/com/mojang/brigadier/1.2.9/brigadier-1.2.9.jar",
            libdir + "/com/mojang/datafixerupper/8.0.16/datafixerupper-8.0.16.jar",
            libdir + "/com/mojang/logging/1.2.7/logging-1.2.7.jar",
            libdir + "/com/mojang/patchy/2.2.10/patchy-2.2.10.jar",
            libdir + "/com/mojang/text2speech/1.17.9/text2speech-1.17.9.jar",
            libdir + "/commons-codec/commons-codec/1.16.0/commons-codec-1.16.0.jar",
            libdir + "/commons-io/commons-io/2.15.1/commons-io-2.15.1.jar",
            libdir + "/commons-logging/commons-logging/1.2/commons-logging-1.2.jar",
            libdir + "/io/netty/netty-buffer/4.1.97.Final/netty-buffer-4.1.97.Final.jar",
            libdir + "/io/netty/netty-codec/4.1.97.Final/netty-codec-4.1.97.Final.jar",
            libdir + "/io/netty/netty-common/4.1.97.Final/netty-common-4.1.97.Final.jar",
            libdir + "/io/netty/netty-handler/4.1.97.Final/netty-handler-4.1.97.Final.jar",
            libdir + "/io/netty/netty-resolver/4.1.97.Final/netty-resolver-4.1.97.Final.jar",
            libdir + "/io/netty/netty-transport-classes-epoll/4.1.97.Final/netty-transport-classes-epoll-4.1.97.Final.jar",
            libdir + "/io/netty/netty-transport-native-epoll/4.1.97.Final/netty-transport-native-epoll-4.1.97.Final-linux-aarch_64.jar",
            libdir + "/io/netty/netty-transport-native-epoll/4.1.97.Final/netty-transport-native-epoll-4.1.97.Final-linux-x86_64.jar",
            libdir + "/io/netty/netty-transport-native-unix-common/4.1.97.Final/netty-transport-native-unix-common-4.1.97.Final.jar",
            libdir + "/io/netty/netty-transport/4.1.97.Final/netty-transport-4.1.97.Final.jar",
            libdir + "/it/unimi/dsi/fastutil/8.5.12/fastutil-8.5.12.jar",
            libdir + "/net/java/dev/jna/jna-platform/5.14.0/jna-platform-5.14.0.jar",
            libdir + "/net/java/dev/jna/jna/5.14.0/jna-5.14.0.jar",
            libdir + "/net/sf/jopt-simple/jopt-simple/5.0.4/jopt-simple-5.0.4.jar",
            libdir + "/org/apache/commons/commons-compress/1.26.0/commons-compress-1.26.0.jar",
            libdir + "/org/apache/commons/commons-lang3/3.14.0/commons-lang3-3.14.0.jar",
            libdir + "/org/apache/httpcomponents/httpclient/4.5.13/httpclient-4.5.13.jar",
            libdir + "/org/apache/httpcomponents/httpcore/4.4.16/httpcore-4.4.16.jar",
            libdir + "/org/apache/logging/log4j/log4j-api/2.22.1/log4j-api-2.22.1.jar",
            libdir + "/org/apache/logging/log4j/log4j-core/2.22.1/log4j-core-2.22.1.jar",
            libdir + "/org/apache/logging/log4j/log4j-slf4j2-impl/2.22.1/log4j-slf4j2-impl-2.22.1.jar",
            libdir + "/org/jcraft/jorbis/0.0.17/jorbis-0.0.17.jar",
            libdir + "/org/joml/joml/1.10.5/joml-1.10.5.jar",
            libdir + "/org/lz4/lz4-java/1.8.0/lz4-java-1.8.0.jar",
            libdir + "/org/slf4j/slf4j-api/2.0.9/slf4j-api-2.0.9.jar",
            libdir + "/net/fabricmc/intermediary/1.21/intermediary-1.21.jar",
            libdir + "/org/ow2/asm/asm/9.6/asm-9.6.jar",
            libdir + "/org/ow2/asm/asm-analysis/9.6/asm-analysis-9.6.jar",
            libdir + "/org/ow2/asm/asm-commons/9.6/asm-commons-9.6.jar",
            libdir + "/org/ow2/asm/asm-tree/9.6/asm-tree-9.6.jar",
            libdir + "/org/ow2/asm/asm-util/9.6/asm-util-9.6.jar",
            libdir + "/net/fabricmc/sponge-mixin/0.13.3+mixin.0.8.5/sponge-mixin-0.13.3+mixin.0.8.5.jar",
            libdir + "/net/fabricmc/fabric-loader/0.15.11/fabric-loader-0.15.11.jar",
            libdir + "/com/mojang/minecraft/1.21/minecraft-1.21-client.jar"
        });
    }

    private static List<String> get_args(String gameDir){
        String mainClass = "net.fabricmc.loader.impl.launch.knot.KnotClient";

        // Set up JVM arguments
        List<String> jvmArgs = new ArrayList<>();
        jvmArgs.add("java");
        jvmArgs.add("-Xmx512M"); // Adjust max heap size as needed
        jvmArgs.add("-XX:+UnlockExperimentalVMOptions");
        jvmArgs.add("-XX:+UseG1GC");
        jvmArgs.add("-XX:G1NewSizePercent=20");
        jvmArgs.add("-XX:G1ReservePercent=20");
        jvmArgs.add("-XX:MaxGCPauseMillis=50");
        jvmArgs.add("-XX:G1HeapRegionSize=32M");
        jvmArgs.add("-cp");
        jvmArgs.add(get_classpaths(gameDir+"/libraries"));
        jvmArgs.add("-Djava.library.path=\"" + gameDir + "/natives\"");
//            jvmArgs.add(minecraftJar);
        jvmArgs.add(mainClass);

        // Add Minecraft arguments
        jvmArgs.add("--version");
        jvmArgs.add("1.21"); // Adjust to your Minecraft version
        jvmArgs.add("--gameDir");
        jvmArgs.add(gameDir);
        jvmArgs.add("--assetsDir");
        jvmArgs.add(gameDir+"/assets");
        jvmArgs.add("--assetIndex");
        jvmArgs.add("17"); // Adjust to your asset index
        jvmArgs.add("--uuid");
        jvmArgs.add(java.util.UUID.randomUUID().toString());
        jvmArgs.add("--accessToken");
        jvmArgs.add("0"); // Use a valid access token for online play
        jvmArgs.add("--userType");
        jvmArgs.add("mojang");

        return jvmArgs;
    }

    private int botid;
    private String username;
    private String gamedir;
    private ServerAddress addr;

    public launcher(String gamedir, String username, int botid, ServerAddress address){
        this.gamedir = gamedir;
        this.username = username;
        this.botid = botid;
        this.addr = address;
    }

    private Process process;

    private BufferedOutputStream stdin;

    private Thread processThread = null;
    private Thread socketThread = null;

    private BufferedReader mcoutput = null;
    public PrintWriter mcinput;

    private void run(){
        System.out.println(addr.toString());

        try {
            List<String> jvmArgs = get_args(gamedir);
            jvmArgs.add("--username");
            jvmArgs.add(username + botid);

            System.out.println(String.join(" ", jvmArgs.toArray(new String[0])));

            // Create process builder
            ProcessBuilder pb = new ProcessBuilder(jvmArgs);
            pb.directory(new File("."));
//            pb.inheritIO();

            // Start the process
            process = pb.start();
            mcoutput = new BufferedReader(new InputStreamReader(process.getInputStream()));

            String str;
            while ((str = mcoutput.readLine()) != null) {
                System.out.println(str);
                if(str.contains("(no-more-render) Started!"))
                    finishInit();
            }

//
            System.out.println("Minecraft exited with code: " + process.waitFor());

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void finishInit(){
        System.out.println("Started connecting ...");
        socketThread = new Thread(() -> {
            while(!socketThread.isInterrupted()) {
                try (Socket socket = new Socket("localhost", 65000 + botid)) {

                    System.out.println("Conencted to bot " + botid + "!");

                    mcinput = new PrintWriter(socket.getOutputStream(), true);
                    mcinput.println("connect " + addr.getAddress() + " " + addr.getPort());

                    BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));

                    String line;
                    while ((line = reader.readLine()) != null && !socketThread.isInterrupted()) {
                        System.out.println(line);
                    }
                } catch (UnknownHostException ex) {

                    System.out.println("Server not found: " + ex.getMessage());

                } catch (IOException ex) {

                    System.out.println("I/O error: " + ex.getMessage());
                }
            }
        });
        socketThread.start();
    }







    public void start(){
        processThread = new Thread(this::run);
        processThread.start();
    }

    public void stop(){
        if(processThread != null) {
            processThread.interrupt();
            processThread = null;
        }
        if(process != null){
            process.destroyForcibly();
            process = null;
        }
        if(socketThread != null){
            socketThread.interrupt();
            socketThread = null;
        }
    }
}
