﻿using System.IO;
using System.Threading;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
using IntPtr = System.IntPtr;
using Console = System.Console;

public enum JsType
{
	Void,
	Bool,
	Int,
	Double,
	String,
	IntArray = 258,
	StringArray = 260,
}

public class WebViewController : MonoBehaviour
{
	public static WebViewController Create(bool scriptOnly, int options = 0, string url = null, string name = "Web View Controller")
	{
		GameObject gameObj = new GameObject(name, typeof(WebViewController));
		WebViewController controller = gameObj.GetComponent<WebViewController>();
		controller._scriptOnly = scriptOnly;
		controller._rect = scriptOnly ? new Rect(0, 0, 1, 1) : new Rect();
		controller._options = options
#if UNITY_EDITOR || UNITY_STANDALONE
				| OPTION_INVERSE_COLOR | OPTION_INVERSE_Y
#endif
				;
		if(url != null)
			controller._url = url;
		gameObj.hideFlags = HideFlags.DontSave;
		Debug.Log("WebViewController.Create," + gameObj.GetInstanceID() + "," + controller.GetInstanceID()
				+ ",options=0x" + controller._options.ToString("x"));
		return controller;
	}

	public bool _scriptOnly, _enableLog;
#if UNITY_EDITOR || UNITY_STANDALONE
	public object LoadScript(string path, bool stringAsPointer, JsType type = JsType.Void)
	{
		int size;
		IntPtr ret;
		using(FileStream file = new FileStream(path, FileMode.Open))
		{
			byte[] bytes = new byte[file.Length];
			GCHandle handle;
			file.Read(bytes, 0, bytes.Length);
			handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
			size = EvaluateWeb(id, handle.AddrOfPinnedObject(), ref type, out ret);
			handle.Free();
		}
		return ReturnScript(type, size, ret, stringAsPointer);
	}
	public object RunScript(string script, bool stringAsPointer, JsType type = JsType.Void)
	{
		int size;
		IntPtr ret, s = Marshal.StringToHGlobalAnsi(script);
		object result;
		if(_enableLog)
			Console.WriteLine("WebViewController[" + id + "].RunScript," + script);
		size = EvaluateWeb(id, s, ref type, out ret);
		result = ReturnScript(type, size, ret, stringAsPointer);
		if(_enableLog)
			Console.WriteLine("WebViewController[" + id + "].RunScript," + result + ",type=" + type + ",size=" + size);
		Marshal.FreeHGlobal(s);
		return result;
	}
	object ReturnScript(JsType type, int size, IntPtr ptr, bool stringAsPointer)
	{
		switch(type)
		{
		case JsType.Bool:
			switch(size)
			{
			case 1: return Marshal.ReadByte(ptr) != 0;
			case 2: return Marshal.ReadInt16(ptr) != 0;
			default: return Marshal.ReadInt32(ptr) != 0;
			}
			break;
		case JsType.Int: return Marshal.ReadInt32(ptr);
		case JsType.Double: return System.BitConverter.Int64BitsToDouble(Marshal.ReadInt64(ptr));
		case JsType.String: return !stringAsPointer ? (object)Marshal.PtrToStringAnsi(ptr) : new IntPtr[] { (IntPtr)size, ptr };
		case JsType.IntArray:
			{
				int[] result = new int[size];
				for(int i = 0; i < size; ++i, ptr = (IntPtr)(ptr.ToInt64() + 4))
					result[i] = Marshal.ReadInt32(ptr);
				return result;
			}
		case JsType.StringArray:
			if(!stringAsPointer)
			{
				string[] result = new string[size];
				for(int i = 0; i < size; ++i, ptr = (IntPtr)(ptr.ToInt64() + 8))
					result[i] = Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(ptr));
				return result;
			}
			else
			{
				IntPtr[] result = new IntPtr[size];
				for(int i = 0; i < size; ++i, ptr = (IntPtr)(ptr.ToInt64() + 8))
					result[i] = Marshal.ReadIntPtr(ptr);
				return result;
			}
		}
		return null;
	}
	public string source
	{
		get
		{
			JsType type = JsType.String;
			IntPtr ret;
			int size = EvaluateWeb(_id, IntPtr.Zero, ref type, out ret);
			return (string)ReturnScript(type, size, ret, false);
		}
	}
#endif
	public Rect _rect;
	public int _options;
	public string _url;
#if UNITY_EDITOR || UNITY_STANDALONE
	public string url
	{
		get { return _url; }
		set
		{
			_url = value;
			_browsableUrl = null;
			LoadWeb(0, 0, 0, 0, value, _options, IntPtr.Zero, ref _id);
			Debug.Log("Unity.Web[" + id + "],redirecting," + _url + "...");
		}
	}
	[System.NonSerialized]
	public string _browsableUrl;
