// the game object
var g = false;
// constants
var PENALTY_HINT = 5000;
var PENALTY_UNDO = 1000;
var periodicalExec = new PeriodicalExecuter(
	function(pe)
	{
		if( !ajax || !g ) return;
		ajax.loadMiniHighscore(g.Type);
	}, 60);

// tileset definitions
var TileSets = {
	'*': {
		R1280: {
			SpaceWidth: 44, SpaceHeight: 51,	
			TileWidth: 51, TileHeight: 59,
			FrameLeft: 1, FrameTop: 3,
			FrameWidth: 36, FrameHeight: 42,
			ImageRes: 32,
			TextTop: 10, TextLeft: 14,
			TileImage: 'images/mahjong_bright.png',
			ImageLeft: 6, ImageTop: 12,
			OffsetZX: 5, OffsetZY: 6,
			FontWeight: 900		 				
		},
		RDefault: {
			SpaceWidth: 35, SpaceHeight: 51,	
			TileWidth: 40, TileHeight: 58,
			FrameLeft: 1, FrameTop: 3,
			FrameWidth: 26, FrameHeight: 40,
			ImageRes: 25,
			TextTop: 10, TextLeft: 10,
			TileImage: 'images/mahjong_bright.png',
			ImageLeft: 5, ImageTop: 12,
			OffsetZX: 4, OffsetZY: 6,
			FontWeight: 500		 				
		},
		RMini: {
			SpaceWidth: 32, SpaceHeight: 35,	
			TileWidth: 37, TileHeight: 40,
			FrameLeft: 3, FrameTop: 3,
			FrameWidth: 24, FrameHeight: 27,
			ImageRes: 25,
			TextTop: 5, TextLeft: 11,
			TileImage: 'images/mahjong_bright.png',
			ImageLeft: 4, ImageTop: 6,
			OffsetZX: 3, OffsetZY: 4,
			FontWeight: 500,
			DivClass: 'Tile_Mini',
			Highlight: "solid 1px #FF0000",
			Hover: "dashed 1px #FFA001",
			Hint: "solid 1px #AA00AA"
		}
	},
	4: {	// kids
		R1280: {
			SpaceWidth: 55, SpaceHeight: 64,	
			TileWidth: 64, TileHeight: 75,
			FrameLeft: 1, FrameTop: 3,
			FrameWidth: 48, FrameHeight: 57,
			ImageRes: 40,
			TextTop: 16, TextLeft: 19,
			TileImage: 'images/mahjong_bright.png',
			ImageLeft: 7, ImageTop: 13,
			OffsetZX: 5, OffsetZY: 6,
			FontWeight: 900		 				
		},
		RDefault: {
			SpaceWidth: 55, SpaceHeight: 60,	
			TileWidth: 64, TileHeight: 71,
			FrameLeft: 1, FrameTop: 3,
			FrameWidth: 47, FrameHeight: 53,
			ImageRes: 40,
			TextTop: 16, TextLeft: 19,
			TileImage: 'images/mahjong_bright.png',
			ImageLeft: 7, ImageTop: 13,
			OffsetZX: 5, OffsetZY: 6,
			FontWeight: 700		 				
		},
		RMini: {
			SpaceWidth: 32, SpaceHeight: 35,	
			TileWidth: 37, TileHeight: 40,
			FrameLeft: 1, FrameTop: 3,
			FrameWidth: 24, FrameHeight: 26,
			ImageRes: 25,
			TextTop: 5, TextLeft: 11,
			TileImage: 'images/mahjong_bright.png',
			ImageLeft: 4, ImageTop: 7,
			OffsetZX: 3, OffsetZY: 4,
			FontWeight: 500,
			DivClass: 'Tile_Mini'		 				
		}
	},
	5: {	// Mini
		RDefault: {
			SpaceWidth: 18, SpaceHeight: 23,	
			TileWidth: 20, TileHeight: 25,
			FrameLeft: 1, FrameTop: 3,
			FrameWidth: 11, FrameHeight: 13,
			ImageRes: 12,
			TextTop: 1, TextLeft: 5,
			TileImage: 'images/mahjong_bright.png',
			ImageLeft: 7, ImageTop: 13,
			OffsetZX: 2, OffsetZY: 3,
			FontWeight: 500,
			DivClass: 'Tile_Mini'
		}
	}
};

var TilePrefs = {
	Colors: {
		redgreenblue: ["#FF0000", "#00BB33", "#0000FF"],
		redblackblue: ["#FF0000", "#023302", "#0000FF"]
	},
	Tileset: {
		tdefault: ["images/ball_%col_%res_%tid.png", 0,1,2,3,4,5,6,7,8,9,'+'],
		teasy: [0,1,2,3,4,5,6,7,8,9,'+','#', '%', '$'],
		tkids: ["images/car_%col_%res_%tid.png", "images/blume_%col_%res_%tid.png", "images/ball_%col_%res_%tid.png", '+', '#', 1, 2, 3, 4, 5, 6, 7]
	}
};

var DefaultPrefs = {
	'*': {
		Colors: 'redgreenblue',
		Tileset: 'tdefault'
	},
	4: {		// kids
		Colors: 'redgreenblue',
		Tileset: 'tkids'
	},
	5: {		// mini
		Colors: 'redgreenblue',
		Tileset: 'teasy'
	}
};

