/*
 AGI Emulator for The Web - agi.js
 Copyright (c) 2006 António Afonso (aadsm@rnl.ist.utl.pt)

 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

/*
 PLEASE READ THIS BEFORE LOOKING AT THE CODE:
 
 I must state here that this project was coded while on vacation, so it's not
 the best piece of code you'll ever see in your life, quite the opposite actually :P
 I made some shortcuts and some hacks to finish it in time for the beggining
 of the new semester, so read this code with a little grain of salt :-)
 Since i made the analysis of the real interperter on the go, there were some
 things about it that i only spotted in the middle of the project, i kind of
 prototyped it all the way till the end, and that's the main reason for some
 hacks that you'll be able to find in this file.
 Albeit, i've tryied my best to comment all stuff that i didn't like about the code,
 and also wrote some ideas to refactor it when i have the time, so, despite all
 the hacks, you still know where they are :D
*/

/*
 * IMPORTANT NOTICE ABOUT RESOLUTIONS:
 *
 * The game resolution is 160x168, this means that any object can have an x and y
 * between 0-159 and 0-167.
 * The screen resolution, used to display graphics on the web page is 640x336.
 * This means that when placing an object on the screen one must first multiply
 * the x coordinate by 4, and the y coordinate by 2.
 * (Obs: the backgrounds codified in the game data are in 160x168 resolution but 
 * the server side of the engine is streching them to 320x168).
 *
 * To know the GAME width and height of a sprite (a loop/cell of a view) one 
 * must multiply the width of the image by 0.5 and the height by 1.
 * (Obs: every width of a sprite is even).
 *
 * All coordinates are in GAME resolution, they are only converted to SCREEN
 * resolutions in the functions that place/changes the object coordinates
 * in the screen: AGIObject.draw() that uses the left_coord() and top_coord() 
 * to know the top and left coordinates in SCREEN resolution.
 * One other convertion is made, when creating the Image object, in
 * the View constructor.
 */

var browserConsole = window.console;

var PROBLEM_WITH_SERVER = 'There was a problem while retrieving data from server.';
var MISSING_CONTROL_LINES = 'The control lines were missing in the server response.';
var MSG_UNKNOWN_CMD = 'Unknown command';

// possible results of run_logic
var PRINT_WAITING = 1;
var INPUT_WAITING = 2;
var MENU_WAITING = 3;
var INVENTORY_WAITING = 4;

var DEBUG = false;
var DEBUG_HIGHLIGHT_OBJS = false;

var STOP = 0;
var NORTH = 1;
var NORTHEAST = 2;
var EAST = 3;
var SOUTHEAST = 4;
var SOUTH = 5;
var SOUTHWEST = 6;
var WEST = 7;
var NORTHWEST = 8;

var NO_BORDER = 0;
var BORDER_NORTH = 1;
var BORDER_EAST = 2;
var BORDER_SOUTH = 3;
var BORDER_WEST = 4;

var SCREEN_LEFT = 150;
var SCREEN_TOP = 125;
var SCREEN_WIDTH = 640;
var SCREEN_HEIGHT = 336;

var PIC_WIDTH = 320;
var PIC_HEIGHT = 168;

var GAME_WIDTH = 160;
var GAME_HEIGHT = 168;

var TEXT_COLS = 40;
var TEXT_ROWS = 25;
var STATUS_LINE_COLS = 58;

var KEY_RIGHT = 39;
var KEY_LEFT = 37;
var KEY_UP = 38;
var KEY_DOWN = 40;
var KEY_BACKSPACE = 8;
var KEY_ENTER = 13;
var KEY_ESC = 27;
var KEY_CONSOLE = 92;
var KEY_ALT_X = 171;
var KEY_F1 = 112;
var KEY_F2 = 113;
var KEY_F3 = 114;
var KEY_F4 = 115;
var KEY_F5 = 116;
var KEY_F6 = 117;
var KEY_F7 = 118;
var KEY_F8 = 119;
var KEY_F9 = 120;
var KEY_F10 = 121;

var MAX_OBJECTS = 100;
var MAX_STRINGS = 24;
var MAX_CONTROLLER = 100;

// AGIObject constants
var BLOCK_SURFACE = 0;
var CONDITIONAL_SURFACE = 1;
var ALARM_SURFACE = 2;
var WATER_SURFACE = 3;

var ON_ANYTHING = 1;
var ON_LAND = 2;
var ON_WATER = 3;

var BIT_OF_PICTURE = -1;
var INVENTORY_PICTURE = -1;

var DEFAULT_HORIZON = 36;

var DISABLED_ITEM = -1;
var SELECTED_ITEM = 1;

// types of object movement
var NO_MOVEMENT = 0;
var FOLLOW_EGO = 1;
var WANDER = 2;
var MOVE_OBJ = 3;
var MOVE_ONCE = 4;

document.onkeydown = handle_keydown;
document.onkeypress = handle_key;

//document.addEventListener( "keydown", handle_keydown, false);
//document.addEventListener( "keypress", handle_key, false);

//document.onkeypress = ignore_keys;

//window.onkeydown = handle_keydown;
//window.onkeypress = handle_key;

var runCommand = null;

var stack = {
	_logic: new Array(),
	_part: new Array(),
	frame: -1,
	push: function( logic, part )
	{
		this._logic.push( logic );
		this._part.push( part );
		//this.logic = logic;
		//this.part = part;
		this.frame = this.size()-1;
	}
	,
	pop: function()
	{
		this._logic.pop();
		this._part.pop();
		
		var size = this.size();
		//if ( size > 0 )
		//{
		//	this.logic = this._logic[size-1];
		//	this.part = this._part[size-1];
		//}
		this.frame = size-1;
	}
	,
	size: function()
	{
		return this._logic.length;
	}
	,
	getFrame: function()
	{
		return this.frame;
	}
	,
	setFrame: function( frame )
	{
		if ( frame >= 0 && frame < this.size() )
		{
			this.frame = frame;
		}
	}
	,
	setPart: function( part )
	{
		this._part[this.frame] = part;
	}
	,
	getPart: function()
	{
		return this._part[this.frame];
	}
	,
	getLogic: function()
	{
		return this._logic[this.frame];
	}
	,
	reset: function()
	{
		this._logic = new Array();
		this._part = new Array();
		this.frame = -1;
	}
	,
	toString: function()
	{
		return "logic: " + this._logic + "\npart: " + this._part + "\nframe: " + this.frame;
	}
}

//////////////////////////////////////
// AGI Data Structures
//////////////////////////////////////
var core = {
	game_id: '',
	agi_interval: null,
	agi_clock: 0,
	logics: null,
	// 256 variables, these variables can only contain numbers
	v: new Array(256),
	// 256 flags, they're either in a true or false state
	f: new Array(256),
	// objects
	o: new Array(MAX_OBJECTS),
	// strings
	s: new Array(MAX_STRINGS),
	// 256 messages
	m: new Array(256),
	// words
	w: new Array(),
	// controller
	c: new Array(MAX_CONTROLLER),
	keys: new Object(),
	// when a picture is loaded it's saved in this array, it contains the
	// xml that came from the server on a picture request.
	picture: new Array(256),
	view: new Array(256),
	horizon: DEFAULT_HORIZON,
	// this is an array of indexes to the objects themselfs, it must be an index
	// instead of a pointer to the object because animate.obj can be called
	// before the creation of the object itself!
	objects: new Array(),
	block: new Object(),
	// image buffer, one of the picture's data
	buffer: null,
	// used for add.to.pic, array of AGIObjects.
	more_buffer: new Array(),
	control_lines: null,
	screen: null,
	intput: null,
	// line in buffer to be parsed
	input_line: '',
	prompt: '',
	// used to check for a key press, needed by have.key()
	have_key: false,
	have_key_indicator: false,
	// code of the last key pressed
	key_pressed: 0,
	key_pressed_indicator: 0,
	special_key_press: false,
	key_event: null,
	// if there's input to be parsed (enter has been issued)
	parse_input: false,
	status_line: null,
	score: 0,
	cursor: '',
	words: null,
	msgbox: null,
	msgboxcontent: null,
	msgbox_showing: false,
	msgbox_ask_close: false,
	inventory_obj: null,
	inventory_showing: false,
	waiting_keypressed: false,
	waiting_input: false,
	new_room: true,
	restart: false,
	quit: false,
	
	player_control: true,
	accept_input: false
}
var $v = core.v;
var $f = core.f;
var $o = core.o;
var $s = core.s;
var $m = null;
var $w = core.w;
var $c = core.c;
var $screen;

