	/*---------------------------------------------------------------------
    Original: Charles Chen, Nov. 2004 (cchen@charliedigital.com)
    Visit: http://www.charliedigital.com

    You may copy and modify the contents of this file and reuse it for
    *non-commercial* purposes only.  Please leave this notice intact!
    -----------------------------------------------------------------------
    Description: This file contains the main game logic.
    ---------------------------------------------------------------------*/		
	
	/* GAME */

	function Solitaire(args,htmlObj){
		function Solitaire(){
			this.PlayingDeck	= new Deck();
			this.UnplayedStack	= new UnplayedStack(document.getElementById(args.UnplayedStack));
			this.PlayedStack	= new ColumnStack(document.getElementById(args.PlayedStack));

			this.ShowTextBackground = args.ShowTextBackground;

			// THE SUITS
			this.Suits	= [new ColumnStack(document.getElementById(args.SpadesStack)),
							new ColumnStack(document.getElementById(args.HeartsStack)),
							new ColumnStack(document.getElementById(args.DiamondsStack)),
							new ColumnStack(document.getElementById(args.ClubsStack))];
			this.MaxSuitCount = [0,0,0,0];

			// THE COLUMNS OF CARDS
			this.Columns = [new ColumnStack(document.getElementById(args.ColumnStack0)),
							new ColumnStack(document.getElementById(args.ColumnStack1)),
							new ColumnStack(document.getElementById(args.ColumnStack2)),
							new ColumnStack(document.getElementById(args.ColumnStack3)),
							new ColumnStack(document.getElementById(args.ColumnStack4)),
							new ColumnStack(document.getElementById(args.ColumnStack5)),
							new ColumnStack(document.getElementById(args.ColumnStack6))];

			this.Origins		= new Array();
			this.InitialPoint	= {X:0,Y:0};	// REFERENCE POINT WHERE THE ACTION STARTED (NOT USED, BUT MAY BE IN THE FUTURE)
			this.InitialStack	= null;			// THE INITIAL STACK WHERE THE ACTION STARTED
			this.TargetStack	= null;			// THE TARGET STACK WHERE THE ACTION WILL END
			this.FreeStack		= new ColumnStack(document.getElementById(args.FreeStack));	// THE FREE FLOATING STACK FOR MOVING CARDS
			this.Selected		= {Obj:this.FreeStack.HtmlObj.parentNode,OffsetX:0,OffsetY:0};	// THE div ELEMENT WRAPPING THE FREE STACK

			// SCORING
			this.Complete						= false;
			this.WinPoints						= args.WinPoints;
			this.RemainingTime					= args.StartingTimeSeconds;
			this.StartingPoints					= args.StartingPoints;
			this.ReStackPoints					= args.ReStackPoints;
			this.FlipCardPoints					= args.FlipCardPoints;
			this.RemainingCardPoints			= args.RemainingCardPoints;
			this.RemainingTimePointsPerSecond	= args.RemainingTimePointsPerSecond;
			this.StackedCardPoints				= args.StackedCardPoints;
			this.ReStackCount					= 0;
			this.Counter						= null;
			this.ScoreDisplay					= new ScoreDisplay(document.getElementById(args.ScoreDisplay),args.EnableScoreDisplay);
			
			// REWARDS/PARTS
			this.RewardsZone	= document.getElementById(args.RewardsZone);
			this.Rewards		= args.Rewards;
			this.RewardImageLo	= new Image();
			this.RewardImageHi	= new Image();
			this.GameBoard		= new GameBoard(document.getElementById(args.GameBoard));
			this.ResultsBoard	= new ResultsBoard(document.getElementById(args.ResultsBoard));
			this.ResultsBoard.onclick = this.ResultsBoard.Hide;

			this.ActionButton	= document.getElementById(args.ActionButton);
			this.ScoreLabel		= document.getElementById(args.ScoreLabel);
			this.PointLabel		= document.getElementById(args.PointLabel);
			this.TimeLabel		= document.getElementById(args.TimeLabel);

			this.HtmlObj			= htmlObj;
		}

		with(Solitaire){
			method('Initialize',function(){
				this.PlayingDeck.Initialize();

				// PUT ALL OF THE CARDS IN THE UNPLAYED STACK
				for(var i = 0; i < this.PlayingDeck.Cards.length; i++){
					this.UnplayedStack.AddCard(this.PlayingDeck.Cards[i]);
				}

				// POPULATE THE COLUMN STACKS AND PRECALCULATE THE TARGETABLE STACKS
				for(var i = 0; i < 7; i++){
					for(var j = 0; j <= i; j++){
						var card = this.UnplayedStack.GetCard();
						if(j == i)
							card.Flip();

						this.Columns[i].AddCard(card);
					}

					this.Origins.push({Stack:this.Columns[i],
						X:Dhtml.GetObjectLeft(this.Columns[i].HtmlObj),
						Y:Dhtml.GetObjectTop(this.Columns[i].HtmlObj)});
				}

				// PRECALCULATE THE ORIGIN POINTS FOR THE TARGETABLE STACKS (NON-COLUMNS)
				for(var i = 0; i < this.Suits.length; i++){
					this.Origins.push({Stack:this.Suits[i],
						X:Dhtml.GetObjectLeft(this.Suits[i].HtmlObj),
						Y:Dhtml.GetObjectTop(this.Suits[i].HtmlObj)});
				}

				// SELECT A RANDOM REWARD AND SET THE REWARD ZONE
				var index = Math.floor(Math.random() * this.Rewards.length);
				this.RewardImageLo.src = this.Rewards[index][0];
				this.RewardImageHi.src = this.Rewards[index][1];
				this.RewardsZone.style.backgroundImage = "url('" + this.RewardImageLo.src + "')";

				this.ScoreLabel.innerHTML = this.StartingPoints;
				this.ActionButton.onclick = this.Reload;
				this.Counter = window.setInterval("Game.KeepTime()",1000);
				document.onmousedown = Events.SelectCard;
				document.onmousemove = Events.MoveCard;
				document.onmouseup = Events.ReleaseCard;
			});

			method('Finalize',function(){
				window.clearInterval(this.Counter);
				this.Score("FINISH");

				// DEDUCT POINTS FOR EACH REMAINING CARD IN THE COLUMNS, PLAYED STACK, AND UNPLAYED STACKS
				Debug.Write("Played stack has: " + this.PlayedStack.Count() + " cards remaining.");
				this.Score("REMAINING",this.PlayedStack.Count());
				Debug.Write("Unplayed stack has: " + this.UnplayedStack.Count() + " cards remaining.");
				this.Score("REMAINING",this.UnplayedStack.Count());
				for(var i = 0; i < this.Columns.length; i++){
					Debug.Write("Column " + i + " has: " + this.Columns[i].Count() + " cards remaining.");
					this.Score("REMAINING",this.Columns[i].Count());
				}

				this.GameBoard.Fade();

				var msg;
				var win = false;
				var score = Math.ceil((this.GetScore()/this.WinPoints)*100);

				if(this.Complete || score >= 100){
					for(var i = 0; i < this.Columns.length; i++){
						this.Columns[i].RemoveAll();
					}

					window.setTimeout("Game.GameBoard.Show()",3000);
					window.setTimeout("Game.ResultsBoard.Hide()",4000);
					this.RewardsZone.style.backgroundImage = "url('" + this.RewardImageHi.src + "')";
					win = true;
					msg = "Awesome! Enjoy the Pic!";
				}
				else{
					msg = "No Soup for You!!";
				}

				this.ResultsBoard.Show(msg,score,win);

				document.onmousedown = null;
				document.onmousemove = null;
				document.onmouseup = null;

				Debug.Write("FINAL!!!");
			});

			method('KeepTime',function(){
				if(Game.RemainingTime >= 0){
					Game.TimeLabel.innerHTML = Math.floor(Game.RemainingTime/60) + ":" + PadZero(Game.RemainingTime % 60);
					Game.RemainingTime--;
				}
				else{
					Game.RemainingTime = 0;
					Game.Finalize();
				}
			});

			method('Score',function(action,multiplier){
				var point = 0;
				multiplier = (multiplier == null ? 1 : multiplier);

				switch(action){
					case "PLAY_CARD":
						point = this.ReStackCount * Game.FlipCardPoints;
						break;
					case "RESTACK":
						point = this.ReStackPoints;
						break;
					case "FINISH":
						point = this.RemainingTime * Game.RemainingTimePointsPerSecond;
						break;
					case "REMAINING":
						point = this.RemainingCardPoints;
						break;
					case "STACK_SUIT":
						point = this.StackedCardPoints;
						break;
					default:
						break;
				}

				point *= multiplier;

				if(point != 0){
					var score = parseInt(Game.ScoreLabel.innerHTML) + point;
					if(score < 0) score = 0;
					this.ScoreLabel.innerHTML = score;
					this.PointLabel.innerHTML = point;
				}

				return point;
			});

			method('Reload',function(){
				document.location.reload();
			});

			method('GetScore',function(){
				return parseInt(this.ScoreLabel.innerHTML);
			});

			function PadZero(num){
				return ((num < 10) ? "0".concat(num) : num);
			}
		}

		return new Solitaire();
	}

	function EventHandler(){
		function EventHandler(){

		}

		with(EventHandler){
			method('SelectCard',function(evt){
				var obj = Dhtml.GetEventSource(evt);
				evt = Dhtml.GetEvent(evt);

				// CLICK EVENT OCCURRED ON A CARD
				if(obj.tagName.toLowerCase() == "div" && obj.className.indexOf("card") > -1){
					Debug.Write('--------- Card clicked. --------');
					if(obj.id.indexOf("false") > -1){
						Debug.Write('*** Initializing select. ***');
						// MOVE THE FREE STACK AND ADD THE SELECTED CARDS TO THE FREE STACK
						Game.InitialPoint.X = Dhtml.GetObjectLeft(obj);
						Game.InitialPoint.Y = Dhtml.GetObjectTop(obj);

						Dhtml.ShiftTo(Game.Selected.Obj, Game.InitialPoint.X, Game.InitialPoint.Y);

						var next;
						do{
							Debug.Write("Attaching node: " + obj.id);
							var card = new Card({C:obj});
							Game.FreeStack.AddCard(card);

							next = obj.parentNode.nextSibling;
							Debug.Write("Next is: " + next);

							// REMOVE THE CARDS FROM THE ORIGINAL STACK
							if(obj.parentNode.parentNode.id.indexOf("column") > -1)
								Game.InitialStack = Game.Columns[parseInt(obj.parentNode.parentNode.id.split('_')[1])];
							else if(obj.parentNode.parentNode.id.indexOf("played") > -1)
								Game.InitialStack = Game.PlayedStack;
							else if(obj.parentNode.parentNode.id.indexOf("suit") > -1)
								Game.InitialStack = Game.Suits[parseInt(obj.parentNode.parentNode.id.split('_')[1])];

							Game.InitialStack.RemoveCard(card);

							obj = ((next) ? next.firstChild : null);
						} while(obj)

						// INITIALIZE THE MOVE ON Selected.Obj (THE <div> WRAPPER AROUND THE STACK)
						if(evt.pageX){
							Game.Selected.OffsetX = evt.pageX - ((Game.Selected.Obj.offsetLeft) ?
								Game.Selected.Obj.offsetLeft : Game.Selected.Obj.left);
							Game.Selected.OffsetY = evt.pageY - ((Game.Selected.Obj.offsetTop) ?
								Game.Selected.Obj.offsetTop : Game.Selected.Obj.top);
						}
						else if(evt.offsetX || evt.offsetY){
							Game.Selected.OffsetX = evt.offsetX - ((evt.offsetX < -2) ?
								0 : document.body.scrollLeft);
							Game.Selected.OffsetY = evt.offsetY - ((evt.offsetX < -2) ?
								0 : document.body.scrollTop);
						}
						else if(evt.clientX){
							Game.Selected.OffsetX = evt.clientX - ((Game.Selected.Obj.offsetLeft) ?
								Game.Selected.Obj.offsetLeft : 0);
							Game.Selected.OffsetY = evt.clientY - ((Game.Selected.Obj.offsetTop) ?
								Game.Selected.Obj.offsetTop : 0);
						}

						Debug.Write('*** Card selected. ***');
					}
					else if(obj.parentNode.parentNode.id.indexOf("unplayed") > -1){
						var card = Game.UnplayedStack.GetCard();
						card.Flip();
						Game.PlayedStack.AddCard(card);
						Game.ScoreDisplay.Show(evt.clientX,evt.clientY,Game.Score("PLAY_CARD"));
						Debug.Write('Flipped new card.');
					}
				}
				else if(obj.id.indexOf("unplayed") > -1){
					Debug.Write('--------- Reset unplayed stack. --------');
					Game.ScoreDisplay.Show(evt.clientX,evt.clientY,Game.Score("RESTACK"));
					Game.ReStackCount++;
					var card = Game.PlayedStack.GetLastCard();
					while(card = Game.PlayedStack.GetLastCard()){
						Game.PlayedStack.RemoveCard(card);
						card.Flip();
						Game.UnplayedStack.AddCard(card);
						card = Game.PlayedStack.GetLastCard();
					}
				}

				return false;
			});

			method('MoveCard',function(evt){
				evt = Dhtml.GetEvent(evt);
				if(Game.FreeStack.HtmlObj.childNodes.length > 0){
					if(evt.pageX){
						Dhtml.ShiftTo(Game.Selected.Obj, (evt.pageX - Game.Selected.OffsetX), (evt.pageY - Game.Selected.OffsetY));
					}
					else if(evt.clientX || evt.clientY){
						Dhtml.ShiftTo(Game.Selected.Obj, (evt.clientX - Game.Selected.OffsetX), (evt.clientY - Game.Selected.OffsetY));
					}
					//Debug.Write('Moving card.');
				}

				evt.cancelBubble = true;
				return true;
			});

			method('ReleaseCard',function(evt){
				var obj = Dhtml.GetEventSource(evt);
				evt = Dhtml.GetEvent(evt);
				var legalMove = false;
				
				if(Game.FreeStack.Count() > 0){
					Debug.Write('*** Card released. ***');
					var target = GetTargetStack(evt.clientX,evt.clientY,Dhtml.GetObjectWidth(Game.Selected.Obj));

					if(target){
						Debug.Write("Target stack is: " + target.HtmlObj.id);
						Game.TargetStack = target;
						
						var card		= Game.FreeStack.GetFirstCard();
						var lastCard	= Game.TargetStack.GetLastCard();

						Debug.Write("Value of released card is: " + card.Value);
						Debug.Write("Value of target card is: " + ((lastCard) ? lastCard.Value : "[Column has no cards]"));
						Debug.Write("Difference in Value is: " + ((lastCard) ? (parseInt(lastCard.Value) - parseInt(card.Value)) : "[Column has no cards]"));

						if((lastCard && ((parseInt(lastCard.Value) - parseInt(card.Value)) == 1) && IsOppositeColor(lastCard,card))
							|| ((!lastCard && card.Value == 13) && Game.TargetStack.HtmlObj.id.indexOf("suit") < 0)){

							legalMove = true;
							Debug.Write("Legal move.");
						}
						else if(((Game.TargetStack.HtmlObj.id.indexOf("suit") > -1)
							&& ((!lastCard && card.Value == 1) || (lastCard && ((lastCard.Value == (card.Value - 1)) && lastCard.Suit == card.Suit)))
							&& Game.FreeStack.Count() == 1)){
							legalMove = true;

							var index = parseInt(Game.TargetStack.HtmlObj.id.split("_")[1]);

							if(Game.TargetStack.Count() + 1 > Game.MaxSuitCount[index]){
								Game.MaxSuitCount[index]++;
								Game.ScoreDisplay.Show(evt.clientX,evt.clientY,Game.Score("STACK_SUIT"));
							}

							Debug.Write("Legal move to suit stack.");
						}

						if(legalMove){
							var next;
							do{
								next = card.HtmlObj.parentNode.nextSibling;
								Game.TargetStack.AddCard(card);
								Game.FreeStack.RemoveCard(card);
								card = ((next) ? new Card({C:next.firstChild}) : null);
							} while(card)

							var initialStackLastCard = Game.InitialStack.GetLastCard();
							if(initialStackLastCard  && initialStackLastCard.FaceDown){
								Game.InitialStack.RemoveCard(initialStackLastCard);
								initialStackLastCard.Flip();
								Game.InitialStack.AddCard(initialStackLastCard);
							}

							// CHECK WINNING CONDITION
							if(Game.Suits[0].Count() == 13 && Game.Suits[1].Count() == 13
								&& Game.Suits[2].Count() == 13 && Game.Suits[3].Count() == 13)
							{
								Debug.Write("<<< GAME COMPLETED! >>>");
								Game.Complete = true;
								Game.Finalize();
							}
						}
					}

					if(!legalMove && Game.FreeStack.HtmlObj.childNodes.length > 0){
						var card = new Card({C:obj});
						var next;
						do{
							next = card.HtmlObj.parentNode.nextSibling;
							Game.InitialStack.AddCard(card);
							Game.FreeStack.RemoveCard(card);
							card = ((next) ? new Card({C:next.firstChild}) : null);
						} while(card)
					}

					Debug.Write('--------- Card release complete. --------');
				}

				Game.TargetStack = null;

				return false;
			});

			var output = null;
			function GetRootStack(obj){
				if(obj.tagName && obj.tagName.toLowerCase() == "ul"){
					 output = obj;
				}
				else if(obj.parentNode){
					GetRootStack(obj.parentNode);
				}

				return output;
			}

			function GetTargetStack(evtX,evtY,cardW){
				var origin = null;

				for(var i = 0; i < Game.Origins.length; i++){
					if(((evtX - Game.Origins[i].X) < cardW)
						&& ((evtX - Game.Origins[i].X) > -1)
						&& (evtY > Game.Origins[i].Y)){
						if((origin == null) || ((evtY - Game.Origins[i].Y) < (evtY - origin.Y))){
							origin = Game.Origins[i];
						}
					}
				}

				if(origin)
					return origin.Stack;
				else
					return null;
			}

			function IsOppositeColor(card1,card2){
				var output = false;

				switch(card1.Suit){
					case 'H':
						output = (card2.Suit == 'C' || card2.Suit == 'S');
						break;
					case 'D':
						output = (card2.Suit == 'C' || card2.Suit == 'S');
						break;
					case 'C':
						output = (card2.Suit == 'H' || card2.Suit == 'D');
						break;
					case 'S':
						output = (card2.Suit == 'H' || card2.Suit == 'D');
						break;
				}
				Debug.Write("Are cards of different color? [" + !output + "]");
				return output;
			}
		}

		return new EventHandler();
	}

	function Debug(enabled,htmlObj){
		function Debug(){
			this.Line = 0;
			this.HtmlObj = document.getElementById(htmlObj);
			this.HtmlObj.value = "";
			this.Enabled = enabled;
			this.Write("<<  DEBUG INITIALIZED  >>");
		}

		with(Debug){
			method('Write',function(str,args){
				if(this.Enabled){
					this.HtmlObj.style.display = 'block';
					//this.HtmlObj.value = "[" + PadZero(parseInt(this.Line)) + "]" + " " + str + "\n" + this.HtmlObj.value;
					var msg = "[" + PadZero(parseInt(this.Line)) + "]" + " " + str;
					this.HtmlObj.insertBefore(document.createTextNode(msg), this.HtmlObj.firstChild);
					this.HtmlObj.insertBefore(document.createElement("br"), this.HtmlObj.firstChild.nextSibling);
					this.Line++;
				}
				else{
					this.HtmlObj.style.display = 'none';
				}
			});
		}

		function PadZero(num){
			var output;

			if(num < 10)
				output = "000".concat(num);
			else if(10 <= num && num < 100)
				output = "00".concat(num);
			else if(100 <= num && num < 1000)
				output = "0".concat(num);
			else
				output = num;

			return output.toString();
		}

		return new Debug();
	}

	/*---------------------------------------------------------------------
    DO NOT MODIFY OR REMOVE.
    -----------------------------------------------------------------------
    jsSolitaire - Skinnable Javascript Solitaire Game
    Copyright (C) 2004 Charles Chen (cchen@charliedigital.com)

    This software package is distributed and protected under the: 
    W3CŪ SOFTWARE NOTICE AND LICENSE
    http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231

    See readme.html and readme.txt for the full versions.    
	---------------------------------------------------------------------*/