/*
 * Kramwerk.de Konsole
 * Copyright (c) Florian Kriener
 */

var konsole;

$(function () {
	konsole = new Konsole($("#konsole/pre").text(), $("#konsole"));
	
	register_kommands(konsole.kommands);
});

function register_kommands(kommands)
{
	//AJAX
	kommands.register("*", function (argv) {
		this.redraw('AJAXing...');
		
		var r = $.post("lib/php/kommand.php", { argv: argv.join(" ") }, createObjectCallback(this, function(data) {
			var ret = $("result", data).text();
			
			if(ret == -1)
			{
				this.print("Kommand not found: " + $("kommand", data).text() + "\n");
			}
			else if (ret < 0)
			{
				this.print("ERROR: "+ -ret +"\n");
			}
			else
			{		
	  			this.print(gtltlise($("text", data).text()) + "\n");
			}
			
			this.le_start();
			this.redraw();
		}));
		
		return 1;
	});
	
	// "Browser"
	/*
	kommands.register("browse", function (argv) {
		var site = argv[1];
	
		if (site == null || site == '')
		{
			this.print('Usage: load &lt;website&gt;\n');
			return;
		}
	
		this.print('Loading http://' + site +"...\n");
		
		$('body')
			.append('<div id="browse" class="window"></div>')
			.find('#browse')
				.hide()
				.append('<div class="clearfix titlebar"></div>')
				.find('div')
					.append('<span class="left"><strong>'+site+'</strong></span>')
					.append('<span class="right"><a href="#">x</a></span>')
					.find('a')
						.click(function () {
							$('#browse').hide('slow', function() { $('#browse').remove(); });
						})
						.end()
					.end()
				.append('<iframe src="http://'+ site +'" width="790px" height="570px"></iframe>')
				.show('slow');

		return 0;
	});
	*/
	
	kommands.register("clear", function (argv) {
		this.clear();
		return 0;
	});

    kommands.register("photos", function (argv) {
            location.href = "http://kriener.org/photos/gent";
            return 0;
    });

    kommands.register("societe-electronique", function (argv) {
            location.href = "http://societe-electronique.org/";
            return 0;
    });
}


/*
 * History
 */
function History()
{
	this.history = new Array();
	this._pos = 0;
}

History.prototype.add = function (buffer)
{
	if (!buffer || buffer == "" || buffer == this.history[this.history.length - 1])
	{
		return;
	}
	
	this.history.push(buffer);
}

History.prototype.get = function ()
{
	return this.history[this._pos];
}

History.prototype.up = function ()
{
	if (this._pos > 0)
	{
		this._pos--;
		return this.history[this._pos];
	}
	
	return null;	
}

History.prototype.down = function ()
{
	if (this._pos < this.history.length)
	{
		this._pos++;
		return this.history[this._pos];
	}
	
	return null;
}

History.prototype.reset = function ()
{
	this._pos = this.history.length;
}

/*
 * LineEditor
 */
function LineEditor(useHistory, callbacks)
{
	this.history = new History();

	// we're using 'Buffer and Save' here
	// i.e. we use buffer for displaying and safe for editing
	//      (buffer is saved to safe on edit)
	this._buffer = "";
	this._safe = null;

	// two callbacks are implemented (return, keypress)
	// but maybe one should use this class as superclass
	// and without callbacks
	this._callbacks = callbacks;
	
	this._lock = true;
	this._use_history = useHistory;
	
	$(document).keypress(createObjectCallback(this, this._keypress));
}

LineEditor.prototype.stop = function ()
{
	this._lock = true;
}

LineEditor.prototype.start = function ()
{
	this._lock = false;
}

LineEditor.prototype.status = function ()
{
	return !this._lock;
}

LineEditor.prototype.addChar = function (charCode)
{
	this._buffer += String.fromCharCode(charCode)
	this._safe = this._buffer;
}

LineEditor.prototype.backspace = function ()
{
	this._buffer = this._buffer.substring(0, this._buffer.length - 1);
	this._safe = this._buffer;
}

