/**************************************************************************** 
 * This notice must be untouched at all times.
 *
 * Soroban.js    v. 2.0
 *
 * Copyright (c) 2007  Alexandru M. Tertisco. All rights reserved.
 *
 *  \ \__                      /\_ \       /'__`\                           
 * \ \ ,_\    __   _ __    __  \//\ \     /\_\L\ \  __  _      _ __   ___   
 *  \ \ \/  /'__`\/\`'__\/'__`\  \ \ \    \/_/_\_<_/\ \/'\    /\`'__\/ __`\ 
 *   \ \ \_/\  __/\ \ \//\ \L\.\_ \_\ \_  __/\ \L\ \/>  </  __\ \ \//\ \L\ \
 *    \ \__\ \____\\ \_\\ \__/.\_\/\____\/\_\ \____//\_/\_\/\_\\ \_\\ \____/
 *     \/__/\/____/ \/_/ \/__/\/_/\/____/\/_/\/___/ \//\/_/\/_/ \/_/ \/___/ 
 * 
 * Created January 2007 by Alexandru M. Tertisco. (Web: http://www.teral.3x.ro)
 * Last modified: Saturday, January 27, 2007
 * 
 *   _|_|_|                                _|                            
 *  _|          _|_|    _|  _|_|    _|_|_|  _|_|_|      _|_|_|  _|_|_|    
 *   _|_|    _|    _|  _|_|      _|    _|  _|    _|  _|    _|  _|    _|  
 *       _|  _|    _|  _|        _|    _|  _|    _|  _|    _|  _|    _|  
 * _|_|_|      _|_|    _|          _|_|_|  _|_|_|      _|_|_|  _|    _| 
 *
 *            - the Japanese Abacus - 
 *
 *  O represents the bead; 
 *  | represents an empty position (the rod)
 *  = represents the frame
 *
 *   ROD
 *  ============= Row Position 
 *    O     0     Upper Deck Rows(Heaven rows)
 *    |     1
 *  =============
 *    |     0
 *    O     1     Lower Deck (Earth rows)
 *    O     2
 *    O     3
 *    O     4
 *  =============
 ****************************************************************************/
/* version info */
var SorobanVersionInfo = "S o r a b a n  2.0 Beta - "+
                         "Copyright(©) 2007 A.M. Tertisco "+
												 "(http://teral.3x.ro)"
/* preloaded images */

	var bead = new Image();
	bead.src="images/bead.gif";
	
	var nobead = new Image();
	nobead.src="images/nobead.gif";

/************************** THE SOROBAN CLASS *******************************/
/**
 * Soroban class Constructor 
 */
function Soroban(
      instanceName, /* the instance name */
			container, /* reference to the the DIV element to contain this instance */ 
			nRods, /* the number of soroban rods */
			//beadWidth, /* the bead image width */
			//beadHeight, /* the bead image height */
			caption /* the caption text to display */
		)
{
  /* Soroban Properties */
  this.name = instanceName;
	this.container = container;
	this.nRods = nRods<10?10:nRods;
	this.beadWidth = 35;
	this.beadHeight = 22;
	this.caption = caption;
	/* The Japanese abacus soroban has the configuration below*/
  this.nHeavenRows = 2;	/* two rows(1 bead, 1 gap on 'Heaven' upper deck)*/
  this.nEarthRows = 5;	/* five rows (4 beads, 1 gap on 'Earth' lower deck)*/
	/* Set container content */
	container.title=SorobanVersionInfo;
	container.innerHTML = 	
  	"<table cellspacing='0' cellpading='0' >\n" + 
    "<tr><td><div id='"+instanceName+"Screen'></div></td></tr>\n" + 
    "<tr><td><div id='"+instanceName+"ControlPanel'></div></td></tr>\n" + 
    "</table>";
  
	/* Contained objects */
	this.upperDeck = new Array(); /* the 'Heaven' upper deck */
	this.lowerDeck = new Array(); /* the 'Earth' lower deck */  
  /* clear the upper deck */
	for(row=0;row<this.nHeavenRows;row++)
	{
	  this.upperDeck[row]=new Array();
		for(col=0;col<this.nRods;col++)
		{
			this.upperDeck[row][col]=(row==this.nHeavenRows-1?0:1);
		}
	}
	/* clear the lower deck */
	for(row=0;row<this.nEarthRows;row++)
	{
	  this.lowerDeck[row]=new Array();
		for(col=0;col<this.nRods;col++)
		{
			this.lowerDeck[row][col]=(row==0?0:1);
		}
	}
	this.screenContainer=document.getElementById(instanceName+"Screen");
  this.controlPanelContainer=document.getElementById(instanceName+"ControlPanel");
	
	this.screen = new SorobanScreen(this);
	this.controlPanel = new SorobanControlPanel(this);
	this.value = 0;
	this.maxValue = Math.pow(10,this.nRods); /* maximum value */
	this.controlPanel.display(0);
	/* Animation parameters */
	this.timeInterval=500; /* defines animation speed */
  this.timeout=null;
  this.animation = false; /* no animation */
  this.count=0; /* animation step counter */
	
	/* Event handlers */
	this.handleResetBtnClk = SorobanHandleResetBtnClk;
	this.handleAnimateBtnClk = SorobanHandleAnimateBtnClk;
	this.handleSpeedBtnClk = SorobanHandleSpeedBtnClk;
	this.handleTopBeadClick = SorobanHandleTopBeadClick;
	this.handleBottomBeadClick = SorobanHandleBottomBeadClick;
	/* methods */
	this.reset = ResetSoroban;
	this.clear = ClearSoroban;
	this.animate = SorobanAnimate;
}

      /**********************************
       * Soroban methods implementation *
       **********************************/

