#if UNITY_EDITOR_WIN || (UNITY_STANDALONE_WIN && !UNITY_EDITOR)
#define ZF_WINDOWS
#endif

#define PROXY_BROWSER_API


using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using System.Runtime.InteropServices;
using System.Text;
using UnityEngine.Assertions;
using System.Reflection;
using AOT;
using UnityEngine.Profiling;
#if UNITY_EDITOR
using UnityEditor;
using System.Diagnostics;
using Debug = UnityEngine.Debug;
#endif



// ReSharper disable InconsistentNaming

namespace ZenFulcrum.EmbeddedBrowser {

/**
 * Wrapper/native callbacks for CEF browser implementation.
 * When changing code in this file you may have to restart the Unity Editor for things to get working again.
 *
 * Note that callbacks given to the native side may be invoked on any thread.
 *
 * Make sure IntPtrs are pinned and callbacks are kept alive from GC while their object lives.
 */
public static class BrowserNative {
#if UNITY_EDITOR
	public const int DebugPort = 9848;
#else
	public const int DebugPort = 9849;
#endif

	public static bool NativeLoaded { get; private set; }

	public static bool SymbolsLoaded { get; private set; }

	/// <summary>
	/// Lock this object before touching any of the zfb_* functions outside the main thread.
	/// (While many of them are thread safe, the shared library can be unloaded, leading
	/// to a possible race condition at shutdown. For example thread A grabs the value of zfb_sendRequestData,
	/// the main thread unloads the shared library, then thread A tries to execute the pointer it has for
	/// zfb_sendRequestData, resulting in sadness.)
	/// </summary>
	public static readonly object symbolsLock = new object();

#if PROXY_BROWSER_API
	public const bool UsingAPIProxy = true;
#else
	public const bool UsingAPIProxy = false;
#endif

	/**
	 * List of command-line switches given to Chromium.
	 * http://peter.sh/experiments/chromium-command-line-switches/
	 *
	 * If you want to change this, be sure to change it before LoadNative gets called.
	 *
	 * Adding or removing flags may lead to instability and/or insecurity.
	 * Make sure you understand what a flag does before you use it.
	 * Be sure to test your use cases thoroughly after changing any flags as
	 * things are more likely to crash or break if you aren't using the default
	 * configuration.
	 *
	 * Extra non-Chromium arguments:
	 *   --zf-cef-log-verbose
	 *     if enabled, we'll write a lot more CEF/Chromium logging to your editor/player log file than usual
	 *   --zf-log-internal
	 *     If enabled, some extra logs will be dumped to the current working directory.
	 *
	 */
	public static List<string> commandLineSwitches = new List<string>() {
		//Smooth scrolling tends to make scrolling act wonky or break.
		"--disable-smooth-scrolling",

		//If you install the PPAPI version of Flash on your system this tells Chromium to try to use it.
		//(download at https://get.adobe.com/flashplayer/otherversions/)
		"--enable-system-flash",
		//For Linux use probably need something like this instead (see docs)
		//"--ppapi-flash-version=29.0.0.113", "--ppapi-flash-path=/usr/lib/adobe-flashplugin/libpepflashplayer.so",

		//getUserMedia (microphone/webcam).
		//Turning this on has security implications, it appears there's no
		//CEF API for authorizing access, it just allows it. (ergo, any website can record the user)
		//"--enable-media-stream",

		//Enable these to get a higher browser framerate at the expense of more CPU usage:
		// "--disable-gpu-vsync",

		//If you want to specify a proxy by hand:
		//"--proxy-server=localhost:8000",


		//"--zf-log-cef-verbose",
		//"--zf-log-internal", 
	};

	/**
	 * WebResources used to resolve local requests.
	 *
	 * This may be replaced with an implementation of your choice, but be sure to set it up before requesting
	 * any URLs.
	 */
	public static WebResources webResources;
	public static string LocalUrlPrefix { get { return "https://game.local/"; } }

	[MonoPInvokeCallback(typeof(MessageFunc))]
	private static void LogCallback(string message) {
		Debug.Log("ZFWeb: " + message);
	}

	/// <summary>
	/// Because AppDomain.CurrentDomain.IsFinalizingForUnload() doesn't work and we don't like crashing the
	/// Unity Editor.
	/// </summary>
	private static bool isAppDomainUnloading = false;

	private static string _profilePath = null;
	/**
	 * Where should we save the user's data and cookies? Leave null to not save them.
	 * Set before the browser system initializes. Also, restart the Editor to apply changes.
	 */
	public static string ProfilePath {
		get { return _profilePath; }
		set {
			if (NativeLoaded) throw new InvalidOperationException("ProfilePath must be set before initializing the browser system.");
			_profilePath = value;
		}
	}

	/**
	 * Loads the shared library and the function symbols so we can call zfb_* functions.
	 */
	public static void LoadSymbols() {
		if (SymbolsLoaded) return;

		if (isAppDomainUnloading) {
			throw new Exception("Tried to start up browser backend while unloading app domain.");
		}

		var dirs = FileLocations.Dirs;

		HandLoadSymbols(dirs.binariesPath);
	}