var inventory = {
	on: false,
	header: "You are carrying:",
	footer: "Press any key to return to the game",
	footer_select: "Press ENTER to select, ESC to cancel",
	bgcolor: 15,
	fgcolor: 0,
	items: null,
	ix: -1,
	select_mode: false,
	enable: function()
	{
		text_screen();
		var x = Math.floor((TEXT_COLS-this.header.length)/2);
		textScreen.display( this.header, x, 0, this.fgcolor, this.bgcolor );
		
		this.items = new Array();
		for ( var j = 0, i = 1; j < core.inventory.length; j++ )
		{
			if ( core.inventory[j].room == HAS_ITEM )
			{
				var name = core.inventory[j].name;
				if ( i % 2 == 0 )
				{
					textScreen.display( name, TEXT_COLS-name.length-1, Math.floor(i/2)+1, this.fgcolor, this.bgcolor );
				}
				else
				{
					textScreen.display( name, 1, Math.floor(i/2)+2, this.fgcolor, this.bgcolor );
				}
				this.items[i-1] = new Object();
				this.items[i-1].name = name;
				this.items[i-1].number = j;
				i++;
			}
		}
		
		this.on = true;
		if ( this.select_mode = $f[13] )
		{
			var x = Math.floor((TEXT_COLS-this.footer_select.length)/2);
			textScreen.display( this.footer_select, x, TEXT_ROWS-1, this.fgcolor, this.bgcolor );
			this.select( 0 );
		}
		else
		{
			var x = Math.floor((TEXT_COLS-this.footer.length)/2);
			textScreen.display( this.footer, x, TEXT_ROWS-1, this.fgcolor, this.bgcolor );
		}
		$v[25] = 255;
	}
	,
	disable: function()
	{
		textScreen.graphicsMode();
		this.on = false;
		start();
	}
	,
	select: function( ix )
	{
		if ( this.ix > -1 )
		{
			this.toggle( this.ix, this.fgcolor, this.bgcolor );
		}
		this.toggle( ix, this.bgcolor, this.fgcolor );
		this.ix = ix;
	}
	,
	toggle: function( ix, fg, bg )
	{
		var name = this.items[ix].name;
		var y = Math.floor((ix)/2)+2;
		var x = (ix % 2) ? (TEXT_COLS-name.length-1) : 1;
		textScreen.display( name, x, y, fg, bg );
	}
	,
	handleKey: function( key )
	{
		switch ( key )
		{
			case KEY_RIGHT:
				if ( this.ix % 2 == 0 && this.ix < this.items.length-1 )
				{
					this.select( this.ix+1 );
				}
				break;
			
			case KEY_LEFT:
				if ( this.ix % 2 )
				{
					this.select( this.ix-1 );
				}
				break;
			
			case KEY_UP:
				if ( this.ix > 1 )
				{
					this.select( this.ix-2 );
				}
				break;
			
			case KEY_DOWN:
				if ( this.ix+2 < this.items.length )
				{
					this.select( this.ix+2 );
				}
				break;
				
			case KEY_ENTER:
				if ( this.select_mode )
				{
					$v[25] = this.items[this.ix].number;
				}
				this.disable();
				break;
				
			case KEY_ESC:
				this.disable();
				break;
				
			default:
				if ( !this.select_mode )
				{
					this.disable();
				}
		}
	}
}

var menu = {
	on: false,
	last_menu: '',
	submenus: new Array(),
	menus: new Array(),
	fgcolor: 0,
	bgcolor: 15,
	submenu_ix: -1,
	submenu_selected: null,
	submenu_width: 0,
	submenu_title_x: 0,
	submenu_x: 0,
	submenu_option: -1,
	addMenu: function( name )
	{
		this.submenus[name] = new Array();
		this.menus[this.menus.length] = name;
		this.last_menu = name;
	}
	,
	addMenuItem: function( name, c )
	{
		var submenu = this.submenus[this.last_menu];
		var i = submenu.length;
		
		submenu[i] = new Object();
		submenu[i].text = name;
		submenu[i].value = c;
	}
	,
	enable: function()
	{
		this.on = true;
		var menuline = this.menus.join( ' ' );
		textScreen.clearLines( 0, 0, this.bgcolor );
		textScreen.display( menuline, 1, 0, this.fgcolor, this.bgcolor );
		this.showSubMenu( 0 );
	}
	,
	disable: function()
	{
		this.on = false;
		this.hideSubMenu();
		status_line_update();
		start();
	}
	,
	showSubMenu: function( ix )
	{
		var submenu;
		var name;
		var title_x = 0;
		// find the menu the x offset
		for ( var i = 0; i <= ix; i++ )
		{
			name = this.menus[i];
			title_x += name.length+1;
		}
		title_x -= name.length+1;
		// calculate the menu width
		var submenu = this.submenus[name];
		var maxlength = 0;
		for ( var i = 0; i < submenu.length; i++ )
		{
			if ( submenu[i].text.length > maxlength )
			{
				maxlength = submenu[i].text.length;
			}
		}
		// highlight submenu name
		textScreen.display( name, title_x+1, 0, this.bgcolor, this.fgcolor );
		// display
		var x = Math.min( title_x, TEXT_COLS-maxlength-2 );
		//var horizontal_border = "+" + "&mdash;".repeat(maxlength) + "+";
		var horizontal_border = "+" + "-".repeat(maxlength) + "+";
		textScreen.display( horizontal_border, x, 1, this.fgcolor, this.bgcolor );
		var y = 0;
		for ( ; y < submenu.length; y++ )
		{
			var line;
			line = "|" + submenu[y].text + " ".repeat(maxlength-submenu[y].text.length) + "|";
			textScreen.display( line, x, y+2, this.fgcolor, this.bgcolor );
		}
		textScreen.display( horizontal_border, x, y+2, this.fgcolor, this.bgcolor );
		
		this.submenu_ix = ix;
		this.submenu_selected = submenu;
		this.submenu_title_x = title_x;
		this.submenu_x = x;
		this.submenu_width = maxlength;
		this.submenu_option = -1;
		this.selectNextMenuOption();
	}
	,
	hideSubMenu: function()
	{
		textScreen.display( this.menus[this.submenu_ix], this.submenu_title_x+1, 0, this.fgcolor, this.bgcolor );
		for ( var i = 1; i < this.submenu_selected.length; i++ )
		{
			textScreen.removeRectangle( this.submenu_x, 1, this.submenu_x+this.submenu_width+1, this.submenu_selected.length+2 );
		}
	}
	,
	selectNextMenu: function()
	{
		var ix = this.submenu_ix;
		if ( ix == this.menus.length-1 )
		{
			ix = 0;
		}
		else
		{
			ix++;
		}
		this.hideSubMenu();
		this.showSubMenu( ix );
	}
	,
	selectPreviousMenu: function()
	{
		var ix = this.submenu_ix;
		if ( ix == 0 )
		{
			ix = this.menus.length-1;
		}
		else
		{
			ix--;
		}
		this.hideSubMenu();
		this.showSubMenu( ix );
	}
	,
	selectNextMenuOption: function()
	{
		if ( this.submenu_option > -1 )
		{
			var text = this.submenu_selected[this.submenu_option].text;
			var line = text + " ".repeat(this.submenu_width-text.length);
			textScreen.display( line, this.submenu_x+1, this.submenu_option+2, this.fgcolor, this.bgcolor );
		}
		
		for ( var i = this.submenu_option+1; i <= this.submenu_selected.length; i++ )
		{
			// if there's no next option then start all over from the top
			if ( i == this.submenu_selected.length )
			{
				i = -1;
				continue;
			}
			// don't select disabled items
			if ( $c[this.submenu_selected[i].value] == DISABLED_ITEM )
			{
				continue;
			}
			this.submenu_option = i;
			break;
		}
		
		var text = this.submenu_selected[this.submenu_option].text;
		var line = text + " ".repeat(this.submenu_width-text.length);
		textScreen.display( line, this.submenu_x+1, this.submenu_option+2, this.bgcolor, this.fgcolor );
	}
	,
	selectPreviousMenuOption: function()
	{
		if ( this.submenu_option > -1 )
		{
			var text = this.submenu_selected[this.submenu_option].text;
			var line = text + " ".repeat(this.submenu_width-text.length);
			textScreen.display( line, this.submenu_x+1, this.submenu_option+2, this.fgcolor, this.bgcolor );
		}
		
		for ( var i = this.submenu_option-1; i >= -1; i-- )
		{
			// if there's no previous option then start all over from the bottom
			if ( i == -1 )
			{
				i = this.submenu_selected.length;
				continue;
			}
			// don't select disabled items
			if ( $c[this.submenu_selected[i].value] == DISABLED_ITEM )
			{
				continue;
			}
			this.submenu_option = i;
			break;
		}
		
		var text = this.submenu_selected[this.submenu_option].text;
		var line = text + " ".repeat(this.submenu_width-text.length);
		textScreen.display( line, this.submenu_x+1, this.submenu_option+2, this.bgcolor, this.fgcolor );
	}
	,
	handleKey: function( key )
	{
		switch ( key )
		{
			case KEY_RIGHT:
				this.selectNextMenu();
				break;
			
			case KEY_LEFT:
				this.selectPreviousMenu();
				break;
			
			case KEY_UP:
				this.selectPreviousMenuOption();
				break;
			
			case KEY_DOWN:
				this.selectNextMenuOption();
				break;
				
			case KEY_ENTER:
				$c[this.submenu_selected[this.submenu_option].value] = true;
				this.disable();
				break;
				
			case KEY_ESC:
				this.disable();
				break;
		}
	}
}

