window.onload = PageLoaded;
CreateStaticData ();

function PageLoaded () {
	GetObjectRefs ();
	GeneratePlayGrid ();
	GeneratePreviewGrid ();
	iHighScore = iGetStoredHighScore ();
	pHighScoreText.firstChild.nodeValue = iHighScore;
	Ready ();
}

function Ready () {
	pInstructions.style.display = 'block';
	document.onkeydown = Begin;
}

function Begin () {
	pInstructions.style.display = 'none';

	bRespondToKeys = false;
	bAccelerated = false;
	bPaused = false;	

	document.onkeydown = HandleKeyDown;
	document.onkeyup = HandleKeyUp;

	iLines = 0;
	iScore = 0;
	iLevel = 0;

	iNextShapeType = iRandom ( 6 );

	ScoreLines ( 0 );
	DropNewShape ();
}

// ---------------------------
// Core ticking-over functions
// ===========================

function KeepOnMoving () {
	if ( bTestDrawShape ( iCurrentShapeType, iCurrentShapeOrientation, iCurrentX, iCurrentY + 1 ) ) {
		DrawShape ( iCurrentShapeType, iCurrentShapeOrientation, iCurrentX, iCurrentY, sClearImageName, constBlockClear, false );
		if ( ! bPaused ) iCurrentY ++;
		DrawShape ( iCurrentShapeType, iCurrentShapeOrientation, iCurrentX, iCurrentY, aBlockImageNames [ iCurrentShapeType ], constBlockCurrentShape, true );
		if ( bAccelerated ) {
			iScore ++;
			DisplayData ( false );
		}
		oTimerID = setTimeout ( 'KeepOnMoving ();', iDropSpeed );
	} else {
		bRespondToKeys = false;
		FixShape ( iCurrentShapeType, iCurrentShapeOrientation, iCurrentX, iCurrentY );
		setTimeout ( 'RemoveLines ();', 10 );
	}
}

function DropNewShape () {
	iCurrentX = 3;
	iCurrentY = 2;
	iCurrentShapeType = iNextShapeType;
	iNextShapeType = iRandom ( 6 );
	DrawPreviewShape ( iNextShapeType );
	iCurrentShapeOrientation = 0;
	Decelerate ();
	oTimerID = setTimeout ( 'KeepOnMoving ();', 10 );
	bRespondToKeys = true;
}

// ------------------
// Dealing with lines
// ==================

function RemoveLines () {
	var iNewLines = 0;
	lRowLoop:
	for ( var i = Math.max ( iCurrentY - 2, 0 ); i < Math.min ( iCurrentY + 2, iPlayGridHeight ); i ++ ) {
		for ( var j = 0; j < iPlayGridWidth; j ++ ) {
			if ( pPlayGridTd ( j, i ).iStatus != constBlockDroppedShape ) {
				continue lRowLoop;
			}
		}
		pPlayGridTd ( 0, i ).iStatus = constBlockBeginLine;
		for ( var j = 0; j < iPlayGridWidth; j ++ ) {
			pPlayGridImg ( j, i ).src = sClearImageName;
		}
		iNewLines ++;
	}
	ScoreLines ( iNewLines );
	setTimeout ( 'CompressLines ();', 10 );
}

function ScoreLines ( iNewLines ) {
	// EVENT TO SIGNIFY NUMBER OF LINES
	if ( iLevel == 0 || Math.floor ( iLines / 10 ) != Math.floor ( ( iLines + iNewLines ) / 10 ) ) {
		iLevel ++;
		iDefaultDropSpeed = ( aLevelSpeeds.length > iLevel ? aLevelSpeeds[ iLevel - 1 ] : aLevelSpeeds[ aLevelSpeeds.length - 1 ] );
		// if ( window.pNewLevelSound && iLevel > 1 ) -> EVENT TO SIGNIFY NEW LEVEL
	}
	iLines += iNewLines;
	iScore += Math.round ( aLineScores [ iNewLines ] * iLevel );
	DisplayData ( true );
}