	public static void LoadNative() {
		if (NativeLoaded) return;

		Profiler.BeginSample("BrowserNative.LoadNative");

		if (webResources == null) {
			//if the user hasn't given us a WebResources to use, use the default
#if UNITY_EDITOR
			webResources = new EditorWebResources();
#else
			var swr = new StandaloneWebResources(Application.dataPath + "/" + StandaloneWebResources.DefaultPath);
			swr.LoadIndex();
			webResources = swr;
#endif
		}


		//For Editor/debug builds, we'll open a port you can just http:// to inspect pages.
		//Don't do this for real builds, though. It makes it really, really easy for the end user to call
		//random JS in the page, potentially affecting or bypassing game logic.
		var debugPort = Debug.isDebugBuild ? DebugPort : 0;


		var dirs = FileLocations.Dirs;

#if UNITY_EDITOR_OSX || UNITY_EDITOR_LINUX
		FixProcessPermissions(dirs);
#endif


#if UNITY_STANDALONE_WIN && !UNITY_EDITOR
		//make sure the child processes can be started (their dependent .dlls are next to the main .exe, not in the Plugins folder)
		var loadDir = Directory.GetParent(Application.dataPath).FullName;
		var path = Environment.GetEnvironmentVariable("PATH");
		path += ";" + loadDir;
		Environment.SetEnvironmentVariable("PATH", path);
#elif UNITY_EDITOR_WIN
		//help it find our .dlls
		//var path =Environment.GetEnvironmentVariable("PATH");
		//path += ";" + dirs.binariesPath;
		//	object value = Environment.GetEnvironmentVariable("PATH", path);
#endif

		LoadSymbols();

		StandaloneShutdown.Create();

		//There never should be any, but just in case, destroy any existing browsers on a re-init
		zfb_destroyAllBrowsers();

		//Caution: Careful with these functions you pass to native. The Unity Editor will
		//reload assemblies, leaving the function pointers dangling. If any native calls try to use them
		//before we load back up and re-register them we can crash.
		//To prevent this, we call zfb_setCallbacksEnabled to disable callbacks before we get unloaded.
		zfb_setDebugFunc(LogCallback);
		zfb_setLocalRequestHandler(NewRequestCallback);
		zfb_setCallbacksEnabled(true);

		var settings = new ZFBInitialSettings() {
			cefPath = dirs.resourcesPath,
			localePath = dirs.localesPath,
			subprocessFile = dirs.subprocessFile,
			userAgent = UserAgent.GetUserAgent(),
			logFile = dirs.logFile,
			profilePath = _profilePath,
			debugPort = debugPort,
			multiThreadedMessageLoop = 0,//this argument is pretty much defunct, the slave just blocks on CefRunMessageLoop on all platforms
		};

		foreach (var arg in commandLineSwitches) zfb_addCLISwitch(arg);

		var initRes = zfb_init(settings);
		if (!initRes) throw new Exception("Failed to initialize browser system.");

		NativeLoaded = true;
		Profiler.EndSample();

		AppDomain.CurrentDomain.DomainUnload += (sender, args) => {
			isAppDomainUnloading = true;

			//Shutdown should happen StandaloneShutdown, but in some cases, like the Unity Editor
			//reloading assemblies, we don't get OnApplicationQuit because we didn't "quit", even though
			//everything gets shut down.
			//Make sure the backend shuts down, in this case, or it will crash when we try to start it again.
			UnloadNative();
		};
	}

	private static void FixProcessPermissions(FileLocations.CEFDirs dirs) {
		/*
		 * The package we get from the Asset Store probably won't have the right executable permissions for
		 * ZFGameBrowser for OS X, so let's fix that for the user right now.
		 */

		var attrs = (uint)File.GetAttributes(dirs.subprocessFile);

		//From https://github.com/mono/mono/blob/master/mono/io-layer/io.c under SetFileAttributes() (also noted in FileAttributes.cs):
		//"Currently we only handle one *internal* case [...]: 0x80000000, which means `set executable bit'"
		//Let's use that now.
		attrs |= 0x80000000;

		//Make it executable.
		File.SetAttributes(dirs.subprocessFile, unchecked((FileAttributes)attrs));
	}


	private static IntPtr moduleHandle;
	/// <summary>
	/// Loads the browser symbols.
	///
	/// We don't use DllImport for a few reasons, historically for DEEPBIND support and multiple CEF versions in the same process,
	/// but now mostly so we can unload the .dll whenever we want and to simplify picking and loading our shared library how we want.
	/// </summary>
	/// <param name="binariesPath"></param>
	private static void HandLoadSymbols(string binariesPath) {
		Profiler.BeginSample("BrowserNative.HandLoadSymbols");

#if PROXY_BROWSER_API
		var coreType = "ZFProxyWeb";
#else
		var coreType = "ZFEmbedWeb";
#endif

#if UNITY_EDITOR_OSX || (!UNITY_EDITOR && UNITY_STANDALONE_OSX)
		var libFile = binariesPath + "/lib" + coreType + ".dylib";
#elif UNITY_EDITOR_LINUX || (!UNITY_EDITOR && UNITY_STANDALONE_LINUX)
		var libFile = binariesPath + "/lib" + coreType + ".so";
#elif UNITY_EDITOR_WIN || (!UNITY_EDITOR && UNITY_STANDALONE_WIN)
		var libFile = binariesPath + "/" + coreType + ".dll";
#else
	#error Unknown OS.
#endif

		//Debug.Log("Loading " + libFile);

		moduleHandle = OpenLib(libFile);

		//Now go through and fill our functions with life.
		int i = 0;
		var fields = typeof(BrowserNative).GetFields(BindingFlags.Static | BindingFlags.Public);
		foreach (var field in fields) {
			if (!field.Name.StartsWith("zfb_")) continue;

			var fp = GetFunc(moduleHandle, field.Name);

			var func = Marshal.GetDelegateForFunctionPointer(fp, field.FieldType);
			field.SetValue(null, func);
			++i;
		}

		//Debug.Log("Loaded " + i + " symbols");

		SymbolsLoaded = true;

		Profiler.EndSample();
	}

