/*
(c) CopyLeft 2003, 2004 Benn Herrera benn@bennherrera.com
Permission granted to copy, use, and modify this code so long as this notice is left intact.
If this code is used in any commercial product, appropriate attribution is required.
Full terms at http://www.gnu.org/copyleft/gpl.html
*/

//
// This file contains the code that backs the UI control surfaces and keyboard shortcuts
// as well as the general game configurations.  It handles interface contstraints such as
// bounding the tile size and number of images to the maxima configured below.
// all control surface text changes are handled here.
// the only things that the block and tetris game objects alter are the main board
// where the bricks are displayed and the score/levels
//

// these can be configured

var DefaultImxFolderURL	= "imx/"; // modified in onLoad() to reflect window.location as parent
var DefaultImxFormat    = "gif";
var DefaultNumImx		= 20;
var TileSizeSnapStep	= 4;  // used when auto-calculating tile size
var MinTileSize			= 16;
var MaxTileSize			= 96;
var BoardWidth			= 10; // in tiles
var BoardHeight			= 20; // in tiles
var StepMilliSeconds	= 50; // # of milliseconds delay between game cycles

// fudge-factor values in pixels.
// used to make the absolute positioning
// of the Tetris blocks fall properly
// within the board. If you change the layout,
// you'll need to adjust these numbers.
var IEBoardCellLeft				= 14;
var IEBoardCellTop				= 20;
var IEBoardCellWidthPad			= 0;
var IEBoardCellHeightPad		= 0;

var NetscapeBoardCellLeft		= 14;
var NetscapeBoardCellTop		= 13;
var NetscapeBoardCellWidthPad	= 0;
var NetscapeBoardCellHeightPad	= 0;

var UITableWidth				= 320;
var LayoutWidthPadFromBorders	= 16;

// version info
var Product				= "JavaScript Image Tetris";
var Version				= "1.5";

// changing these automatically changes
// the image naming convention hint text
var MaxNumImx			= 100;
var ImxRadix			= 10;

// set properly in onLoad() - don't change here
var ImxDigits			= 0;

//
// these must match the id values in the html
// and should probably not be edited
//

// table containing all user interface controls
// and score display
var UITableID			= "uiTable";

var UITable				= null;

// table cell where all the action occurs
var BoardCellID			= "boardCell";

var BoardCell			= null;

// the spans containing the current score and level values
var HeaderCellID		= "headerCell";
var ScoreTxtID			= "scoreTxt";
var LevelTxtID			= "levelTxt";
var NextBlockTableID    = "nextBlockTable";
var NextBlockCellID     = "nextBlockCell";

var HeaderCell			= null;
var ScoreTxt			= null;
var LevelTxt			= null;
var NextBlockTable      = null;
var NextBlockCell       = null;

// the control surfaces for playing the game
var HighScoreRowsID      = "highScoreRows";

var HighScoreRows       = null;

// runtime values - configured in onLoad()
var BoardCellLeft		= 0;
var BoardCellTop		= 0;
var BoardCellWidthPad	= 0;
var BoardCellHeightPad	= 0;

// run-time values for image config
var TileSize			= 0;
var ImxFolderURL		= "";
var ImxFormat           = "";
var NumImx				= 0;
var CurImgIdx			= 0;

// run-time boolean for letting us know if this is NS or IE
var IsNS				= false;

// the instance of the game
var JSTInstance			= null;

// the high score table
var JSTHighScores       = null;

// when paused, this is a DOM object
// displaying the text 'Paused' in the
// middle of the board
var PauseTxt			= null;

// used when pre-loading the tile images.
// keeping an array of the preloads around
// prevents the preload from being optimized away.
var PreloadedImx		= null;