function DisplayData ( bAll ) {
	pScoreText.firstChild.nodeValue = iScore;
	if ( bAll ) {
		pLevelText.firstChild.nodeValue = iLevel;
		pLinesText.firstChild.nodeValue = iLines;
	}
}

function CompressLines () {
	var iCopyOffset = 0;
	var iClearLines = 0;
	for ( var i = iPlayGridHeight - 1; i >= 0; i -- ) {
		if ( pPlayGridTd ( 0, i ).iStatus == constBlockBeginLine ) {
			iCopyOffset ++;
		} else if ( iCopyOffset > 0 ) {
			var bLineIsClear = true;
			for ( var j = 0; j < iPlayGridWidth; j ++ ) {
				var pPGTFrom = pPlayGridTd ( j, i );
				var pPGTTo = pPlayGridTd ( j, i + iCopyOffset );
				pPGTTo.iStatus = pPGTFrom.iStatus;
				pPGTTo.style.backgroundColor = pPGTFrom.style.backgroundColor;
				pPlayGridImg ( j, i + iCopyOffset ).src = pPlayGridImg ( j, i ).src;
				if ( pPlayGridTd ( j, i ).iStatus != constBlockClear ) bLineIsClear = false;
			}
			if ( bLineIsClear ) iClearLines ++;
			if ( iClearLines >= iCopyOffset ) break;
		}
	}
	CheckAllClear ();
	CheckLoss ();
}

function CheckAllClear () {
	for ( var i = 0; i < iPlayGridWidth; i ++ ) {
		if ( pPlayGridTd ( i, iPlayGridHeight - 1 ).iStatus != constBlockClear ) return;
	}
	iScore += 1000 * iLevel;
	DisplayData ( false );
	// EVENT TO SIGNIFY ALL CLEAR
}

function CheckLoss () {
	for ( var i = 0; i < iPlayGridWidth; i ++ ) {
		if ( pPlayGridTd ( i, 3 ).iStatus == constBlockDroppedShape ) {
			if ( iScore > iHighScore ) StoreHighScore ( iScore );
			pInstImg.src = 'gameover.gif';
			pInstructions.style.display = 'block';
			return;
		}
	}
	DropNewShape ();
}

// -----------------------
// Handing keyboard events
// =======================

function HandleKeyDown ( e ) {
	if ( bRespondToKeys ) {
		if ( ! e ) e = window.event;
		var iKeyCode = e.keyCode;
		var sKeyAction = GetKeyAction ( iKeyCode );
		if ( ! bPaused ) {
			switch ( sKeyAction ) {
				case 'moveLeft':
					MoveLR ( -1 );
					break;
				case 'moveRight':
					MoveLR ( 1 );
					break;
				case 'rotateCW':
					Rotate ();
					break;
				case 'accelerate':
					if ( ! bAccelerated ) Accelerate ();
					break;
			}
		}
		if ( sKeyAction == 'pause' ) {
			bPaused = ! bPaused;
			if ( bPaused ) iScore = iScore - ( iLevel * aLineScores [ 2 ] )
			DisplayData ( false );
		}
	}
}

function HandleKeyUp ( e ) {
	if ( ! e ) e = window.event;
	var iKeyCode = e.keyCode;
	if ( GetKeyAction ( iKeyCode ) == 'accelerate' ) Decelerate ();
}

function GetKeyAction ( iKC ) {
	for ( var s in oKeyCodes ) if ( oKeyCodes [ s ].indexOf ( '/' + iKC + '/' ) != -1 ) return s;
}

// --------------------
// Acting on user input
// ====================

function MoveLR ( iXChange ) {
	if ( bTestDrawShape ( iCurrentShapeType, iCurrentShapeOrientation, iCurrentX + iXChange, iCurrentY ) ) {
		DrawShape ( iCurrentShapeType, iCurrentShapeOrientation, iCurrentX, iCurrentY, sClearImageName, constBlockClear, false );
		iCurrentX += iXChange;
		DrawShape ( iCurrentShapeType, iCurrentShapeOrientation, iCurrentX, iCurrentY, aBlockImageNames[ iCurrentShapeType ], constBlockCurrentShape, true );
	}
}

