package net.lax1dude.eaglercraft;

import static org.teavm.jso.webgl.WebGLRenderingContext.*;

import org.json.JSONException;
import org.json.JSONObject;
import org.teavm.jso.JSBody;
import org.teavm.jso.JSExceptions;
import org.teavm.jso.JSFunctor;
import org.teavm.jso.JSObject;
import org.teavm.jso.browser.Window;
import org.teavm.jso.core.JSError;
import org.teavm.jso.dom.html.HTMLCanvasElement;
import org.teavm.jso.dom.html.HTMLDocument;
import org.teavm.jso.dom.html.HTMLElement;
import org.teavm.jso.webgl.WebGLRenderingContext;

import net.lax1dude.eaglercraft.adapter.DetectAnisotropicGlitch;
import net.lax1dude.eaglercraft.adapter.EaglerAdapterImpl2;
import net.minecraft.src.Minecraft;
import net.minecraft.src.ServerList;

public class Client {
	private static final String crashImage = "data:image/png;base64,[encoded data, 31296 characters]";

	// avoid inlining of constant
	private static String crashImageWrapper() {
		return crashImage.substring(0);
	}
	
	@JSBody(params = { }, script = "if(window.eaglercraftOpts) { return (typeof window.eaglercraftOpts === \"string\") ? window.eaglercraftOpts :"
			+ "JSON.stringify(window.eaglercraftOpts); } else { return null; }")
	private static native String getEaglerOpts();
	
	public static HTMLElement rootElement = null;
	public static Minecraft instance = null;
	public static void main(String[] args) {
		String newArgs = getEaglerOpts();
		if(newArgs != null) {
			crashScreenOptsDump = "window.eaglercraftOpts = " + newArgs;
			try {
				newMain(new JSONObject(newArgs));
			}catch(JSONException ex) {
				Window.alert("There's a JSON syntax error in window.eaglercraftOpts:\n" + ex.toString());
				ex.printStackTrace();
				return;
			}
		}else {
			oldMain();
		}
	}
	
	private static String crashScreenOptsDump = null;

	private static void newMain(JSONObject conf) {

		String containerEl = conf.getString("container");

		rootElement = Window.current().getDocument().getElementById(containerEl);
		if(rootElement == null) {
			throw new JSONException("Container element \"" + containerEl + "\" does not exist in page");
		}

		EaglerAdapterImpl2.setServerToJoinOnLaunch(conf.optString("joinServer", null));

		String assetsURI = conf.getString("assetsURI");
		if(assetsURI.length() > 256) {
			conf.put("assetsURI", assetsURI.substring(0, 256) + " ... ");
			crashScreenOptsDump = "window.eaglercraftOpts = " + conf.toString();
		}
		String serverWorkerURI = conf.optString("serverWorkerURI", "worker_bootstrap.js");
		EaglerAdapterImpl2.setWorldDatabaseName(conf.optString("worldsFolder", "MAIN"));
		
		registerErrorHandler();

		try {
			
			EaglerAdapterImpl2.initializeContext(rootElement, assetsURI, serverWorkerURI);

			ServerList.loadDefaultServers(conf);
			AssetRepository.loadOverrides(conf);
			LocalStorageManager.loadStorage();
	
			run0();
			
		}catch(Throwable t) {
			showCrashScreen(t.toString() + "\n\n" + getStackTrace(t));
			return;
		}
	}

	private static void oldMain() {
		String[] e = getOpts();
		crashScreenOptsDump = "window.minecraftOpts = [ ";
		for(int i = 0; i < e.length; ++i) {
			String sh = e[i].length() > 512 ? (e[i].substring(0, 512) + "...") : e[i];
			if(i > 0) {
				crashScreenOptsDump += ", ";
			}
			crashScreenOptsDump += "\"" + sh + "\"";
		}
		crashScreenOptsDump += " ]";
		
		registerErrorHandler();
		
		try {
			
			EaglerAdapterImpl2.initializeContext(rootElement = Window.current().getDocument().getElementById(e[0]), e[1], "worker_bootstrap.js");
			
			LocalStorageManager.loadStorage();
			if(e.length > 2 && e[2].length() > 0) {
				ServerList.loadDefaultServers(e[2]);
			}
			if(e.length > 3) {
				EaglerAdapterImpl2.setServerToJoinOnLaunch(e[3]);
			}
			
			run0();
			
		}catch(Throwable t) {
			showCrashScreen(t.toString() + "\n\n" + getStackTrace(t));
			return;
		}
	}
	