function onLoad() {   
	// elements that will be used again and again
	UITable				= document.getElementById( UITableID );
	HeaderCell			= document.getElementById( HeaderCellID );
	BoardCell			= document.getElementById( BoardCellID );
	ScoreTxt			= document.getElementById( ScoreTxtID );
	LevelTxt			= document.getElementById( LevelTxtID );
    NextBlockTable      = document.getElementById( NextBlockTableID );
    NextBlockCell       = document.getElementById( NextBlockCellID );
    HighScoreRows       = document.getElementById( HighScoreRowsID );
    	
	var url = String( window.location );
	DefaultImxFolderURL = url.substr( 0, url.lastIndexOf( '/' ) + 1  ) + DefaultImxFolderURL;
	
	HeaderCell.innerHTML = Product + ' ' + Version;
	
	// browser detector class defined at the end of this file.
	// it is 3rd party code by Richard Blaylock courtesy of Web Monkey
	var bd	= new BrowserDetector( navigator.userAgent );
    
	if ( bd.browser == "Netscape" ) {
		IsNS				= true;
		BoardCellLeft		= NetscapeBoardCellLeft;
		BoardCellTop		= NetscapeBoardCellTop;
		BoardCellWidthPad	= NetscapeBoardCellWidthPad;
		BoardCellHeightPad	= NetscapeBoardCellHeightPad;
	} else {
		// default to IE settings
		IsNS				= false;
		BoardCellLeft		= IEBoardCellLeft;
		BoardCellTop		= IEBoardCellTop;
		BoardCellWidthPad	= IEBoardCellWidthPad;
		BoardCellHeightPad	= IEBoardCellHeightPad;
	}
		
	// number of digits expected in image names.
	// i.e. if max is 100 and radix is 10 images must be named 000.bmp, 001.bmp...[numImx-1].bmp
	var m = MaxNumImx;
    
	// take ceil( log10( MaxNumImx ) )
	for( ImxDigits = 0; m; m = Math.floor( m / ImxRadix ), ImxDigits++ );
    
	// event handlers
	document.onkeydown  = onKeyDown;
	document.onkeyup    = onKeyUp;
	
	UITable.style.width = UITableWidth + "px";
    
    initJSTControls();
    
	// set up the default image and tile/board size
	restoreImxInfoDefaults();
    
	onResize();
    
    displayNextBlock( "&nbsp;" );
    
    JSTHighScores = new HighScoreTable();
}

function clearHighScores() {
    JSTHighScores.clearScores();
}

function initJSTControls() {
    var cells = document.getElementsByTagName( "td" );
    var cbName;
    var type;
    
    for ( var i = 0; i < cells.length; i++ ) {
        if ( !cells[i].id || cells[i].id.length < 4 )
            continue;
        
        type = cells[i].id.substr( cells[i].id.length - 3 );
        
        if ( type == "Btn" ) {
            if ( cells[i].id.indexOf( "CfgBtn" ) >= 0 ) {
                cbName = cells[i].callback || cells[i].id.substr( 0, cells[i].id.length - 6 );
                new JSTConfigButton( cells[i], eval( cbName ) );
            } else {
                cbName = cells[i].callback || cells[i].id.substr( 0, cells[i].id.length - 3 );
                new JSTGameButton( cells[i], eval( cbName ) );
            }
        } else if ( type == "Edt" ) {
            cbName = cells[i].callback || cells[i].id.substr( 0, cells[i].id.length - 3 );
            
            var changedCB = null;
            var focusLostCB = null;
            
            try {
                changedCB = eval( cbName + "Changed" );
            } catch ( ignored ) {
            }
            
            try {
                focusLostCB = eval( cbName + "FocusLost" );
            } catch ( ignored ) {
            }
            
            new JSTEdit( cells[i], changedCB, focusLostCB );
        }
    }
}