var MjTileSpace = Class.create({
  initialize: function(grid, id, x, y, z) {
  	this.Grid = grid;
  	this.Id = id;
  	this.Z = z;
  	this.Left = [];
  	this.Right = [];
  	this.Above = [];
  	this.Below = [];
  	this.Tile = null;
  	
  	var box = document.createElement('div');
  	box.id = "TileSpace[" + this.Id + "]";
  	box.style.position = "absolute";
  	box.style.width = this.Grid.Game.TileSet["SpaceWidth"] + "px";
  	box.style.height = this.Grid.Game.TileSet["SpaceHeight"] + "px";
  	box.style.display = "block";
  	box.style.zIndex = this.Z;
  	box.style.display = "none";
  	$('GameDiv').appendChild(box);
  	disableSelection(box);
	this.Div = box;
	this.setX(x);
	this.setY(y);
  },
  
  draw: function() {
  	this.Div.style.display = "block";  	  	
  }, 
  
  getX: function() {
  	return (this.X - (this.Z * this.Grid.Game.TileSet["OffsetZX"]));
  },
  
  getY: function() {
  	return (this.Y - (this.Z * this.Grid.Game.TileSet["OffsetZY"]));
  },
  
  setX: function(x) {
  	this.X = x;
	this.Div.style.left = x + "px";  	
  },
  
  setY: function(y) {
  	this.Y = y;
	this.Div.style.top = y + "px";  	
  },
  
  placeTile: function(tile) {
  	this.Div.innerHTML = "";
  	this.Tile = tile;
  	tile.Removed = false;
  	tile.setSpace(this);
  	this.Div.style.width = this.Grid.Game.TileSet["SpaceWidth"] + "px";
  },
  
  hasLeft: function() {
  	for (var i=0; i<this.Left.length; i++) {
  		if (this.Left[i].Tile && !this.Left[i].Tile.Removed) {
  			return true;
  		}
	}
	return false;	
  },
  
  hasRight: function() {
  	for (var i=0; i<this.Right.length; i++) {
  		if (this.Right[i].Tile && !this.Right[i].Tile.Removed) {
  			return true;
  		}
	}
	return false;	
  },
  
  hasBelow: function() {
  	for (var i=0; i<this.Below.length; i++) {
  		if (this.Below[i].Tile && !this.Below[i].Tile.Removed) {
  			return true;
  		}
	}
	return false;	
  },
  
  countBlocked: function(dir) {
  	if (!dir) return this.countBlocked(1) + this.countBlocked(2) + this.countBlocked(3);
  	
  	var ret = 0; 
  	switch (dir) {
  		case 1:					// top->down
			for (var i=0; i<this.Below.length; i++) {
		  		if (this.Below[i].Tile && !this.Below[i].Tile.Removed) {
		  			ret++;
					ret += this.Below[i].countBlocked(1); 		  			
		  		}
			}	  			
  			break;
  		case 2:					// left->right
  			for (var i=0; i<this.Right.length; i++) {
		  		if (this.Right[i].Tile && !this.Right[i].Tile.Removed && !this.Right[i].isSelectable()) {
		  			ret++;
					ret += this.Right[i].countBlocked(2);
		  		}
			}
			break;
  		case 3:					// right->left
  			for (var i=0; i<this.Left.length; i++) {
		  		if (this.Left[i].Tile && !this.Left[i].Tile.Removed && !this.Left[i].isSelectable()) {
		  			ret++;
					ret += this.Left[i].countBlocked(3);
		  		}
			}
			break;
  	}
  	return ret;
  },
  
  isLoose: function() {
  	if (this.hasBelow()) return false;
  	for (var i=0; i<this.Left.length; i++) {
  		if (this.Left[i].Tile && !this.Left[i].Tile.Removed) {
  			if (this.Left[i].hasLeft()) return false;
  		}
	}
	for (var i=0; i<this.Right.length; i++) {
  		if (this.Right[i].Tile && !this.Right[i].Tile.Removed) {
  			if (this.Right[i].hasRight()) return false;
  		}
	}	
	return true;
	},
	
  isSelectable: function() {
  	// something on top => not selectable
  	for (var i=0; i<this.Above.length; i++) {
  		if (this.Above[i].Tile && !this.Above[i].Tile.Removed) return false;
	}
	// else only when tiles on BOTH sides
	var lOcc = this.hasLeft();  
	var rOcc = this.hasRight();  
	var ret = !(lOcc && rOcc);
	return ret;
  }
});

