
Console = (function()
{
	var BORDER = 10;
	var HEIGHT = 200;


	function Console(wm, server)
	{
		// Create the window and its controls
		this.Window = wm.AddWindow("Console", 10, 10, 100, 100);
		this.PageContainer = this.Window.AddControlNew(new WM.Container(10, 10, 400, 160));
		DOM.Node.AddClass(this.PageContainer.Node, "ConsoleText");
		this.AppContainer = this.Window.AddControlNew(new WM.Container(10, 10, 400, 160));
		DOM.Node.AddClass(this.AppContainer.Node, "ConsoleText");
		this.UserInput = this.Window.AddControlNew(new WM.EditBox(10, 5, 400, 30, "Input", ""));
		this.UserInput.SetChangeHandler(Bind(ProcessInput, this));
		this.Window.ShowNoAnim();

		// This accumulates log text as fast as is required
		this.PageTextBuffer = "";
		this.PageTextUpdatePending = false;
		this.AppTextBuffer = "";
		this.AppTextUpdatePending = false;

		// Setup command history control
		this.CommandHistory = LocalStore.Get("App", "Global", "CommandHistory", [ ]);
		this.CommandIndex = 0;
		this.MaxNbCommands = 200;
		DOM.Event.AddHandler(this.UserInput.EditNode, "keydown", Bind(OnKeyPress, this));
		DOM.Event.AddHandler(this.UserInput.EditNode, "focus", Bind(OnFocus, this));

		// At a much lower frequency this will update the console window
		window.setInterval(Bind(UpdateHTML, this), 500);

		// Setup log requests from the server
		this.Server = server;
		server.SetConsole(this);
		server.AddMessageHandler("LOGM", Bind(OnLog, this));
		
		this.Window.SetOnResize(Bind(OnUserResize, this));
	}


	Console.prototype.Log = function(text)
	{
		this.PageTextBuffer = LogText(this.PageTextBuffer, text);
		this.PageTextUpdatePending = true;
	}


	Console.prototype.WindowResized = function(width, height)
	{
		// Place window
		this.Window.SetPosition(BORDER, height - BORDER - 200);
		this.Window.SetSize(width - 2 * BORDER, HEIGHT);

		ResizeInternals(this);
	}


	function OnLog(self, socket, data_view)
	{
		var data_view_reader = new DataViewReader(data_view, 4);
		var text = data_view_reader.GetString();
		self.AppTextBuffer = LogText(self.AppTextBuffer, text);
		self.AppTextUpdatePending = true;
	}


	function LogText(existing_text, new_text)
	{
		// Filter the text a little to make it safer
		if (new_text == null)
			new_text = "NULL";

		// Find and convert any HTML entities, ensuring the browser doesn't parse any embedded HTML code
		// This also allows the log to contain arbitrary C++ code (e.g. assert comparison operators)
		new_text = Convert.string_to_html_entities(new_text);

		// Prefix date and end with new line
		var d = new Date();
		new_text = "[" + d.toLocaleTimeString() + "] " + new_text + "<br>";

		// Append to local text buffer and ensure clip the oldest text to ensure a max size
		existing_text = existing_text + new_text;
		var max_len = 10 * 1024;
		var len = existing_text.length;
		if (len > max_len)
			existing_text = existing_text.substr(len - max_len, max_len);

		return existing_text;
	}

	function OnUserResize(self, evt)
	{
		ResizeInternals(self);
	}

	function ResizeInternals(self)
	{
		// Place controls
		var parent_size = self.Window.Size;
		var mid_w = parent_size[0] / 3;
		self.UserInput.SetPosition(BORDER, parent_size[1] - 2 * BORDER - 30);
		self.UserInput.SetSize(parent_size[0] - 100, 18);
		var output_height = self.UserInput.Position[1] - 2 * BORDER;
		self.PageContainer.SetPosition(BORDER, BORDER);
		self.PageContainer.SetSize(mid_w - 2 * BORDER, output_height);
		self.AppContainer.SetPosition(mid_w, BORDER);
		self.AppContainer.SetSize(parent_size[0] - mid_w - BORDER, output_height);
	}


	function UpdateHTML(self)
	{
		// Reset the current text buffer as html

		if (self.PageTextUpdatePending)
		{
			var page_node = self.PageContainer.Node;
			page_node.innerHTML = self.PageTextBuffer;
			page_node.scrollTop = page_node.scrollHeight;
			self.PageTextUpdatePending = false;
		}

		if (self.AppTextUpdatePending)
		{
			var app_node = self.AppContainer.Node;
			app_node.innerHTML = self.AppTextBuffer;
			app_node.scrollTop = app_node.scrollHeight;
			self.AppTextUpdatePending = false;
		}
	}


	function ProcessInput(self, node)
	{
		// Send the message exactly
		var msg = node.value;
		self.Server.Send("CONI" + msg);

		// Emit to console and clear
		self.Log("> " + msg);
		self.UserInput.SetValue("");

		// Keep track of recently issued commands, with an upper bound
		self.CommandHistory.push(msg);
		var extra_commands = self.CommandHistory.length - self.MaxNbCommands;
		if (extra_commands > 0)
			self.CommandHistory.splice(0, extra_commands);

		// Set command history index to the most recent command
		self.CommandIndex = self.CommandHistory.length;

		// Backup to local store
		LocalStore.Set("App", "Global", "CommandHistory", self.CommandHistory);

		// Keep focus with the edit box
		return true;
	}


	function OnKeyPress(self, evt)
	{
		evt = DOM.Event.Get(evt);

		if (evt.keyCode == Keyboard.Codes.UP)
		{
			if (self.CommandHistory.length > 0)
			{
				// Cycle backwards through the command history
				self.CommandIndex--;
				if (self.CommandIndex < 0)
					self.CommandIndex = self.CommandHistory.length - 1;
				var command = self.CommandHistory[self.CommandIndex];
				self.UserInput.SetValue(command);
			}

			// Stops default behavior of moving cursor to the beginning
			DOM.Event.StopDefaultAction(evt);
		}

		else if (evt.keyCode == Keyboard.Codes.DOWN)
		{
			if (self.CommandHistory.length > 0)
			{
				// Cycle fowards through the command history
				self.CommandIndex = (self.CommandIndex + 1) % self.CommandHistory.length;
				var command = self.CommandHistory[self.CommandIndex];
				self.UserInput.SetValue(command);
			}

			// Stops default behavior of moving cursor to the end
			DOM.Event.StopDefaultAction(evt);
		}
	}


	function OnFocus(self)
	{
		// Reset command index on focus
		self.CommandIndex = self.CommandHistory.length;
	}


	return Console;
})();
