// directions
function DirectionX() {
	var m_x = -1; var m_y = 0; var m_z = 0;

	this.Begin = function () {
		m_x = -1; m_y = 0; m_z = 0;
	}
	this.Next = function () {
		m_z++;
		if (m_z >= 4) {
			m_y++; m_z = 0;
		}
	}
	this.End = function () {
		return (m_y >= 4);
	}
	this.GetRow = function (field) {
		var row = new Array(4);
		for (var i = 0; i < 4; i++)
			row[i] = field[i][m_y][m_z];
		return row;
	}
	this.Increment = function (row) {
		for (var i = 0; i < 4; i++)
			g_BestPlace.m[i][m_y][m_z] += row[i];
	}

	this.GetIndices = function() {
		var out = new Array(4);
		for (var i = 0; i < 4; i++) {
			out[i] = new Array(3);
			out[i][0] = i;
			out[i][1] = m_y;
			out[i][2] = m_z;
		}
		return out;
	}
}

function DirectionY() {
	var m_x = 0; var m_y = -1; var m_z = 0;

	this.Begin = function () {
		m_x = 0; m_y = -1; m_z = 0;
	}
	this.Next = function () {
		m_z++;
		if (m_z >= 4) {
			m_x++; m_z = 0;
		}
	}
	this.End = function () {
		return (m_x >= 4);
	}
	this.GetRow = function (field) {
		var row = new Array(4);
		for (var i = 0; i < 4; i++)
			row[i] = field[m_x][i][m_z];
		return row;
	}
	this.Increment = function (row) {
		for (var i = 0; i < 4; i++)
			g_BestPlace.m[m_x][i][m_z] += row[i];
	}

	this.GetIndices = function() {
		var out = new Array(4);
		for (var i = 0; i < 4; i++) {
			out[i] = new Array(3);
			out[i][0] = m_x;
			out[i][1] = i;
			out[i][2] = m_z;
		}
		return out;
	}
}

function DirectionZ() {
	var m_x = 0; var m_y = 0; var m_z = -1;

	this.Begin = function () {
		m_x = 0; m_y = 0; m_z = -1;
	}
	this.Next = function () {
		m_y++;
		if (m_y >= 4) {
			m_x++; m_y = 0;
		}
	}
	this.End = function () {
		return (m_x >= 4);
	}
	this.GetRow = function (field) {
		var row = new Array(4);
		for (var i = 0; i < 4; i++)
			row[i] = field[m_x][m_y][i];
		return row;
	}
	this.Increment = function (row) {
		for (var i = 0; i < 4; i++)
			g_BestPlace.m[m_x][m_y][i] += row[i];
	}

	this.GetIndices = function() {
		var out = new Array(4);
		for (var i = 0; i < 4; i++) {
			out[i] = new Array(3);
			out[i][0] = m_x;
			out[i][1] = m_y;
			out[i][2] = i;
		}
		return out;
	}
}

function DirectionXY() {
	var m_x = -1; var m_y = -1; var m_z = 0;
	var m_dir = 0; // bool

	this.Begin = function () {
		m_x = -1; m_y = -1; m_z = 0; m_dir = 0;
	}
	this.Next = function () {
		m_z++;
		if (m_z >= 4) {
			m_dir++; m_z = 0;
		}
	}
	this.End = function () {
		return (m_dir >= 2);
	}
	this.GetRow = function (field) {
		var row = new Array(4);
		for (var i = 0; i < 4; i++) {
			if (m_dir == 0)
				row[i] = field[i][i][m_z];
			else
				row[i] = field[i][3-i][m_z];
		}
		return row;
	}
	this.Increment = function (row) {
		for (var i = 0; i < 4; i++) {
			if (m_dir == 0)
				g_BestPlace.m[i][i][m_z] += row[i];
			else
				g_BestPlace.m[i][3-i][m_z] += row[i];
		}
	}

	this.GetIndices = function() {
		var out = new Array(4);
		for (var i = 0; i < 4; i++) {
			out[i] = new Array(3);
			out[i][0] = i;
			if (m_dir == 0)
				out[i][1] = i;
			else
				out[i][1] = 3-i;
			out[i][2] = m_z;
		}
		return out;
	}
}