var MjGrid = Class.create({
  initialize: function(game) {
  	this.TileSpace = [];
  	this.Game = game;  	
  },
  
  getWidth: function() {
	return this.Game.TileSet["SpaceWidth"];  
  },
  
  getHeight: function() {
	return this.Game.TileSet["SpaceHeight"];  
  },
  
  addSpace: function(x, y, z) {
	var id = this.TileSpace.length;	  
	this.TileSpace[id] = new MjTileSpace(this, id, x * this.getWidth(), y * this.getHeight(), z);
  },
  
  initLogo: function(grid) {
	grid.addSpace(-6, 0, 1);
	grid.addSpace(-4, 1.5, 1);
	grid.addSpace(-2, 2, 1);
	grid.addSpace(-0.5, 2.5, 2);
	grid.addSpace(1.5, 3, 2);
	grid.addSpace(2.5, 3.5, 2);
	grid.addSpace(-5, 4, 1);
	grid.addSpace(-4, 4.5, 1);
	grid.addSpace(0, 5, 1);
	grid.addSpace(1.5, 5.5, 1);
	grid.addSpace(3, 6, 1);
	grid.addSpace(4.5, 6.5, 1);
	grid.addSpace(6, 7, 1);
	grid.addSpace(5, 7.5, 1);
	
  },
 
 initTest: function(grid) {
	

	for (var i=0; i<2; i++) {
  		for (var j=0; j<2; j++) {
  			grid.addSpace((j-5), (i-6), 0);
  		}
	}
  		
  },
      
  initTurtle: function(grid) {
  	grid.addSpace(-7, -0.5, 0);
  	grid.addSpace(-6, -1, 0);
  	grid.addSpace(-6, 0, 0);
  	
  	grid.addSpace(-5, -2, 0);
  	grid.addSpace(-5, -1, 0);
  	grid.addSpace(-5, 0, 0);
  	grid.addSpace(-5, 1, 0);  	
  	
  	grid.addSpace(-6, -4, 0);
  	grid.addSpace(-5, -4, 0);
  	grid.addSpace(-6, 3, 0);
  	grid.addSpace(-5, 3, 0);
  	
  	// first layer: 8x8 tiles in center
  	for (var i=0; i<8; i++) {
  		for (var j=0; j<8; j++) {
  			grid.addSpace(j-4, i-4, 0);
  		}
  	}
  	for (var i=0; i<6; i++) {
  		for (var j=0; j<6; j++) {
  			grid.addSpace((j-3), (i-3), 1);
  		}
  	}  	
  	for (var i=0; i<4; i++) {
  		for (var j=0; j<4; j++) {
  			grid.addSpace((j-2), (i-2), 2);
  		}
  	}
  	for (var i=0; i<2; i++) {
  		for (var j=0; j<2; j++) {
  			grid.addSpace((j-1), (i-1), 3);
  		}
  	}
  	grid.addSpace(-0.5, -0.5, 4);  	
  	
  	grid.addSpace(4, -2, 0);
  	grid.addSpace(4, -1, 0);
  	grid.addSpace(4, 0, 0);
  	grid.addSpace(4, 1, 0);  	
  	
  	grid.addSpace(4, -4, 0);
  	grid.addSpace(5, -4, 0);
  	grid.addSpace(4, 3, 0);
  	grid.addSpace(5, 3, 0);
  	
  	grid.addSpace(5, -1, 0);
	grid.addSpace(5, 0, 0);
	grid.addSpace(6, -0.5, 0);
	grid.addSpace(7, -0.5, 0);
  },
  
  initEasy: function(grid) {
  
  	grid.addSpace(-5, -1.5, 0);
  	grid.addSpace(-4, -2, 0);
  	grid.addSpace(-4, -1, 0);  	
  	grid.addSpace(-5, -4, 0);
  	grid.addSpace(-5, 1, 0);
  	grid.addSpace(-4, -4, 0);
  	grid.addSpace(-4, 1, 0);
  	
	for (var i=0; i<6; i++) {
  		for (var j=0; j<6; j++) {
  			grid.addSpace((j-3), (i-4), 0);
  		}
  	}  	
  	for (var i=0; i<4; i++) {
  		for (var j=0; j<4; j++) {
  			grid.addSpace((j-2), (i-3), 1);
  		}
  	}
  	for (var i=0; i<2; i++) {
  		for (var j=0; j<2; j++) {
  			grid.addSpace((j-1), (i-2), 2);
  		}
  	}
  	grid.addSpace(-0.5, -1.5, 3);  	
  	grid.addSpace(3, -4, 0);
  	grid.addSpace(4, -4, 0);  	
  	grid.addSpace(3, 1, 0);
  	grid.addSpace(4, 1, 0);
  	grid.addSpace(3, -2, 0);
	grid.addSpace(3, -1, 0);
	grid.addSpace(4, -1.5, 0);
	grid.addSpace(5, -1.5, 0);  	 
  },
  
  initKids: function(grid) {

	grid.addSpace( -5, -2.5, 0);
	grid.addSpace( -5, -1.5, 0);
	grid.addSpace( -4, -3, 0);
	grid.addSpace( -4, -2, 0);
	grid.addSpace( -4, -1, 0);
	grid.addSpace( -4, 0, 0);
	grid.addSpace( -4, 1, 0);
	grid.addSpace( -4, 2, 0);
	grid.addSpace( -4, 3, 0);
	grid.addSpace( -3, -2.5, 0);
	grid.addSpace( -3, -1.5, 0);

	grid.addSpace( -1, -1, 0);
	grid.addSpace( -1, 0, 0);
	grid.addSpace( -1, 1, 0);
	grid.addSpace( -1, 2, 0);
	grid.addSpace( -1, 3, 0);

	grid.addSpace( 0, -2, 0);
	grid.addSpace( 0, -1, 0);
	grid.addSpace( 0, 1, 0);
	grid.addSpace( 0, 2, 0);
	grid.addSpace( 0, 3, 0);

	grid.addSpace( 1, -3, 0);
	grid.addSpace( 1, -2, 0);
	grid.addSpace( 1, -1, 0);
	grid.addSpace( 1, -0, 0);
	grid.addSpace( 1, 1, 0);
	grid.addSpace( 1, 2, 0);
	grid.addSpace( 1, 3, 0);

	grid.addSpace( 2, -2, 0);
	grid.addSpace( 2, -1, 0);
	grid.addSpace( 2, 1, 0);


	grid.addSpace( 3, -1, 0);
	grid.addSpace( 3, -0, 0);
	grid.addSpace( 3, 1, 0);
	grid.addSpace( 3, 2, 0);
	grid.addSpace( 3, 3, 0);

  },
  	  
// automatically calculate constraints
  // @todo ...rrrrreally inefficient
  initConstraints: function() {
  	for (var i=0; i<this.TileSpace.length; i++) {
  		var si = this.TileSpace[i];
  		if (!si) {
  			alert("TileSpace Error: " + i);
  			break;
  		}
		for (var j=0; j<this.TileSpace.length; j++) {
			var sj = this.TileSpace[j];
			if (!sj) {
	  			alert("TileSpace Error: " + j);
	  			break;
	  		}
			if (si.Z == sj.Z && sj.X == si.X + this.getWidth()) {
				if (sj.Y >= si.Y && sj.Y < si.Y + this.getHeight() || sj.Y < si.Y && sj.Y + this.getHeight() > si.Y) {
					this.addRight(i, j);  
				}
			} else if (sj.Z == si.Z + 1) {
				if ((sj.Y >= si.Y && sj.Y < si.Y + this.getHeight() || sj.Y < si.Y && sj.Y + this.getHeight() > si.Y)
					&& (sj.X >= si.X && sj.X < si.X + this.getWidth() || sj.X < si.X && sj.X + this.getWidth() > si.X)) {
					
					this.addTop(i, j);					
				}				
			}
		}
  	}
  },
  
  init: function(gameInit) {
  	gameInit(this);	
	this.initConstraints();	
	// correct tilespace position
	var minY = 0;
	this.TileSpace.each(function(ts) {
		if (ts.getY() < minY) minY = ts.getY();
	});
	var gdPos = getElementPosition($('GameDiv'));
	var w = $('GameDiv').getWidth() / 2;
	if( this.Game.Type == 5 ) w -= 10;
	this.TileSpace.each(function(ts) {
		ts.setY(gdPos[1] + ts.getY() - minY);
		ts.setX(gdPos[0] + ts.getX() + w);
	});	
  },
  
  addRight: function(sid, right) {
  	var ts = this.TileSpace[sid];
  	this.TileSpace[right].Left.push(ts);
	ts.Right.push(this.TileSpace[right]);
  },
  
  addTop: function(sid, onTop) {
  	var ts = this.TileSpace[sid];
  	this.TileSpace[onTop].Below.push(ts);
	ts.Above.push(this.TileSpace[onTop]);
  },
    
  draw: function() {
  	this.TileSpace.each(function(ts) {
		ts.draw();
	});
  },
  
  placeTile: function(position, tile) {
  	if (!this.TileSpace[position]) {
  		alert("No TileSpace at " + position);
  		return false;
  	}
  	return this.TileSpace[position].placeTile(tile);  	
  },
  
  getSize: function() {
  	return this.TileSpace.length;
  },
  
  removeTile: function(tile) {
  	tile.Space.Div.innerHTML = "";
  	tile.Removed = true;
  	tile.Space.Div.style.width = "0px";  	  	
  }
});