function onResize() {	
	var w = IsNS ? window.innerWidth  : document.body.clientWidth;
	var h = IsNS ? window.innerHeight : document.body.clientHeight;
	
	// alert( 'onResize()' );
	
	// get to the width where the tiles have to live
	w -= ( BoardCellLeft + BoardCellWidthPad + UITableWidth + LayoutWidthPadFromBorders );
	
	// divide up by the number of tiles across
	w /= BoardWidth;
	
	// snap to a multiple of TileSizeSnapStep
	w = Math.floor( w / TileSizeSnapStep ) * TileSizeSnapStep;
	
	// get to the height where the tiles have to live
	h -= ( BoardCellTop + BoardCellHeightPad );
	
	// divide up by the number of tiles down
	h /= BoardHeight;
	
	// snap to a multiple of TileSizeSnapStep
	h = Math.floor( h / TileSizeSnapStep ) * TileSizeSnapStep;
	
	// the smaller one is the limiting factor
	w = Math.min( w, h );
	
	// bound value
	if( w < MinTileSize )
		w = MinTileSize
	else if( w > MaxTileSize )
		w = MaxTileSize
	
	if( w != TileSize ) {
		TileSize = w;
		updateBoardSize();
	}
}

function createBoardTextOverlay( text ) {
	var overlayTxt = document.createElement( "div" );

	BoardCell.appendChild( overlayTxt );

	overlayTxt.style.fontWeight			= "bold";
	overlayTxt.style.fontSize			= "16px";
	overlayTxt.style.fontFamily			= "courier";
	overlayTxt.style.backgroundColor	= "black";
	overlayTxt.style.color				= "beige";
	overlayTxt.style.textAlign			= "center";
	overlayTxt.style.position			= "absolute";
	overlayTxt.style.top				= ( BoardCellTop + ( BoardHeight/2 * TileSize ) - 8 ) + "px";
	overlayTxt.style.width				= 128 + "px";
	overlayTxt.style.left				= ( BoardCellLeft + ( BoardWidth/2 * TileSize ) - 64 ) + "px";
	overlayTxt.innerHTML				= text;
	
	return overlayTxt;
}

function startPause() {
	if( JSTInstance ) {
		if( JSTInstance.isRunning ) {
			JSTInstance.pause();
			getJSTCtl("startPauseBtn").setLabel( "Resume[p]" );
			
			PauseTxt = createBoardTextOverlay( "Paused" );
		} else {
			getJSTCtl("startPauseBtn").setLabel( "Pause[p]" );
			BoardCell.removeChild( PauseTxt );
			PauseTxt = null;
			JSTInstance.resume();
		}
	} else {
		JSTInstance = new JSTetris();
		JSTInstance.start();
		getJSTCtl("startPauseBtn").setLabel( "Pause[p]" );
	}	
}

var MouseOverBoard = false;

function jstBoardOnMouseOver() {
    MouseOverBoard = true;
    
    if ( JSTInstance && !JSTInstance.isRunning )
        startPause();
}

function jstBoardOnMouseOut() {
    MouseOverBoard = false;
    
    if ( JSTInstance && JSTInstance.isRunning )
        startPause();
}

function endGame() {
	if( !JSTInstance )
		return;

	if( JSTInstance.isRunning ) {
		// gameOver will call endGame()
		// after turning isRunning off
		// and doing its own cleanup.
		JSTInstance.gameOver();

		return;
	}
    
    saveScore();

	JSTInstance = null;

	createBoardTextOverlay( "Game Over" );

	getJSTCtl( "startPauseBtn" ).setLabel( "Start[F12]" );
}

// called by the controls
function rotateLeft() {
    if ( JSTInstance )
        JSTInstance.rotate( -1 );
}

function rotateRight() {
    if ( JSTInstance )
        JSTInstance.rotate( 1 );
}

function moveLeft() {
    if ( JSTInstance )
        JSTInstance.move( -1 );
}

function moveRight() {
    if ( JSTInstance )
        JSTInstance.move( 1 );
}

function rushBlock() {    
    if ( JSTInstance )
        JSTInstance.rush();
}