	/// <summary>
	/// Clears out the symbols so, if the shared library has been unloaded, we get null exceptions instead
	/// of crashes.
	/// </summary>
	private static void ClearSymbols() {
		SymbolsLoaded = false;

		var fields = typeof(BrowserNative).GetFields(BindingFlags.Static | BindingFlags.Public);
		foreach (var field in fields) {
			if (!field.Name.StartsWith("zfb_")) continue;
			field.SetValue(null, null);
		}
	}


	private static string GetLibError() {
#if ZF_WINDOWS
		return new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()).Message;
#else
		return Marshal.PtrToStringAnsi(dlerror());
#endif
	}

	private static IntPtr OpenLib(string name) {
#if ZF_WINDOWS
		var handle = LoadLibraryW(name);
		if (handle == IntPtr.Zero) {
//			throw new DllNotFoundException("ZFBrowser failed to load " + name + ": " + Marshal.GetLastWin32Error());
			throw new DllNotFoundException("ZFBrowser failed to load " + name + ": " +
				new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()).Message
			);
		}
		return handle;
#else
		//Call this now because running a DllImport method the first time will end up calling dlerror
		//which will clear the error we were trying to get from dlerror.
		dlerror();

		var handle = dlopen(name, (int)(DLFlags.RTLD_LAZY));
		if (handle == IntPtr.Zero) {
			throw new DllNotFoundException("ZFBrowser failed to load " + name + ": " + getDlError());
		}
		return handle;
#endif
	}

	private static void CloseLib() {
		if (moduleHandle == IntPtr.Zero) return;

		ClearSymbols();

#if ZF_WINDOWS
		var success = FreeLibrary(moduleHandle);
#else
		var success = dlclose(moduleHandle) == 0;
#endif

		if (!success) {
			throw new DllNotFoundException(
				"Failed to unload library: " +
				GetLibError()
			);
		}

		//Debug.Log("Unloaded shared library");

		moduleHandle = IntPtr.Zero;
	}

	private static IntPtr GetFunc(IntPtr libHandle, string fnName) {
#if ZF_WINDOWS
		var addr = GetProcAddress(libHandle, fnName);
		if (addr == IntPtr.Zero) {
			throw new DllNotFoundException("ZFBrowser failed to load method " + fnName + ": " + Marshal.GetLastWin32Error());
		}
		return addr;
#else
		var fp = dlsym(libHandle, fnName);
		if (fp == IntPtr.Zero) {
			throw new DllNotFoundException("ZFBrowser failed to load method " + fnName + ": " + getDlError());
		}
		return fp;
#endif
	}

#if !ZF_WINDOWS
	[Flags]
	public enum DLFlags {
		RTLD_LAZY = 1,
		RTLD_NOW = 2,
		RTLD_DEEPBIND = 8,
	}
	[DllImport("__Internal")] static extern IntPtr dlopen(string filename, int flags);
	[DllImport("__Internal")] static extern IntPtr dlsym(IntPtr handle, string symbol);
	[DllImport("__Internal")] static extern int dlclose(IntPtr handle);
	[DllImport("__Internal")] static extern IntPtr dlerror();
	private static string getDlError() {
		var err = dlerror();
		return Marshal.PtrToStringAnsi(err);
	}

