package me.coley.recaf.util.self;

import com.nqzero.permit.Permit;
import me.coley.recaf.Recaf;
import me.coley.recaf.util.ClasspathUtil;
import me.coley.recaf.util.Log;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Deque;
import java.util.List;
import java.util.Locale;
import java.util.Map;

/**
 * Utility for patching self when missing dependencies.
 *
 * @author Matt
 */
public class SelfPatcher {
	private static final Path DEPENDENCIES_DIR_PATH = Recaf.getDirectory("dependencies");
	private static final String[] DEPENDENCIES = new String[] {
		"https://repo1.maven.org/maven2/org/openjfx/javafx-media/11.0.2/javafx-media-11.0.2-%s.jar",
		"https://repo1.maven.org/maven2/org/openjfx/javafx-controls/11.0.2/javafx-controls-11.0.2-%s.jar",
		"https://repo1.maven.org/maven2/org/openjfx/javafx-graphics/11.0.2/javafx-graphics-11.0.2-%s.jar",
		"https://repo1.maven.org/maven2/org/openjfx/javafx-base/11.0.2/javafx-base-11.0.2-%s.jar"
	};

	/**
	 * Patch in any missing dependencies, if any.
	 */
	public static void patch() {
		// Do nothing if JavaFX is detected
		if (ClasspathUtil.classExists("javafx.application.Platform"))
			return;

		Log.info("Missing JavaFX dependencies, attempting to patch in missing classes");
		// Check if dependencies need to be downloaded
		if (!hasCachedDependencies())
		{
			Log.info(" - No local cache, downloading dependencies...");
			try {
				fetchDependencies();
			} catch(IOException ex) {
				Log.error(ex, "Failed to download dependencies!");
				System.exit(-1);
			}
		} else {
			Log.info(" - Local cache found!");
		}
		// Add the dependencies
		try {
			loadFromCache();
		} catch(IOException ex) {
			Log.error(ex, ex.getMessage());
			System.exit(-1);
		} catch(ReflectiveOperationException ex) {
			Log.error(ex, "Failed to add dependencies to classpath!");
			System.exit(-1);
		}
		Log.info(" - Done!");
	}


	/**
	 * Inject them into the current classpath.
	 *
	 * @throws IOException
	 * 		When the locally cached dependency urls cannot be resolved.
	 * @throws ReflectiveOperationException
	 * 		When the call to add these urls to the system classpath failed.
	 */
	@SuppressWarnings({"unchecked", "rawtypes"})
	private static void loadFromCache() throws IOException, ReflectiveOperationException {
		Log.info(" - Loading dependencies...");
		Log.info(" - Disabling access system");
		// Bypass the access system
		Permit.godMode();
		// Get Jar URLs
		File[] jars = DEPENDENCIES_DIR_PATH.toFile().listFiles();
		if(jars == null)
			throw new IOException("Failed to iterate over dependencies directory!");
		URL[] jarUrls = new URL[jars.length];
		for(int i = 0; i < jars.length; i++)
			jarUrls[i] = new URL("file:/"  + jars[i].getAbsolutePath());
		if (Float.parseFloat(System.getProperty("java.class.version")) > 52) {
			// Fetch UCP of application's ClassLoader
			// - ((ClassLoaders.AppClassLoader) ClassLoaders.appClassLoader()).ucp
			Class<?> clsClassLoaders = Class.forName("jdk.internal.loader.ClassLoaders");
			Object appClassLoader = clsClassLoaders.getDeclaredMethod("appClassLoader").invoke(null);
			Field fieldUCP = appClassLoader.getClass().getDeclaredField("ucp");
			fieldUCP.setAccessible(true);
			Object ucp = fieldUCP.get(appClassLoader);
			Class<?> clsUCP = ucp.getClass();
			Method m = clsUCP.getDeclaredMethod("addURL", URL.class);
			m.setAccessible(true);
			// Add each jar.
			for(int i = 0; i < jars.length; i++) {
				m.invoke(ucp, jarUrls[i]);
			}
		} else {
			Method m = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
			m.setAccessible(true);
			URLClassLoader loader = (URLClassLoader) SelfPatcher.class.getClassLoader();
			for(int i = 0; i < jars.length; i++) {
				m.invoke(loader, jarUrls[i]);
			}
		}
	}

	/**
	 * Download dependencies.
	 *
	 * @throws IOException
	 * 		When the files cannot be fetched or saved.
	 */
	private static void fetchDependencies() throws IOException {
		// Get dir to store dependencies in
		File dependenciesDir = DEPENDENCIES_DIR_PATH.toFile();
		if (!dependenciesDir.exists()) {
			dependenciesDir.mkdirs();
		}
		// Download each dependency
		String os = getOSType();
		for (String dependencyPattern : DEPENDENCIES) {
			String dependencyUrlPath = String.format(dependencyPattern, os);
			URL depURL = new URL(dependencyUrlPath);
			Path dependencyFilePath = DEPENDENCIES_DIR_PATH.resolve(getFileName(dependencyUrlPath));
			Files.copy(depURL.openStream(), dependencyFilePath, StandardCopyOption.REPLACE_EXISTING);
		}
	}

	/**
	 * @return {@code true} when the dependencies directory has files in it.
	 */
	private static boolean hasCachedDependencies() {
		String[] files = DEPENDENCIES_DIR_PATH.toFile().list();
		if (files == null)
			return false;
		return files.length >= DEPENDENCIES.length;
	}

	/**
	 * @return Operating system short-hand name.
	 */
	private static String getOSType() {
		String s = System.getProperty("os.name").toLowerCase(Locale.ROOT);
		if (s.contains("win")) {
			return "win";
		}
		if (s.contains("mac") || s.contains("osx")) {
			return "mac";
		}
		return "linux";
	}

	/**
	 * @param url
	 * 		Full url path.
	 *
	 * @return Name of file at url.
	 */
	private static String getFileName(String url) {
		return url.substring(url.lastIndexOf('/') + 1);
	}

	/**
	 * @return Java version version.
	 */
	private static float getVersion() {
		return Float.parseFloat(System.getProperty("java.class.version")) - 44;
	}
}

@@ -2954,71 +2954,24 @@
 if (
-Float.parseFloat(System.getProperty(%22java.class.v
+getV
 ersion
-%22)
+(
 ) %3E 
-52
+8
 ) %7B%0A

Use getVersion()