function Rotate () {
	var iNewShapeOrientation = ( iCurrentShapeOrientation == 3 ? 0 : iCurrentShapeOrientation + 1 );
	if ( bTestDrawShape ( iCurrentShapeType, iNewShapeOrientation, iCurrentX, iCurrentY ) ) {
		DrawShape ( iCurrentShapeType, iCurrentShapeOrientation, iCurrentX, iCurrentY, sClearImageName, constBlockClear, false );
		iCurrentShapeOrientation = iNewShapeOrientation;
		DrawShape ( iCurrentShapeType, iCurrentShapeOrientation, iCurrentX, iCurrentY, aBlockImageNames[ iCurrentShapeType ], constBlockCurrentShape, true );
	}
}

function Accelerate () {
	clearTimeout ( oTimerID );
	bAccelerated = true;
	iDropSpeed = iAcceleratedDropSpeed;
	KeepOnMoving ();
}

function Decelerate () {
	bAccelerated = false;
	iDropSpeed = iDefaultDropSpeed;
}

// -----------------------------------------
// Manipulating preview and play grid status
// =========================================

function bTestDrawShape ( iShapeType, iShapeOrientation, x, y ) {
	for ( var i = 3; i >= 0; i -- ) {
		// working backwards is optimal since obstruction more likely at bottom
		var iBlockX = x + aShapeData[ iShapeType ][ iShapeOrientation ][ i ][ 0 ];
		var iBlockY = y + aShapeData[ iShapeType ][ iShapeOrientation ][ i ][ 1 ];
		if ( iBlockX < 0 || iBlockX > iPlayGridWidth - 1 ) return false;
		if ( iBlockY > iPlayGridHeight - 1 ) return false;
		if ( iBlockY >= 0 && pPlayGridTd ( iBlockX, iBlockY ).iStatus == constBlockDroppedShape ) return false;
	}
	return true;
}

function DrawShape ( iShapeType, iShapeOrientation, x, y, sImageName, iStatusConstant, bBG ) {
	for ( var i = 0; i < 4; i ++ ) {
		var iBlockX = x + aShapeData[ iShapeType ][ iShapeOrientation ][ i ][ 0 ];
		var iBlockY = y + aShapeData[ iShapeType ][ iShapeOrientation ][ i ][ 1 ];
		if ( iBlockY >= 0 ) {
			var pPGT = pPlayGridTd ( iBlockX, iBlockY );
			pPGT.iStatus = iStatusConstant;
			pPGT.style.backgroundColor = bBG ? sBlockBGColor : ( iBlockX % 2 ? sOddColBGColor : sEvenColBGColor );
			pPlayGridImg ( iBlockX, iBlockY ).src = sImageName;
		}
	}
}

function DrawPreviewShape ( iShapeType ) {
	for ( var i = 0; i < 4; i ++ ) {
		for ( var j = 0; j < 3; j ++ ) {
			var pPGT = pPreviewGridTd ( j, i );
			pPGT.style.backgroundColor = 'transparent';
			if ( pPGT.hasChildNodes () ) {
				pPGT.removeChild ( pPreviewGridImg ( j, i ) );
			}
		}
	}
	for ( var i = 0; i < 4; i ++ ) {
		var iBlockX = -1 + aShapeData[ iShapeType ][ 0 ][ i ][ 0 ];
		var iBlockY = 2 + aShapeData[ iShapeType ][ 0 ][ i ][ 1 ];
		var x = document.createElement ( 'img' );
		x.height = 18;
		x.width = 18;
		x.src = aBlockImageNames[ iShapeType ];
		var pPGT = pPreviewGridTd ( iBlockX, iBlockY );
		pPGT.style.backgroundColor = sBlockBGColor;
		pPGT.appendChild ( x );
		
	}
}