var MjTile = Class.create({
  initialize: function(game, id, tileType) {
  	this.Game = game;
  	this.Id = id;
	this.TileType = tileType;
	this.Space = null;
	this.Div = null;
	this.Removed = false;
	this.Div = document.createElement('div');
	this.FrameDiv = document.createElement('div');
	this.Div.id = "Tile[" + this.Id + "]";
  	this.Div.style.display = "none";
  	this.Highlighted = false;
  	this.InnerDiv = null;
  },
  
  setSpace: function(space) {
	this.Space = space;
	this.Space.Div.innerHTML = "";
	this.Space.Div.appendChild(this.Div);
	this.Div.onclick = "";
	bindOnClick(this.Div.id, this.click, this);
	this.Div.Tile = this;
	this.Div.onmouseover = function() {
		this.Tile.toggleHover(true);
	};
	this.Div.onmouseout = function() {
		this.Tile.toggleHover(false);
	};	
  },
  
  getType: function() {
  	return this.TileType;
  },
  
  draw: function() {
	this.Div.style.position = "absolute";
  	
  	this.Div.style.width = this.Game.TileSet["TileWidth"] + "px";
  	this.Div.style.height = this.Game.TileSet["TileHeight"] + "px";
  	this.Div.style.zIndex = this.Space.Z;
	this.Div.style.fontWeight = this.Game.TileSet["FontWeight"];
	
	var ttop = this.Game.TileSet["TextTop"];
	var tleft = this.Game.TileSet["TextLeft"];		  	
  	if (!isNaN(this.TileType)) {
	  	var nColorSep = this.Game.Tiles.length / 12;
	  	if (nColorSep < 1) nColorSep = 1;
	  	nColorSep = Math.round(nColorSep);
	  	var pcols = this.Game.Preferences['Colors'];
	  	this.Div.style.color = TilePrefs['Colors'][pcols][Math.floor(this.TileType / nColorSep)];
	  	 
		var t = this.TileType % nColorSep;
		t = TilePrefs['Tileset'][this.Game.Preferences['Tileset']][this.TileType % nColorSep];
		var regrepl = /%col/;
		var repl = regrepl.exec(t); 
		if (repl && repl.length > 0){
			t = t.replace(/%col/, this.Game.Preferences['Colors'])
			t = t.replace(/%res/, this.Game.TileSet["ImageRes"])
			t = t.replace(/%tid/, Math.floor(this.TileType / nColorSep));
			var twidth = "";
			if (this.Game.TileSet["ImageWidth"]) twidth = "width=\"" + this.Game.TileSet["ImageWidth"] + "\"";
			t = "<img src='" + t + "' " + twidth + " />";
			tleft = this.Game.TileSet["ImageLeft"]; 
			ttop = this.Game.TileSet["ImageTop"];
		}	
	} else {
		var t = this.TileType;
		switch (this.TileType) {
			case 'W': this.Div.style.color = "#FF0000"; tleft = 9; break;
			case 'M': this.Div.style.color = "#FF0000"; tleft = 11; break; 
			case 'e':
			case 'a': 
			case 'n': 
			case 'o': this.Div.style.color = "#0000FF"; break;
			case 'b': 
			case 'h': 
			case 'j': this.Div.style.color = "#00AA00"; break;
			case 'g': this.Div.style.color = "#FF0000";break;
			
		}
	}
	
	// @todo: style auslagern
	var cl = "class='Tile_Default'";
	if (this.Game.TileSet["DivClass"]) cl = "class='" + this.Game.TileSet["DivClass"] + "'";
	var arrSt = [
		"top: " + ttop + "px",
		"left: " + tleft + "px",
		"position: absolute"		
	];
	this.Div.innerHTML = "<div " + cl + " id='TileInnerDiv[" + this.Id + "]' style='" + arrSt.join(";") + "'>"
		+ t + "</div><img src='" + this.Game.TileSet["TileImage"] + "' width='" 
		+ this.Game.TileSet["TileWidth"] + "px' height='" + this.Game.TileSet["TileHeight"] + "px' />";
	//alert(this.Div.innerHTML);
	this.FrameDiv.style.position = "absolute";
	this.FrameDiv.style.top = this.Game.TileSet["FrameTop"] + "px";
	this.FrameDiv.style.left = this.Game.TileSet["FrameLeft"] + "px";
	this.FrameDiv.style.width = this.Game.TileSet["FrameWidth"] + "px";
	this.FrameDiv.style.height = this.Game.TileSet["FrameHeight"] + "px";
	this.Div.appendChild(this.FrameDiv);	
	this.Div.style.display = "block";	
  },
  
  isSelectable: function() {
	return this.Space.isSelectable();		  	
  },
  
  isLoose: function() {
  	return this.Space.isLoose();
  },
  
  click: function() {
  	this.Game.clickTile(this);
  },
  
  toggleHighlight: function(on, highlight) {
  	var hl = "solid 2px #FF0000";
  	if (this.Game.TileSet["Highlight"]) hl = this.Game.TileSet["Highlight"]; 
  	if (!highlight) highlight = hl; 
  	if (on) {  
  		this.FrameDiv.style.border = highlight;		
  	} else {
  		this.FrameDiv.style.border = "";
  	}
  	this.Highlighted = on;
  },
  
  hint: function(on) {
  	var hl = "solid 2px #AA00AA";
  	if (this.Game.TileSet["Hint"]) hl = this.Game.TileSet["Hint"]; 
  	if (on) {  
  		this.FrameDiv.style.border = hl;		
  	} else {
  		if (this.Highlighted) {
  			this.toggleHighlight(true);
  		} else {
  			this.FrameDiv.style.border = "";
  		}
  	}
  },
  
  toggleHover: function(on, highlight) {
  	if (this.Highlighted) return;
  	var hl = "dashed 2px #FFC001";
  	if (this.Game.TileSet["Hover"]) hl = this.Game.TileSet["Hover"]; 
  	
  	if (!highlight) highlight = hl; 
  	if (on) {
  		this.FrameDiv.style.border = highlight;		
  	} else {
  		this.FrameDiv.style.border = "";
  	}
  }
});