var textScreen = {
	chars: new Array( TEXT_ROWS ),
	fgcolor: 15,
	bgcolor: 0,
	input_y: 0,
	status_y: 0,
	status_on: false,
	TOP: 109,
	LEFT: SCREEN_LEFT,
	text_background: null,
	//STYLE: 'color: white; background-color: black',
	init: function()
	{
		for ( var i = 0; i < TEXT_ROWS; i++ )
		{
			this.chars[i] = new Array( TEXT_COLS );
			for ( var j = 0; j < TEXT_COLS; j++ )
			{
				this.chars[i][j] = null;
			}
		}
		this.text_background = document.getElementById( 'text' );
	}
	,
	textMode: function()
	{
		this.removeRectangle( 0, 0, TEXT_COLS-1, TEXT_ROWS-1 );
		core.input.style.visibility = 'hidden';
		this.text_background.style.backgroundColor = pallet[15];
		this.text_background.style.visibility = 'visible';
	}
	,
	graphicsMode: function()
	{
		this.removeRectangle( 0, 0, TEXT_COLS-1, TEXT_ROWS-1 );
		
		if ( core.accept_input )
		{
			core.input.style.visibility = 'visible';
		}
		this.text_background.style.visibility = 'hidden';
		status_line_update();
	}
	,
	draw: function( x1, y1, x2, y2 )
	{
		var span;
		
		for ( var y = y1; y <= y2; y++ )
		{
			for ( var x = x1; x <= x2; x++ )
			{
				if ( this.chars[y][x].span )
				{
					span = this.chars[y][x].span;
				}
				else
				{
					span = document.createElement( 'span' );
					span.className = 'c';
					span.style.left = this.LEFT + x*16 + 'px';
					span.style.top = this.TOP + y*16 + 'px';
					this.chars[y][x].span = span;
					$screen.appendChild( span );
				}
				span.style.color = this.chars[y][x].fg;
				span.style.backgroundColor = this.chars[y][x].bg;
				//span.attributes.getNamedItem( 'style' ).value += this.styles[y][x];
				span.innerHTML = this.chars[y][x].c;
			}
		}
	}
	,
	display: function( msg, x, y, fg, bg )
	{
		if ( typeof(fg) == 'undefined' ) { fg = this.fgcolor; }
		if ( typeof(bg) == 'undefined' ) { bg = this.bgcolor; }
		
		var j = 0;
		for ( var i = 0; i < msg.length; i++, j++ )
		{
			var c = msg.charAt(i);
			
			if ( c == '&' )
			{
				var ix = msg.substring( i ).indexOf( ';' );
				if ( ix > -1 )
				{
					c = msg.substring( i, ix+i );
					i += ix;
				}
			}
			
			if ( !this.chars[y][x+j] )
			{
				this.chars[y][x+j] = new Object();
			}
			this.chars[y][x+j].c = c;
			this.chars[y][x+j].fg = pallet[fg];
			this.chars[y][x+j].bg = pallet[bg];
		}
		this.draw( x, y, x+j-1, y );
	}
	,
	clear: function()
	{
		for ( var i = 0; i < TEXT_ROWS; i++ )
		{
			for ( var j = 0; j < TEXT_COLS; j++ )
			{
				if ( this.chars[i][j] && this.chars[i][j].span )
				{
					$screen.removeChild( this.chars[i][j].span );
				}
				this.chars[i][j] = null;
			}
		}
	}
	,
	clearLines: function( y1, y2, c )
	{
		this.clearRectangle( 0, y1, TEXT_COLS-1, y2, c );
		// HACK: yap, it's a hack, because I use a diferent scheme for user input.
		// clear.lines may be called to clear up the input line, since I don't use
		// the standard text matrix for user input, I try to detect those clear.lines
		// and erase the input line.
		if ( this.input_y >= y1 && this.input_y <= y2 )
		{
			core.input.innerHTML = '';
		}
	}
	,
	clearScreen: function( c )
	{
		if ( typeof(c) == 'undefined' ) { c = this.fgcolor; }
		textScreen.clearLines( 0, TEXT_ROWS-1, c );
	}
	,
	clearRectangle: function( x1, y1, x2, y2, c )
	{
		for ( var y = y1; y <= y2; y++ )
		{
			for ( var x = x1; x <= x2; x++ )
			{
				if ( !this.chars[y][x] )
				{
					this.chars[y][x] = new Object();
				}
				this.chars[y][x].c = '';
				this.chars[y][x].fg = pallet[this.fgcolor];
				this.chars[y][x].bg = pallet[c];
			}
		}
		this.draw( x1, y1, x2, y2 );
	}
	,
	removeRectangle: function( x1, y1, x2, y2 )
	{
		for ( var y = y1; y <= y2; y++ )
		{
			for ( var x = x1; x <= x2; x++ )
			{
				if ( !this.chars[y][x] )
				{
					continue;
				}
				if ( this.chars[y][x].span )
				{
					$screen.removeChild( this.chars[y][x].span );
				}
				this.chars[y][x] = null;
			}
		}
	}
	,
	setInput: function( y )
	{
		core.input.style.top = this.TOP + y*16 + 'px';
		core.input.innerHTML = $s[0] + core.cursor;
		this.input_y = y;
	}
	,
	setStatus: function( y )
	{
		this.status_y = y;
	}
	,
	getNum: function( msg, variable )
	{
		core.input.innerHTML = msg;
		// core.input.innerHTML may be diferent, less spaces for example, it happens on IE
		core.prompt = core.input.innerHTML;
		core.input.innerHTML += core.cursor;

		core.input.style.visibility = 'visible';
		core.waiting_input = true;
		this.get_num = true;
		this.get_num_var = variable;
	}
	,
	getString: function( str, msg, x, y, l )
	{
		core.input.innerHTML = msg;
		// core.input.innerHTML may be diferent, less spaces for example, it happens on IE
		core.prompt = core.input.innerHTML;
		core.input.innerHTML += core.cursor;

		core.input.style.visibility = 'visible';
		core.waiting_input = true;
		this.get_string = true;
		this.get_string_str = str;
		this.get_string_max = l;
		this.get_string_input_top = core.input.style.top;
		this.get_string_input_left = core.input.style.left;
		
		core.input.style.top = this.TOP + y*16 + 'px';
		core.input.style.left = this.LEFT + x*16 + 'px';
	}
	,
	clearInput: function()
	{
		core.input.innerHTML = '';
	}
}

function View( xml ) {
	var number = 0;
	var loops = xml.getElementsByTagName( 'loop' );
	this.images = new Array( loops.length );
	this.loops = loops.length;
	this.cells = new Array( loops.length );
	// total number of images
	this.number_images = xml.getElementsByTagName( 'cell' ).length;
	
	///////////////////////////////////////
	var loop;
	var cells;
	var img;
	var width, height;
	
	var view = xml.getElementsByTagName( 'view' );
	this.number = view.item(0).attributes.getNamedItem( 'number' ).nodeValue;
	
	var desc = xml.getElementsByTagName( 'description' );
	if ( desc.item(0).childNodes.length > 0 )
	{
		this.description = desc.item(0).childNodes[0].data;
	}
	
	for ( var i = 0; i < loops.length; i++ )
	{
		loop = loops.item(i).attributes.getNamedItem( 'number' ).nodeValue;
		cells = loops.item(i).getElementsByTagName( 'cell' );
		
		this.cells[loop] = cells.length;
		this.images[loop] = new Array( cells.length );
		
		for ( var j = 0; j < cells.length; j++ )
		{
			cell = cells.item(j).attributes.getNamedItem( 'number' ).nodeValue;
			width = cells.item(j).attributes.getNamedItem( 'width' ).nodeValue;
			height = cells.item(j).attributes.getNamedItem( 'height' ).nodeValue;
			
			img = new Image();
			img.src = cells.item(j).firstChild.data;
			// Safari doesn't have this property
			if( !img.style ) { img.style = new Object(); }
			img._width = width;
			img._height = height;
			img.style.height =  img._height*2 + 'px';
			img.style.width = img._width*2 + 'px';
			this.images[loop][cell] = img;
		}
	}	
	/*
	if ( this.number == 89 )
		alert( "View: width: " + this.images[1][0]._width + "; height: " + this.images[1][0]._height );
	*/
}
View.prototype = {
	toString: function()
	{
		return "view: " + this.number + "\nloops: " + this.loops;
	}
}