// return a string corresponding to a pressed key
// does conversion from arrow/keypad keys
// to the keyboard keys used to control the game
function keyEventToKeyString( e ) {
	if( !e ) e = window.event;

	if( typeof( e.which ) == 'number' ) {
        //NS 4, NS 6+, Mozilla 0.9+, Opera
        e = e.which;
    } else if( typeof( e.keyCode ) == 'number'  ) {
        //IE, NS 6+, Mozilla 0.9+
        e = e.keyCode;
    } else if( typeof( e.charCode ) == 'number'  ) {
        //also NS 6+, Mozilla 0.9+
        e = e.charCode;
	}

	//alert( e );

	switch( e ) {
	case 27: return 'ESC'; 
	case 123: return 'F12';

	case 19: return 'P'; // pause/break

	// kp left, kp 4
	case 100:
	case 37: return 'J'; // left

	// kp up, kp 8
	case 104:
	case 38: return 'L'; // up

	// semi, KP right, kp 6
	case 186:
	case 102:
	case 39: return ';'; // right

	// KP 2/down, down arrow
	case 98:
	case 40: return 'K';

	// enter, KP 0 /KP INS
	case 13:
	case 45:
	case 96:
		return ' '; // enter
	}

	return String.fromCharCode( e ).toUpperCase();
}

// convert a key string to a control
// this is used to map keys to the control buttons below the game board.
function keyStringToCtlId( k ) {
	if( MouseOverBoard && (k == 'J' || k == 'A') )
		return "moveLeftBtn";
	
	if( MouseOverBoard && (k == 'K' || k == 'S') )
		return "rotateLeftBtn";
	
	if( MouseOverBoard && (k == 'L' || k == 'D') )
		return "rotateRightBtn";

	if( MouseOverBoard && (k == ';' || k == 'F') )
		return "moveRightBtn";

	if( MouseOverBoard && k == ' ' )
		return "rushBlockBtn";

	if( k == 'P' && JSTInstance && MouseOverBoard )
		return "startPauseBtn";

	if( k == 'ESC' && JSTInstance )
		return "endGameBtn";

	if( MouseOverBoard && k == 'F12' && !JSTInstance )
		return "startPauseBtn";
	
	return null;
}

// map key down/up events to corresponding mouse down/up events
// over the controls of the game - implements the keyboard shortcuts
function onKeyDown( e ) {
    e = e ? e : window.event;
    
    if ( jstCtlOnKeyDown( e ) )
        return;
    
	e = keyStringToCtlId( keyEventToKeyString( e ) );
    
	if( e )
		getJSTCtl( e ).jstCtlOnMouseDown();
}

function onKeyUp( e ) {
    e = e ? e : window.event;
    
	if( jstCtlOnKeyUp( e ) )
        return;
    
    e = keyStringToCtlId( keyEventToKeyString( e ) );
    
	if( e )
		getJSTCtl( e ).jstCtlOnMouseUp();
}

function updateRangeTxt() {
    var maxImx = (NumImx - 1);
    var zeros="", rangeTxt="";
   
    maxImx = maxImx.toString( ImxRadix );
    
    for( m = 0; m < ImxDigits; m++, zeros+="0" );
	
    rangeTxt = zeros + "." + ImxFormat + " - ";
    
    zeros = "";
    for ( m = maxImx.length; m < ImxDigits; m++, zeros+="0" );
    
    rangeTxt += zeros + (NumImx - 1) + "." + ImxFormat;
    
	document.getElementById( "imgRangeTxt" ).innerHTML = rangeTxt;
}

// reset the image info controls to the default values
function restoreImxInfoDefaults() {
	ImxFolderURL	    = DefaultImxFolderURL;
	ImxFormat           = DefaultImxFormat;
	NumImx              = DefaultNumImx;
    
    getCtl( "imxFolderURLEdt" ).value = ImxFolderURL;
    getCtl( "imxFormatEdt" ).value = ImxFormat;
    getCtl( "numImxEdt" ).value = NumImx;
    
    updateRangeTxt();

	preloadImx();	
}