#else
	[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Ansi)]
	static extern IntPtr LoadLibraryW([MarshalAs(UnmanagedType.LPWStr)]string lpFileName);
	[DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
	static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
	[DllImport("kernel32", SetLastError = true)]
	private static extern bool FreeLibrary(IntPtr hModule);
#endif

	[MonoPInvokeCallback(typeof(NewRequestFunc))]
	private static void NewRequestCallback(int requestId, string url) {
		webResources.HandleRequest(requestId, url);
	}


	/** Shuts down the native browser library and CEF. */
	public static void UnloadNative() {
		if (!NativeLoaded) return;

		lock (symbolsLock) {
			//Debug.Log("Stop CEF");

			zfb_destroyAllBrowsers();
			zfb_shutdown();
			zfb_setCallbacksEnabled(false);
			NativeLoaded = false;
			CloseLib();
		}
	}


	/** Call this with a message to debug it to a console somewhere. */
	[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
	public delegate void MessageFunc(string message);

	/**
	 * Callback for starting a new local request.
	 * url is the url requested
	 * At present only GET requests with no added headers are supported.
	 * After this is called, you are responsible for calling zfb_sendRequestHeaders (once)
	 * then zfb_sendRequestData (as much as needed) to finish up the request.
	 */
	[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
	public delegate void NewRequestFunc(int requestId, string url);


	/** Called when the native backend is ready to start receiving orders. */
	[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
	public delegate void ReadyFunc(int browserId);

	/** Called on console.log, console.err, etc. */
	[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
	public delegate void ConsoleFunc(int browserId, string message, string source, int line);

	/**
	 * Called when JS calls back to us.
	 * callbackId is the first argument,
	 * data (UTF-8 null-terminated string) (and its included size) are the second argument.
	 */
	[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
	public delegate void ForwardJSCallFunc(int browserId, int callbackId, string data, int size);

	/**
	 * Called when a browser opens a new window.
	 * creatorBrowserId - id of the browser that cause the window to be created
	 * newBrowserId - a newly created (as if by zfb_createBrowser) browser tab
	 * 
	 * May be called on any thread.
	 */
	[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
	public delegate void NewWindowFunc(int creatorBrowserId, int newBrowserId, IntPtr initialURL);

	/**
	 * Called when an item from ChangeType happens.
	 * See the documentation for the given ChangeType for info on what the args mean or how to get more information.
	 */
	[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
	public delegate void ChangeFunc(int browserId, ChangeType changeType, string arg1);

	/**
	 * This is called when the browser wants to display a dialog of some sort.
	 * dialogType - the type, or DLT_HIDE to hide any existing dialogs.
	 * dialogText - main text for the dialog, usually from in-page JavaScript
	 * initialPromptText - if we are doing a JavaScript prompt(), the default text to display
	 * sourceURL - the URL of the page that is causing the dialog
	 *
	 * Once the user has responded to the dialog (if we were showing one), call zfb_sendDialogResults
	 * with the user's input.
	 */
	[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
	public delegate void DisplayDialogFunc(
		int browserId, DialogType dialogType, IntPtr dialogText,
		IntPtr initialPromptText, IntPtr sourceURL
	);

	/**
	 * Called by the backend when a context menu should be shown or hidden.
	 * If menuJSON is null, hide the context menu.
	 * If it's not, show the given menu and eventually call zfb_sendContextMenuResults.
	 * For more information on the menu format, look at BrowserDialogs.html
	 *
	 * x and y report the position the menu was summoned, relative to the top-left of the view.
	 * origin indicates on what type of item the context menu was created.
	 */
	[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
	public delegate void ShowContextMenuFunc(int browserId, string menuJSON, int x, int y, ContextMenuOrigin origin);

	/**
	 * Used with zfb_getCookies, this will be called once for each cookie.
	 */
	[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
	public delegate void GetCookieFunc(NativeCookie cookie);

	/**
	 * Called when nav state (can go back/forward, loaded, url) changes.
	 */
	[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
	public delegate void NavStateFunc(int browserId, bool canGoBack, bool canGoForward, bool lodaing, IntPtr url);

	/**
	 * Called by a native OS windows gets an event like mouse move click, etc.
	 * data contains json details on the event
	 */
	[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
	public delegate void WindowCallbackFunc(int windowId, IntPtr data);



	public enum LoadChange : int {
		LC_STOP = 1,
		LC_RELOAD,
		LC_FORCE_RELOAD,
	}

	public enum MouseButton : int {
		MBT_LEFT = 0,
		MBT_MIDDLE,
		MBT_RIGHT,
	}

	public enum ChangeType : int {
		/** The cursor has changed. Use zfb_getMouseCursor/zfb_getMouseCustomCursor to see what it is now. */
		CHT_CURSOR = 0,
		/** The browser has been closed and can no longer receive commands. */
		CHT_BROWSER_CLOSE,
		/**
		 * We have the HTML for the top-level page.
		 * arg1 (JSON) contains HTTP {status} code and the {url}
		 * Note that successfully fetching errors from a server (404, 500) are treated
		 * as successful, CHT_FETCH_FAILED won't be triggered.
		 */
		CHT_FETCH_FINISHED,
		/**
		 * Failed to fetch a page (timeout, network issues, etc)
		 * arg1 (JSON) contains an {error} code and the {url}
		 */
		CHT_FETCH_FAILED,
		/**
		 * The page has reached onload
		 * arg1 (JSON) contains HTTP {status} code and the {url}
		 */
		CHT_LOAD_FINISHED,
		/** SSL certificate error. arg1 has some JSON about the issue. Often followed by a CHT_FETCH_FAILED */
		CHT_CERT_ERROR,
		/** Renderer process crashed/was killed/etc. */
		CHT_SAD_TAB,
		/**
		 * The user/page has initialized a download.
		 * arg1 (JSON) contains:
		 *  download {id}
		 *  {mimeType}
		 *  {url} of the download
		 *  {originalURL} of the download before redirection
		 *  {suggestedName} you might save the file as
		 *  {size} number of bytes in the download (if known)
		 *  {contentDisposition}
		 *
		 * Call zfb_downloadCommand(browserId, download["id"], DownloadAction.xxyy, fileName) to cancel or save the file
		 * (and afterward control it).
		 */
		CHT_DOWNLOAD_STARTED,
		/**
		 * Progress/status update on a download.
		 * arg1 (JSON) contains:
		 *  download {id}
		 *  {speed} in bytes/sec
		 *  {percentComplete} int in [0, 100], or -1 if unknown
		 *  {received} number of bytes received
		 *  {statusStr} download status. One of: complete, canceled, working, unknown
		 *  {fullPath} we are saving to. (If you had the user pick the destination you can get it here.)
		 *
		 * Call zfb_downloadCommand(browserId, download["id"], DownloadAction.xxyy, null) to cancel/pause/resume the download.
		 */
		CHT_DOWNLOAD_STATUS,
		/**
		 * The element with keyboard focus has changed.
		 * You can use this to show/hide a keyboard when needed.
		 * arg1 (JSON) contains:
		 *  {tagName} of the focused node, or empty of no node is focused (focus loss)
		 *  {editable} true if it's some sort of editable text
		 *  textual {value} of the node, if it's simple (doesn't work for things like ContentEditable nodes)
		 */
		CHT_FOCUSED_NODE,
	}

	public enum DownloadAction {
		Begin,
		Cancel,
		Pause,
		Resume,
	}

	/** @see cef_cursor_type_t in cef_types.h */
	public enum CursorType : int {
		Pointer = 0,
		Cross,
		Hand,
		IBeam,
		Wait,
		Help,
		EastResize,
		NorthResize,
		NorthEastResize,
		NorthWestResize,
		SouthResize,
		SouthEastResize,
		SouthWestResize,
		WestResize,
		NorthSouthResize,
		EastWestResize,
		NorthEastSouthWestResize,
		NorthWestSouthEastResize,
		ColumnResize,
		RowResize,
		MiddlePanning,
		EastPanning,
		NorthPanning,
		NorthEastPanning,
		NorthWestPanning,
		SouthPanning,
		SouthEastPanning,
		SouthWestPanning,
		WestPanning,
		Move,
		VerticalText,
		Cell,
		ContextMenu,
		Alias,
		Progress,
		NoDrop,
		Copy,
		None,
		NotAllowed,
		ZoomIn,
		ZoomOut,
		Grab,
		Grabbing,
		Custom,
	}

	public enum DialogType {
		DLT_HIDE = 0,
		DLT_ALERT,
		DLT_CONFIRM,
		DLT_PROMPT,
		DLT_PAGE_UNLOAD,
		DLT_PAGE_RELOAD,//like unload, but the user is just refreshing the page
		DLT_GET_AUTH,
	};

	public enum NewWindowAction {
		NWA_IGNORE = 1,
		NWA_REDIRECT,
		NWA_NEW_BROWSER,
		NWA_NEW_WINDOW,
	};

	[Flags]
	public enum ContextMenuOrigin {
		Editable = 1 << 1,
		Image = 1 << 2,
		Selection = 1 << 3,
		Other = 1 << 0,
	}

	public enum FrameCommand {
		Undo,
		Redo,
		Cut,
		Copy,
		Paste,
		Delete,
		SelectAll,
		ViewSource,
	};

	public enum CookieAction {
		Delete,
		Create,
	};

	[StructLayout(LayoutKind.Sequential, Pack = 1)]
	public struct ZFBInitialSettings {
		public string cefPath, localePath, subprocessFile, userAgent, logFile, profilePath;
		public int debugPort, multiThreadedMessageLoop;
	}

	[StructLayout(LayoutKind.Sequential, Pack = 1)]
	public struct ZFBSettings {
		public int bgR, bgG, bgB, bgA;
		public int offscreen;
	}

	[StructLayout(LayoutKind.Sequential, Pack = 1)]
	public struct RenderData {
		public IntPtr pixels;
		public int w, h;
	}

	[StructLayout(LayoutKind.Sequential, Pack = 1)]
	public class NativeCookie {
		public string name, value, domain, path;
		public string creation, lastAccess, expires;
		public byte secure, httpOnly;
	}



	/*
	 * See HandLoadSymbols() for an explanation of what's going on here and why we use a bunch of delegates instead
	 * of DllImport.
	 *
	 * Don't use this API directly unless you want to deal with things breaking.
	 * Though it is accessible, it's not considered part of the public API for versioning purposes.
	 * That, and you can shoot yourself in the foot and crash your app.
	 *
	 * Also, if you want to call any of these functions off the main thread make sure:
	 *   - It's documented as supporting such.
	 *   - To acquire a lock on symbolsLock first. (See its docs for why.)
	 */

	/** Does nothing. */
	public delegate void Calltype_zfb_noop();
	public static Calltype_zfb_noop zfb_noop;


	/**
	 * Allocates and initializes a block of memory suitable for use with LoadRawTextureData to clear a texture
	 * to the given color.
	 * Call zfb_free on the pointer when your are done using it.
	 * Does not require the browser system to be initialized, thread safe.
	 */
	public delegate IntPtr Calltype_zfb_flatColorTexture(int pixelCount, int r, int g, int b, int a);
	public static Calltype_zfb_flatColorTexture zfb_flatColorTexture;

	/**
	 * Copies from a zfb_getImage buffer to a Color32[] buffer.
	 * Does not require the browser system to be initialized, thread safe.
	 */
	public delegate void Calltype_zfb_copyToColor32(IntPtr src, IntPtr dest, int pixelCount);
	public static Calltype_zfb_copyToColor32 zfb_copyToColor32;

	/**
	 * Some functions allocate memory to give you a response (see their docs). Call this to free it.
	 * Does not require the browser system to be initialized, thread safe.
	 */
	public delegate void Calltype_zfb_free(IntPtr mem);
	public static Calltype_zfb_free zfb_free;


	/**
	 * Plain old memcpy. Because sometimes Marshal.Copy falls short of our needs.
	 * Does not require the browser system to be initialized, thread safe.
	 */
	public delegate void Calltype_zfb_memcpy(IntPtr dst, IntPtr src, int size);
	public static Calltype_zfb_memcpy zfb_memcpy;


	/**
	 * Returns the Chrome(ium) version as a static C string.
	 * Does not require the browser system to be initialized, thread safe.
	 */
	public delegate IntPtr Calltype_zfb_getVersion();
	public static Calltype_zfb_getVersion zfb_getVersion;


	/** Sets a function to call for Debug.Log-style messages. */
	public delegate void Calltype_zfb_setDebugFunc(MessageFunc debugFunc);
	public static Calltype_zfb_setDebugFunc zfb_setDebugFunc;


	/** Sets callbacks for when a local (https://game.local/) request is started. */
	public delegate void Calltype_zfb_setLocalRequestHandler(NewRequestFunc requestFunc);
	public static Calltype_zfb_setLocalRequestHandler zfb_setLocalRequestHandler;

	/**
	 * Sends the headers for a response.
	 * responseLength should be the number of bytes in the response, or -1 if unknown.
	 * headersJSON should contain a string:string map of headers, JSON encoded
	 *    - You should include a "Content-Type" header.
	 *		- You may also include a ":status:" pseudoheader to set the status to a non-200 value
	 *		- You may also include a ":statusText:" pseudoheader to set the status text
	 * After calling this, call zfb_sendRequestData to send the actual response body.
	 *
	 * May be called from any thread.
	 */
	public delegate void Calltype_zfb_sendRequestHeaders(int requestId, int responseLength, string headersJSON);
	public static Calltype_zfb_sendRequestHeaders zfb_sendRequestHeaders;

	/**
	 * Sends the body for a response after calling zfb_sendRequestHeaders.
	 *
	 * You must always write at least one byte except as described below.
	 *
	 * If you sent a responseLength >= 0, make sure all calls to this function add up to exactly that value.
	 * If you sent a responseLength < 0, call this a final time with size = 0 to signify the
	 * end of the response.
	 *
	 * May be called from any thread.
	 */
	public delegate void Calltype_zfb_sendRequestData(int requestId, IntPtr data, int dataSize);
	public static Calltype_zfb_sendRequestData zfb_sendRequestData;



	/** Enabled/disables user callbacks. Useful for disabling all callbacks when mono assemblies reload. */
	public delegate void Calltype_zfb_setCallbacksEnabled(bool enabled);
	public static Calltype_zfb_setCallbacksEnabled zfb_setCallbacksEnabled;


	/** Destroys all browser instances. */
	public delegate void Calltype_zfb_destroyAllBrowsers();
	public static Calltype_zfb_destroyAllBrowsers zfb_destroyAllBrowsers;


	/** Adds a command-line switch to Chromium, must call before zfb_init */
	public delegate void Calltype_zfb_addCLISwitch(string value);
	public static Calltype_zfb_addCLISwitch zfb_addCLISwitch;


	/** Initializes the system so we can start making browsers. */
	public delegate bool Calltype_zfb_init(ZFBInitialSettings settings);
	public static Calltype_zfb_init zfb_init;


	/** Shuts down the system. It cannot be re-initialized. */
	public delegate void Calltype_zfb_shutdown();
	public static Calltype_zfb_shutdown zfb_shutdown;


	/**
	 * Creates a new browser, returning the id.
	 * Call zfb_setReadyCallback and wait for it to fire before doing anything else.
	 */
	public delegate int Calltype_zfb_createBrowser(ZFBSettings settings);
	public static Calltype_zfb_createBrowser zfb_createBrowser;


	/** Reports the number of un-destroyed browsers. Slow. */
	public delegate int Calltype_zfb_numBrowsers();
	public static Calltype_zfb_numBrowsers zfb_numBrowsers;


	/**
	 * Closes and cleans up a browser instance.
	 */
	public delegate void Calltype_zfb_destroyBrowser(int id);
	public static Calltype_zfb_destroyBrowser zfb_destroyBrowser;


	/** Call once per frame if the multi-threaded message loop isn't enabled. */
	public delegate void Calltype_zfb_tick();
	public static Calltype_zfb_tick zfb_tick;


	/**
	 * Registers a function to call when the browser instance is ready to start taking orders.
	 * {cb} may be executed immediately or on any thread.
	 */
	public delegate void Calltype_zfb_setReadyCallback(int id, ReadyFunc cb);
	public static Calltype_zfb_setReadyCallback zfb_setReadyCallback;


	/** Resizes the browser. */
	public delegate void Calltype_zfb_resize(int id, int w, int h);
	public static Calltype_zfb_resize zfb_resize;


	/**
	 * Adds the given browser {overlayBrowserId} as an overlay of this browser {browserId}.
	 * The overlaid browser will appear transparently over the top of {browser}.
	 * {overlayBrowser} must not have an overlay and must be sized exactly the same as {browser}.
	 * Remove the overlay before closing either browser.
	 *
	 * While {overlayBrowser} is overlaying another browser, do not call zfb_getImage on it.
	 */
	public delegate void Calltype_zfb_setOverlay(int browserId, int overlayBrowserId);
	public static Calltype_zfb_setOverlay zfb_setOverlay;


	/**
	 * Gets the image data for the current frame.
	 * Do not hang onto the returned data across frames or resizes.
	 *
	 * If there are no changes since last call, the pixel data will be null (unless you specify forceDirty).
	 */
	public delegate RenderData Calltype_zfb_getImage(int id, bool forceDirty);
	public static Calltype_zfb_getImage zfb_getImage;

	/**
	 * Registers a callback for nav state updates.
	 * Keep track of what it tells you to answer questions like what the current URL is and if we can go back/forward.
	 * (The URL overlaps a bit with CHT_FETCH_*, but this should fire earlier (when we start) as opposed to when it's done.)
	 */
	public delegate void Calltype_zfb_registerNavStateCallback(int id, NavStateFunc callback);
	public static Calltype_zfb_registerNavStateCallback zfb_registerNavStateCallback;

	/**
	 * Navigates to the given URL. If force it ture, it will go there right away.
	 * If force is false, the pages that wish to can prompt the user and possibly cancel the
	 * navigation.
	 */
	public delegate void Calltype_zfb_goToURL(int id, string url, bool force);
	public static Calltype_zfb_goToURL zfb_goToURL;

	/**
	 * Loads the given HTML string as if it were the given URL.
	 * Use http://-like porotocols or else things may not work right.
	 */
	public delegate void Calltype_zfb_goToHTML(int id, string html, string url);
	public static Calltype_zfb_goToHTML zfb_goToHTML;

	/** Go back (-1) or forward (1) */
	public delegate void Calltype_zfb_doNav(int id, int direction);
	public static Calltype_zfb_doNav zfb_doNav;


	public delegate void Calltype_zfb_setZoom(int id, double zoom);
	public static Calltype_zfb_setZoom zfb_setZoom;


	/** Stop, refresh, or force-refresh */
	public delegate void Calltype_zfb_changeLoading(int id, LoadChange what);
	public static Calltype_zfb_changeLoading zfb_changeLoading;


	public delegate void Calltype_zfb_showDevTools(int id, bool show);
	public static Calltype_zfb_showDevTools zfb_showDevTools;


	/**
	 * Informs the browser if it's focused for keyboard input.
	 * Among other things, this controls if the blinking text cursor appears in an active text field.
	 */
	public delegate void Calltype_zfb_setFocused(int id, bool focused);
	public static Calltype_zfb_setFocused zfb_setFocused;


	/**
	 * Reports the mouse's current location.
	 * x and y are in the range [0,1]. (0, 0) is top-left, (1, 1) is bottom-right
	 */
	public delegate void Calltype_zfb_mouseMove(int id, float x, float y);
	public static Calltype_zfb_mouseMove zfb_mouseMove;


	public delegate void Calltype_zfb_mouseButton(int id, MouseButton button, bool down, int clickCount);
	public static Calltype_zfb_mouseButton zfb_mouseButton;


	/** Reports a mouse scroll. One "tick" of a scroll wheel is generally around 120 units. */
	public delegate void Calltype_zfb_mouseScroll(int id, int deltaX, int deltaY);
	public static Calltype_zfb_mouseScroll zfb_mouseScroll;


	/**
	 * Report a key down/up event. Repeated "virtual" keystrokes are simulated by repeating the down event without
	 * an interveneing up event.
	 */
	public delegate void Calltype_zfb_keyEvent(int id, bool down, int windowsKeyCode);
	public static Calltype_zfb_keyEvent zfb_keyEvent;


	/**
	 * Report a typed character. This typically interleaves with calls to zfb_keyEvent
	 */
	public delegate void Calltype_zfb_characterEvent(int id, int character, int windowsKeyCode);
	public static Calltype_zfb_characterEvent zfb_characterEvent;


	/** Register a function to call when console.log etc. is called in the browser. */
	public delegate void Calltype_zfb_registerConsoleCallback(int id, ConsoleFunc callback);
	public static Calltype_zfb_registerConsoleCallback zfb_registerConsoleCallback;


	public delegate void Calltype_zfb_evalJS(int id, string script, string scriptURL);
	public static Calltype_zfb_evalJS zfb_evalJS;


	/** Registers a callback to call when window._zfb_event(int, string) is called in the browser. */
	public delegate void Calltype_zfb_registerJSCallback(int id, ForwardJSCallFunc cb);
	public static Calltype_zfb_registerJSCallback zfb_registerJSCallback;


	/** Registers a callback that is called when something from ChangeType happens. */
	public delegate void Calltype_zfb_registerChangeCallback(int id, ChangeFunc cb);
	public static Calltype_zfb_registerChangeCallback zfb_registerChangeCallback;


	/**
	 * Gets the current mouse cursor. If the type is CursorType.Custom, width and height will be filled with
	 * the width and height of the custom cursor.
	 */
	public delegate CursorType Calltype_zfb_getMouseCursor(int id, out int width, out int height);
	public static Calltype_zfb_getMouseCursor zfb_getMouseCursor;


	/**
	 * Call this if zfb_getMouseCursor tells you there's a custom cursor.
	 * This will fill buffer (RGBA bottom-top, 4 bytes * width * height) with the contents of the cursor.
	 * Use the size you got from zfb_getMouseCursor.
	 * If width or height don't match the results from zfb_getMouseCursor, does nothing.
	 *
	 * {hotX} and {hoyY} will be filled with the cursor's hotspot.
	 */
	public delegate void Calltype_zfb_getMouseCustomCursor(int id, IntPtr buffer, int width, int height, out int hotX, out int hotY);
	public static Calltype_zfb_getMouseCustomCursor zfb_getMouseCustomCursor;


	/** Registers a DisplayDialogFunc for this browser. */
	public delegate void Calltype_zfb_registerDialogCallback(int id, DisplayDialogFunc cb);
	public static Calltype_zfb_registerDialogCallback zfb_registerDialogCallback;


	/** Callback for a dialog. See the docs on DisplayDialogFunc. */
	public delegate void Calltype_zfb_sendDialogResults(int id, bool affirmed, string text1, string text2);
	public static Calltype_zfb_sendDialogResults zfb_sendDialogResults;


	/** Registers a NewWindowFunc for pop ups. */
	public delegate void Calltype_zfb_registerPopupCallback(int id, NewWindowAction windowAction, ZFBSettings baseSettings, NewWindowFunc cb);
	public static Calltype_zfb_registerPopupCallback zfb_registerPopupCallback;


	/** Registers a ShowContextMenuFunc for the context menu. */
	public delegate void Calltype_zfb_registerContextMenuCallback(int id, ShowContextMenuFunc cb);
	public static Calltype_zfb_registerContextMenuCallback zfb_registerContextMenuCallback;


	/**
	 * After your ShowContextMenuFunc has been called,
	 * call this to report what item the user selected.
	 * If the menu was canceled, send -1.
	 */
	public delegate void Calltype_zfb_sendContextMenuResults(int id, int commandId);
	public static Calltype_zfb_sendContextMenuResults zfb_sendContextMenuResults;


	/**
	 * Sends a command, such as copy, paste, or select to the focused frame in the given browser.
	 */
	public delegate void Calltype_zfb_sendCommandToFocusedFrame(int id, FrameCommand command);
	public static Calltype_zfb_sendCommandToFocusedFrame zfb_sendCommandToFocusedFrame;


	/** Fetches all the cookies, calling the given callback for every cookie. */
	public delegate void Calltype_zfb_getCookies(int id, GetCookieFunc cb);
	public static Calltype_zfb_getCookies zfb_getCookies;


	/** Alters the given cookie as specified. */
	public delegate void Calltype_zfb_editCookie(int id, NativeCookie cookie, CookieAction action);
	public static Calltype_zfb_editCookie zfb_editCookie;


	/**
	 * Deletes all the cookies.
	 * (Though it takes a browser, this will typically clear all cookies for all browsers.)
	 */
	public delegate void Calltype_zfb_clearCookies(int id);
	public static Calltype_zfb_clearCookies zfb_clearCookies;

	/**
	 * Take an action on a download.
	 * fileName is ignored except when beginning a download.
	 * At the outset:
	 *   Begin: Starts the download. Saves to the given file if given. If fileName is null, the user will be prompted.
	 *   Cancel: Does nothing with a download.
	 * After starting a download:
	 *   Pause, Cancel, Resume: Does what it says on the tin.
	 * Once a download is finished or canceled it is not valid to call this function for that download any more.
	 */
	public delegate void Calltype_zfb_downloadCommand(int id, int downloadId, DownloadAction command, string fileName);
	public static Calltype_zfb_downloadCommand zfb_downloadCommand;


#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
	/**
	 * Creates a new OS-native window in this process, returning an id.
	 */
	public delegate int Calltype_zfb_windowCreate(String title, WindowCallbackFunc eventHandler);
	public static Calltype_zfb_windowCreate zfb_windowCreate;

	/**
	 * Renders the contents of the given browser into the given OS window.
	 */
	public delegate void Calltype_zfb_windowRender(int windowId, int browserId);
	public static Calltype_zfb_windowRender zfb_windowRender;

	/**
	 * Closes the given window.
	 * Pass -1 for the id to close all windows.
	 */
	public delegate void Calltype_zfb_windowClose(int windowId);
	public static Calltype_zfb_windowClose zfb_windowClose;
#endif
}

}