package net.lax1dude.eaglercraft.v1_8.internal.teavm;

import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.json.JSONException;
import org.teavm.jso.JSBody;
import org.teavm.jso.JSFunctor;
import org.teavm.jso.JSObject;
import org.teavm.jso.browser.Window;
import org.teavm.jso.core.JSArrayReader;
import org.teavm.jso.core.JSError;
import org.teavm.jso.dom.css.CSSStyleDeclaration;
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.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.EaglercraftVersion;
import net.lax1dude.eaglercraft.v1_8.internal.PlatformApplication;
import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime;
import net.lax1dude.eaglercraft.v1_8.internal.teavm.opts.JSEaglercraftXOptsAssetsURI;
import net.lax1dude.eaglercraft.v1_8.internal.teavm.opts.JSEaglercraftXOptsRoot;
import net.lax1dude.eaglercraft.v1_8.log4j.ILogRedirector;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.profile.EaglerProfile;
import net.lax1dude.eaglercraft.v1_8.sp.internal.ClientPlatformSingleplayer;
import net.minecraft.client.main.Main;

/**
 * Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 */
public class ClientMain {
	
	private static final String crashImage = "data:image/png;base64, ";

	// avoid inlining of constant
	private static String crashImageWrapper() {
		return crashImage.substring(0);
	}

	@JSBody(params = {}, script = "if((typeof window.__isEaglerX188Running === \"string\") && window.__isEaglerX188Running === \"yes\") return true; window.__isEaglerX188Running = \"yes\"; return false;")
	private static native boolean getRunningFlag();