/**
 * Reset Soroban status 
 */
function ResetSoroban()
{
 		/* clear the upper deck */
		for(row=0;row<this.nHeavenRows;row++)
		{
		 	for(col=0;col<this.nRods;col++)
			{
					this.upperDeck[row][col]=(row==this.nHeavenRows-1?0:1);
			}
		}
		/* clear the lower deck */
		for(row=0;row<this.nEarthRows;row++)
		{
		 	for(col=0;col<this.nRods;col++)
			{
					this.lowerDeck[row][col]=(row==0?0:1);
			}
		}
		this.value = 0;
}

/**
 * Soroban animation - counts from 0 to 10^nRods - 1
 */
function SorobanAnimate()
{
  this.reset();
	/* set soroban according to the current animation step counter value */
	var val = this.count; /* current value to display */
	/* set the soroban status arrays (upperDeck and lowerDeck) according 
	   to the new value to display */
	for(col = this.nRods-1; col >= 0; col--)
	{
	   if(val == 0)
		 { /* 0 - nothing to set. Skip setting next rods (already set to 0)*/
		   break;
		 }
  	 rem = Math.floor(val % 10); /* reminder */
  	 val = Math.floor(val/10); /* next value*/
  	 lower = Math.floor(rem%5); /* lower deck value (base 5)*/
  	 upper = (rem < 5? 0:1); /* upper deck value (base 5)*/
		 /* set upper deck */
  	 this.upperDeck[0][col]= 1-upper; //complement
  	 this.upperDeck[1][col]= upper;
		 /* set lower deck */
  	 row = lower; /* rod row to remove bead. */  
  	 if(row!=0)
  	 {   /* not empty row - make it empty. Remove bead and move it to row 0 */
    		 this.lowerDeck[row][col]= 0; /* remove */
    		 this.lowerDeck[0][col]= 1; /* place to empty row 0 */
 		 } 
	}
	this.screen.update(); /* update GUI */
	this.controlPanel.display(this.count); /* display current counter value */
	/* increment counter and truncate to max value to avoid overflow */
	this.count = (this.count+1)%this.maxValue;
	/* reschedule next animation step */
	this.timeout = setTimeout(this.name+".animate()",this.timeInterval);	
}

/**
 * Handle 'Reset' button click event.
 */
function SorobanHandleResetBtnClk()
{
  this.clear();
}

/**
 * Clear soroban.
 */
function ClearSoroban()
{
	this.reset(); /* clear status arrays and value */
	this.screen.update(); /* update screen */
	this.controlPanel.display(0); /* clear control panel text display */
}
          /******************************
           * Soroban GUI event handlers *
           ******************************/
/**
 * Handle 'Animate' button click event. 
 */