function DirectionXZ() {
	var m_x = -1; var m_y = 0; var m_z = -1;
	var m_dir = 0; // bool

	this.Begin = function () {
		m_x = -1; m_y = 0; m_z = -1; m_dir = 0;
	}
	this.Next = function () {
		m_y++;
		if (m_y >= 4) {
			m_dir++; m_y = 0;
		}
	}
	this.End = function () {
		return (m_dir >= 2);
	}
	this.GetRow = function (field) {
		var row = new Array(4);
		for (var i = 0; i < 4; i++) {
			if (m_dir == 0)
				row[i] = field[i][m_y][i];
			else
				row[i] = field[i][m_y][3-i];
		}
		return row;
	}
	this.Increment = function (row) {
		for (var i = 0; i < 4; i++) {
			if (m_dir == 0)
				g_BestPlace.m[i][m_y][i] += row[i];
			else
				g_BestPlace.m[i][m_y][3-i] += row[i];
		}
	}

	this.GetIndices = function() {
		var out = new Array(4);
		for (var i = 0; i < 4; i++) {
			out[i] = new Array(3);
			out[i][0] = i;
			out[i][1] = m_y;
			if (m_dir == 0)
				out[i][2] = i;
			else
				out[i][2] = 3-i;
		}
		return out;
	}
}

function DirectionYZ() {
	var m_x = 0; var m_y = -1; var m_z = -1;
	var m_dir = 0; // bool

	this.Begin = function () {
		m_x = 0; m_y = -1; m_z = -1; m_dir = 0;
	}
	this.Next = function () {
		m_x++;
		if (m_x >= 4) {
			m_dir++; m_x = 0;
		}
	}
	this.End = function () {
		return (m_dir >= 2);
	}
	this.GetRow = function (field) {
		var row = new Array(4);
		for (var i = 0; i < 4; i++) {
			if (m_dir == 0)
				row[i] = field[m_x][i][i];
			else
				row[i] = field[m_x][i][3-i];
		}
		return row;
	}
	this.Increment = function (row) {
		for (var i = 0; i < 4; i++) {
			if (m_dir == 0)
				g_BestPlace.m[m_x][i][i] += row[i];
			else
				g_BestPlace.m[m_x][i][3-i] += row[i];
		}
	}

	this.GetIndices = function() {
		var out = new Array(4);
		for (var i = 0; i < 4; i++) {
			out[i] = new Array(3);
			out[i][0] = m_x;
			out[i][1] = i;
			if (m_dir == 0)
				out[i][2] = i;
			else
				out[i][2] = 3-i;
		}
		return out;
	}
}

function DirectionXYZ() {
	var m_dir = 0; // bool

	this.Begin = function () {
		m_dir = 0;
	}
	this.Next = function () {
		m_dir++;
	}
	this.End = function () {
		return (m_dir >= 4);
	}
	this.GetRow = function (field) {
		var row = new Array(4);
		for (var i = 0; i < 4; i++) {
			if (m_dir == 0)
				row[i] = field[i][i][i];
			else if (m_dir == 1)
				row[i] = field[i][i][3-i];
			else if (m_dir == 2)
				row[i] = field[i][3-i][i];
			else
				row[i] = field[i][3-i][3-i];
		}
		return row;
	}
	this.Increment = function (row) {
		for (var i = 0; i < 4; i++) {
			if (m_dir == 0)
				g_BestPlace.m[i][i][i] += row[i];
			else if (m_dir == 1)
				g_BestPlace.m[i][i][3-i] += row[i];
			else if (m_dir == 2)
				g_BestPlace.m[i][3-i][i] += row[i];
			else
				g_BestPlace.m[i][3-i][3-i] += row[i];
		}
	}

	this.GetIndices = function() {
		var out = new Array(4);
		for (var i = 0; i < 4; i++) {
			out[i] = new Array(3);
			out[i][0] = i;
			if (m_dir == 0 || m_dir == 1)
				out[i][1] = i;
			else
				out[i][1] = 3-i;
			if (m_dir == 0 || m_dir == 2)
				out[i][2] = i;
			else
				out[i][2] = 3-i;
		}
		return out;
	}
}

// 3D matrix object
function Matrix()
{
	this.m = new Array(0);

	this.Resize = function(size, data) {
		this.m = new Array(size);
		for (var i = 0; i < size; i++) {
			this.m[i] = new Array(size);
			for (var j = 0; j < size; j++) {
				this.m[i][j] = new Array(size);
				for (var k = 0; k < size; k++) {
					this.m[i][j][k] = data;
				}
			}
		}
	}

	this.Reset = function(data) {
		for (var i = 0; i < this.m.length; i++) {
			for (var j = 0; j < this.m[i].length; j++) {
				for (var k = 0; k < this.m[i][j].length; k++) {
					this.m[i][j][k] = data;
				}
			}
		}
	}
}