function AGIObject( number ) {
	this.number = number;
	this.x = 0;
	this.y = 0;
	this.view = null;
	this.loop = 0;
	this.cell = 0;
	this.priority = 15;
	this.cycle_time = 1;
	this.cycle_time_i = 0;
	this.step_time = 1;
	this.step_time_i = 0;
	this.step_size = 1;
	this.direction = STOP;
	this.surface = ON_ANYTHING;
	// flags
	this.observe_blocks = true;
	this.automatic_priority = true;
	this.observe_horizon = true;
	this.cycling = true;
	this.on_water = false;
	this.observe_objs = true;
	this.automatic_loop = true;
	// needed for the end.of.loop function
	this.end_of_loop = false;
	this.end_of_loop_flag = -1;
	// needed for the reverse.loop function
	this.reverse_loop = false;
	this.reverse_loop_loop = -1;
	// nedded for the follow.ego function
	this.follow_ego_pixels = 0;
	this.follow_ego_flag = -1;
	this.follow_ego_moved = false;
	// nedded for the move.obj function
	this.move_obj_pixels = 0;
	this.move_obj_flag = -1;
	this.move_obj_moved = false;
	// type of movement
	this.movement = NO_MOVEMENT;
	this.img = document.createElement( 'img' );
	// asks to use budin_sonneveld_effect
	this.call_budin_sonneveld_effect = false;
	// indicates if the this.img is on $screen
	this.on_screen = false;
	// array that maps the direction of the object with the loop used to display
	// him.
	this.direction2loop_2 = [-1,-1,0,0,0,-1,1,1,1];
	this.direction2loop_4 = [-1,3,0,0,0,2,1,1,1];
	
	///////////////////////////////////////
	//this.img.style.border = 'solid 2px red';
	
	// must do this, otherwise the image will inherit the #screen img definitions
	// making the image the same size of the screen, these properties will be
	// correctly setted when the image is completly loaded.
	this.img.style.width = '0px';
	this.img.style.height = '0px';	
}
AGIObject.prototype = {
	setView: function( num )
	{
		try {
		this.view = core.view[num];
		var loops = this.view.loops;
		} catch ( e ) { alert( "set.view: " + num + "\n" + stack ); }
		if ( this.loop >= loops ) { this.loop = 0; }
		
		var cells = this.view.cells[this.loop];
		if ( this.cell >= cells ) { this.cell = 0; }
		
		this.updateImage();
	}
	,
	left: function()
	{
		return left_coord( this );
	}
	,
	top: function()
	{
		return top_coord( this );
	}
	,
	width: function()
	{
		return this.img._width;
	}
	,
	height: function()
	{
		return this.img._height;
	}
	,
	positionRightEdge: function()
	{
		this.x = GAME_WIDTH-this.width();
	}
	,
	positionBottomEdge: function()
	{
		this.y = GAME_HEIGHT-1;
	}
	,
	facing: function( dir )
	{
		switch ( dir )
		{
			case WEST: return this.direction == NORTHWEST || this.direction == WEST || this.direction == SOUTHWEST;
			case EAST: return this.direction == NORTHEAST || this.direction == EAST || this.direction == SOUTHEAST;
			case NORTH: return this.direction == NORTHWEST || this.direction == NORTH || this.direction == NORTHEAST;
			case SOUTH: return this.direction == SOUTHWEST || this.direction == SOUTH || this.direction == SOUTHEAST;
		}
		
		return false;
	}
	,
	endOfLoop: function( flag )
	{
		this.end_of_loop = true;
		this.end_of_loop_flag = flag;
		$f[flag] = false;
		this.cycling = true;
		
		if ( !core.objects.exists( this.number ) )
		{
			core.objects[core.objects.length] = this.number;
		}
	}
	,
	reverseLoop: function( flag )
	{
		this.reverse_loop = true;
		this.reverse_loop_flag = flag;
		$f[flag] = false;
		this.cycling = true;
		
		if ( !core.objects.exists( this.number ) )
		{
			core.objects[core.objects.length] = this.number;
		}
	}
	,
	followEgo: function( pixels, flag )
	{
		this.movement = FOLLOW_EGO;
		this.follow_ego_flag = flag;
		$f[flag] = false;
		this.follow_ego_moved = false;
		this.follow_ego_pixels = pixels > this.step_size ? pixels : this.step_size;
		this.automatic_priority = true;
	}
	,
	wander: function()
	{
		this.movement = WANDER;
		this.automatic_priority = true;
		
		if ( this.number == 0 )
		{
			core.player_control = false;
		}
	}
	,
	moveObj: function( x, y, pixels, flag )
	{
		this.movement = MOVE_OBJ;
		this.move_obj_x = x;
		this.move_obj_y = y;
		// save step_size to restore later
		this.move_obj_step_size = this.step_size;
		if ( pixels != 0 ) { this.step_size = pixels; }
		this.move_obj_flag = flag;
		$f[flag] = false;
		this.move_obj_moved = false;
		
		if ( this.number == 0 )
		{
			core.player_control = false;
		}		
	}
	,
	normalMotion: function()
	{
		this.movement = NO_MOVEMENT;
	}
	,
	repositionTo: function( x, y )
	{
		this.movement = MOVE_ONCE;
		this.x = x;
		this.y = y;
		//this.budin_sonneveld_effect();
		this.call_budin_sonneveld_effect = true;
	}
	,
	positionTo: function( x, y )
	{
		this.x = x;
		this.y = y;
		//this.budin_sonneveld_effect();
		this.call_budin_sonneveld_effect = true;
	}
	,
	moveOnce: function()
	{
		if ( this.movement == NO_MOVEMENT && this.on_screen )
		{
			this.movement = MOVE_ONCE;
		}
	}
	,
	setCell: function( cell )
	{
		this.cell = cell;
		this.updateImage();
		this.moveOnce();
	}
	,
	setLoop: function( loop )
	{
		this.loop = loop;
		this.cell = 0;
		this.updateImage();
		this.moveOnce();
	}
	,
	// code ripped from SARIEN! it just moves the sprite in a spiral way till
	// it can be repositioned again, looked like the best way to do this function.
	budin_sonneveld_effect: function()
	{
		var dir = 0;
	    var count = 1, tries = 1;
		var x = this.x;
		var y = this.y;
		
		while (!this.canMove(x, y))
		{
			switch (dir)
			{
				case 0:			/* west */
					x--;
					if (--count) continue;
					dir = 1;
					break;
				case 1:			/* south */
					y++;
					if (--count) continue;
					dir = 2;
					tries++;
					break;
				case 2:			/* east */
					x++;
					if (--count) continue;
					dir = 3;
					break;
				case 3:			/* north */
					y--;
					if (--count) continue;
					dir = 0;
					tries++;
					break;
			}
			count = tries;
		}
		
		this.x = x;
		this.y = y;
		if ( this.automatic_priority ) { this.calculatePriority(); }
	}
	,
	draw: function()
	{
		// the budin_sonneveld_effect should be applied to every object, but for
		// performance issues, it is instead only called when explicited requested.
		// it's called here because it's really the best place for it.
		if ( this.call_budin_sonneveld_effect )
		{
			this.budin_sonneveld_effect();
			this.call_budin_sonneveld_effect = false;
		}
		
		this.updateImage();
		
		this.img.style.left = this.left() + 'px';
		this.img.style.top = this.top() + 'px';
		
		if ( this.automatic_priority )
		{
			this.img.style.zIndex = this.priority*100 + (this.number == BIT_OF_PICTURE ? 0 : this.y%12);
		}
		else
		{
			this.img.style.zIndex = this.priority*100 + (this.number == BIT_OF_PICTURE ? 0 : 1 );
		}
		
		if ( DEBUG_HIGHLIGHT_OBJS && this.number >= 0 )
		{
			this.img.style.border = '1px solid red';
		}
	}
	,
	updateImage: function()
	{
		try {
			var img = this.view.images[this.loop][this.cell];
			this.img.src = img.src;
		} catch ( e ) {
			alert( 'o' + this.number );
			alert( this );
			alert( this.view.number );
			alert( this.view.images );
			alert( this.loop + ':' + this.cell );
			alert( this.view.images[this.loop] );
		}
		
		//var img = this.view.images[this.loop][this.cell];
		//this.img.src = img.src;
		
		// TODO: can't seem to set these variables, they always stay at 0 :-(
		//this.img.height = img.height*2;
		//this.img.width = img.width*2;
		this.img._height = img._height;
		this.img._width = img._width/2;
		this.img.style.height = img.style.height;
		this.img.style.width = img.style.width;
	}
	,
	show: function()
	{
		if ( !this.on_screen )
		{
			if ( this.automatic_priority ) { this.calculatePriority(); }
			$screen.appendChild( this.img );
			this.on_screen = true;
		}
	}
	,
	erase: function()
	{
		if ( this.on_screen )
		{
			$screen.removeChild( this.img );
			this.on_screen = false;
		}
	}
	,
	recycle: function()
	{
		//this.loop = 0;
		//this.cell = 0;
		this.priority = 15;
		this.cycle_time = 1;
		this.cycle_time_i = 0;
		this.step_time = 1;
		this.step_time_i = 0;
		this.step_size = 1;
		this.movement = NO_MOVEMENT;
		this.cycling = true;
		this.reverse_loop = false;
		this.end_of_loop = false;
		this.observe_blocks = true;
		/*
		// HACK: sarien doesn't change direction in any object because they have
		// a much more clever scheme than mine to detect changes...
		// This is needed so that the EGO continues to walk when changing rooms.
		if ( this.number != 0 )
		{
			this.direction = STOP;
		}
		*/
		// I give up! when moving from room 11 to 12 if EGO doesn't stop then
		// it goes between both rooms ad eternium, I prefer making EGO stop
		// every time he changes rooms, it looks like harmless..
		// I don't know how sarien handles this kind of stuff, at least in this
		// particular case.
		this.direction = STOP;
		this.on_screen = false;
		this.automatic_priority = true;
		this.observe_objs = true;
		this.automatic_loop = true;
	}
	,
	cycle: function()
	{
		var cells = this.view.cells[this.loop];
		// reverse.cycle
		if ( this.reverse_loop )
		{
			this.cell--;
			if ( this.cell <= 0 )
			{
				this.cell = 0;
				$f[this.reverse_loop_flag] = true;
				this.reverse_loop = false;
				this.cycling = false;
			}
		}
		else
		{
			this.cell = (this.cell+1) % cells;
		}
		// end.of.loop
		if ( this.end_of_loop && this.cell == cells-1 )
		{
			$f[this.end_of_loop_flag] = true;
			this.end_of_loop = false;
			this.cycling = false;
		}
	}
	,
	fix_loop: function()
	{
		var old_loop = this.loop;
		
		if ( this.view.loops <= 1 )
		{
			return;
		}
		
		if ( this.view.loops < 4 )
		{
			this.loop = this.direction2loop_2[this.direction];
		}
		else
		{
			this.loop = this.direction2loop_4[this.direction];
		}
		
		// -1 means that the current loop number is retained.
		if ( this.loop == -1 )
		{
			this.loop = old_loop;
		}
		// if the loop has changed then set the cell to 0
		if ( old_loop != this.loop )
		{
			this.cell = 0;
		}
	}
	,
	/*
	 * Moves in the direction of the object
	 */
	move: function()
	{
		// check for the different kinds of movement
		switch ( this.movement )
		{
			case FOLLOW_EGO:
				// check to see if object's stucked
				if ( this.direction == STOP && this.follow_ego_moved )
				{
					this.direction = Math.floor( Math.random()*8 ) + 1;
				}
				else
				{
					this.direction = get_direction( this, $o[0].x, $o[0].y, this.follow_ego_pixels );
				}
				
				if ( this.direction == STOP )
				{
					this.movement = NO_MOVEMENT;
					$f[this.follow_ego_flag] = true;
				}
				
				this.follow_ego_moved = true;
				break;

			case WANDER:
				if ( this.direction == STOP )
				{
					this.direction = Math.floor( Math.random()*8 ) + 1;
				}			
				break;
			
			case MOVE_OBJ:
				// check to see if object's stucked
				if ( this.direction != STOP || !this.move_obj_moved )
				{
					this.direction = get_direction( this, this.move_obj_x, this.move_obj_y, this.step_size );
				}
				
				if ( this.direction == STOP )
				{
					this.movement = NO_MOVEMENT;
					$f[this.move_obj_flag] = true;
					this.step_size = this.move_obj_step_size;
				}
				
				this.move_obj_moved = true;
				break;
				
			case MOVE_ONCE:
				this.movement = NO_MOVEMENT;
				break;
		}
		
		if ( this.automatic_loop )
		{
			this.fix_loop();
		}
		
		var x = this.x;
		var y = this.y;
		var s = this.step_size;
		switch ( this.direction )
		{
			case NORTH: y-=s; break;
			case NORTHEAST: y-=s; x+=s; break;
			case EAST: x+=s; break;
			case SOUTHEAST: y+=s; x+=s; break;
			case SOUTH: y+=s; break;
			case SOUTHWEST: y+=s; x-=s; break;
			case WEST: x-=s; break;
			case NORTHWEST: y-=s; x-=s; break;
		}
		
		if ( this.canMove( x, y ) )
		{
			this.x = x;
			this.y = y;
			if ( this.automatic_priority ) { this.calculatePriority(); }
			this.checkBorders();
		}
		else
		{
			this.checkBorders();
			this.direction = STOP;
		}		
	}
	,
	checkBorders: function()
	{
		var width = this.width();
		var height = this.height();
		
		var border_code = NO_BORDER;
		
		if ( this.x <= 0 && this.facing( WEST ) )
		{
			border_code = BORDER_WEST;
		}
		if ( this.x >= GAME_WIDTH-width && this.facing( EAST ) )
		{
			border_code = BORDER_EAST;
		}
		if ( (this.y-height <= 0 
			|| (this.observe_horizon && this.y <= core.horizon )) && this.facing( NORTH ) )
		{
			border_code = BORDER_NORTH;
		}
		if ( this.y >= GAME_HEIGHT-1 && this.facing( SOUTH ) )
		{
			border_code = BORDER_SOUTH;
		}
		
		if ( this.number == 0 )
		{
			$v[2] = border_code;
		}
		else if ( border_code != NO_BORDER )
		{
			$v[4] = this.number;
			$v[5] = border_code;
		}
	}
	,
	calculatePriority: function()
	{
		// this.priority = Math.floor(this.y/12) + 1
		if ( this.y == 168 )
		{
			this.priority = 15;
		} else if ( this.y >= 156 && this.y <= 167 ) {
			this.priority = 14;
		} else if ( this.y >= 144 && this.y <= 155 ) {
			this.priority = 13;
		} else if ( this.y >= 132 && this.y <= 143 ) {
			this.priority = 12;
		} else if ( this.y >= 120 && this.y <= 131 ) {
			this.priority = 11;
		} else if ( this.y >= 108 && this.y <= 119 ) {
			this.priority = 10;
		} else if ( this.y >= 96 && this.y <= 107 ) {
			this.priority = 9;
		} else if ( this.y >= 84 && this.y <= 95 ) {
			this.priority = 8;
		} else if ( this.y >= 72 && this.y <= 83 ) {
			this.priority = 7;
		} else if ( this.y >= 60 && this.y <= 71 ) {
			this.priority = 6;
		} else if ( this.y >= 48 && this.y <= 59 ) {
			this.priority = 5;
		}
		
		//this.img.style.zIndex = (this.priority*10)+1;
		// WHY: should this be here?... isn't superflous?..
		this.img.style.zIndex = this.priority*100 + (this.number == BIT_OF_PICTURE ? 0 : this.y%12);
	}
	,
	canMove: function( x, y )
	{
		var width = this.width();
		var height = this.height();
		
		// check image bounds && horizon
		if ( x < 0 || x > GAME_WIDTH-width || y-height+1 < 0 || y > GAME_HEIGHT-1
			|| (this.observe_horizon && y < core.horizon) )
		{
			return false;
		}
		try {
		var line = core.control_lines[y].substring( x, x + width );
		} catch( e ) { alert( "bug: x: " + x + "; y: " + y + "\ncontrol:" + core.control_lines ); }
		var water = 0;
		var alarm = false;
		var surf;
		// if the bottom line of the object sprite clashes with a blue/black
		// point then the movement cannot be performed
		// http://www.agigames.com/agiwiki/index.php/Animated_object#Control_lines_on_the_priority_screen
		for ( var i = 0; i < width; i++ )
		{
			// uncondictional(black) control lines are always checked
			surf = line.charAt(i);
			if ( surf == BLOCK_SURFACE )
			{
				return false;
			}
			// conditional(blue) control lines should only be checked when the observe_blocks
			// flag is setted
			if ( this.observe_blocks && surf == CONDITIONAL_SURFACE )
			{
				return false;
			}
			if ( surf == ALARM_SURFACE )
			{
				alarm = true;
			}
			// count number of pixels of the baseline object in water surface
			if ( surf == WATER_SURFACE )
			{
				water++;
			}
		}
		
		// check for other objects
		if ( this.observe_objs )
		{
			for ( var i = 0; i < core.objects.length; i++ )
			{
				var o2 = $o[core.objects[i]];
				if ( !o2.observe_objs || !o2.on_screen || this.number == o2.number )
				{
					continue;
				}
				
				if ( !(y != o2.y || x+this.width() < o2.x || x > o2.x+o2.width()) )
				{
					return false;
				}
			}
		}
		
		// check blocks
		if ( core.block.active  && this.observe_blocks
			&& !(x+this.width() < core.block.x1 || x > core.block.x2
				|| y < core.block.y1 || y+this.height() > core.block.y2) )
		{
			return false;
		}
		// check to see if the object can only be on water
		if ( this.surface == ON_WATER && water != width )
		{
			return false;
		}
		// check to see if the object can only be on land
		if ( this.surface == ON_LAND && water > 0 )
		{
			return false;
		}
		
		if ( this.number == 0 )
		{
			// f0 - Ego base line is completely on pixels with priority = 3 (water surface).
			if ( water == width )
			{
				$f[0] = true;
			}
			else
			{
				$f[0] = false;
			}
			
			// f3 - Ego base line has touched a pixel with priority 2 (signal).
			$f[3] = alarm;
		}
		
		return true;
	}
	,
	toString: function()
	{
		return '[x: ' + this.x + ', y: ' + this.y + ', loop: ' + this.loop
				+ ', cell: ' + this.cell + ', pri: ' +  this.priority 
				+ ', dir: ' + this.direction + ', cyc: ' + this.cycling 
				+ ', view: ' + this.view.number + ', mov: ' + this.movement
				+ ', objs: ' + this.observe_objs
				+ ', z: ' + this.img.style.zIndex + ']';
	}
}