function SorobanHandleAnimateBtnClk()
{
  if(!this.animation)
	{  /* animation is not running. */
		 this.animation=true; /* set flag to indicate animation is running */
		 this.count = 0; /* clear animation step counter */
		 /* change Animation button label to 'Stop' */
	   this.controlPanel.animateBtn.value = "   Stop   "; 
		 this.controlPanel.resetBtn.disabled=true; /* disable reset button */
		 this.controlPanel.resetBtn.style.cursor = "default";
		 this.controlPanel.speedBtn.disabled=false; /* enable speed control button */
		 this.controlPanel.speedBtn.style.cursor = "hand";
		 /* reschedule running the animation method in 100ms*/
		 this.timeout = setTimeout(this.name+".animate()",100);	 
	}
	else
	{  /* animation is in progress. */
	   this.animation=false; /* set flag to indicate animation is not running */
		 /* change Animation button label back to 'Animate' */
	   this.controlPanel.animateBtn.value = "Animate";
		 /* reset animation time interval to default 500ms (fast)*/
		 this.timeInterval = 500; 
		 this.controlPanel.resetBtn.disabled=false; /* enable reset button */
		 this.controlPanel.resetBtn.style.cursor = "hand";
		 this.controlPanel.speedBtn.disabled=true; /* disable speed control button */
		 this.controlPanel.speedBtn.style.cursor = "default";
		 /* change spead control button label to 'Slower' (default) */
	   this.controlPanel.speedBtn.value = "Slower ";
		 clearTimeout(this.timeout); /* clear timeout */
		 this.clear(); /* clear soroban */
	}
}

/**
 * Handle speed control button click event. 
 */
function SorobanHandleSpeedBtnClk()
{
  if(this.timeInterval==500)
	{ /* 'fast' mode animation in progress. Change to 'slow' mode  */
	 	this.timeInterval=1000; /*  set 'slow' mode timeout interval - 1000ms*/
		/* change spead control button label to 'Faster' */
		this.controlPanel.speedBtn.value = "Faster ";
	}
	else
	{ /* 'slow' mode animation in progress. Change to 'fast' mode  */
	 	this.timeInterval=500; /*  set 'fast' mode timeout interval - 500ms*/
		/* change spead control button label to 'Slower' */
		this.controlPanel.speedBtn.value = "Slower ";
	}
}

/**
 * Handle upper deck beads click event. 
 * args: row, col - the upper deck clicked bead's row and column 
 */ 
function SorobanHandleTopBeadClick(row, col)
{
  if(this.upperDeck[row][col]!=0)
	{
	 	var empty;
		/* search the empty rod position */
		for(_row=0;_row<this.nHeavenRows;_row++)
		{
		  if(this.upperDeck[_row][col]==0)
			{ /* found */
			  empty=_row;
				break;
			}
		}
		/* swap the clicked bead and empty place on the screen*/
		document.getElementById(this.name+"Top"+row+col).src=nobead.src;
		document.getElementById(this.name+"Top"+empty+col).src=bead.src;
		/* swap the clicked bead and empty place elements in the upperDeck array*/
		this.upperDeck[empty][col]=1;
		this.upperDeck[row][col]=0;
		/* calculate the new value */
		expn=this.nRods-col-1; /* exponent - power of 10*/
		this.value += (empty-row )*5 * Math.pow(10,expn); /* store the new value */
		this.controlPanel.display(this.value); /* display the new value */
		
	}
}

/**
 * Handle lower deck beads click event. 
 * args: row, col - the lower deck clicked bead's row and column 
 */ 
function SorobanHandleBottomBeadClick(row, col)
{
   if(this.lowerDeck[row][col]!=0)
	 {
  	 	var empty;
  		for(_row=0;_row<this.nEarthRows;_row++)
  		{
  		  if(this.lowerDeck[_row][col]==0)
  			{
  			  empty=_row;
  				break;
  			}
  		}
			
  		document.getElementById(this.name+"Bot"+row+col).src=nobead.src;
  		document.getElementById(this.name+"Bot"+empty+col).src=bead.src;
			
  		this.lowerDeck[empty][col]=1;
  		this.lowerDeck[row][col]=0;
  		expn=this.nRods-col-1
			this.value +=(row-empty ) * Math.pow(10,expn);
			this.controlPanel.display(this.value);
	 }
}