#endif
#if UNITY_EDITOR
	void OnApplicationQuit() { OnDisable(); }
#elif UNITY_STANDALONE
	void OnApplicationQuit() { UnloadAllWebs(); }
#endif
#if UNITY_EDITOR || UNITY_STANDALONE
	int _id = -1;
	public int id { get { return _id; } }
	void OnDisable()
	{
		if(_id < 0)
			return;
		Debug.Log("WebViewController(" + GetInstanceID() + ").OnDisable," + _id);
		if(_onUnloadWeb != null)
			_onUnloadWeb();
		_onUnloadWeb = null;
		Cursor.SetCursor(null, Vector2.zero, CursorMode.Auto);
		if(_arrow != null)
			DestroyImmediate(_arrow);
		if(_hand != null)
			DestroyImmediate(_hand);
		UnloadWeb(_id);
		_id = -1;
	}
	public System.Action _onUnloadWeb;

	Texture2D _texture;
	Color32[] _buffer;
	GCHandle _handle;
	void Start()
	{
#if UNITY_EDITOR
		if(!UnityEditor.EditorApplication.isPlaying)
			return;
#endif
		if(_rect.width <= 0 || _rect.height <= 0)
		{
			_rect.width = Screen.width - _rect.x;
			_rect.height = Screen.height - _rect.y;
		}
		_texture = new Texture2D((int)_rect.width, (int)_rect.height, TextureFormat.ARGB32, false, true);
		_buffer = _texture.GetPixels32();
		_handle = GCHandle.Alloc(_buffer, GCHandleType.Pinned);
		LoadWeb((int)_rect.x, (int)_rect.y, (int)_rect.width, (int)_rect.height, _url, _options, _handle.AddrOfPinnedObject(), ref _id);
		WebViewCleaner.Create();
		if(!_scriptOnly)
			SetCursor(ref _arrow, Vector2.zero, UPDATE_CURSOR_ARROW);
	}

	int _state = UPDATE_INVALID;
	public int state { get { return _state; } }
	public bool hasStarted
	{
		get
		{
			if(_state == UPDATE_INVALID)
				return false;
			if(!_scriptOnly)
				return true;
			JsType type = JsType.Void;
			int r;
			IntPtr ret, s = Marshal.StringToHGlobalAnsi("");
			r = EvaluateWeb(id, s, ref type, out ret);
			Marshal.FreeHGlobal(s);
			return r >= 0;
		}
	}
	public bool justLoaded
	{
		get { return _state != UPDATE_INVALID && (_state & UPDATE_LOADED) != 0; }
		set { if(!value) _state &= ~UPDATE_LOADED; else _state |= UPDATE_LOADED; }
	}
	void Update() 
	{
#if UNITY_EDITOR
		if(!UnityEditor.EditorApplication.isPlaying)
			return;
#endif
		if(_focused && !string.IsNullOrEmpty(Input.inputString))
			foreach(char c in Input.inputString)
				InputWeb(_id, INPUT_KEYBOARD | INPUT_CHARACTER, c, c, IntPtr.Zero);
		if((Time.frameCount % 3) != 0)
			return;
		IntPtr u;
		_state = UpdateWeb(_id, out u);
		if(_state != UPDATE_INVALID && _state != UPDATE_NONE)
		{
			if((_state & UPDATE_BROWSABLE) != 0)
				_browsableUrl = Marshal.PtrToStringAnsi(u);
			else if((_state & UPDATE_ALERTED) != 0)
				_dialog = Marshal.PtrToStringAnsi(u);
#if DEBUG
			else if((_state & UPDATE_METRIC) != 0)
				_metric = Marshal.PtrToStringAnsi(u);
#endif
			_updated |= (_state & UPDATE_AVAILABLE) != 0;
			_focused = (_state & UPDATE_FOCUSED) != 0;
			switch(_state & UPDATE_CURSOR_MASK)
			{
			case 0: break; // no cursor change
			case UPDATE_CURSOR_HAND: SetCursor(ref _hand, new Vector2(6, 0), UPDATE_CURSOR_HAND); break;
			default: SetCursor(ref _arrow, Vector2.zero, UPDATE_CURSOR_ARROW); break;
			}
		}
	}
	Texture2D _arrow, _hand;
	void SetCursor(ref Texture2D cursor, Vector2 hotspot, int type)
	{
		if(cursor == null)
		{
			//Debug.Log("CreateCursor," + type);
			cursor = CreateCursor(type);
		}
		Cursor.SetCursor(cursor, hotspot, CursorMode.Auto);
	}

	bool _focused, _updated;
	Vector2 _mouse;
	string _dialog;
	Rect _dialogRect, _dialogButtonRect;
	GUIStyle _dialogStyle, _dialogButtonStyle;
	Texture2D _dialogBackground, _dialogInverseBackground;