function imxFolderFocusLost() {
    updateImxInfo();
}

function numImxFocusLost() {
    updateImxInfo();
}

function imxFormatFocusLost() {
    updateImxInfo();
}

function updateImxInfo() {   
	NumImx			= parseInt( getCtl( "numImxEdt" ).value );
	ImxFolderURL	= getCtl( "imxFolderURLEdt" ).value;
    ImxFormat       = getCtl( "imxFormatEdt" ).value;

	if( NumImx == NaN || NumImx <= 0 || ImxFolderURL == "" ) {
		restoreImxInfoDefaults();
		return;
	}

	if( NumImx > MaxNumImx ) {
		NumImx = MaxNumImx;
		getCtl( "numImxEdt" ).value = NumImx;
	}
    
    updateRangeTxt();

	preloadImx();
}

//
// get complete URL for the next image in the
// sequence of images.  updates the current image index.
// non-reentrant
//
function getNextImg() {
    CurImgIdx = (CurImgIdx + 1) % NumImx;

	return peekNextImg( 0 );
}

//
// get complete url for next idx image w/o changing what will come next
// reentrant.
//
function peekNextImg( idx ) {
    var imgIdx = CurImgIdx + idx;
    
    if ( imgIdx >= NumImx )
        imgIdx = 0;

	// convert from number to string
	var imgName = imgIdx.toString( ImxRadix );

	// zero-pad appropriately
	for( var numZeros = ImxDigits - imgName.length; numZeros > 0; numZeros-- )
		imgName = "0" + imgName;

	// return the string
	return ImxFolderURL + imgName + "." + ImxFormat;
}

// preload images
function preloadImx() {
	// put putting cur image past end
	// it will start at the beginning
	// because 'getNextImg()' increments name idx
	// before getting the image
	CurImgIdx = NumImx;

	// don't do this while the game is running.
	// will cause too much of a delay.
	if( JSTInstance )
		return;

	PreloadedImx = new Array( NumImx );
	
	for( var i = 0; i < NumImx; i++ ) {
		PreloadedImx[i] = new Image( TileSize, TileSize );
		PreloadedImx[i].src = getNextImg();
	}
}

function updateBoardSize() {
	var height = (TileSize * BoardHeight + BoardCellHeightPad) + "px";

	BoardCell.style.width	   = (TileSize * BoardWidth  + BoardCellWidthPad)  + "px";
	BoardCell.style.height	   = height;
	UITable.style.height	   = height;
    NextBlockTable.style.width = ((TileSize * 3) + TileSize / 2) + "px";
    NextBlockCell.style.height = ((TileSize * 4) + TileSize / 2) + "px";
    
    var previewImx = NextBlockCell.getElementsByTagName( "img" );
    
    if ( previewImx ) {
        for ( var i = 0; i < previewImx.length; i++ ) {
            previewImx[i].style.width  = TileSize + "px";
            previewImx[i].style.height = TileSize + "px";
        }
    }
  
	if( JSTInstance ) {
		if( PauseTxt ) {
			PauseTxt.style.top				= ( BoardCellTop + ( BoardHeight/2 * TileSize ) - 8 ) + "px";
			PauseTxt.style.width			= 128 + "px";
			PauseTxt.style.left				= ( BoardCellLeft + ( BoardWidth/2 * TileSize ) - 64 ) + "px";
		}
        
		JSTInstance.resize();
	} else {
		BoardCell.innerHTML = Product + ' ' + Version;
	}
}

function displayNextBlock( blockPreviewCode ) {
    NextBlockCell.innerHTML = blockPreviewCode;
}


function saveScore() {
    var name  = getCtl( "playerNameEdt" ).value;
    var score = JSTInstance.score;
    
    JSTHighScores.insertScore( name, score );
    JSTHighScores.packScoresCookie();
    JSTHighScores.displayScores();
}