/************************* SOROBAN SCREEN CLASS *****************************/
/** 
 * Soroban Screen constructor 
 * argument: parent - the reference to Soroban parent class.
 */
function SorobanScreen(parent)
{
    this.parent= parent;
    this.nCols = this.parent.nRods; /* number of upper and lower deck columns */
		
		var innerHTML= new String("");
	  /* The soroban frame as HTML table*/
		innerHTML +='<table cellspacing="0" cellpading="0" border="4" '+
		            'style="background-color: gray;">'+
		            '<caption>'+ this.parent.caption + '</caption><tr><td>';
		/* The 'Heaven' uper deck frame as HTML table*/
		innerHTML += '<table cellspacing="0" cellpading="0" border="0" '+
		             'style="background-color: white;">';
    /* place the upper deck bead images */
		for(row=0;row<this.parent.nHeavenRows;row++)
		{  
		 	for(col=0;col<this.parent.nRods;col++)
			{
			 	innerHTML+='<td style="width: '+ this.parent.beadWidth+'; height: '+ 
				  this.parent.beadHeight+'; padding: 0px;"><image id="'+ 
					this.parent.name+'Top'+
					row+col+'" src='+
					(row==this.parent.nHeavenRows-1?"images/nobead.gif":"images/bead.gif")+
					' style="margin: 0px ;width: '+ 
					this.parent.beadWidth+'; height: '+ this.parent.beadHeight+'; ' +
					'cursor: '+(row==this.parent.nHeavenRows-1?'default;':'hand;') + 
					'" OnClick="'+ this.parent.name+'.handleTopBeadClick('+row+','+
					col+')"/></td>';
			}
			innerHTML+='</tr>';
		}
		innerHTML+='</table></td></tr><tr><td>';
		
		/* The 'Earth' lower deck frame as HTML table*/
		innerHTML+='<table cellspacing="0" cellpading="0" border="0" '+
		           'style="background-color: white;">';
		/* place the lower deck bead images */
		for(row=0;row<this.parent.nEarthRows;row++)
		{
		 	for(col=0;col<this.parent.nRods;col++)
			{
			 	innerHTML+='<td style="width: '+ this.beadWidth+'; height: '+ 
				  this.parent.beadHeight+'; padding: 0px;"><image id="'+ 
					this.parent.name+'Bot'+
					row+col+'" src='+(row==0?"images/nobead.gif":"images/bead.gif")+
					' style="margin: 0px ; width: '+ 
					this.parent.beadWidth+'; height: '+ this.parent.beadHeight+';' +
					'cursor: ' + (row==0? 'default;':'hand;')+
					'" OnClick="'+ this.parent.name+'.handleBottomBeadClick('+row+','+
					col+')"/></td>';
			}
			innerHTML+='</tr>';
		}
		innerHTML+='</table></td></tr></table>';
		
		/* fill the soroban screen container DIV with the inner HTML content 
		   to be displayed */
		this.parent.screenContainer.innerHTML=innerHTML; 	
		/* class methods */
		this.update = UpdateSorobanScreen; 
}

/** 
 * updates the Soroban screen according to upperDeck and lowerDeck arrays
 */
function UpdateSorobanScreen()
{
 	  /* set the upper deck GUI*/
		for(row=0;row<this.parent.nHeavenRows;row++)
		{
		  for(col=0;col<this.parent.nRods;col++)
			{
				document.getElementById(this.parent.name+"Top"+row+col).src=
				  (this.parent.upperDeck[row][col]==0?nobead.src:bead.src);
			}
		}
		/* set the lower deck GUI*/
		for(row=0;row<this.parent.nEarthRows;row++)
		{
		 	for(col=0;col<this.parent.nRods;col++)
			{
			  document.getElementById(this.parent.name+"Bot"+row+col).src=
				  (this.parent.lowerDeck[row][col]==0?nobead.src:bead.src);
			}
		}
}

/**************************** SOROBAN CONTROL PANEL CLASS *******************/

/**
 * Soroban Control panel constructor 
 * argument: parent - the reference to Soroban parent class.
 */