#if DEBUG
	float _fps, _lastTime;
	int _lastFrames;
	string _metric;
#endif
	void OnGUI()
	{
#if UNITY_EDITOR
		if(!UnityEditor.EditorApplication.isPlaying)
			return;
#endif
		if(_scriptOnly)
			return;

		if(_texture != null)
		{
			if (_updated)
			{
				_texture.SetPixels32(_buffer);
				_texture.Apply(false, false);
				_updated = false;
			}
			GUI.DrawTexture(_rect, _texture, ScaleMode.ScaleAndCrop, false, 0);
#if DEBUG
			if(_lastTime == 0)
			{
				_lastTime = Time.realtimeSinceStartup;
				_lastFrames = Time.frameCount;
			}
			else if(Time.realtimeSinceStartup - _lastTime >= 1.0f)
			{
				_fps = (float)(Time.frameCount - _lastFrames) * 0.9f + _fps * 0.1f;
				_lastTime = Time.realtimeSinceStartup;
				_lastFrames = Time.frameCount;
			}
			GUI.Label(new Rect(0, 0, 100, 20), _fps.ToString("f1") + "(" + _metric + ")");
#endif
		}
		else
		{
			Debug.LogWarning("WebViewController.OnGUI,invalid instance(" + GetInstanceID() + ")!");
			DestroyImmediate(this);
		}
		if(!string.IsNullOrEmpty(_dialog))
		{
			if(_dialogStyle == null)
			{
				Color32 bordC = new Color32(0x00, 0x00, 0xFF, 0xFF ), fillC = new Color32(0xFF, 0xFF, 0xFF, 0xF0);
				float lineHeight = GUI.skin.font.lineHeight, maxHeight = lineHeight * 4, maxWidth = lineHeight * 60,
						minWidth = lineHeight * 40;
				_dialogRect = _rect;
				_dialogRect.width -= lineHeight * 2;
				if(_dialogRect.width > maxWidth)
					_dialogRect.width = maxWidth;
				else if(_dialogRect.width < minWidth)
					_dialogRect.width = minWidth;
				_dialogRect.height = maxHeight;
				_dialogRect.x = (_rect.width - _dialogRect.width) / 2;
				_dialogRect.y = (_rect.height - _dialogRect.height) / 2;
				_dialogButtonRect.width = lineHeight * 4;
				_dialogButtonRect.height = lineHeight;
				_dialogButtonRect.x = (_dialogRect.width - _dialogButtonRect.width) / 2 + _dialogRect.x;
				_dialogButtonRect.y = _dialogRect.height - _dialogButtonRect.height - 10 + _dialogRect.y;
				_dialogBackground = new Texture2D(4, 4, TextureFormat.ARGB32, false, false);
				_dialogBackground.SetPixels32(new Color32[] { bordC, bordC, bordC, bordC,
						bordC, fillC, fillC, bordC,
						bordC, fillC, fillC, bordC,
						bordC, bordC, bordC, bordC });
				_dialogBackground.Apply(false, true);
				_dialogInverseBackground = new Texture2D(1, 1, TextureFormat.ARGB32, false, false);
				_dialogInverseBackground.SetPixels32(new Color32[] { bordC });
				_dialogInverseBackground.Apply(false, true);
				_dialogStyle = new GUIStyle();
				_dialogStyle.alignment = TextAnchor.UpperCenter;
				_dialogStyle.padding = new RectOffset(0, 0, (int)(lineHeight / 2), 0);
				_dialogStyle.border = new RectOffset(2, 2, 2, 2);
				_dialogStyle.normal.textColor = Color.blue;
				_dialogStyle.normal.background = _dialogBackground;
				_dialogButtonStyle = new GUIStyle();
				_dialogButtonStyle.alignment = TextAnchor.MiddleCenter;
				_dialogButtonStyle.border = new RectOffset(2, 2, 2, 2);
				_dialogButtonStyle.normal.textColor = Color.black;
				_dialogButtonStyle.normal.background = _dialogBackground;
				_dialogButtonStyle.hover.textColor = Color.blue;
				_dialogButtonStyle.hover.background = _dialogBackground;
				_dialogButtonStyle.active.textColor = Color.white;
				_dialogButtonStyle.active.background = _dialogInverseBackground;
			}
			GUI.backgroundColor = Color.white;
			GUI.Box(_dialogRect, _dialog, _dialogStyle);
			if(GUI.Button(_dialogButtonRect, "OK", _dialogButtonStyle))
			{
				_dialog = null;
				InputWeb(_id, INPUT_DIALOG | INPUT_OK, 0, 0, IntPtr.Zero);
			}
			return;
		}

		Event e = Event.current;
		//if(e.type != EventType.Repaint && e.type != EventType.Layout)
		//	Debug.Log("WebViewController.OnGUI," + e.type + ",c=" + (int)e.character + ",key=" + e.keyCode
		//			  + ",command=" + e.commandName + ",mouse=" + Input.mousePosition);
		float mouseX = Input.mousePosition.x - _rect.x;
		float mouseY = Screen.height - Input.mousePosition.y - _rect.y;
		int modifier = (e.shift ? INPUT_SHIFT : 0) | (e.alt ? INPUT_ALT : 0) | (e.control | e.command ? INPUT_CTRL : 0);
		bool focused = string.IsNullOrEmpty(GUI.GetNameOfFocusedControl());
		if(!focused && _focused)
		{
			if(!_forceFocus)
				InputWeb(_id, INPUT_KEYBOARD | INPUT_LOST, 0, 0, IntPtr.Zero);
			else
			{
				GUI.FocusControl(null);
				_forceFocus = false;
			}
		}
		switch(e.type)
		{
		case EventType.ScrollWheel:
			InputWeb(_id, INPUT_MOUSE | INPUT_SCROLL, (int)-e.delta.x * 10, (int)-e.delta.y * 10, IntPtr.Zero);
			e.Use();
			break;
		case EventType.MouseDown:
			InputWeb(_id, INPUT_MOUSE | INPUT_PRESS, (int)mouseX, (int)mouseY, IntPtr.Zero);
			if(_rect.Contains(Input.mousePosition)) //force focusing to activate NSTextInputContext
			{
				if(!focused)
					GUI.FocusControl(null);
				InputWeb(_id, INPUT_KEYBOARD | INPUT_FOCUS, 0, 0, IntPtr.Zero);
			}
			e.Use();
			break;
		case EventType.MouseUp:
			InputWeb(_id, INPUT_MOUSE | INPUT_RELEASE, (int)mouseX, (int)mouseY, IntPtr.Zero);
			e.Use();
			break;
		case EventType.KeyDown:
			//Debug.Log("WebViewController.OnGUI," + Time.frameCount + ',' + e.type + ',' + e.keyCode + ',' + (int)e.character
			//		+ ",focus=" + GUI.GetNameOfFocusedControl());
			if(focused && e.character == 0)
			{
				switch(e.keyCode)
				{
				case KeyCode.Tab: _forceFocus = true; break;
				}
				//case KeyCode.Return:
				//case KeyCode.Backspace:
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
				InputWeb(_id, INPUT_KEYBOARD | INPUT_PRESS | modifier, NativeKeyCodes[e.keyCode], 0, IntPtr.Zero);
#else
				InputWeb(_id, INPUT_KEYBOARD | INPUT_PRESS | modifier, NativeKeyCodes[e.keyCode], VirtualKeyCodes[e.keyCode], IntPtr.Zero);
#endif
				//	break;
				//default:
				//	InputWeb(_id, INPUT_KEYBOARD | INPUT_PRESS | modifier, NativeKeyCodes[e.keyCode], 0, IntPtr.Zero);
				//	break;
				//}
				_inputKey = true;
				e.Use();
			}
			break;
		case EventType.KeyUp:
			if(_inputKey)
			{
				InputWeb(_id, INPUT_KEYBOARD | INPUT_RELEASE | modifier, NativeKeyCodes[e.keyCode], VirtualKeyCodes[e.keyCode], IntPtr.Zero);
				_inputKey = false;
				e.Use();
			}
			break;
		default:
			if(_mouse.x != mouseX || _mouse.y != mouseY)
				InputWeb(_id, INPUT_MOUSE | INPUT_MOVE, (int)mouseX, (int)mouseY, IntPtr.Zero);
			break;
		}
		_mouse.x = mouseX;
		_mouse.y = mouseY;
		//_updated |= e.type == EventType.Repaint;
	}
	bool _inputKey, _forceFocus;

	const int OPTION_INVERSE_COLOR = 0x1;
	const int OPTION_INVERSE_Y = 0x2;
	const int OPTION_RELOAD_RESOURCE = 0x4;
	const int OPTION_CLEAR_COOKIES = 0x8;

	const int UPDATE_INVALID = -1;
	const int UPDATE_NONE = 0;
	const int UPDATE_AVAILABLE = 0x100;
	const int UPDATE_BROWSABLE = 0x200;
	const int UPDATE_FOCUSED = 0x400;
	const int UPDATE_ALERTED = 0x800;
	const int UPDATE_LOADED = 0x1000;
	const int UPDATE_METRIC = 0x8000;
	const int UPDATE_CURSOR_ARROW = 1;
	const int UPDATE_CURSOR_HAND = 3;
	const int UPDATE_CURSOR_MASK = 0xFF;

	const int INPUT_NONE = 0;
	const int INPUT_EVENT = 0xF0;
	const int INPUT_PRESS = 0x10;
	const int INPUT_RELEASE = 0x20;
	const int INPUT_MOVE = 0x30;
	const int INPUT_SCROLL = 0x40;
	const int INPUT_CHARACTER = 0x50;
	const int INPUT_FOCUS = 0x60;
	const int INPUT_LOST = 0x70;
	const int INPUT_OK = 0x80;
	const int INPUT_CANCEL = 0x90;
	const int INPUT_MODIFIER = 0xF00;
	const int INPUT_SHIFT = 0x100;
	const int INPUT_ALT = 0x200;
	const int INPUT_CTRL = 0x400;
	const int INPUT_MASK = 0xF000;
	const int INPUT_MOUSE = 0x1000;
	const int INPUT_KEYBOARD = 0x2000;
	const int INPUT_DIALOG = 0x4000;

	[DllImport("web")]
	public static extern int LoadWeb(int x, int y, int w, int h, string url, int options, IntPtr buffer, ref int web);
	[DllImport("web")]
	public static extern int UnloadWeb(int web);
	[DllImport("web")]
	public static extern int UnloadAllWebs();
	[DllImport("web")]
	public static extern int UpdateWeb(int web, out IntPtr url);
	[DllImport("web")]
	public static extern int InputWeb(int web, int type, int x, int y, IntPtr rawEvent);
	[DllImport("web")]
	public static extern int EvaluateWeb(int web, IntPtr script, ref JsType type, out IntPtr result);

	internal static Dictionary<KeyCode, int> VirtualKeyCodes = new Dictionary<KeyCode, int>()
	{
		{ KeyCode.Backspace, 0x08 },
		{ KeyCode.Delete, 0x2E },
		{ KeyCode.Tab, 0x09 },
		{ KeyCode.Clear, 0x0C },
		{ KeyCode.Return, 0x0D },
		{ KeyCode.Pause, 0x13 },
		{ KeyCode.Escape, 0x1B },
		{ KeyCode.Space, 0x20 },
		{ KeyCode.Keypad0, 0x60 },
		{ KeyCode.Keypad1, 0x61 },
		{ KeyCode.Keypad2, 0x62 },
		{ KeyCode.Keypad3, 0x63 },
		{ KeyCode.Keypad4, 0x64 },
		{ KeyCode.Keypad5, 0x65 },
		{ KeyCode.Keypad6, 0x66 },
		{ KeyCode.Keypad7, 0x67 },
		{ KeyCode.Keypad8, 0x68 },
		{ KeyCode.Keypad9, 0x69 },
		{ KeyCode.KeypadPeriod, 0x6E },
		{ KeyCode.KeypadDivide, 0x6F },
		{ KeyCode.KeypadMultiply, 0x6A },
		{ KeyCode.KeypadMinus, 0x6D },
		{ KeyCode.KeypadPlus, 0x6B },
		{ KeyCode.KeypadEnter, 0x10F },
		{ KeyCode.KeypadEquals, 0x110 },
		{ KeyCode.UpArrow, 0x26 },
		{ KeyCode.DownArrow, 0x28 },
		{ KeyCode.RightArrow, 0x27 },
		{ KeyCode.LeftArrow, 0x25 },
		{ KeyCode.Insert, 0x2D },
		{ KeyCode.Home, 0x24 },
		{ KeyCode.End, 0x23 },
		{ KeyCode.PageUp, 0x21 },
		{ KeyCode.PageDown, 0x22 },
		{ KeyCode.F1, 0x70 },
		{ KeyCode.F2, 0x71 },
		{ KeyCode.F3, 0x72 },
		{ KeyCode.F4, 0x73 },
		{ KeyCode.F5, 0x74 },
		{ KeyCode.F6, 0x75 },
		{ KeyCode.F7, 0x76 },
		{ KeyCode.F8, 0x77 },
		{ KeyCode.F9, 0x78 },
		{ KeyCode.F10, 0x79 },
		{ KeyCode.F11, 0x7A },
		{ KeyCode.F12, 0x7B },
		{ KeyCode.F13, 0x7C },
		{ KeyCode.F14, 0x7D },
		{ KeyCode.F15, 0x7E },
		{ KeyCode.Alpha0, 0x30 },
		{ KeyCode.Alpha1, 0x31 },
		{ KeyCode.Alpha2, 0x32 },
		{ KeyCode.Alpha3, 0x33 },
		{ KeyCode.Alpha4, 0x34 },
		{ KeyCode.Alpha5, 0x35 },
		{ KeyCode.Alpha6, 0x36 },
		{ KeyCode.Alpha7, 0x37 },
		{ KeyCode.Alpha8, 0x38 },
		{ KeyCode.Alpha9, 0x39 },
		{ KeyCode.Exclaim, 0x21 },
		{ KeyCode.DoubleQuote, 0x22 },
		{ KeyCode.Hash, 0x23 },
		{ KeyCode.Dollar, 0x24 },
		{ KeyCode.Ampersand, 0x26 },
		{ KeyCode.Quote, 0xDE },
		{ KeyCode.LeftParen, 0x28 },
		{ KeyCode.RightParen, 0x29 },
		{ KeyCode.Asterisk, 0x2A },
		{ KeyCode.Plus, 0x2B },
		{ KeyCode.Comma, 0xBC },
		{ KeyCode.Minus, 0xBD },
		{ KeyCode.Period, 0xBE },
		{ KeyCode.Slash, 0xBF },
		{ KeyCode.Colon, 0x3A },
		{ KeyCode.Semicolon, 0xBA },
		{ KeyCode.Less, 0x3C },
		{ KeyCode.Equals, 0x3D },
		{ KeyCode.Greater, 0x3E },
		{ KeyCode.Question, 0x3F },
		{ KeyCode.At, 0x40 },
		{ KeyCode.LeftBracket, 0x5B },
		{ KeyCode.Backslash, 0xDC },
		{ KeyCode.RightBracket, 0x5D },
		{ KeyCode.Caret, 0x5E },
		{ KeyCode.Underscore, 0x5F },
		{ KeyCode.BackQuote, 0xC0 },
		{ KeyCode.A, 0x41 },
		{ KeyCode.B, 0x42 },
		{ KeyCode.C, 0x43 },
		{ KeyCode.D, 0x44 },
		{ KeyCode.E, 0x45 },
		{ KeyCode.F, 0x46 },
		{ KeyCode.G, 0x47 },
		{ KeyCode.H, 0x48 },
		{ KeyCode.I, 0x49 },
		{ KeyCode.J, 0x4A },
		{ KeyCode.K, 0x4B },
		{ KeyCode.L, 0x4C },
		{ KeyCode.M, 0x4D },
		{ KeyCode.N, 0x4E },
		{ KeyCode.O, 0x4F },
		{ KeyCode.P, 0x50 },
		{ KeyCode.Q, 0x51 },
		{ KeyCode.R, 0x52 },
		{ KeyCode.S, 0x53 },
		{ KeyCode.T, 0x54 },
		{ KeyCode.U, 0x55 },
		{ KeyCode.V, 0x56 },
		{ KeyCode.W, 0x57 },
		{ KeyCode.X, 0x58 },
		{ KeyCode.Y, 0x59 },
		{ KeyCode.Z, 0x5A },
		{ KeyCode.Numlock, 0x90 },
		{ KeyCode.CapsLock, 0x14 },
		{ KeyCode.ScrollLock, 0x91 },
		{ KeyCode.RightShift, 0xA1 },
		{ KeyCode.LeftShift, 0xA0 },
		{ KeyCode.RightControl, 0xA3 },
		{ KeyCode.LeftControl, 0xA2 },
		{ KeyCode.RightAlt, 0xA5 },
		{ KeyCode.LeftAlt, 0xA4 },
		{ KeyCode.LeftCommand, 0x5B },
		{ KeyCode.LeftWindows, 0x5B },
		{ KeyCode.RightCommand, 0x5C },
		{ KeyCode.RightWindows, 0x5C },
		{ KeyCode.AltGr, 0x139 },
		{ KeyCode.Help, 0x2F },
		{ KeyCode.Print, 0x2A },
		{ KeyCode.SysReq, 0x13D },
		{ KeyCode.Break, 0x03 },
		{ KeyCode.Menu, 0x12 },
		{ KeyCode.None, 0 }
	};