g_BestPlace = new Matrix();
g_BestPlace.Resize(4, 0);

function FIR3D()
{
	// the play field
	var m_Field = new Matrix();
	m_Field.Resize(4, 0);

	// all directions
	var m_dir = new Array(7);
	m_dir[0] = new DirectionX();
	m_dir[1] = new DirectionY();
	m_dir[2] = new DirectionZ();
	m_dir[3] = new DirectionXY();
	m_dir[4] = new DirectionXZ();
	m_dir[5] = new DirectionYZ();
	m_dir[6] = new DirectionXYZ();

	// last move made by computer
	var m_LastMove = [-1, -1, -1];

	// indices of row that won
	var m_WinRow;

	// priviliged members
	this.Reset = function() {
		m_Field.Reset(0);
		m_LastMove = [-1, -1, -1];
		m_WinRow = 0;
	}

	this.IsEmpty = function(x, y, z) {
		return (m_Field.m[x][y][z] == 0);
	}

	this.GetLastMove = function() {
		return m_LastMove;
	}

	this.GetWinRow = function() {
		return m_WinRow;
	}

	this.PlayerMoves = function(x, y, z) {
		if (x >= 0 && x < 4 && y >= 0 && y < 4 && z >= 0 && z < 4 &&
			m_Field.m[x][y][z] == 0)
		{
			m_Field.m[x][y][z] = 1;
			if (CheckForWin(1))
				return 1;
			else
				return 0;
		}
		else {
			//alert("Cannot put a cross on coordinates (" + x + ", " + y + ", " + z + ")");
			return -1;
		}
	}

	this.ComputerMoves = function() {
		CalcBestPlace();
		var place = ChoosePlace();
		if (place.length != 3) {
			alert("CM: Size of array returned by ChoosePlace is wrong");
			return -1;
		}
		else if (place[0] < 0 || place[0] >= 4 ||
				place[1] < 0 || place[1] >= 4 ||
				place[2] < 0 || place[2] >= 4)
		{
			alert("CM: Indices returned by ChoosePlace out of range");
			return -1;
		}
		else if (m_Field.m[place[0]][place[1]][place[2]] != 0) {
			alert("CM: Place returned by ChoosePlace is already in use");
			return -1;
		}
		else {
			m_Field.m[place[0]][place[1]][place[2]] = 2;
			m_LastMove = place;
		}
		if (CheckForWin(2))
			return 1;
		else
			return 0;
	}

	this.GetHint = function() {
		// calculate best place
		g_BestPlace.Reset(0);
		for (var d = 0; d < 7; d++) {
			for (m_dir[d].Begin(); !m_dir[d].End(); m_dir[d].Next()) {
				var row = m_dir[d].GetRow(m_Field.m);
				var row2 = RowSolve(row, 2);
				m_dir[d].Increment(row2);
			}
		}
		// choose the best place
		var max = -1;
		var pos = new Array(3);

		for (var x = 0; x < 4; x++) {
			for (var y = 0; y < 4; y++) {
				for (var z = 0; z < 4; z++) {
					if (g_BestPlace.m[x][y][z] > max) {
						max = g_BestPlace.m[x][y][z];
						pos = [x, y, z];
					}
				}
			}
		}

		return pos;
	}

	// private members
	function CheckForWin(n) {
		var win = false;
		for (var d = 0; !win && d < 7; d++) {
			for (m_dir[d].Begin(); !win && !m_dir[d].End(); m_dir[d].Next()) {
				var row = m_dir[d].GetRow(m_Field.m);
				win = true;
				for (var k = 0; win && k < 4; k++) {
					if (row[k] != n)
						win = false;
				}
				if (win) {
					m_WinRow = m_dir[d].GetIndices();
				}
			}
		}
		return win;
	}

	function CalcBestPlace() {
		g_BestPlace.Reset(0);
		for (var d = 0; d < 7; d++) {
			for (m_dir[d].Begin(); !m_dir[d].End(); m_dir[d].Next()) {
				var row = m_dir[d].GetRow(m_Field.m);
				var row2 = RowSolve(row, 1);
				m_dir[d].Increment(row2);
			}
		}
	}

	function ChoosePlace() {
		var place = new Array(1); // can be extended later
		place[0] = new Array(3);

		var n = 0;
		var max = -1;

		for (var x = 0; x < 4; x++) {
			for (var y = 0; y < 4; y++) {
				for (var z = 0; z < 4; z++) {
					if (g_BestPlace.m[x][y][z] > max) {
						max = g_BestPlace.m[x][y][z];
						place[0] = [x, y, z];
						n = 1;
					}
					else if (g_BestPlace.m[x][y][z] == max) {
						place[n] = [x, y, z];
						n++;
					}
				}
			}
		}

		// choose one
		var r = Math.floor(Math.random() * n);
		return place[r];
	}

	function RowSolve(row, pl) {
		var value23 = 16100;
		var value22 = 900;
		var value21 = 90;
		var value20 = 9;
		var value13 = 4000;
		var value12 = 200;
		var value11 = 10;

		var com;
		if (pl == 1)
			com = 2;
		else
			com = 1;

		var done1 = false;
		var done2 = false;

		var out = new Array(4);

		// check for pl2 && pl1
		for (var i = 0; i < 4; i++) {
			out[i] = 0;
			if (row[i] == 1)
				done1 = true;
			if (row[i] == 2)
				done2 = true;
		}

		if (!done1 || !done2) {
			// assign points to places
			if (row[0] == pl) {
				if (row[1] == pl) {
					if (row[2] == pl) // row[3] == 0
						out[3] = value13;
					else if (row[3] == pl) // row[2] == 0
						out[2] = value13;
					else { // row[2] = [3] == 0
						out[2] = value12;
						out[3] = value12;
					}
				}
				else { // row[1] == 0
					if (row[2] == pl) {
						if (row[3] == pl)
							out[1] = value13;
						else { // row[3] == 0
							out[1] = value12;
							out[3] = value12;
						}
					}
					else { // row[2] == 0
						if (row[3] == pl) {
							out[1] = value12;
							out[2] = value12;
						}
						else { // row[3] == 0
							out[1] = value11;
							out[2] = value11;
							out[3] = value11;
						}
					}
				}
			}
			else if (row[0] == com) {
				if (row[1] == com) {
					if (row[2] == com) // row[3] == 0
						out[3] = value23;
					else if (row[3] == com) // row[2] == 0
						out[2] = value23;
					else { // row[2] = [3] == 0
						out[2] = value22;
						out[3] = value22;
					}
				}
				else { // row[1] == 0
					if (row[2] == com) {
						if (row[3] == com)
							out[1] = value23;
						else { // row[3] == 0
							out[1] = value22;
							out[3] = value22;
						}
					}
					else { // row[2] == 0
						if (row[3] == com) {
							out[1] = value22;
							out[2] = value22;
						}
						else { // row[3] == 0
							out[1] = value21;
							out[2] = value21;
							out[3] = value21;
						}
					}
				}
			}
			else { // row[0] == 0
				if (row[1] == pl) {
					if (row[2] == pl) {
						if (row[3] == pl)
							out[0] = value13;
						else { // row[3] == 0
							out[0] = value12;
							out[3] = value12;
						}
					}
					else { // row[2] == 0
						if (row[3] == pl) {
							out[0] = value12;
							out[2] = value12;
						}
						else { // row[3] == 0
							out[0] = value11;
							out[2] = value11;
							out[3] = value11;
						}
					}
				}
				else if (row[1] == com) {
					if (row[2] == com) {
						if (row[3] == com)
							out[0] = value23;
						else { // row[3] == 0
							out[0] = value22;
							out[3] = value22;
						}
					}
					else { // row[2] == 0
						if (row[3] == com) {
							out[0] = value22;
							out[2] = value22;
						}
						else { // row[3] == 0
							out[0] = value21;
							out[2] = value21;
							out[3] = value21;
						}
					}
				}
				else { // row[1] == 0
					if (row[2] == pl) {
						if (row[3] == pl) {
							out[0] = value12;
							out[1] = value12;
						}
						else { // row[3] == 0
							out[0] = value11;
							out[1] = value11;
							out[3] = value11;
						}
					}
					else if (row[2] == com) {
						if (row[3] == com) {
							out[0] = value22;
							out[1] = value22;
						}
						else { // row[3] == 0
							out[0] = value21;
							out[1] = value21;
							out[3] = value21;
						}
					}
					else { // row[2] == 0
						if (row[3] == pl) {
							out[0] = value11;
							out[1] = value11;
							out[2] = value11;
						}
						else if (row[3] == com) {
							out[0] = value21;
							out[1] = value21;
							out[2] = value21;
						}
						else { // row[3] == 0
							out[0] = value20;
							out[1] = value20;
							out[2] = value20;
							out[3] = value20;
						}
					}
				}
			}
		}

		return out;
	}
}