function SorobanControlPanel(parent)
{
  this.parent = parent;
	/* generates control panel innerHTML code */
	var innerHTML = new String(
  	"    <table width='100%' cellspacing='0' cellpadding='2'  frame='box'>\n" + 
    "      <tr style='font-size:12px ; text-align: center; background-color: "+
		"darkgray; color: white;' >\n"); 
	for(expN=parent.nRods-1;expN>=0;expN--)
	{ 
	  innerHTML+="        <td " + 
		(expN<2?"style='background-color: black ;'":"")+ 
		" >10<sup>"+(expN-2)+"</sup></td>\n"; 
	}
	this.textDisplaySize = parent.nRods + Math.floor(parent.nRods/3);
  innerHTML+="      </tr>\n" + 
  "      <tr style='background-color: silver;' >\n" + 
  "        <td colspan='"+this.parent.nRods+"' >\n" + 
  "      	<form >\n" + 
  "          <input type='text'  size='" + this.textDisplaySize + 
	"' id='"+parent.name+"TextDisplay' />\n" + 
  "        	<input type='button' value=' Reset ' id='" +
	  parent.name + "ResetBtn' style='cursor: Hand;'\n" + 
  "					       onclick='"+parent.name+".handleResetBtnClk();' />\n" + 
  "					<input type='button' value='Animate' id='" + 
	  parent.name+"AnimateBtn' style='cursor: Hand;'\n" + 
  "					       onClick='"+parent.name+".handleAnimateBtnClk()' />\n" + 
  "					<input type='button' value='Slower ' id='" + 
	  parent.name + "SpeedBtn' \n" + 
  "					       onClick='"+parent.name+".handleSpeedBtnClk()'  " + 
	"disabled='disabled' />\n" + 
  "        </form>\n" +"      	</td>\n" + "      </tr>\n" + "    </table>";
	/* fill the DIV container element with the control panel HTML code */
	parent.controlPanelContainer.innerHTML = innerHTML;
	
	/* store reference to text display element */
	this.textDisplay = document.getElementById(parent.name+"TextDisplay"); 
	/* generate initial value display string '0.000.000. ... .000'*/
	var initialString = new String(",00");
	var separator = '.';
	for(rod=1;rod<= parent.nRods-2;rod++)
	{
		initialString = ((rod%3 == 0)&& (rod != parent.nRods) ? separator:'')+
			                + '0'+ initialString;
	}
	this.initialString = initialString; /* store initial string */
	/* init the control panel text display */
	this.textDisplay.value = initialString;
	/* methods */
	this.buildDisplaySting = SorobanControlPanelBuildDisplaySting;
	this.display = SorobanControlPanelDisplay;
	/* references to control panel buttons */
	this.resetBtn = document.getElementById(parent.name+"ResetBtn");
	this.animateBtn = document.getElementById(parent.name+"AnimateBtn");
	this.speedBtn = document.getElementById(parent.name+"SpeedBtn");
}

/**
 * A recursive function to build the string 
 * 'n.nnn.nnn. ... .nnn' to be displayed.
 * argument: value - an integer to be converted to string with separators.
 */
function SorobanControlPanelBuildDisplaySting(value)
{
 	
	if(value>=1000) 
	{  /* more than three digits - recursion */
	   var zeros=new String("000");
		 var value_str = (value%1000).toString();
		 var offset=zeros.length - value_str.length;
		 var valueString = zeros.substring(0,offset)+value_str;
		 return SorobanControlPanelBuildDisplaySting(Math.floor(value/1000))+
		        "."+ valueString;
	}
	else
	{  /* last three digits or less - end of recursion. return digits string... */
	   return value.toString();
	}
}

/**
 * Displays the value in conrol panel text display.
 * argument: value - an integer value to be displayed.
 */
function SorobanControlPanelDisplay(value)
{
  /* the less significant two digits of the 
	value are displayed as decimals (cents) */
	var decimals = ((value%100)<10?"0":"")+(value%100).toString(); 
	var displayString = this.buildDisplaySting(Math.floor(value/100))+
	                    "," + decimals;
	var offset=this.textDisplaySize - displayString.length;
	if(offset !=0)
	{ /* prefix value string with '0'-s ...*/
	  displayString = this.initialString.substring(0,offset) + displayString;
	}
	this.textDisplay.value = displayString; /* display */
}
/****************************************************************************/