	public static void _main() {
		PrintStream systemOut = System.out;
		PrintStream systemErr = System.err;
		if(getRunningFlag()) {
			systemErr.println("ClientMain: [ERROR] eaglercraftx is already running!");
			return;
		}
		try {
			systemOut.println("ClientMain: [INFO] eaglercraftx is starting...");
			JSObject opts = getEaglerXOpts();
			
			if(opts == null) {
				systemErr.println("ClientMain: [ERROR] the \"window.eaglercraftXOpts\" variable is undefined");
				systemErr.println("ClientMain: [ERROR] eaglercraftx cannot start");
				Window.alert("ERROR: game cannot start, the \"window.eaglercraftXOpts\" variable is undefined");
				return;
			}
			
			try {
				JSEaglercraftXOptsRoot eaglercraftOpts = (JSEaglercraftXOptsRoot)opts;
				
				configRootElementId = eaglercraftOpts.getContainer();
				if(configRootElementId == null) {
					throw new JSONException("window.eaglercraftXOpts.container is undefined!");
				}
				configRootElement = Window.current().getDocument().getElementById(configRootElementId);
				
				String epkSingleURL = eaglercraftOpts.getAssetsURI();
				if(epkSingleURL != null) {
					configEPKFiles = new EPKFileEntry[] { new EPKFileEntry(epkSingleURL, "") };
				}else {
					JSArrayReader<JSEaglercraftXOptsAssetsURI> epkURLs = eaglercraftOpts.getAssetsURIArray();
					int len = epkURLs.getLength();
					if(len == 0) {
						throw new JSONException("assetsURI array cannot be empty!");
					}
					configEPKFiles = new EPKFileEntry[len];
					for(int i = 0; i < len; ++i) {
						JSEaglercraftXOptsAssetsURI etr = epkURLs.get(i);
						String url = etr.getURL();
						if(url == null) {
							throw new JSONException("assetsURI is missing a url!");
						}
						configEPKFiles[i] = new EPKFileEntry(url, etr.getPath(""));
					}
				}
				
				configLocalesFolder = eaglercraftOpts.getLocalesURI("lang");
				if(configLocalesFolder.endsWith("/")) {
					configLocalesFolder = configLocalesFolder.substring(0, configLocalesFolder.length() - 1);
				}
				
				((TeaVMClientConfigAdapter)TeaVMClientConfigAdapter.instance).loadNative(eaglercraftOpts);
				
				systemOut.println("ClientMain: [INFO] configuration was successful");
			}catch(Throwable t) {
				systemErr.println("ClientMain: [ERROR] the \"window.eaglercraftXOpts\" variable is invalid");
				EagRuntime.debugPrintStackTraceToSTDERR(t);
				systemErr.println("ClientMain: [ERROR] eaglercraftx cannot start");
				Window.alert("ERROR: game cannot start, the \"window.eaglercraftXOpts\" variable is invalid: " + t.toString());
				return;
			}
			
			systemOut.println("ClientMain: [INFO] registering crash handlers");
			
			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());
				}
				
			});
			
			systemOut.println("ClientMain: [INFO] initializing eaglercraftx runtime");
			
			LogManager.logRedirector = new ILogRedirector() {
				@Override
				public void log(String txt, boolean err) {
					PlatformApplication.addLogMessage(txt, err);
				}
			};
			
			try {
				EagRuntime.create();
			}catch(PlatformRuntime.PlatformIncompatibleException ex) {
				systemErr.println("ClientMain: [ERROR] this browser is incompatible with eaglercraftx!");
				systemErr.println("ClientMain: [ERROR] Reason: " + ex.getMessage());
				try {
					showIncompatibleScreen(ex.getMessage());
				}catch(Throwable t) {
				}
				return;
			}catch(Throwable t) {
				systemErr.println("ClientMain: [ERROR] eaglercraftx's runtime could not be initialized!");
				EagRuntime.debugPrintStackTraceToSTDERR(t);
				showCrashScreen("EaglercraftX's runtime could not be initialized!", t);
				systemErr.println("ClientMain: [ERROR] eaglercraftx cannot start");
				return;
			}

			systemOut.println("ClientMain: [INFO] launching eaglercraftx main thread");

			try {
				Main.appMain(new String[0]);
			}catch(Throwable t) {
				systemErr.println("ClientMain: [ERROR] unhandled exception caused main thread to exit");
				EagRuntime.debugPrintStackTraceToSTDERR(t);
				showCrashScreen("Unhandled exception caused main thread to exit!", t);
			}
			
		}finally {
			systemErr.println("ClientMain: [ERROR] eaglercraftx main thread has exited");
		}
	}
	
	@JSBody(params = {}, script = "if(typeof window.eaglercraftXOpts === \"undefined\") {return null;}"
			+ "else if(typeof window.eaglercraftXOpts === \"string\") {return JSON.parse(window.eaglercraftXOpts);}"
			+ "else {return window.eaglercraftXOpts;}")
	private static native JSObject getEaglerXOpts();

	public static class EPKFileEntry {
		
		public final String url;
		public final String path;
		
		protected EPKFileEntry(String url, String path) {
			this.url = url;
			this.path = path;
		}
	}

	public static String configRootElementId = null;
	public static HTMLElement configRootElement =  null;
	public static EPKFileEntry[] configEPKFiles = null;
	public static String configLocalesFolder = null;
	
	@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);
	
	public static void showCrashScreen(String message, Throwable t) {
		try {
			showCrashScreen(message + "\n\n" + EagRuntime.getStackTrace(t));
		}catch(Throwable tt) {
		}
	}

	private static boolean isCrashed = false;

	public static void showCrashScreen(String t) {
		if(!isCrashed) {
			isCrashed = true;
			
			HTMLDocument doc = Window.current().getDocument();
			if(configRootElement == null) {
				configRootElement = doc.getElementById(configRootElementId);
			}

			HTMLElement el = configRootElement;

			StringBuilder str = new StringBuilder();
			str.append("Game Crashed! I have fallen and I can't get up!\n\n");
			str.append(t);
			str.append('\n').append('\n');
			str.append("eaglercraft.version = \"").append(EaglercraftVersion.projectForkVersion).append("\"\n");
			str.append("eaglercraft.minecraft = \"1.8.8\"\n");
			str.append("eaglercraft.brand = \"" + EaglercraftVersion.projectForkVendor + "\"\n");
			str.append("eaglercraft.username = \"").append(EaglerProfile.getName()).append("\"\n");
			str.append('\n');
			str.append(addWebGLToCrash());
			str.append('\n');
			str.append("window.eaglercraftXOpts = ");
			str.append(TeaVMClientConfigAdapter.instance.toString()).append('\n');
			str.append('\n');
			str.append("currentTime = ");
			str.append(EagRuntime.fixDateFormat(new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z")).format(new Date())).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(el == null ? "undefined" : el.getClientWidth()).append('\n');
			str.append("rootElement.clientHeight = ").append(el == null ? "undefined" : el.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');
			addDebug(str, "currentContext");
			str.append('\n');
			addDebugLocation(str, "href");
			str.append('\n');
			
			if(el == null) {
				Window.alert("Root element not found, crash report was printed to console");
				System.err.println(str.toString());
				return;
			}

			String s = el.getAttribute("style");
			el.setAttribute("style", (s == null ? "" : s) + "position:relative;");
			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:50px;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;");
			el.appendChild(img);
			el.appendChild(div);
			div.appendChild(doc.createTextNode(str.toString()));
			
			PlatformRuntime.removeEventHandlers();

		}else {
			System.err.println();
			System.err.println("An additional crash report was supressed:");
			String[] s = t.split("[\\r\\n]+");
			for(int i = 0; i < s.length; ++i) {
				System.err.println("  " + s[i]);
			}
		}
	}

	private static String addWebGLToCrash() {
		StringBuilder ret = new StringBuilder();
		
		WebGLRenderingContext ctx = PlatformRuntime.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(PlatformRuntime.webgl != null) {
				ret.append("webgl.version = ").append(ctx.getParameterString(WebGLRenderingContext.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(WebGLRenderingContext.RENDERER) + " [masked]").append('\n');
				ret.append("webgl.vendor = ").append(ctx.getParameterString(WebGLRenderingContext.VENDOR) + " [masked]").append('\n');
			}
			//ret.append('\n').append("\nwebgl.anisotropicGlitch = ").append(DetectAnisotropicGlitch.hasGlitch()).append('\n'); //TODO
			ret.append('\n').append("webgl.ext.HDR16f = ").append(ctx.getExtension("EXT_color_buffer_half_float") != null).append('\n');
			ret.append("webgl.ext.HDR32f = ").append(ctx.getExtension("EXT_color_buffer_float") != null).append('\n');
			
		}else {
			ret.append("Failed to query GPU info!\n");
		}
		
		return ret.toString();
	}

	public static void showIncompatibleScreen(String t) {
		if(!isCrashed) {
			isCrashed = true;
			
			HTMLDocument doc = Window.current().getDocument();
			if(configRootElement == null) {
				configRootElement = doc.getElementById(configRootElementId);
			}
			
			HTMLElement el = configRootElement;
			
			if(el == null) {
				System.err.println("Compatibility error: " + t);
				return;
			}
			
			String s = el.getAttribute("style");
			el.setAttribute("style", (s == null ? "" : s) + "position:relative;");
			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:50px;background-color:white;border:1px solid #cccccc;overflow-x:hidden;overflow-y:scroll;font:18px sans-serif;padding:40px;");
			el.appendChild(img);
			el.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 style=\"margin-left:10px;font:0.9em monospace;\">Current Date: " + EagRuntime.fixDateFormat(new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z")).format(new Date()) + "</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")));
			
			PlatformRuntime.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(WebGLRenderingContext.RENDERER);
						if(r != null) {
							r += " [masked]";
						}
					}
					if(r != null) {
						webGLRenderer = r;
					}
				}
			}catch(Throwable tt) {
			}
			
			div.querySelector("#crashWebGL").appendChild(doc.createTextNode(webGLRenderer));
			
		}
	}

	private static HTMLElement integratedServerCrashPanel = null;

	public static void showIntegratedServerCrashReportOverlay(String report, int x, int y, int w, int h) {
		if(integratedServerCrashPanel == null) {
			HTMLDocument doc = Window.current().getDocument();
			if(configRootElement == null) {
				configRootElement = doc.getElementById(configRootElementId);
			}
			
			integratedServerCrashPanel = doc.createElement("div");
			integratedServerCrashPanel.setAttribute("style", "z-index:99;position:absolute;background-color:black;color:white;overflow-x:hidden;overflow-y:scroll;overflow-wrap:break-word;white-space:pre-wrap;font:18px sans-serif;padding:20px;display:none;");
			configRootElement.appendChild(integratedServerCrashPanel);
		}
		String sourceURL = ClientPlatformSingleplayer.getLoadedWorkerSourceURLTeaVM();
		String workerURL = ClientPlatformSingleplayer.getLoadedWorkerURLTeaVM();
		String currentDate = EagRuntime.fixDateFormat(new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z")).format(new Date());
		if(workerURL != null) {
			report = "WORKER SRC: " + sourceURL +"\nWORKER URL: " + workerURL + "\n\nCURRENT DATE: " + currentDate + "\n\n" + report.replaceAll(workerURL, "<worker_url>");
		}else {
			report = "CURRENT DATE: " + currentDate + "\n\n" + report;
		}
		setInnerText(integratedServerCrashPanel, "");
		setInnerText(integratedServerCrashPanel, report);
		CSSStyleDeclaration style = integratedServerCrashPanel.getStyle();
		float s = (float)Window.current().getDevicePixelRatio();
		style.setProperty("top", "" + (y / s) + "px");
		style.setProperty("left", "" + (x / s) + "px");
		style.setProperty("width", "" + ((w / s) - 20) + "px");
		style.setProperty("height", "" + ((h / s) - 20) + "px");
		style.setProperty("display", "block");
	}

	public static void hideIntegratedServerCrashReportOverlay() {
		if(integratedServerCrashPanel != null) {
			integratedServerCrashPanel.getStyle().setProperty("display", "none");
		}
	}

	@JSBody(params = { "el", "str" }, script = "el.innerText = str;")
	private static native void setInnerText(HTMLElement el, String str);

	@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);

}