//////////////////////////////////////
// Miscelanious Stuff
//////////////////////////////////////
var _infobox;

function load_words()
{
	var request = http_request();
	var url = 'agi.php?words';
	
	request.open( 'GET', url, false );
	request.send( null );
	
	var xml = request.responseXML;
	
	var groups = xml.getElementsByTagName( 'group' );
	var total = 0;
	var parser = new Object();
	
	for ( var i = 0; i < groups.length; i++ )
	{
		var group_id = groups.item(i).attributes.getNamedItem( 'number' ).nodeValue;
		var words = groups.item(i).getElementsByTagName( 'word' );
		
		for ( var j = 0; j < words.length; j++ )
		{
			if ( words.item(j).firstChild )
			{
				parser[words.item(j).firstChild.data] = group_id;
			}
		}
	}
	
	core.words = parser;
}

function load_inventory()
{
	var request = http_request();
	var url = 'agi.php?inventory';
	
	request.open( 'GET', url, false );
	request.send( null );
	
	var xml = request.responseXML;
	
	var items = xml.getElementsByTagName( 'item' );
	var inventory = new Array();
	
	for ( var i = 0; i < items.length; i++ )
	{
		var id = items.item(i).attributes.getNamedItem( 'id' ).nodeValue;
		var room = items.item(i).attributes.getNamedItem( 'room' ).nodeValue;
		var name;
		// IE puts CDATA on the 0 index, all other browsers on index 1
		if ( items.item(i).childNodes.length > 1 )
		{
			name = items.item(i).childNodes[1].data;
		}
		else
		{
			name = items.item(i).childNodes[0].data;
		}
		
		inventory[id] = new Object();
		inventory[id].name = name;
		inventory[id].room = room;
	}
	
	core.inventory = inventory;
}

function init_agi()
{
	for ( var i = 0; i < 256; i++ )
	{
		core.v[i] = 0;
		core.f[i] = false;
		core.picture[i] = null;
	}
	for ( var i = 0; i < MAX_OBJECTS; i++ )
	{	
		core.o[i] = new AGIObject( i );
	}
	for ( var i = 0; i < MAX_CONTROLLER; i++ )
	{
		core.c[i] = false;
	}
	
	core.screen = document.getElementById( 'screen' );
	core.input = document.getElementById( 'input' );
	core.msgbox = document.getElementById( 'msgbox' );
	core.msgboxcontent = document.getElementById( 'msgboxcontent' );
	core.status_line = document.getElementById( 'status' );
	// TODO: this should be only defined in the css file
	core.screen.left = SCREEN_LEFT;
	core.screen.top = SCREEN_TOP;
	core.input.style.height = '19px';
	
	$screen = core.screen;
	
	load_words();
	load_inventory();
	init_console();
	textScreen.init();
	_infobox = document.getElementById( 'info' );
}

/*
 * Entry point function that puts the interperter running
 */
function run_agi()
{
	$f[5] = true;
	// restart_game command has been executed.
	$f[6] = false;
	// sound is turned off
	$f[9] = false;
	// Time delay between interpreter cycles in 1/20 second intervals.
	// the sierra interperter machine defaults to 2, /*i'm putting 3 to avoid
	// cpu high usage... (at least in my 3,5 year old powerbook..)*/
	$v[10] = 2;
	// Determines the output mode of print and print_at commands:
	// 1: message window is left on the screen
	// 0: message window is closed when ENTER or ESC key are pressed. 
	//    If v21 is not 0, the window is closed automatically after 1/2 * v21 seconds
	$f[15] = false;
	$v[21] = 0;
	
	core.logics = new Array();
	core.new_room = true;
	
	load_logics( 0 );
	$m = core.logics[0].m;
	/*
	core.logics[0] = new Array();
	core.logics[0].start = 0;
	core.logics[0][0] = logic0_0;
	*/
	stack.push( 0, 0 );
	start();
}

function restart()
{
	load_inventory();
	
	core.block.active = false;
	core.block.x1 = core.block.y1 = core.block.x2 = core.block.y2 = 0;
	for ( var i = 0; i < 256; i++ )
	{
		core.v[i] = 0;
		core.f[i] = false;
	}
	stack.reset();
	stack.push( 0, 0 );
	new_room( 0 );
}