function FixShape ( iShapeType, iShapeOrientation, x, y ) {
	// EVENT TO SIGNIFY SHAPE FIXED
	for ( var i = 0; i < 4; i ++ ) {
		var iBlockX = x + aShapeData[ iShapeType ][ iShapeOrientation ][ i ][ 0 ];
		var iBlockY = y + aShapeData[ iShapeType ][ iShapeOrientation ][ i ][ 1 ];
		if ( iBlockY >= 0 ) pPlayGridTd ( iBlockX, iBlockY ).iStatus = constBlockDroppedShape;
	}
}

// ----------------------------------------------
// Finding preview and play grid cells and images
// ==============================================

function pPlayGridTd ( x, y ) {
	return pPlayGridTBody.getElementsByTagName ( 'tr' )[ y ].getElementsByTagName ( 'td' )[ x ];
}

function pPlayGridImg ( x, y ) {
	return pPlayGridTd ( x, y ).getElementsByTagName ( 'img' ) [ 0 ];
}

function pPreviewGridTd ( x, y ) {
	return pPreviewGridTBody.getElementsByTagName ( 'tr' )[ y ].getElementsByTagName ( 'td' )[ x ];
}

function pPreviewGridImg ( x, y ) {
	return pPreviewGridTd ( x, y ).getElementsByTagName ( 'img' ) [ 0 ];
}

// ----------------------------------
// Storing and retrieving high scores
// ==================================

function iGetStoredHighScore () {
	var sHighScore = getCookie ( 'DRMTetrisHighScore' );
	return ( sHighScore ? sHighScore : 0 );
}

function StoreHighScore ( iScore ) {
	var iOneHundredYearsHence = new Date ();
	iOneHundredYearsHence.setTime ( iOneHundredYearsHence.getTime () + 1000 * 60 * 60 * 24 * 365.2425 * 100 );
	setCookie ( 'DRMTetrisHighScore', iScore, iOneHundredYearsHence, null, null, null );
}

// -----------------------------
// Create preview and play grids
// =============================

function GeneratePlayGrid () {
	for ( var i = 0; i < iPlayGridHeight; i ++ ) {
		var x = document.createElement ( 'tr' );
		if ( i < 4 ) x.style.display = 'none';
		pPlayGridTBody.appendChild ( x );
		for ( var j = 0; j < iPlayGridWidth; j ++ ) {
			var y = document.createElement ( 'td' );
			y.style.backgroundColor = j % 2 ? sOddColBGColor : sEvenColBGColor;
			y.iStatus = constBlockClear;
			x.appendChild ( y );
			var z = document.createElement ( 'img' );
			z.src = sClearImageName;
			z.height = 18;
			z.width = 18;
			y.appendChild ( z );
		}
	}
}

function GeneratePreviewGrid () {
	for ( var i = 0; i < 4; i ++ ) {
		var x = document.createElement ( 'tr' );
		pPreviewGridTBody.appendChild ( x );
		for ( var j = 0; j < 3; j ++ ) {
			var y = document.createElement ( 'td' );
			x.appendChild ( y );
		}
	}
}

// -----------------------------------
// Initialise static variables/objects
// ===================================