	private static String getStackTrace(Throwable t) {
		JSObject obj = JSExceptions.getJSException(t);
		if(obj != null) {
			JSError err = (JSError)obj;
			return err.getStack() == null ? "[no stack trace]" : err.getStack();
		}else {
			return "[no stack trace]";
		}
	}

	private static void run0() {
		System.out.println(" -------- starting minecraft -------- ");
		instance = new Minecraft();
		run1();
	}

	private static void run1() {
		instance.run();
		//IntegratedServer.startIntegratedServer();
	}

	@JSBody(params = { }, script = "return window.minecraftOpts;")
	public static native String[] getOpts();
	
	public static void registerErrorHandler() {
		setWindowErrorHandler(new WindowErrorHandler() {

			@Override
			public void call(String message, String file, int line, int col, JSError error) {
				StringBuilder str = new StringBuilder();
				
				str.append("Native Browser Exception\n");
				str.append("----------------------------------\n");
				str.append("  Line: ").append((file == null ? "unknown" : file) + ":" + line + ":" + col).append('\n');
				str.append("  Type: ").append(error == null ? "generic" : error.getName()).append('\n');
				
				if(error != null) {
					str.append("  Desc: ").append(error.getMessage() == null ? "null" : error.getMessage()).append('\n');
				}
				
				if(message != null) {
					if(error == null || error.getMessage() == null || !message.endsWith(error.getMessage())) {
						str.append("  Desc: ").append(message).append('\n');
					}
				}
				
				str.append("----------------------------------\n\n");
				str.append(error.getStack() == null ? "No stack trace is available" : error.getStack()).append('\n');
				
				showCrashScreen(str.toString());
			}
			
		});
	}

	@JSFunctor
	private static interface WindowErrorHandler extends JSObject {
		void call(String message, String file, int line, int col, JSError error);
	}
	
	@JSBody(params = { "handler" }, script = "window.addEventListener(\"error\", function(e) { handler("
			+ "(typeof e.message === \"string\") ? e.message : null,"
			+ "(typeof e.filename === \"string\") ? e.filename : null,"
			+ "(typeof e.lineno === \"number\") ? e.lineno : 0,"
			+ "(typeof e.colno === \"number\") ? e.colno : 0,"
			+ "(typeof e.error === \"undefined\") ? null : e.error); });")
	public static native void setWindowErrorHandler(WindowErrorHandler handler);

	private static boolean isCrashed = false;

	private static void showCrashScreen(String t) {
		if(!isCrashed) {
			isCrashed = true;

			StringBuilder str = new StringBuilder();
			str.append("Game Crashed! I have fallen and I can't get up! If this has happened more than once then please copy the text on this screen and publish it in the issues feed of this fork's GitHub repository.\n\nThe URL to this fork's GitHub repository is: " + ConfigConstants.forkMe + "\n\n");
			str.append(t);
			str.append('\n').append('\n');
			str.append("eaglercraft.version = \"").append(ConfigConstants.version).append("\"\n");
			str.append("eaglercraft.minecraft = \"1.6.4\"\n");
			str.append("eaglercraft.brand = \"Catfoolyou\"\n");
			str.append("eaglercraft.username = \"").append(EaglerProfile.username).append("\"\n");
			str.append('\n');
			str.append(addWebGLToCrash());
			str.append('\n');
			str.append(crashScreenOptsDump).append('\n');
			str.append('\n');
			addDebugNav(str, "userAgent");
			addDebugNav(str, "vendor");
			addDebugNav(str, "language");
			addDebugNav(str, "hardwareConcurrency");
			addDebugNav(str, "deviceMemory");
			addDebugNav(str, "platform");
			addDebugNav(str, "product");
			str.append('\n');
			str.append("rootElement.clientWidth = ").append(rootElement.getClientWidth()).append('\n');
			str.append("rootElement.clientHeight = ").append(rootElement.getClientHeight()).append('\n');
			addDebug(str, "innerWidth");
			addDebug(str, "innerHeight");
			addDebug(str, "outerWidth");
			addDebug(str, "outerHeight");
			addDebug(str, "devicePixelRatio");
			addDebugScreen(str, "availWidth");
			addDebugScreen(str, "availHeight");
			addDebugScreen(str, "colorDepth");
			addDebugScreen(str, "pixelDepth");
			str.append('\n');
			addDebugLocation(str, "href");
			str.append("\n----- Begin Minecraft Config -----\n");
			str.append(LocalStorageManager.dumpConfiguration());
			str.append("\n----- End Minecraft Config -----\n\n");
			addDebug(str, "minecraftServer");

			String s = rootElement.getAttribute("style");
			rootElement.setAttribute("style", (s == null ? "" : s) + "position:relative;");
			HTMLDocument doc = Window.current().getDocument();
			HTMLElement img = doc.createElement("img");
			HTMLElement div = doc.createElement("div");
			img.setAttribute("style", "z-index:100;position:absolute;top:10px;left:calc(50% - 151px);");
			img.setAttribute("src", crashImageWrapper());
			div.setAttribute("style", "z-index:100;position:absolute;top:135px;left:10%;right:10%;bottom:30px;background-color:white;border:1px solid #cccccc;overflow-x:hidden;overflow-y:scroll;overflow-wrap:break-word;white-space:pre-wrap;font: 14px monospace;padding:10px;");
			rootElement.appendChild(img);
			rootElement.appendChild(div);
			div.appendChild(doc.createTextNode(str.toString()));
			
			EaglerAdapterImpl2.removeEventHandlers();

		}
	}