function start()
{
	// 50ms = 1/20s
	core.agi_interval = setInterval( "agi_cycle()", $v[10] * 50 );
}

function stop()
{
	clearInterval( core.agi_interval );
}

/*
 * Main interperter cycle, constantly calls the logic 0 code
 */
function agi_cycle()
{
	var state;
	var cycle_delay;

	// TODO: lame place to be... quick hack
	if ( core.restart )
	{
		core.restart = false;
		restart();
		$f[6] = true;
	}
	if ( core.quit )
	{
		core.quit = false;
		restart();
	}
		
	if ( stack.getPart() == 0 && stack.getLogic() == 0 )
	{
		cycle_delay = $v[10];
		// The player has issued a command line.
		$f[2] = false;
		// 'said' command has accepted the user input.
		$f[4] = false;
		if ( core.parse_input ) { parse_input( core.input_line ); }
		//if ( core.have_key_indicator ) { core.have_key = true; core.have_key_indicator = false; }
		if ( core.key_press_indicator )
		{
			check_controller( core.key_press_indicator, core.special_key_press );
			core.have_key = true;
			core.key_press_indicator = 0;
		}
	}
	
	if ( core.msgbox_ask_close )
	{
		close_msgbox();
	}
	
	// Go to the previous stack frame if needed
	if ( stack.size() > 1 )
	{
		stack.setFrame( stack.size()-1 );
		while ( stack.size() > 1 )
		{
			// load the next function
			state = run_stack();
			if ( state == 0 )
			{
				stack.pop();
			}
			else
			{
				break;
			}
		}
	}
	else
	{
		// do business as usual
		state = run_stack();
	}
	
	// for debug console
	if ( runCommand )
	{
		eval( runCommand );
		runCommand = null;
	}
	
	if ( stack.getPart() == 0 && stack.getLogic() == 0 )
	{
		// The code of border touched by the object in v4.
		$v[5] = 0;
		$v[4] = 0;
		
		$f[6] = false;
		
		// If we have not entered in a new room then reset the new_room flag (f5)
		// otherwise reset the core.new_room indicator to make the new_room happen
		// just once.
		if ( core.new_room )
		{
			core.new_room = false;
		}
		else
		{
			$f[5] = false;
		}
		
		// 'restore_game' command has been executed.
		$f[12] = false;
		// check change of score
		if ( $v[3] != core.score )
		{
			core.score = $v[3];
			status_line_update();
		}
		
		update_clock();
		update_objects();
		
		core.have_key = false;
		core.key_pressed = 0;
		
		// cycle delay was changed!
		if ( $v[10] != cycle_delay )
		{
			stop();
			start();
		}
	}
}

function run_stack()
{
	var logic = core.logics[stack.getLogic()];
	var part = stack.getPart();
	//if ( $v[0] == 11 && stack.getLogic() == 0 && DEBUG )
	var logic_n = stack.getLogic();
	//var old_part = part;
	try {
		while ( (part = logic[part]()) != 0 )
		{
			stack.setPart( part );
			if ( core.waiting_keypressed )
			{
				stop();
				stack.setFrame(stack.getFrame()-1);
				return PRINT_WAITING;
			}
			else if ( core.waiting_input )
			{
				stop();
				stack.setFrame(stack.getFrame()-1);
				return INPUT_WAITING;
			}
			else if ( menu.on )
			{
				stop();
				stack.setFrame(stack.getFrame()-1);
				return MENU_WAITING;
			}
			else if ( inventory.on )
			{
				stop();
				stack.setFrame(stack.getFrame()-1);
				return INVENTORY_WAITING;
			}
			
			/*
			if ( part != old_part )
			{
				old_part = part;
			}
			*/
		}
	
	} catch ( e ) {
		alert( "[AGI: Error running function]\n\nlogic:" + logic_n + "\npart:" + part + "\n\nLine:" + e.lineNumber + "\nMessage:" + e.message + "\n\n" + stack );
	}
	stack.setPart( part );
	return 0;
}

/*
 * Update all controlled objects on the screen.
 */
function update_objects()
{
	for ( var i = 0; i < core.objects.length; i++ )
	{
		//var v = core.objects[i];
		var obj = $o[core.objects[i]];
		
		if ( !obj.on_screen ) { continue; }
		// using a variable to determine if an object has to be updated greatly
		// improves performance because we don't have to redraw stopped objects.
		var draw = false;
		
		if ( obj.cycling )
		{
			obj.cycle_time_i++;
			if ( obj.cycle_time_i % obj.cycle_time == 0 )
			{
				obj.cycle();
				obj.cycle_time_i = 0;
				draw = true;
			}
		}
		// move object
		if ( obj.movement != NO_MOVEMENT || obj.direction != STOP )
		{
			obj.step_time_i++;
			if ( obj.step_time_i % obj.step_time == 0 )
			{
				// calculate direction and new coordinates.
				// WARNING: in the documentation the direction is calculated 
				// before calling logic0...
				obj.move();
				obj.step_time_i = 0;
				draw = true;
			}
		}
		if ( draw ) { obj.draw(); }
	}
	// update v6 variable to contain the EGO direction
	if ( $o[0] )
	{
		$v[6] = $o[0].direction;
	}
}

function update_clock()
{
	core.agi_clock++;
	// 20: value taken from sarien
	if ( core.agi_clock <= 20 )
	{
		return;
	}
	
	core.agi_clock = 0;
	// seconds
	$v[11] = ($v[11]+1) % 60;
	if ( $v[11] == 0 )
	{
		// minutes
		$v[12] = ($v[12]+1) % 60;
		if ( $v[12] == 0 )
		{
			// hours
			$v[13] = ($v[13]+1) % 24;
			if ( $v[13] == 0 )
			{
				// days
				$v[14]++;
			}
		}
	}
}

var _safari_key_hack = 0;
// This function is garbage, totally hacked, must be completely refactored.
function handle_key( e )
{
	if ( typeof(event) == 'object' )
	{
		e = event;
	}
	
	// special key
	var skey = false;	
	var key = e.charCode;

	if ( !e.charCode || e.charCode == 0 )
	{
		key = e.keyCode;
		if ( typeof(e.charCode) != 'undefined' ) skey = true;
	}
	if ( key > 60000 ) { skey = true; }
	// ignore ALT press (only in Opera)
	if ( key == 18 ) { return; }
	//browserConsole.log("key: " + key);
	
	//alert( 'key: ' + key + ' : ' + (key == "X".charCodeAt(0)) + ' : ' + core.key_event.altKey );
	// detect alt-x for older browsers
	if ( core.key_event.altKey && (key == "X".charCodeAt(0) || key == "x".charCodeAt(0)) )
	{
		key = KEY_ALT_X;
	}
	
	// Deal with Safari bug that handles the cursor keydown event incorrectly.
	// When a cursor key in Safari is pressed, two equal events are fired,
	// misleading the programmer into thinking that the cursor key was pressed
	// two times in a row, this hack tries to suppress this bug.
	if ( isSafari() && skey )//key >= 63232 && key <= 63235 || key == KEY_ESC )
	{
	    if( false ) { // NOTE: not needed anymore it seems
	        _safari_key_hack++;
    		// already received this keypressed event
    		if ( _safari_key_hack == 2 )
    		{
    			_safari_key_hack = 0;
    			return;
    		}
	    }
		
		// normalize key scan codes (for Safari)
		if ( key >= 63232 && key <= 63243 )
		{
			var trans = [38,40,37,39,112,113,114,115,116,117,118,119];
			key = trans[key-63232];
		}
	}
	
	if ( menu.on )
	{
		menu.handleKey( key );
		return ignore_keys( e );
	}
	if ( inventory.on )
	{
		inventory.handleKey( key );
		return ignore_keys( e );
	}
	
	// v19 - Key pressed on the keyboard.
	// change the variable according even if input has been disabled.
	$v[19] = key;
	//core.key_press_indicator = key;
	
	// HACK: this stuff's SO ugly :-(
	// ignore all input until ENTER or ESC is pressed to dismiss the message box
	if ( core.waiting_keypressed )
	{
		if ( key == KEY_ENTER || key == KEY_ESC )
		{
			if ( core.restart && key == KEY_ESC )
			{
				core.restart = false;
			}
			if ( core.quit && key == KEY_ESC )
			{
				core.quit = false;
			}
			
			core.waiting_keypressed = false;
			core.msgbox_ask_close = true;
			start();
		}
		return ignore_keys( e );
	}
	// handle input functions such as get.num, get.string, etc.
	if ( core.waiting_input && key == KEY_ENTER )
	{
		if ( core.input.innerHTML == core.prompt + core.cursor )
		{
			return ignore_keys( e );
		}
		
		var s = core.input.innerHTML.substring( core.prompt.length, core.input.innerHTML.length-1 );
		if ( textScreen.get_num )
		{
			//alert( "'" + core.prompt + "' : " + core.prompt.length );
			//alert( "'" + core.input.innerHTML + "' : " + core.input.innerHTML.length );
			$v[textScreen.get_num_var] = parseInt( s );
			if ( isNaN( $v[textScreen.get_num_var] ) )
			{
				core.input.innerHTML = core.prompt + core.cursor;
				return ignore_keys( e );
			}
			textScreen.get_num = false;
			if ( core.accept_input )
			{
				accept_input();
			}
		}
		else if ( textScreen.get_string )
		{
			$s[textScreen.get_string_str] = s;
			core.input.style.top = textScreen.get_string_input_top;
			core.input.style.left = textScreen.get_string_input_left;
			textScreen.get_string = false;
			if ( core.accept_input )
			{
				accept_input();
			}
		}
		
		core.waiting_input = false;
		start();
		
		return ignore_keys( e );
	}
	
	// dodge debug console.
	if ( key != KEY_CONSOLE && !console.active )
	{
		core.have_key_indicator = true;
		core.key_press_indicator = key;
		core.special_key_press = skey;
		//alert( "key: " + core.key_press_indicator + " skey: " + core.special_key_press );
	}
	
	switch ( key )
	{
		case KEY_RIGHT:
			if ( core.player_control && $o[0] != null )
			{
				$o[0].direction = ($o[0].direction == EAST ? STOP : EAST );
			}
			break;
		
		case KEY_LEFT:
			if ( core.player_control && $o[0] != null )
			{
				$o[0].direction = ($o[0].direction == WEST ? STOP : WEST );
			}
			break;
		
		case KEY_UP:
			if ( core.player_control && $o[0] != null )
			{
				$o[0].direction = ($o[0].direction == NORTH ? STOP : NORTH );
			}
			break;
		
		case KEY_DOWN:
			if ( core.player_control && $o[0] != null )
			{
				$o[0].direction = ($o[0].direction == SOUTH ? STOP : SOUTH );
			}
			break;
			
		case KEY_CONSOLE:
			console.toggle();
			break;
			
		default:
			// give input to console if it is active
			if ( console.active )
			{
				console.handleKey( e );
			}
			else if ( core.accept_input || core.waiting_input )
			{
				var input_line = core.input.innerHTML;
				
				if ( key == KEY_BACKSPACE )
				{
					// remember the else...
					if ( input_line != core.prompt + core.cursor )
					{
						input_line = input_line.substr( 0, input_line.length-2 ) + core.cursor;
					}
				}
				else if ( key == KEY_ENTER )
				{
					core.input_line = input_line.substring( core.prompt.length, input_line.length - core.cursor.length );
					core.parse_input = true;
					input_line = $s[0] + core.cursor;
				}
				else if ( !skey 
						&& ((key >= 65 && key <= 90) || (key >= 97 && key <= 122)
						|| (key >= 48 && key <= 57)
						|| key == 32) )
				{
					input_line = input_line.substr( 0, input_line.length-1 ) 
								+ String.fromCharCode( key ).toLowerCase()
								+ core.cursor;
				}
				core.input.innerHTML = input_line;
			}
	}
	
	return ignore_keys( e );
}