LineEditor.prototype.get = function ()
{
	return this._buffer;
}

LineEditor.prototype.reset = function ()
{
	if (this._use_history)
	{
		this.history.add(this._buffer);
		this.history.reset();
	}
	
	this._buffer = "";
	this._safe = "";
}

LineEditor.prototype._keypress = function (evt)
{
	if (this._lock)
	{
		return;
	}

	evt = (evt) ? evt : ((event) ? event : null);

	if (evt)
	{
		if (evt.charCode)
		{
			this.addChar(evt.charCode);
		}
		else if (evt.keyCode)
		{
			switch (evt.keyCode)
			{
				//backspace
				case 8:
					this.backspace();
					break;
				
				//up
				case 38:
					var hist = this.history.up();
					this._buffer = hist ? hist : this._buffer;
					break;
				
				//down
				case 40:
					var hist = this.history.down();
					this._buffer = hist ? hist : this._safe;
					break;
				
				//return
				case 13:
					var h = this.get();
								
					this.stop();
					this.reset();
					
					//FIXME (check if callback exists)
					this._callbacks.return(h);					
					break;
			}
		}
		
		//FIXME (check if callback exists)
		this._callbacks.keypress(evt);
	}
}

/*
 * Kommands
 */
function Kommands(env)
{
	this._kommands = new Array;
	this._env = env;
}

Kommands.prototype.register = function (name, fn)
{
	this._kommands[name] = fn;
}

Kommands.prototype.execute = function (name, args)
{
	if (!this._kommands[name])
	{
		return -100;
	}
	
	return this._kommands[name].call(this._env, args);
}

/*
 * Konsole
 */
function Konsole(content, object)
{
	this._buffer = content;
	this._container = $(object);
	
	this._cursor_interval = null;
	this._scroll_interval = null;
	this._scroll_pos = 0;
	
	this.prompt = '[kramwerk.de]% ';
	
	this.line_editor = new LineEditor(true, {
		return:   createObjectCallback(this, this.execute),
		keypress: createObjectCallback(this, this._keypress)
	});
	
	this.kommands = new Kommands({
		// Generate an intermediate object for our kommands
		print:     createObjectCallback(this, this.print),
		redraw:    createObjectCallback(this, this.redraw),
		le_stop:   createObjectCallback(this.line_editor, this.line_editor.stop),
		le_start:  createObjectCallback(this.line_editor, this.line_editor.start),
		clear:     createObjectCallback(this, this.clear),
	});

	// use our container and add a <pre> block
	// container must have position: relative;	
	this._container
		.html("<pre>"+ content +"</pre>")
		.append('<img src="img/arrow_up.png" class="scroll-up" />')
		.find('.scroll-up')
			.mousedown(createObjectCallback(this, this.start_scroll_up))
			.mouseup(createObjectCallback(this, this.stop_scroll))
			.mouseout(createObjectCallback(this, this.stop_scroll))
			.end()
		.append('<img src="img/arrow_down.png" class="scroll-down" />')
		.find('.scroll-down')
			.mousedown(createObjectCallback(this, this.start_scroll_down))
			.mouseup(createObjectCallback(this, this.stop_scroll))
			.mouseout(createObjectCallback(this, this.stop_scroll));

	this._screen = this._container.find('pre');
	this._screen.css({
		padding: "10px 0",
		position: "absolute",
		bottom: this._scroll_pos + "px"
	});
	
	// blink the cursor on focus
	$(document).focus(createObjectCallback(this, this._start_cursor_interval));
	$(document).blur(createObjectCallback(this, this._stop_cursor_interval));

	this.line_editor.start();	
	this.redraw();
}

Konsole.prototype._keypress = function (evt)
{
	if (evt.charCode)
	{
		this.scroll_to(0);
	}
	
	if (evt.keyCode)
	{
		switch(evt.keyCode)
		{
			// page up
			case 33:
				this.scroll_by(-(this._container[0].clientHeight - 50));
				break;

			//page down
			case 34:
				this.scroll_by(this._container[0].clientHeight - 50);
				break;
			
			//end	
			case 35:
				this.scroll_to(0);
				break;
			
			//home
			case 36:
				this.scroll_to(- (this._screen[0].scrollHeight - this._container[0].clientHeight));
				break;
		}
	}
	
	this.redraw();
}