function CreateStaticData () {
	iPlayGridWidth = 10; // NB. this is also hard-coded in the HTML
	iPlayGridHeight = 22; // NB. top 4 rows are invisible

	aLineScores = new Array ( 0, 40, 100, 300, 1200 );
	aLevelSpeeds = new Array ( 300, 250, 210, 180, 160, 150, 140, 130, 125, 120, 115, 110, 105, 100, 95, 90, 85, 80, 75, 70, 65, 60, 55, 50, 45, 40, 35, 30, 25, 20, 15, 10 );
	iAcceleratedDropSpeed = 10;
	
	oKeyCodes = {
		rotateCW: "/38/36/83/75/",
		rotateCCW: "//",
		moveLeft: "/37/46/74/65/",
		moveRight: "/39/34/76/68/",
		accelerate: "/40/35/32/",
		pause: "/80/"
	};
	
	sClearImageName = 'clearblock.gif';
	aBlockImageNames = new Array ( 'block_lightblue.gif', 'block_red.gif', 'block_purple.gif', 'block_green.gif', 'block_yellow.gif', 'block_orange.gif', 'block_darkblue.gif' );
	sBlockBGColor = '#cccccc';
	sOddColBGColor = '#f4f4f4';
	sEvenColBGColor = '#ffffff';

	// Preload block images
	var preImgs = new Array ();
	preImgs [ 0 ] = new Image ();
	preImgs [ 0 ].src = sClearImageName;
	for ( var i = 0; i < aBlockImageNames.length; i ++ ) {
		preImgs [ i + 1 ] = new Image ();
		preImgs [ i + 1 ].src = aBlockImageNames[ i ];
	}

	// Preload instructions/gameover images
	preImgs [ i + 2 ] = new Image ();
	preImgs [ i + 2 ].src = 'instructions.gif';
	preImgs [ i + 3 ] = new Image ();
	preImgs [ i + 3 ].src = 'gameover.gif';

	constBlockClear = 0;
	constBlockCurrentShape = 1;
	constBlockDroppedShape = 2;
	constBlockBeginLine = 3;

	aShapeData = new Array (
		new Array (
			new Array (
				new Array ( 1, 0 ), // Square
				new Array ( 2, 0 ),
				new Array ( 1, 1 ),
				new Array ( 2, 1 )
			),
			new Array (
				new Array ( 1, 0 ),
				new Array ( 2, 0 ),
				new Array ( 1, 1 ),
				new Array ( 2, 1 )
			),
			new Array (
				new Array ( 1, 0 ),
				new Array ( 2, 0 ),
				new Array ( 1, 1 ),
				new Array ( 2, 1 )
			),
			new Array (
				new Array ( 1, 0 ),
				new Array ( 2, 0 ),
				new Array ( 1, 1 ),
				new Array ( 2, 1 )
			)
		),
		new Array (
			new Array (
				new Array ( 1, -1 ), // Wiggly x1
				new Array ( 1, 0 ),
				new Array ( 2, 0 ),
				new Array ( 2, 1 )
			),
			new Array (
				new Array ( 2, 0 ),
				new Array ( 3, 0 ),
				new Array ( 1, 1 ),
				new Array ( 2, 1 )
			),
			new Array (
				new Array ( 1, -1 ),
				new Array ( 1, 0 ),
				new Array ( 2, 0 ),
				new Array ( 2, 1 )
			),
			new Array (
				new Array ( 2, 0 ),
				new Array ( 3, 0 ),
				new Array ( 1, 1 ),
				new Array ( 2, 1 )
			)
		),
		new Array (
			new Array (
				new Array ( 3, -1 ), // Wiggly x2
				new Array ( 3, 0 ),
				new Array ( 2, 0 ),
				new Array ( 2, 1 )
			),
			new Array (
				new Array ( 1, 0 ),
				new Array ( 2, 0 ),
				new Array ( 2, 1 ),
				new Array ( 3, 1 )
			),
			new Array (
				new Array ( 3, -1 ),
				new Array ( 3, 0 ),
				new Array ( 2, 0 ),
				new Array ( 2, 1 )
			),
			new Array (
				new Array ( 1, 0 ),
				new Array ( 2, 0 ),
				new Array ( 2, 1 ),
				new Array ( 3, 1 )
			)
		),
		new Array (
			new Array (
				new Array ( 2, -2 ), // Straight
				new Array ( 2, -1 ),
				new Array ( 2, 0 ),
				new Array ( 2, 1 )
			),
			new Array (
				new Array ( 0, 0 ),
				new Array ( 1, 0 ),
				new Array ( 2, 0 ),
				new Array ( 3, 0 )
			),
			new Array (
				new Array ( 2, -2 ),
				new Array ( 2, -1 ),
				new Array ( 2, 0 ),
				new Array ( 2, 1 )
			),
			new Array (
				new Array ( 0, 0 ),
				new Array ( 1, 0 ),
				new Array ( 2, 0 ),
				new Array ( 3, 0 )
			)
		),
		new Array (
			new Array (
				new Array ( 1, -1  ), // L x1
				new Array ( 2, -1 ),
				new Array ( 2, 0 ),
				new Array ( 2, 1 )
			),
			new Array (
				new Array ( 3, -1 ),
				new Array ( 1, 0 ),
				new Array ( 2, 0 ),
				new Array ( 3, 0 )
			),
			new Array (
				new Array ( 2, -1 ),
				new Array ( 2, 0 ),
				new Array ( 2, 1 ),
				new Array ( 3, 1 )
			),
			new Array (
				new Array ( 1, 0 ),
				new Array ( 2, 0 ),
				new Array ( 3, 0 ),
				new Array ( 1, 1 )
			)
		),
		new Array (
			new Array (
				new Array ( 3, -1 ), // L x2
				new Array ( 2, -1 ),
				new Array ( 2, 0 ),
				new Array ( 2, 1 )
			),
			new Array (
				new Array ( 1, 0 ),
				new Array ( 2, 0 ),
				new Array ( 3, 0 ),
				new Array ( 3, 1 )
			),
			new Array (
				new Array ( 2, -1 ),
				new Array ( 2, 0 ),
				new Array ( 2, 1 ),
				new Array ( 1, 1 )
			),
			new Array (
				new Array ( 1, -1 ),
				new Array ( 1, 0 ),
				new Array ( 2, 0 ),
				new Array ( 3, 0 )
			)
		),
		new Array (
			new Array (
				new Array ( 2, -1 ), // Spikey
				new Array ( 2, 0 ),
				new Array ( 3, 0 ),
				new Array ( 2, 1 )
			),
			new Array (
				new Array ( 1, 0 ),
				new Array ( 2, 0 ),
				new Array ( 3, 0 ),
				new Array ( 2, 1 )
			),
			new Array (
				new Array ( 2, -1 ),
				new Array ( 1, 0 ),
				new Array ( 2, 0 ),
				new Array ( 2, 1 )
			),
			new Array (
				new Array ( 2, -1 ),
				new Array ( 1, 0 ),
				new Array ( 2, 0 ),
				new Array ( 3, 0 )
			)
		)
	);
}