function handle_keydown( e )
{
	core.key_event = typeof(event) == 'object' ? event : e;
	
	// IE and Safari doesn't dispatch events to keypress when these keys are pressed
	if ( browser.ie || browser.safari )
	{
		var kc = event.keyCode;
		if ( kc == KEY_LEFT || kc == KEY_UP || kc == KEY_RIGHT 
			|| kc == KEY_DOWN || kc == KEY_BACKSPACE || core.key_event.altKey )
		{
		    core.key_event.preventDefault();
			return handle_key( e );
		}
	}
}

function ignore_keys( e )
{
	// keys to be ignored by the browser! stop event bubble
	//if ( e.keyCode == KEY_BACKSPACE )
	//{
		e.cancelBubble = true;
		e.returnValue = false;
		e.cancel = true;
		return false;
	//}
	
	//return true;
}

function check_controller( key, skey )
{
	switch ( key )
	{
		case KEY_ALT_X: key = (45<<8); break;
		case KEY_F1: if ( skey ) { key = (59<<8) }; break;
		case KEY_F2: if ( skey ) { key = (60<<8) }; break;
		case KEY_F3: if ( skey ) { key = (61<<8) }; break;
		case KEY_F4: if ( skey ) { key = (62<<8) }; break;
		case KEY_F5: if ( skey ) { key = (63<<8) }; break;
		case KEY_F6: if ( skey ) { key = (64<<8) }; break;
		case KEY_F7: if ( skey ) { key = (65<<8) }; break;
		case KEY_F8: if ( skey ) { key = (66<<8) }; break;
		case KEY_F9: if ( skey ) { key = (67<<8) }; break;
		case KEY_F10: if ( skey ) { key = (68<<8) }; break;
	}
	
	if ( core.keys[key] )
	{
		$c[core.keys[key]] = true;
	}
}
/*
 * Here is how the input is matched. After the player types a message and presses ENTER, the input line is processed by the interpreter in the following way:
 *	1.	The interpreter removes all punctuation marks.
 *	2.	All characters are converted to lowercase.
 *	3.	All sequences of more than one space are replaced with a single space.
 *	4.	Starting with the first word of the input, the interpreter looks up the vocabulary, trying to find the longest character sequence matching the input line.
 *
 * If the search is unsuccessful, v9 is assigned the number of the word in the message that failed to match and the processing ends. If all the words have been assigned some codes:
 *	- The Interpreter removes from the sequence of codes all zeros (that means all vocabulary words with zero codes are ignored).
 *	- f2 (the user has entered an input line) is set to 1
 *	- f4 (said command accepted the user input) is set to 0.
 */
function parse_input( input_str )
{
	core.parse_input = false;
	
	$w = new Array();
	$v[9] = 0;
	
	var input = get_input( input_str );
	if ( input.length == 0 ) { return; }
	
	for ( var i = 0, j = 1; i < input.length; i++ )
	{
		var n = core.words[input[i]];
		
		if ( typeof(n) == 'undefined' )
		{
			$w[j] = input[i];
			$v[9] = j;
			$f[2] = true;
			return;
		}
		
		if ( n != WORD_IGNORE )
		{
			$w[j] = input[i];
			j++;
		}
	}
	core.w = $w;
	$f[2] = true;
	$f[4] = false;
}

/*
 * Return an array with the input line parsed, analyse each word and try to
 * do the best matching with the words given by the game.
 */
function get_input( input_line )
{
	if ( input_line.trim() == '' ) { return new Array(); }
	var input = input_line.trim().split( /\s+/ );
	
	for ( var i = 0; i < input.length; i++ )
	{
		var word = input[i];
		var best_word = word;
		var n = core.words[word];
		var j = i+1;
		var num_words = 1;
		
		// find the best word, the best is the longest
		while ( j < input.length )
		{
			word += ' ' + input[j];
			n = core.words[word];
			if ( typeof(n) != 'undefined' )
			{
				best_word = word;
				num_words = j-i+1;
			}
			j++;
		}
		
		input[i] = best_word;
		while ( num_words-- > 1 )
		{
			input = input.remove( input[i+1] );
		}
	}
	
	return input;
}

function open_msgbox( msg, len, x, y )
{
	var CHAR_WIDTH = 11;
	// 8 for the msgbox padding
	// 15 for the msgboxcontent padding
	var MSGBOX_BORDER = 8+15;
	
	var width = (len*CHAR_WIDTH)+MSGBOX_BORDER;
	core.msgbox.style.width = width + 'px';
	
	core.msgboxcontent.innerHTML = htmlize_string( msg, len );
	
	x = (x <= -1) ? (SCREEN_WIDTH-width)/2 : (SCREEN_WIDTH/TEXT_COLS)*x;
	y = (y <= -1) ? (SCREEN_HEIGHT-core.msgbox.clientHeight)/2 : (SCREEN_HEIGHT/TEXT_ROWS)*y;
	
	var left = SCREEN_LEFT + x;
	var top = SCREEN_TOP + y;
	
	core.msgbox.style.left = left + 'px';
	core.msgbox.style.top = top + 'px';
	core.msgbox.style.visibility = 'visible';
	
	core.msgbox_showing = true;
}

function close_msgbox()
{
	core.msgbox.style.visibility = 'hidden';
	core.msgbox_showing = false;
	core.msgbox_ask_close = false;
	
	// check to see if an item is on screen
	if ( core.inventory_showing )
	{
		core.inventory_obj.erase();
		core.inventory_obj = null;
		core.inventory_showing = false;
	}
}

function htmlize_string( str, len )
{
	//return str.replace( /\n/gm, '<br>' );
	var res = '';
	var space, newline;
	var s;
	
	while ( str.length > 0 )
	{
		s = str.substring( 0, len+1 );
		space = s.lastIndexOf( ' ' );
		if ( space == -1 ) { space = len; }
		newline = s.indexOf( "\n" );
		if ( newline == -1 ) { newline = len; }
		ix = Math.min( space, newline );
		
		res += str.substring( 0, ix ).replace( / /gm, '&nbsp;' ) + str.substring( ix, ix+1 );
		//alert( s + ":" + s.length + "\n\ns:'" + str.substring( 0, ix ) + "'\nc:'" + str.substring( ix, ix+1 ) + "'\nstr:" + str.substring( ix+1 ) );
		//alert( "ix: " + ix + "\n'" + s + "'\ns:'" + str.substring( 0, ix ) + "'\nc:'" + str.substring( ix, ix+1 ) + "'\nstr:" + str.substring( ix+1 ) );
		str = str.substring( ix+1 );
	}
	
	/*
	while ( str.length > len || str.indexOf( "\n" )>-1 )
	{
		s = str.substring( 0, len )
		str = str.substring( len );
		
		// find the last space or the first new line to break the line
		space = s.lastIndexOf( ' ' );
		if ( space == -1 ) { space = maxlen; }
		newline = s.indexOf( "\n" );
		if ( newline == -1 ) { newline = maxlen; }
		c = Math.min( space, newline );
		
		str = s.substring( c+1 ) + str;
		res += s.substring( 0, c ).replace( / /gm, '&nbsp;' ) + s.substring( c, c+1 );
		//alert( "s:" + s.substring( 0, c ) + "\nc:" + s.substring( c, c+1 ) + "\str:" + str );
	}
	*/
	
	return res.replace( /\n/gm, '<br>' );
}

function calculate_word_wrap( str, len )
{
	var s;
	var maxc = -1;
	var maxlen = str.length;
	var c;
	
	while ( str.length > len || str.indexOf( "\n" )>-1 )
	{
		s = str.substring( 0, len )
		str = str.substring( len );
		
		// find the last space or the first new line to break the line
		space = s.lastIndexOf( ' ' );
		if ( space == -1 ) { space = maxlen; }
		newline = s.indexOf( "\n" );
		if ( newline == -1 ) { newline = maxlen; }
		c = Math.min( space, newline );
		
		if ( c > maxc ) { maxc = c; }
		str = s.substring( c+1 ) + str;
	}
	
	if ( str.length > maxc ) { maxc = str.length; }
	
	return maxc;
}