var MjGame = Class.create({
  initialize: function(tileSet) {
  	this.Grid = new MjGrid(this);
  	this.Tiles = [];
  	this.Selection1 = null;
  	this.Selection2 = null;
  	this.UndoStack = [];
  	this.Timer = new PeriodicalExecuter(timer, 0.2);
  	this.StartTime = new Date();
  	this.GameId = 0;
  	this.TileSet = tileSet;
  	this.TilePrefs = TilePrefs['RGB']; 
  	this.Type = -1;
  	this.Penalties = 0;
  	this.RemovedTypes = [];
  	this.PauseTime = 0;
  	this.PauseStart = null;
  	this.GameTypes = [
  		{name: 'Test', init: this.Grid.initTest}, 
  		{name: 'Turtle', init: this.Grid.initTurtle}, 
  		{name: 'Easy', init: this.Grid.initEasy},
  		{name: 'Logo', init: this.Grid.initLogo},
  		{name: 'Kids', init: this.Grid.initKids},
  		{name: 'Mini', init: this.Grid.initEasy}
  	];
  	this.Preferences = {};  	
  },
    
  init: function(nGameType) {
  	this.Type = nGameType;
  	this.reloadPreferences(nGameType);
  	this.Grid.init(this.GameTypes[nGameType]["init"]);
    this.Grid.draw();
	for (var i=0; i<this.Grid.getSize(); i++) {
		this.Tiles.push(new MjTile(this, i, Math.floor(i / 4)));		
	}
	for (var i=0; i<this.Tiles.length; i++) {
		this.Grid.placeTile(i, this.Tiles[i]);
	}
	$('DivTilesLeft').innerHTML = this.Tiles.length;    
  },
  
  initLogo: function() {
  	var nGameType = 3;
  	this.Type = nGameType; 
	this.Grid.init(this.GameTypes[nGameType]["init"]);
    this.Grid.draw();
	
	this.Tiles.push(new MjTile(this, 11, 'g'));		
	this.Tiles.push(new MjTile(this, 12, 'g'));		
	this.Tiles.push(new MjTile(this, 13, 'g'));
	this.Tiles.push(new MjTile(this, 0, 'W'));		
	this.Tiles.push(new MjTile(this, 1, 'e'));		
	this.Tiles.push(new MjTile(this, 2, 'b'));		
	this.Tiles.push(new MjTile(this, 3, 'M'));		
	this.Tiles.push(new MjTile(this, 4, 'a'));		
	this.Tiles.push(new MjTile(this, 5, 'h'));		
	this.Tiles.push(new MjTile(this, 6, 'j'));		
	this.Tiles.push(new MjTile(this, 7, 'o'));		
	this.Tiles.push(new MjTile(this, 8, 'n'));
	this.Tiles.push(new MjTile(this, 9, 'g'));		
	this.Tiles.push(new MjTile(this, 10, 'g'));
			
				
	for (var i=0; i<this.Tiles.length; i++) {
		this.Grid.placeTile(i, this.Tiles[i]);
	}
	$('DivTilesLeft').innerHTML = this.Tiles.length;    
  },
  
  resetUndo: function() {
  	this.UndoStack = [];
  	$('BtnUndo').disable();
  },
  
  shuffle: function() {
	if (this.Selection1) {
		this.Selection1.toggleHighlight(false);				
		this.Selection1 = null;
	}
	
	// push all remaining tiles and their grid spaces to arrays  	
	var arrTiles = [];
  	var arrSpaces = [];
  	for (var i=0; i<this.Tiles.length; i++) {
  		if (!this.Tiles[i].Removed) {
  			arrTiles.push(i);
			arrSpaces.push(this.Tiles[i].Space.Id);  			
  		}
  	}
  	
  	// choose a random tile from the stack, remove it and place it in the next grid space    	
	for (var i=0; i<arrSpaces.length; i++) {
		var j = Math.floor(Math.random() * arrTiles.length);
		var s = arrTiles.splice(j, 1);
		this.Grid.placeTile(arrSpaces[i], this.Tiles[s]); 	
	}
	this.resetUndo();
	this.showPairs();		
  },
  
  draw: function() {
  	this.Tiles.each(function(tile) {
  		tile.draw();
  	});
  },
  
  setPreference: function(pref, val) {
  	this.Preferences[pref] = val;
  	$('Pref' + pref).value = val;	
  },
  
  reloadPreferences: function(nGameType) {
	var pref = xGetCookie('Preferences');
	if (pref) {	
		this.Preferences = pref.evalJSON(true);		
	} else {
		this.Preferences = DefaultPrefs[nGameType];
		if (!this.Preferences) this.Preferences = DefaultPrefs['*']; 
	}
	
	if( $('PrefColors') )
	{
		$('PrefColors').value = this.Preferences['Colors'];
		$('PrefTileset').value = this.Preferences['Tileset'];
	}
  },
  
  hint: function() {
  	var bFound = false;
	for (var i=0; i<this.Tiles.length; i++) {
		if (!this.Tiles[i].Removed && this.Tiles[i].isSelectable()) {
			for (var j=i+1; j<this.Tiles.length; j++) {
				if (!this.Tiles[j].Removed && this.Tiles[j].isSelectable() && this.Tiles[i].TileType == this.Tiles[j].TileType) {
					this.Tiles[i].hint(true);
					this.Tiles[j].hint(true);
					
					setTimeout("g.Tiles[" + i + "].hint(false)", 400);
					setTimeout("g.Tiles[" + j + "].hint(false)", 400);
					bFound = true;
					break;															
				}
			}
		}
		if (bFound) break;
	}
	this.Penalties += PENALTY_HINT;			  	
  },
  
  countPairs: function() {
  	var count = 0;
	for (var i=0; i<this.Tiles.length; i++) {
		if (!this.Tiles[i].Removed && this.Tiles[i].isSelectable()) {
			for (var j=i+1; j<this.Tiles.length; j++) {
				if (!this.Tiles[j].Removed && this.Tiles[j].isSelectable() && this.Tiles[i].TileType == this.Tiles[j].TileType) {
					count++;															
				}
			}
		}
	}
	return count;
  },
  
  getPairs: function() {
  	var tFree = this.getFreeTiles();
  	var ret = [];
  	for (var i=0; i<tFree.size(); i++) {
  		for (var j=i+1; j<tFree.size(); j++) {
			if (tFree[i].TileType == tFree[j].TileType) ret.push([tFree[i], tFree[j]]);  			
  		}
  	}
  	return ret;
  },
  
  getGroups: function() {
  	var tFree = this.getFreeTiles();
  	var ret = [];
  	for (var i=0; i<tFree.size(); i++) {
  		if (!ret[tFree[i].TileType]) ret[tFree[i].TileType] = []; 	
  		ret[tFree[i].TileType].push(tFree[i]);
  	}
  	return ret;
  },
  
  getFreeTiles: function() {
  	var ret = [];
  	for (var i=0; i<this.Tiles.length; i++) {
		if (!this.Tiles[i].Removed && this.Tiles[i].isSelectable()) ret.push(this.Tiles[i]);
	}	
	return ret;
  },
  
  showPairs: function() {
  	var remPairs = this.countPairs();
  	$('DivPairsLeft').innerHTML = remPairs;
  	if (remPairs == 0) {
  		$('BtnHint').disable();
		$('DivPairsLeft').style.fontWeight = "900";
		$('DivPairsLeft').style.color = "#FF0000";
  	} else {
  		$('BtnHint').enable();
  		$('DivPairsLeft').style.fontWeight = "500";
  		$('DivPairsLeft').style.color = "#000000";
  	} 
  },
  
  getLeftTiles: function() {
  	var ret = [];
  	// @todo: rrrreally inefficient
  	for (var i=0; i<this.Tiles.length; i++) {
  		if (!this.Tiles[i].Removed) ret.push(this.Tiles[i]);
  	}
	return ret;
  },
  
  countLeftTiles: function() {
  	return this.getLeftTiles().size();  	
  },
    
  showLeftTiles: function() {
  	$('DivTilesLeft').innerHTML = this.countLeftTiles(); 
  },
  
  finishGame: function() {
  	var dtNow = new Date();
	var diff = dtNow.getTime() - this.StartTime.getTime();
	this.Timer.stop();
	$('BtnReshuffle').disable();
	$('BtnUndo').disable();
	$('BtnHint').disable();
	$('BtnPause').disable();
	ajax.finishGame(this.GameId, diff, this.Penalties - this.PauseTime);
  },
  
  isSolvable: function() {
  	var lt = 0;
  	var bRem = true;
  	while (lt = this.countLeftTiles() > 0 && bRem) {
  		
  		var pLeft = this.countPairs();
  		if (pLeft == 0) break;
  		
  		bRem = false;
  		// first remove groups where all tiles are selectable
  		var arrGroups = this.getGroups();
  		for (var i=0; i<arrGroups.size(); i++) {
			if (bRem) break;
			if (!arrGroups[i]) continue;
			// remove groups where all tiles are selectable  
			if (arrGroups[i].size() == 4) {
				arrGroups[i][0].toggleHighlight(true);
				arrGroups[i][1].toggleHighlight(true);
				arrGroups[i][2].toggleHighlight(true);
				arrGroups[i][3].toggleHighlight(true);
				
				this.Selection1 = arrGroups[i][0];
				this.Selection2 = arrGroups[i][1];
				this.removeSelection();
				this.Selection1 = arrGroups[i][2];
				this.Selection2 = arrGroups[i][3];
				this.Selection1.toggleHighlight(true);
				this.Selection2.toggleHighlight(true);
				this.removeSelection();
				bRem = true;
			// remove groups where 3 are selectable and 1 of them is 'loose'								  				
			} else if (arrGroups[i].size() == 3) {
				for (var j=0; j<3; j++) {
					if (arrGroups[i][0].isLoose()) {
						this.Selection1 = arrGroups[i][1];
						this.Selection2 = arrGroups[i][2];
						this.removeSelection();
						bRem = true;
						break;														
					} else if (arrGroups[i][1].isLoose()) {
						this.Selection1 = arrGroups[i][0];
						this.Selection2 = arrGroups[i][2];
						this.removeSelection();
						bRem = true;
						break;														
					} else if (arrGroups[i][2].isLoose()) {
						this.Selection1 = arrGroups[i][0];
						this.Selection2 = arrGroups[i][1];
						this.removeSelection();
						bRem = true;
						break;														
					}
				}				
			// remove groups where 2 are selectable and another pair is already removed								  				
			} else if (arrGroups[i].size() == 2 && this.RemovedTypes[i] > 0) {
				this.Selection1 = arrGroups[i][0];
				this.Selection2 = arrGroups[i][1];
				this.removeSelection();
				bRem = true;						
			}
		}
		
		if (!bRem) {
			var arrp = this.getPairs();
			if (arrp.size() > 0) {
				var blockMax = 0;
				var blockMaxIdx = 0;
				for (var i=0; i<arrp.size(); i++) {
					var b = arrp[i][0].Space.countBlocked() + arrp[i][1].Space.countBlocked();
					if (b>blockMax) {
						blockMax = b;
						blockMaxIdx = i;
					}
				}
				arrp[blockMaxIdx][0].toggleHighlight(true);
				arrp[blockMaxIdx][1].toggleHighlight(true);
				
				this.Selection1 = arrp[blockMaxIdx][0];
				this.Selection2 = arrp[blockMaxIdx][1];
				this.removeSelection();
				bRem = true;
			}  						
		}  		
	}
	var ret = this.countLeftTiles() == 0;
	while (this.undo(true)); 	
	return ret; 
  },
  
  undo: function(bSuppressPenalty) {
  	if (this.UndoStack.length == 0) return false;
  	
  	if (this.Selection1) {
		this.Selection1.toggleHighlight(false);				
		this.Selection1 = null;
	}	
	var p = this.UndoStack.pop();
  	this.Grid.placeTile(p[0].Space.Id, p[0]);
  	p[0].draw();
  	this.Grid.placeTile(p[1].Space.Id, p[1]);
  	p[1].draw();
  	p[0].toggleHighlight(false);
  	p[1].toggleHighlight(false);
  	this.showPairs();
  	this.showLeftTiles();  		  	
  	if (this.UndoStack.length == 0) this.resetUndo();
  	if (!bSuppressPenalty) this.Penalties += PENALTY_UNDO;
  	$('BtnReshuffle').enable();
  	return true;
  },
  
  removeSelection: function() {
  	if (!this.Selection1 || !this.Selection2) return false;
  	
  	var p = [this.Selection1, this.Selection2]; 				
	this.UndoStack.push(p);
	this.Grid.removeTile(this.Selection1);
	this.Grid.removeTile(this.Selection2);
	if (!this.RemovedTypes[this.Selection1.TileType]) this.RemovedTypes[this.Selection1.TileType] = 0;
	this.RemovedTypes[this.Selection1.TileType] += 2;
	this.Selection1 = null;
	this.Selection2 = null;
	$('BtnUndo').enable();
	if (!this.canReshuffle()) $('BtnReshuffle').disable();
	return true;				
  },
  
  canReshuffle: function() {
  	var arrRem = this.getLeftTiles(); 
  	if (arrRem.size() <= 2) return false;
  	if (arrRem.size() == 4) {
  		var nBelow = 0;
  		for (var i=0; i<4; i++) {
  			if (arrRem[i].Space.hasBelow()) nBelow++;
  		}
  		return nBelow != 3;
  	}
  	return true;
  },
  
  pause: function() {
	this.PauseStart = new Date();  	
	$('GameDiv').hide();
	$('StatDiv').hide();
	showModalDialog('PauseDiv', T['Pause'], 200, 100, 10);	  	
  },
  
  resume: function() {
  	var pend = new Date();
  	this.PauseTime += pend.getTime() - this.PauseStart.getTime();
  	hideModalDialog();
  	$('GameDiv').show();
  	$('StatDiv').show();
  },
  
  reshuffle: function() {
  	if (!this.canReshuffle()) return false;
  	
  	var bSolved = false;
	for (t=0; t<10; t++) {
  		this.shuffle();
  		if (this.countLeftTiles() < 50) {
  			if (this.isSolvable()) {
  				bSolved = true;  		
  				break;
  			}
  		} else {
  			bSolved = true;
  			break;
  		}
  	}
  	if (!bSolved && this.countLeftTiles() < 8) {
  		alert(T["NoSolution"]);
  	}
  	this.draw();
  	return true;
  },
  
  clickTile: function(tile) {
  	if (tile == this.Selection1) {
  		tile.toggleHighlight(false);
  		this.Selection1 = null;
  		return;
  	}
  	if (tile.isSelectable()) {
  		if (!this.Selection1) {
			tile.toggleHighlight(true);
			this.Selection1 = tile;
		} else {
			if (!this.Selection2 && tile.TileType == this.Selection1.TileType ) {
				tile.toggleHighlight(true);
				this.Selection2 = tile;
				this.removeSelection();
				this.showLeftTiles();
				this.showPairs();
				
				if (this.countLeftTiles() == 0) this.finishGame();   				
			} else {
				this.Selection1.toggleHighlight(false);
				if (this.Selection2) this.Selection2.toggleHighlight(false);
				this.Selection1 = tile;
				tile.toggleHighlight(true);  																	
			}
		} 		 	  		
  	}
  }
  
});