	private static String addWebGLToCrash() {
		StringBuilder ret = new StringBuilder();
		
		WebGLRenderingContext ctx = EaglerAdapterImpl2.webgl;
		
		if(ctx == null) {
			HTMLCanvasElement cvs = (HTMLCanvasElement) Window.current().getDocument().createElement("canvas");
			
			cvs.setWidth(64);
			cvs.setHeight(64);
			
			ctx = (WebGLRenderingContext)cvs.getContext("webgl");
		}
		
		if(ctx != null) {
			if(EaglerAdapterImpl2.webgl != null) {
				ret.append("webgl.version = ").append(ctx.getParameterString(VERSION)).append('\n');
			}
			if(ctx.getExtension("WEBGL_debug_renderer_info") != null) {
				ret.append("webgl.renderer = ").append(ctx.getParameterString(/* UNMASKED_RENDERER_WEBGL */ 0x9246)).append('\n');
				ret.append("webgl.vendor = ").append(ctx.getParameterString(/* UNMASKED_VENDOR_WEBGL */ 0x9245)).append('\n');
			}else {
				ret.append("webgl.renderer = ").append("" + ctx.getParameterString(RENDERER) + " [masked]").append('\n');
				ret.append("webgl.vendor = ").append("" + ctx.getParameterString(VENDOR) + " [masked]").append('\n');
			}
			ret.append("\nwebgl.anisotropicGlitch = ").append(DetectAnisotropicGlitch.hasGlitch()).append('\n');
		}else {
			ret.append("Failed to query GPU info!\n");
		}
		
		return ret.toString();
	}