#if !UNITY_EDITOR_OSX && !UNITY_STANDALONE_OSX
	static Dictionary<KeyCode, int> NativeKeyCodes = VirtualKeyCodes;
#else
	static Dictionary<KeyCode, int> NativeKeyCodes = new Dictionary<KeyCode, int>()
	{
		{ KeyCode.A, 0x00 },
		{ KeyCode.S, 0x01 },
		{ KeyCode.D, 0x02 },
		{ KeyCode.F, 0x03 },
		{ KeyCode.H, 0x04 },
		{ KeyCode.G, 0x05 },
		{ KeyCode.Z, 0x06 },
		{ KeyCode.X, 0x07 },
		{ KeyCode.C, 0x08 },
		{ KeyCode.V, 0x09 },
		{ KeyCode.B, 0x0B },
		{ KeyCode.Q, 0x0C },
		{ KeyCode.W, 0x0D },
		{ KeyCode.E, 0x0E },
		{ KeyCode.R, 0x0F },
		{ KeyCode.Y, 0x10 },
		{ KeyCode.T, 0x11 },
		{ KeyCode.Alpha1, 0x12 },
		{ KeyCode.Alpha2, 0x13 },
		{ KeyCode.Alpha3, 0x14 },
		{ KeyCode.Alpha4, 0x15 },
		{ KeyCode.Alpha6, 0x16 },
		{ KeyCode.Alpha5, 0x17 },
		{ KeyCode.Equals, 0x18 },
		{ KeyCode.Alpha9, 0x19 },
		{ KeyCode.Alpha7, 0x1A },
		{ KeyCode.Minus, 0x1B },
		{ KeyCode.Alpha8, 0x1C },
		{ KeyCode.Alpha0, 0x1D },
		{ KeyCode.RightBracket, 0x1E },
		{ KeyCode.O, 0x1F },
		{ KeyCode.U, 0x20 },
		{ KeyCode.LeftBracket, 0x21 },
		{ KeyCode.I, 0x22 },
		{ KeyCode.P, 0x23 },
		{ KeyCode.L, 0x25 },
		{ KeyCode.J, 0x26 },
		{ KeyCode.Quote, 0x27 },
		{ KeyCode.K, 0x28 },
		{ KeyCode.Semicolon, 0x29 },
		{ KeyCode.Backslash, 0x2A },
		{ KeyCode.Comma, 0x2B },
		{ KeyCode.Slash, 0x2C },
		{ KeyCode.N, 0x2D },
		{ KeyCode.M, 0x2E },
		{ KeyCode.Period, 0x2F },
		{ KeyCode.BackQuote, 0x32 },
		{ KeyCode.KeypadPeriod, 0x41 },
		{ KeyCode.KeypadMultiply, 0x43 },
		{ KeyCode.KeypadPlus, 0x45 },
		{ KeyCode.KeypadDivide, 0x4B },
		{ KeyCode.KeypadEnter, 0x4C },
		{ KeyCode.KeypadMinus, 0x4E },
		{ KeyCode.KeypadEquals, 0x51 },
		{ KeyCode.Keypad0, 0x52 },
		{ KeyCode.Keypad1, 0x53 },
		{ KeyCode.Keypad2, 0x54 },
		{ KeyCode.Keypad3, 0x55 },
		{ KeyCode.Keypad4, 0x56 },
		{ KeyCode.Keypad5, 0x57 },
		{ KeyCode.Keypad6, 0x58 },
		{ KeyCode.Keypad7, 0x59 },
		{ KeyCode.Keypad8, 0x5B },
		{ KeyCode.Keypad9, 0x5C },
		{ KeyCode.Return, 0x24 },
		{ KeyCode.Tab, 0x30 },
		{ KeyCode.Space, 0x31 },
		{ KeyCode.Backspace, 0x33 },
		{ KeyCode.Escape, 0x35 },
		{ KeyCode.LeftCommand, 0x37 },
		{ KeyCode.LeftShift, 0x38 },
		{ KeyCode.CapsLock, 0x39 },
		{ KeyCode.LeftAlt, 0x3A },
		{ KeyCode.LeftControl, 0x3B },
		{ KeyCode.RightCommand, 0x36 },
		{ KeyCode.RightShift, 0x3C },
		{ KeyCode.RightAlt, 0x3D },
		{ KeyCode.RightControl, 0x3E },
		{ KeyCode.F5, 0x60 },
		{ KeyCode.F6, 0x61 },
		{ KeyCode.F7, 0x62 },
		{ KeyCode.F3, 0x63 },
		{ KeyCode.F8, 0x64 },
		{ KeyCode.F9, 0x65 },
		{ KeyCode.F11, 0x67 },
		{ KeyCode.F13, 0x69 },
		{ KeyCode.F14, 0x6B },
		{ KeyCode.F10, 0x6D },
		{ KeyCode.F12, 0x6F },
		{ KeyCode.F15, 0x71 },
		{ KeyCode.Home, 0x73 },
		{ KeyCode.PageUp, 0x74 },
		{ KeyCode.Delete, 0x75 },
		{ KeyCode.F4, 0x76 },
		{ KeyCode.End, 0x77 },
		{ KeyCode.F2, 0x78 },
		{ KeyCode.PageDown, 0x79 },
		{ KeyCode.F1, 0x7A },
		{ KeyCode.LeftArrow, 0x7B },
		{ KeyCode.RightArrow, 0x7C },
		{ KeyCode.DownArrow, 0x7D },
		{ KeyCode.UpArrow, 0x7E },
		// not used in MacOS
		{ KeyCode.Clear, 0x0C },
		{ KeyCode.Pause, 0x13 },
		{ KeyCode.Insert, 0x2D },
		{ KeyCode.Exclaim, 0x21 },
		{ KeyCode.DoubleQuote, 0x22 },
		{ KeyCode.Hash, 0x23 },
		{ KeyCode.Dollar, 0x24 },
		{ KeyCode.Ampersand, 0x26 },
		{ KeyCode.LeftParen, 0x28 },
		{ KeyCode.RightParen, 0x29 },
		{ KeyCode.Asterisk, 0x2A },
		{ KeyCode.Plus, 0x2B },
		{ KeyCode.Colon, 0x3A },
		{ KeyCode.Less, 0x3C },
		{ KeyCode.Greater, 0x3E },
		{ KeyCode.Question, 0x3F },
		{ KeyCode.At, 0x40 },
		{ KeyCode.Caret, 0x5E },
		{ KeyCode.Underscore, 0x5F },
		{ KeyCode.Numlock, 0x90 },
		{ KeyCode.ScrollLock, 0x91 },
		{ KeyCode.LeftWindows, 0x5B },
		{ KeyCode.RightWindows, 0x5C },
		{ KeyCode.AltGr, 0x139 },
		{ KeyCode.Help, 0x2F },
		{ KeyCode.Print, 0x2A },
		{ KeyCode.SysReq, 0x13D },
		{ KeyCode.Break, 0x03 },
		{ KeyCode.Menu, 0x12 },
		{ KeyCode.None, 0 }
	};