function timer(pe) {
	var dtNow = new Date();
	var diff = dtNow.getTime() - g.StartTime.getTime() + g.Penalties - g.PauseTime;
	var min = "" + Math.floor(diff / 60000);
	if (min.length == 1) min = "0" + min;	
  	var sec = "" + Math.floor((diff % 60000) / 1000);
  	if (sec.length == 1) sec = "0" + sec;	
  	$('DivTime').innerHTML = min + ":" + sec;
};

function showLogin(signup, hsEmbed) {
	$('BtnSignupSubmit').enable();
	$('BtnCancelSignup').enable();
	
	if (signup) {
		if (hsEmbed) {
			updateID('HsSignupDiv', $('SignupDiv').innerHTML);
			$('HsSignupDiv').show();
		} else {
			if ($('LoginDiv').visible()) hideModalDialog('LoginDiv');
			showModalDialog('SignupDiv', "", 310, 285, 10);
		}
	} else {
		if ($('SignupDiv').visible()) hideModalDialog('SignupDiv');
		if ($('HsSignupDiv')) {
			if ($('HsSignupDiv').visible()) $('HsSignupDiv').hide();
		}
		showModalDialog('LoginDiv', "", 310, 220, 10); 
	}
}

function init(gameType) {
	if (!$('BtnRestart')) return;
	
	$('BtnRestart').disable();
	EnableLevelButtons(false);
	
	$('GameDiv').show();
	var tileRes = "RMini";
	if ($('GameDiv').getWidth() > 500) {
		tileRes = "RDefault";
	}
	if ($('GameDiv').getWidth() > 700) {
		tileRes = "R1280";
	}
	
	// delete old game if its still running
	if (g) {
		if (g.GameId > 0) {
			if (gameType < 0) gameType = g.Type;
			ajax.destroyGame(g.GameId);
			g.Timer.stop();
		}
	}
	var tileSet = TileSets[gameType];
	if (!tileSet) tileSet = TileSets['*'];
	if( tileSet[tileRes] )
	{
		tileSet = tileSet[tileRes];
	} else
	{
		tileRes = "RDefault";
		tileSet = tileSet[tileRes];		
	}
	$('GameDiv').innerHTML = "";
	$('HighscoreEnvDiv').hide();
	$('StatDiv').show();
	disableSelection($('GameDiv'));	
	g = new MjGame(tileSet);
	g.init(gameType);
	//g.initLogo();
	g.shuffle();
	g.draw();
	$('BtnReshuffle').enable();
	$('BtnUndo').disable();
	$('BtnPause').enable();
	$('StatDiv').show();
	xSetCookie("GameLevel", gameType);
	lastGameType = gameType;
	ajax.startGame(gameType, 0);
	ajax.loadMiniHighscore(gameType);
	if( myjonggindex >= 0 )
	{
		var gdPos = getElementPosition($('GameDiv'));
		$('MyJonggImage').src = myjonggimages[myjonggindex].thumbnail;
		var w = myjonggimages[myjonggindex].width;
		if( gameType != 1)
		{
			if( w > 350 ) w = 350;
			$('MyJonggImage').style.width = w + 'px';
		}
		$('MyJonggImage').style.left = (gdPos[0] + $('GameDiv').getWidth() / 2 - w / 2) + 'px';
		$('MyJonggImage').style.top = (gdPos[1] + $('GameDiv').getHeight() / 2 - myjonggimages[myjonggindex].height / 2 - 10) + 'px';
		
		myjonggindex = (myjonggindex+1) % myjonggimages.size();
	}	
};