function parse_message( msg )
{
	var regexp;
	var res;
	// variables
	regexp = /%v(\d+)/gm;
	while ( (res = regexp.exec( msg )) != null )
	{
		msg = msg.replace( res[0], $v[res[1]] );
	}
	// messages
	regexp = /%m(\d+)/gm;
	while ( (res = regexp.exec( msg )) != null )
	{
		msg = msg.replace( res[0], $m[res[1]] );
	}
	// input word
	regexp = /%w(\d+)/gm;
	while ( (res = regexp.exec( msg )) != null )
	{
		msg = msg.replace( res[0], $w[res[1]] );
	}
	// string
	regexp = /%s(\d+)/gm;
	while ( (res = regexp.exec( msg )) != null )
	{
		msg = msg.replace( res[0], $s[res[1]] );
	}
	
	return ""+msg;
}

var _direction_table = [ NORTHWEST, NORTH, NORTHEAST,
						 WEST     , STOP , EAST     , 
						 SOUTHWEST, SOUTH, SOUTHEAST ];
function get_direction( from, to_x, to_y, pixels )
{
	// stopped: center of the direction table
	var x = 1;
	var y = 1;
	var from_x = from.x;
	var from_y = from.y;
	//alert( "from.x: " + from_x + "; from_y: " + from_y + "\nto.x: " + to_x + "; to.y: " + to_y + "; pixels: " + pixels );
	if ( from_x + pixels <= to_x )
	{
		x = 2;
	}
	else if ( from_x - pixels >= to_x )
	{
		x = 0;
	}
	
	if ( from_y + pixels <= to_y )
	{
		y = 2;
	}
	else if ( from_y - pixels >= to_y )
	{
		y = 0;
	}
	//alert( "h: " + from.height() + "; x: " + x + "; y: " + y + "; px: " + pixels + "; dir: " + _direction_table[y*3 + x] );
	return _direction_table[y*3 + x];
}
/*
function logic0_0()
{
	//logic0_messages();
	if ( $f[5] )
	{
		set_cursor_char("_");
		set_string(0,"]");
		configure_screen(1,23,0);
		$v[7] = 222;
		status_line_on();
		
		$v[100] = 11;
		load_pic($v[100]);
		draw_pic($v[100]);
		discard_pic($v[100]);
		
		load_view(50);
		// Lefty's neon
		animate_obj(2);
 		set_view($o[2],50);
		position($o[2],36,83);
		set_loop($o[2],1);
		//set_priority($o[2],13);
		draw($o[2]);
		$v[87] = 4;
		cycle_time($o[2],$v[87]);
		
		// Cocktail neon
		animate_obj(3);
 		set_view($o[3],50);
		position($o[3],83,84);
		set_loop($o[3],2);
		draw($o[3]);
		$v[87] = 1;
		cycle_time($o[3],$v[87]);
		
		// Hotel neon
		animate_obj(4);
 		set_view($o[4],50);
		position($o[4],134,67);
		set_loop($o[4],0);
		draw($o[4]);
		$v[87] = 5;
		cycle_time($o[4],$v[87]);
		
		// Door
		load_view(52);
		animate_obj(1);
 		set_view($o[1],52);
		position($o[1],60,131);
		
		set_priority($o[1],8);
		stop_cycling($o[1]);
		draw($o[1]);
  		
  		// Larry
  		load_view(0);
  		animate_obj(0);
  		set_view($o[0],0);
  		position($o[0],63,156);
  		set_loop($o[0],3);
  		draw($o[0]);
  		step_size($o[0],1);
  		step_time($o[0],1);
		cycle_time($o[0],1);

		show_pic();
	}
	
	get_posn($o[0],77,78);
	if ( $v[77] == $v[40] && $v[78] == $v[41] )
	{
		stop_cycling($o[0]);
	}
	else
	{
		start_cycling($o[0]);
	}
	
	if ( !$f[4] && $f[2] && $v[9] > 0 )
	{
		$f[4] = true;
		print($m[$v[9]]);
	}
	
	return 0;
}

function logic0_1()
{
	if ( said("look") )
	{
		print($m[20]);
	}
	
	return 2;
}

function logic0_200()
{
	$v[40] = $v[77];
	$v[41] = $v[78];
	
	return 0;
}

function logic0_messages()
{
	$m[1] = "%m8 %w1?";
	$m[2] = "%m8 %w2?";
	$m[3] = "%m8 %w3?";
	$m[4] = "%m8 %w4?";
	$m[5] = "%m8 %w5?";
	$m[6] = "%m8 %w6?";
	$m[7] = "%m8 %w7?";
	$m[8] = "What's a";
	$m[20] = "Average Kuwaiti diplomat has 246.2 unpaid New York parking tickets.";
	$m[21] = "true";
}
*/
//////////////////////////////////////
// Miscleanious
//////////////////////////////////////
function info( msg )
{
	_infobox.style.width = (msg.length*10 - 1) + 'px';
	_infobox.innerHTML = msg;
	_infobox.style.visibility = 'visible';
}

function clear_info()
{
	_infobox.style.visibility = 'hidden';
}

function left_coord( obj )
{
	// +4 to give it a fix
	return $screen.left + obj.x*4;
}

function top_coord( obj )
{
	// +2 to give it a fix
	return $screen.top + obj.y*2 - obj.height()*2 + 2;
}

function sleep( msec )
{
	var request = http_request();
	request.open( 'GET', 'sleep.php?msec=' + msec, false );
	request.send( null );
}

function pause(numberMillis)
{
	var now = new Date();
	var exitTime = now.getTime() + numberMillis;
	while ( 1 )
	{
		now = new Date();
		if (now.getTime() > exitTime) { return; };
	}
}

// it's just needed to handle key event handling correctly...
var browser = {
	opera: navigator.userAgent.toLowerCase().indexOf( 'opera' ) != -1,
	ie: navigator.userAgent.toLowerCase().indexOf( 'msie' ) != -1 && !this.opera,
	safari: navigator.userAgent.toLowerCase().indexOf( 'safari' ) != -1
}

function isSafari()
{
	return browser.safari;
}

function isOpera()
{
	return browser.opera;
}
//////////////////////////////////////
// XML Http Request Handling
//////////////////////////////////////
function http_request()
{
	var http_request;
	
	if (window.XMLHttpRequest) // Mozilla, Safari,...
	{
		http_request = new XMLHttpRequest();
	}
	else if (window.ActiveXObject) // IE
	{
		try
		{
			http_request = new ActiveXObject("Msxml2.XMLHTTP");
		} catch (e) {
			try {
				http_request = new ActiveXObject("Microsoft.XMLHTTP");
			} catch (e) {
				alert( e );
			}
		}
	}
	
	return http_request;
}

String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ''); };
String.prototype.repeat = function(l){return new Array(l+1).join(this);};
Array.prototype.remove = function( el )
{
	var newarr = new Array();
	for ( var i = 0, j = 0; i < this.length; i++ )
	{
		if ( this[i] != el )
		{
			newarr[j++] = this[i];
		}
	}
	
	return newarr;
}
Array.prototype.removeEmpty = function()
{
	var newarr = new Array();
	for ( var i = 0, j = 0; i < this.length; i++ )
	{
		if ( this[i].trim() != '' )
		{
			newarr[j++] = this[i];
		}
	}
	
	return newarr;
}
Array.prototype.exists = function( el )
{
	for ( var i = 0; i < this.length; i++ )
	{
		if ( this[i] == el )
		{
			return true;
		}
	}
	return false;
}
Array.prototype.append = function( el )
{
	this[this.length] = el;
}

var pallet = ['#000000','#0000AA','#00AA00','#00AAAA',
			  '#AA0000','#AA00AA','#AA5500','#AAAAAA',
			  '#555555','#5555FF','#55FF55','#55FFFF',
			  '#FF5555','#FF55FF','#FFFF55','#FFFFFF'];
//////////////////////////////////////
// Garbage
//////////////////////////////////////
/*
function prepare_image( view, img )
{
	// must do this, otherwise the image will inherit the #screen img definitions
	// making the image the same size of the screen, these properties will be
	// correctly setted when the image is completly loaded.
	img.style.width = '0px';
	img.style.height = '0px';
	img.onload = function()
	{
		// if this is not a hack, then i don't know what it is!
		// only an Image() can give me the width and the height of a loaded
		// img, it's needed to double it.
		var m = new Image();
		m.src = img.src;
		img.width = m.width*2;
		img.height = m.height*2;
		img.style.width = m.width*2 + 'px';
		img.style.height = m.height*2 + 'px';
		// HACK: another one :( i need to know the height of the img while
		//       drawing, but since i can be asked to draw it before the complete
		//       load of the image, the height will be unknown at the moment,
		//       so i update the top of the image when i know the height for
		//       the first time.
		//img.style.top = top_coord( view ) + 'px';
	}
}

function keypressed()
{
	core.waiting_keypressed = true;
	while ( core.waiting_keypressed )
	{
		//alert( 'sleep' );
		sleep( 100 );
	}
}

function run_logic( logic_n, part_n )
{
	//core.current_logic = logic_n;
	//core.logic_part = part_n;
	//stack.push( logic_n, part_n );
	
	var logic = core.logics[stack.logic];
	
	//try {
		while ( (part_n = logic[part_n]()) != 0 )
		{
			stack.setPart( part_n );
			if ( core.waiting_keypressed )
			{
				stop();
				stack.setFrame(stack.getFrame()-1);
				show_state();
				return PRINT_WAITING;
			}
		}
	//} catch ( e ) {
	//	alert( "[AGI: Error running function]\n\nlogic:" + logic_n + "\npart:" + core.logic_part + "\n\nLine:" + e.lineNumber + "\nMessage:" + e.message );
	//}
	
	//stack.pop();
	return 0;
}

*/