#endif //!UNITY_EDITOR_OSX && !UNITY_STANDALONE_OSX
	static Texture2D CreateCursor(int cursor)
	{
		int size = 32;
		Texture2D result = new Texture2D(size, size, TextureFormat.ARGB32, false, false);
		Color32[] pixels = (cursor == UPDATE_CURSOR_HAND) ? HandCursor() : ArrowCursor();
		for(int y = 0; y < size / 2; ++y)
			for(int x = 0; x < size; ++x)
			{
				int i = y * size + x, j = (size - y - 1) * size + x;
				Color32 pixel = pixels[i];
				pixels[i] = pixels[j];
				pixels[j] = pixel;
			}
		result.SetPixels32(pixels);
		result.Apply(false, false);
#if UNITY_EDITOR
		result.alphaIsTransparency = true;
#endif
		return result;
	}
	static Color32[] ArrowCursor()
	{
		Color32 _ = new Color32(0x00, 0x00, 0x00, 0x00), f = new Color32(0xFF, 0xFF, 0xFF, 0xFF),
				s = new Color32(0x00, 0x00, 0x00, 0xFF);
		return new Color32[]
				{ s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  s, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  s, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  s, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  s, f, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  s, f, f, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  s, f, f, f, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  s, f, f, f, f, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  s, f, f, f, f, f, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  s, f, f, f, f, f, f, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  s, f, f, f, f, f, f, f, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  s, f, f, f, f, f, f, f, f, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  s, f, f, f, f, f, f, f, f, f, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  s, f, f, f, f, f, f, f, s, s, s, s, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  s, f, f, f, f, f, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  s, f, f, f, s, f, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  s, f, f, s, _, s, f, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  s, f, s, _, _, s, f, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  s, s, _, _, _, _, s, f, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, _, _, s, f, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, _, _, _, s, s, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _ };
	}
	static Color32[] HandCursor()
	{
		Color32 _ = new Color32(0x00, 0x00, 0x00, 0x00), f = new Color32(0xFF, 0xFF, 0xFF, 0xFF),
				s = new Color32(0x00, 0x00, 0x00, 0xFF);
		return new Color32[]
				{ _, _, _, _, _, s, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, s, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, s, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, s, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, s, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, s, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, s, f, f, s, s, s, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, s, f, f, s, f, f, s, s, s, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, s, f, f, s, f, f, s, f, f, s, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  s, s, s, _, s, f, f, s, f, f, s, f, f, s, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  s, f, f, s, s, f, f, s, f, f, s, f, f, s, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  s, f, f, f, s, f, f, f, f, f, f, f, f, s, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, s, f, f, f, f, f, f, f, f, f, f, f, f, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, s, f, f, f, f, f, f, f, f, f, f, f, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, s, f, f, f, f, f, f, f, f, f, f, f, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, s, f, f, f, f, f, f, f, f, f, f, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, s, f, f, f, f, f, f, f, f, f, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, s, f, f, f, f, f, f, f, f, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, s, f, f, f, f, f, f, f, f, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, _, s, f, f, f, f, f, f, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, _, s, f, f, f, f, f, f, f, f, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, _, s, s, s, s, s, s, s, s, s, s, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
				  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _ };
	}
#endif //UNITY_EDITOR || UNITY_STANDALONE
}