function disableSelection(element) {
	element.onselectstart = function() {
        return false;
    };
    element.unselectable = "on";
    element.style.MozUserSelect = "none";
    element.style.cursor = "default";
}

function signup() {
	$('BtnSignupSubmit').disable();
	$('BtnCancelSignup').disable();
	ajax.signup($('SignupForm').serialize(true));
}

function login() {
	ajax.login($('LoginForm').serialize(true));
}

function cancelSignup() {
	if ($('SignupDiv').visible()) hideModalDialog('SignupDiv');
	if ($('HsSignupDiv').visible()) $('HsSignupDiv').hide();
	DontSignup = true;
}

function cancelLogin() {
	if ($('LoginDiv').visible()) hideModalDialog('LoginDiv');
	if ($('HsSignupDiv')) {
		if ($('HsSignupDiv').visible()) $('HsSignupDiv').hide();
	}
	DontSignup = true;
}

function savePreferences() {
	// only store locally
	var h = $('PreferencesForm').serialize(true);
	xSetCookie('Preferences', Object.toJSON(h));
	hideModalDialog();
	if (g) {
		g.reloadPreferences(lastGameType);
		g.draw();
	}
}

function CreateTeam()
{
	if( $('TeamName').value.trim() == "" ) return;
	if( !confirm(T['CreateTeam'].replace('[teamname]', $('TeamName').value)) ) return;
	ajax.createTeam($('TeamForm').serialize(true));
}