function GetObjectRefs () {
	if ( ! window.pScoreText ) {
		pScoreText = document.getElementById ( 'pScoreText' );
		pLevelText = document.getElementById ( 'pLevelText' );
		pLinesText = document.getElementById ( 'pLinesText' );
		pHighScoreText = document.getElementById ( 'pHighScoreText' );
		pPlayGridTBody = document.getElementById ( 'pPlayGridTBody' );
		pPreviewGridTBody = document.getElementById ( 'pPreviewGridTBody' );
		pInstructions = document.getElementById ( 'pInstructions' );
		pInstImg = document.getElementById ( 'pInstImg' );
		pTopTable = document.getElementById ( 'pTopTable' );
	}
}

// -----------------------------
// Generalised library functions
// =============================

function iRandom ( iLimit ) {
	return Math.floor ( Math.random () * ( iLimit + 1 ) );
}

// Cookie API  v1.0.1
// http://www.dithered.com/javascript/cookies/index.html
// maintained by Chris Nott (chris@NOSPAMdithered.com - remove NOSPAM)

// Write a cookie value
function setCookie(name, value, expires, path, domain, secure) {
	var curCookie = name + '=' + escape(value) + ((expires) ? '; expires=' + expires.toGMTString() : '') + ((path) ? '; path=' + path : '') + ((domain) ? '; domain=' + domain : '') + ((secure) ? '; secure' : '');
	document.cookie = curCookie;
}

// Retrieve a named cookie value
function getCookie(name) {
	var dc = document.cookie;

	// find beginning of cookie value in document.cookie
	var prefix = name + "=";
	var begin = dc.indexOf("; " + prefix);
	if (begin == -1) {
		begin = dc.indexOf(prefix);
		if (begin != 0) return null;
	}
	else begin += 2;

	// find end of cookie value
	var end = document.cookie.indexOf(";", begin);
	if (end == -1) end = dc.length;

	// return cookie value
	return unescape(dc.substring(begin + prefix.length, end));
}