	public static void showIncompatibleScreen(String t) {
		if(!isCrashed) {
			isCrashed = true;
			
			String s = rootElement.getAttribute("style");
			rootElement.setAttribute("style", (s == null ? "" : s) + "position:relative;");
			HTMLDocument doc = Window.current().getDocument();
			HTMLElement img = doc.createElement("img");
			HTMLElement div = doc.createElement("div");
			img.setAttribute("style", "z-index:100;position:absolute;top:10px;left:calc(50% - 151px);");
			img.setAttribute("src", crashImageWrapper());
			div.setAttribute("style", "z-index:100;position:absolute;top:135px;left:10%;right:10%;bottom:30px;background-color:white;border:1px solid #cccccc;overflow-x:hidden;overflow-y:scroll;font:18px sans-serif;padding:40px;");
			rootElement.appendChild(img);
			rootElement.appendChild(div);
			div.setInnerHTML("<h2><svg style=\"vertical-align:middle;margin:0px 16px 8px 8px;\" xmlns=\"http://www.w3.org/2000/svg\" width=\"48\" height=\"48\" viewBox=\"0 0 48 48\" fill=\"none\"><path stroke=\"#000000\" stroke-width=\"3\" stroke-linecap=\"square\" d=\"M1.5 8.5v34h45v-28m-3-3h-10v-3m-3-3h-10m15 6h-18v-3m-3-3h-10\"/><path stroke=\"#000000\" stroke-width=\"2\" stroke-linecap=\"square\" d=\"M12 21h0m0 4h0m4 0h0m0-4h0m-2 2h0m20-2h0m0 4h0m4 0h0m0-4h0m-2 2h0\"/><path stroke=\"#000000\" stroke-width=\"2\" stroke-linecap=\"square\" d=\"M20 30h0 m2 2h0 m2 2h0 m2 2h0 m2 -2h0 m2 -2h0 m2 -2h0\"/></svg>+ This device is incompatible with Eaglercraft&ensp;:(</h2>"
					+ "<div style=\"margin-left:40px;\">"
					+ "<p style=\"font-size:1.2em;\"><b style=\"font-size:1.1em;\">Issue:</b> <span style=\"color:#BB0000;\" id=\"crashReason\"></span><br /></p>"
					+ "<p style=\"margin-left:10px;font:0.9em monospace;\" id=\"crashUserAgent\"></p>"
					+ "<p style=\"margin-left:10px;font:0.9em monospace;\" id=\"crashWebGL\"></p>"
					+ "<p><br /><span style=\"font-size:1.1em;border-bottom:1px dashed #AAAAAA;padding-bottom:5px;\">Things you can try:</span></p>"
					+ "<ol>"
					+ "<li><span style=\"font-weight:bold;\">Just try using Eaglercraft on a different device</span>, it isn't a bug it's common sense</li>"
					+ "<li style=\"margin-top:7px;\">If you are on a mobile device, please try a proper desktop or a laptop computer</li>"
					+ "<li style=\"margin-top:7px;\">If you are using a device with no mouse cursor, please use a device with a mouse cursor</li>"
					+ "<li style=\"margin-top:7px;\">If you are not using Chrome/Edge, try installing the latest Google Chrome</li>"
					+ "<li style=\"margin-top:7px;\">If your browser is out of date, please update it to the latest version</li>"
					+ "<li style=\"margin-top:7px;\">If you are using an old OS such as Windows 7, please try Windows 10 or 11</li>"
					+ "<li style=\"margin-top:7px;\">If you have a GPU launched before 2009, WebGL 2.0 support may be impossible</li>"
					+ "</ol>"
					+ "</div>");
			
			div.querySelector("#crashReason").appendChild(doc.createTextNode(t));
			div.querySelector("#crashUserAgent").appendChild(doc.createTextNode(getStringNav("userAgent")));
			
			EaglerAdapterImpl2.removeEventHandlers();
			
			String webGLRenderer = "No GL_RENDERER string could be queried";
			
			try {
				HTMLCanvasElement cvs = (HTMLCanvasElement) Window.current().getDocument().createElement("canvas");
				
				cvs.setWidth(64);
				cvs.setHeight(64);
				
				WebGLRenderingContext ctx = (WebGLRenderingContext)cvs.getContext("webgl");
				
				if(ctx != null) {
					String r;
					if(ctx.getExtension("WEBGL_debug_renderer_info") != null) {
						r = ctx.getParameterString(/* UNMASKED_RENDERER_WEBGL */ 0x9246);
					}else {
						r = ctx.getParameterString(RENDERER);
						if(r != null) {
							r += " [masked]";
						}
					}
					if(r != null) {
						webGLRenderer = r;
					}
				}
			}catch(Throwable tt) {
			}
			
			div.querySelector("#crashWebGL").appendChild(doc.createTextNode(webGLRenderer));
			
		}
	}

	@JSBody(params = { "v" }, script = "try { return \"\"+window[v]; } catch(e) { return \"<error>\"; }")
	private static native String getString(String var);

	@JSBody(params = { "v" }, script = "try { return \"\"+window.navigator[v]; } catch(e) { return \"<error>\"; }")
	private static native String getStringNav(String var);

	@JSBody(params = { "v" }, script = "try { return \"\"+window.screen[v]; } catch(e) { return \"<error>\"; }")
	private static native String getStringScreen(String var);

	@JSBody(params = { "v" }, script = "try { return \"\"+window.location[v]; } catch(e) { return \"<error>\"; }")
	private static native String getStringLocation(String var);

	private static void addDebug(StringBuilder str, String var) {
		str.append("window.").append(var).append(" = ").append(getString(var)).append('\n');
	}

	private static void addDebugNav(StringBuilder str, String var) {
		str.append("window.navigator.").append(var).append(" = ").append(getStringNav(var)).append('\n');
	}

	private static void addDebugScreen(StringBuilder str, String var) {
		str.append("window.screen.").append(var).append(" = ").append(getStringScreen(var)).append('\n');
	}

	private static void addDebugLocation(StringBuilder str, String var) {
		str.append("window.location.").append(var).append(" = ").append(getStringLocation(var)).append('\n');
	}

	private static void addArray(StringBuilder str, String var) {
		str.append("window.").append(var).append(" = ").append(getArray(var)).append('\n');
	}

	@JSBody(params = { "v" }, script = "try { return (typeof window[v] !== \"undefined\") ? JSON.stringify(window[v]) : \"[\\\"<error>\\\"]\"; } catch(e) { return \"[\\\"<error>\\\"]\"; }")
	private static native String getArray(String var);

}