function QuitTeam(teamId, strTeamName)
{
	var strConfirm = T['TeamQuitConfirm'].replace('[teamname]', strTeamName); 
	if( !confirm(strConfirm) ) return;	
	ajax.quitTeam(teamId);
}
 
function LockTeam(teamId, strTeamName, nMemberCount)
{
	if( nMemberCount < 2 )
	{
		alert(T['TeamNotEnoughMembers']);
		return;
	}
	
	var strConfirm = T['TeamLockConfirm'].replace('[teamname]', strTeamName); 
	if( !confirm(strConfirm) ) return;	
	ajax.lockTeam(teamId);
}
 
function KickTeamUser(strTeamName, teamId, strUser, teamUserId, teamInvId)
{
	if( !confirm(T['TeamKickUser'].replace('[teamname]', strTeamName).replace('[teamuser]', strUser)) ) return;
	ajax.kickTeamUser(teamId, teamUserId, teamInvId);
}
 
function SubmitTeamForm()
{
	ajax.submitTeamForm($('TeamForm').serialize(true));
}
  
function ConfirmInvitation(strTeamName, teamId)
{
	if( !confirm(T['TeamConfirmInvitation'].replace('[teamname]', strTeamName)) ) return;
	ajax.confirmInvitation(teamId);
}

function RejectInvitation(strTeamName, teamId)
{
	if( !confirm(T['TeamRejectInvitation'].replace('[teamname]', strTeamName)) ) return;
	ajax.quitTeam(teamId);	
}

function EnableLevelButtons(on) {
	var lvlBtn = document.getElementsByName("LevelButton");
	for (var i=0; i<lvlBtn.length; i++) {
		if (on) {
			$(lvlBtn[i].id).enable();
		} else {
			$(lvlBtn[i].id).disable();
		}
	}
}