Konsole.prototype.start_scroll_up = function ()
{
	if (this._scroll_interval)
	{
		return;
	}
	
	this._scroll_interval = setInterval(createObjectCallback(this, function() { this.scroll_by(-3); }), 15);
}

Konsole.prototype.start_scroll_down = function ()
{
	if (this._scroll_interval)
	{
		return;
	}
	
	this._scroll_interval = setInterval(createObjectCallback(this, function() { this.scroll_by(+3); }), 15);
}

Konsole.prototype.scroll_by = function (offset)
{
	this._scroll_pos += offset;
	
	if (this._screen[0].scrollHeight < this._container[0].clientHeight)
	{   // screen < container
		this._scroll_pos -= offset;
	}
	else if (this._scroll_pos > 0)
	{   // Lower border from screen = lower border from container
		this._scroll_pos = 0;
	}
	else if (this._scroll_pos + (this._screen[0].scrollHeight - this._container[0].clientHeight) < 0)
	{   // upper border from screen = upper border from container
		this._scroll_pos = - (this._screen[0].scrollHeight - this._container[0].clientHeight);
	}
	
	this._screen.css('bottom', this._scroll_pos + "px");
}

Konsole.prototype.scroll_to = function (pos)
{
	this._scroll_pos = pos;
	this._screen.css('bottom', this._scroll_pos + "px");
}

Konsole.prototype.stop_scroll = function ()
{
	clearInterval(this._scroll_interval);
	this._scroll_interval = null;
}

Konsole.prototype.clear = function ()
{
	this._buffer = "";
	this.redraw();
}

Konsole.prototype.redraw = function (text)
{
	if (this.line_editor.status())
	{
		this._screen.html(this._buffer + this.prompt + gtltlise(this.line_editor.get()) + '<span id="cursor">_</span>\n\n');
	}
	else
	{
		text = text ? text : '';
		this._screen.html(this._buffer + text + "\n\n");
	}
}

Konsole.prototype.print = function (text)
{
	this._buffer += text;
	this.redraw();
}

Konsole.prototype._start_cursor_interval = function ()
{
	if (this._cursor_interval)
	{
		return;
	}
	
	$('#cursor').toggleClass('black');
	this._cursor_interval = setInterval("$('#cursor').toggleClass('black')", 700);
}

Konsole.prototype._stop_cursor_interval = function ()
{
	if (this._cursor_interval)
	{
		clearInterval(this._cursor_interval);
		$('#cursor').removeClass('black');
		this._cursor_interval = null;
	}
}

Konsole.prototype.execute = function (kommand_line)
{
	var argv = kommand_line.split(" ");

	this.print(this.prompt + gtltlise(kommand_line) + "\n");

	if (!argv[0] || argv[0] == "")
	{
		return;
	}

	var ret = this.kommands.execute(argv[0], argv);

	if (-100 == ret)
	{   // kommand not found
	    // use * kommand (this usually means ajax)
		ret = this.kommands.execute("*", argv);
	
		if (-100 == ret)
		{   // there was no * kommand
			this.print("Kommand not found: " + argv[0]);
		}
	}

	// if kommand return 1 we leave it to the kommand
	// to start the line editor (this is for ajax)
	if (1 != ret)
	{
		this.line_editor.start();
	}

	this.redraw();
}


/*
 * Misc
 */
function debug(pre, msg)
{
	// FIXME
	//$("#debug").prepend("<strong>" + pre + ":</strong> " + msg + "<br />");
}

function gtltlise(str)
{
	return str.replace(/</g, '&lt;').replace(/>/g, '&gt;');
}

function createObjectCallback(obj, fn)
{
	return function() { fn.apply(obj, arguments); };
} 
