
    /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    +                  Sudoku JS v0.9 by Michael Loesler                   +
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    + Copyright (C) 2006 by Michael Loesler, http//derletztekick.de        +
    +                                                                      +
    +                                                                      +
    + This program is free software; you can redistribute it and/or modify +
    + it under the terms of the GNU General Public License as published by +
    + the Free Software Foundation; either version 2 of the License, or    +
    + (at your option) any later version.                                  +
    +                                                                      +
    + This program is distributed in the hope that it will be useful,      +
    + but WITHOUT ANY WARRANTY; without even the implied warranty of       +
    + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        +
    + GNU General Public License for more details.                         +
    +                                                                      +
    + You should have received a copy of the GNU General Public License    +
    + along with this program; if not, write to the                        +
    + Free Software Foundation, Inc.,                                      +
    + 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.            +
    +                                                                      +
     ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/		

	Array.prototype.zeros = function(o,p) {
		for (var i=0; i<o; i++){
			this[i] = new Array();
			for (var j=0; j<p; j++)
				this[i][j] = new Token(0,i,j,0);	
		}
	}

	Array.prototype.canInstert = function(T){
		var SudokuType = Math.floor(Math.sqrt(this.length));
		var check = new Array();
		for (var i=0; i<this.length; i++)
			if (this[i][T.col].number == T.number)
				return false;

		for (var i=0; i<this[0].length; i++)
			if (this[T.row][i].number == T.number)
				return false;
		
		for (var i=Math.floor(T.row/SudokuType)*SudokuType; i<Math.floor(T.row/SudokuType)*SudokuType+SudokuType; i++)
			for (var j=Math.floor(T.col/SudokuType)*SudokuType; j<Math.floor(T.col/SudokuType)*SudokuType+SudokuType; j++)
				if (this[i][j].number == T.number)
					return false;
					
		return true;	
	}
	
	Array.prototype.inArray = function(val){
		for (var i=0; i<this.length; i++)
			if (typeof(this[i]) == "object"){
				return this[i].inArray(val);
			}
			else if (this[i] == val)
					return true;
		return false;
	}
	
	Array.prototype.search = function(val){
		for (var i=0; i<this.length; i++)
			if (typeof(this[i]) == "object"){
				return this[i].search(val);
			}
			else if (this[i] == val)
					return i;
		return false;
	}
	
	Number.prototype.round = function(n){
		return Math.round(this*Math.pow(10,n))/Math.pow(10,n);
	}
	
	function trim(str){
		return str.replace(/^\s*|\s*$/g, "");
	}

	function Token(number, row, col, maxlen){
		this.number = number>maxlen?1:number;
		this.row = row;
		this.col = col;
		this.maxlen = maxlen;
		this.canceledValues = new Array();
		this.addCanceledValues = function(val){
			val = val>this.maxlen?1:val;
			this.canceledValues[this.canceledValues.length]=val;
		}
		this.isCanceledValues = function(val){
			return this.canceledValues.inArray(val);
		}
		this.isEqual = function(T){
			if (this.number == T.number && this.row == T.row && this.col == T.col)
				return true;
			return false;
		}
	}

	var Sudoku = {
		startTime : new Date(),
		counter : 1,
		hoodedFields : 0,
		SudokuType : 0,
		Range : new Image(176,20),
		Controller : new Image(9,16),
		SD : new Array(),
		solvingSD : new Array(),
		initSudoku : function(ST, Level){
			this.SudokuType = ST;
			var Levels = [176, 155, 134, 113, 92, 70, 50, 29, 7];
			var ParentEl = document.getElementById("sudoku");		
			this.startTime = new Date();
			this.Controller.src = "./gfx/sudoku_controller.png";
			this.Controller.alt = "Schwierigkeitsgrad";
			this.Controller.title = "Schwierigkeitsgrad";
			this.Controller.style.cursor = "e-resize";
			this.Range.alt = "";
			this.Range.src = "./gfx/sudoku_range.png";
			this.Controller.style.position = "relative";
		
			if (Level == null){
				this.setLevel(4);
				this.Controller.style.left = (-Levels[4]) + "px";
			}
			else {
				this.setLevel(Levels.search(Level));
				this.Controller.style.left = (-Levels[Levels.search(Level)]) + "px";
			}

			Drag.init(this.Controller, null, -this.Range.width, -this.Controller.width, 0, 0);
			this.Controller.Instanz = this;
			this.Controller.onDragEnd = function(x, y) { 
				x = Math.abs(x);
				for (var i=0; i<Levels.length; i++){
					if ((Levels[i]-10)<=x && (Levels[i]+11)>=x)
						this.Instanz.Controller.style.left = (-1*Levels[i]) + "px";
					
				}
			};	
			
			this.Table = this.createTable(this.SudokuType,this.SudokuType,0,"sudoku_panel");
			ParentEl.replaceChild(this.Table, document.getElementById("sudoku").firstChild);
			ParentEl.appendChild(this.Range);
			ParentEl.appendChild(this.Controller);
			
			this.counter = 1;
			this.SD.zeros(this.SudokuType,this.SudokuType);
			this.solvingSD.zeros(this.SudokuType,this.SudokuType);
			this.SD.isReady = false;
			var T = new Token(Math.floor(Math.random()*this.SudokuType)+1,0,0,this.SudokuType);
			this.createSudoku(T, true);				
		},
		
		setLevel : function(l){
			this.hoodedFields = (l*this.SudokuType>0?l*this.SudokuType:1);
		},
		
		setSudoku2Table : function (){
			function Numsort (a, b) {
				return a - b;
			}
			if (this.SD.isReady){
				var randomNumbers = new Array();

				do {
					var r = Math.floor(Math.random()*this.SudokuType*this.SudokuType)+1;
					if (!randomNumbers.inArray(r))
						randomNumbers.push(r);
				}
				while(randomNumbers.length<this.hoodedFields);
				randomNumbers.sort(Numsort);

				var k=0;
				var Rows = this.Table.getElementsByTagName("tbody")[0].rows;
				for (var i=0; i<this.SudokuType; i++){
					for (var j=0; j<this.SudokuType; j++){
						if (randomNumbers[k] == (i*this.SudokuType)+(j+1)){
							Rows[i].cells[j].firstChild.replaceData(0, Rows[i].cells[j].firstChild.nodeValue.length,"");
							var Input = document.createElement("input");
							Input.value = "";
							Input.type = "text";
							Input.title = "Wert";
							Input.maxLength = 1;
							Input.randomNumbers = randomNumbers;
							Input.id = "uid_"+this.SD[i][j].number;
							Input.Instanz = this;
							Input.sudokuIsSolved = false;
							Input.onclick   = function() { if (this.Instanz.isSolved(this.randomNumbers) && !this.sudokuIsSolved) { this.sudokuIsSolved = true; this.Instanz.getSolutionMessage(this.Instanz.startTime, this.randomNumbers);  }; };
							Input.onchange  = function() { if (this.Instanz.isSolved(this.randomNumbers) && !this.sudokuIsSolved) { this.sudokuIsSolved = true; this.Instanz.getSolutionMessage(this.Instanz.startTime, this.randomNumbers);  }; };
							Input.onkeydown = function() { if (this.Instanz.isSolved(this.randomNumbers) && !this.sudokuIsSolved) { this.sudokuIsSolved = true; this.Instanz.getSolutionMessage(this.Instanz.startTime, this.randomNumbers);  }; };
							Input.onkeyup   = function() { if (this.Instanz.isSolved(this.randomNumbers) && !this.sudokuIsSolved) { this.sudokuIsSolved = true; this.Instanz.getSolutionMessage(this.Instanz.startTime, this.randomNumbers);  }; };
							Input.onfocus   = function() { if (this.Instanz.isSolved(this.randomNumbers) && !this.sudokuIsSolved) { this.sudokuIsSolved = true; this.Instanz.getSolutionMessage(this.Instanz.startTime, this.randomNumbers);  }; };
							Rows[i].cells[j].appendChild(Input); 
							var Br = document.createElement("br");
							Rows[i].cells[j].appendChild(document.createElement("br"));
							var Notice = document.createElement("input");
							Notice.value = "";
							Notice.className = "notice";
							Notice.title = "Notizen";
							Notice.type = "text";
							Rows[i].cells[j].appendChild(Notice);
							Rows[i].cells[j].className += " notice"
							k++;
						}
						else {
							Rows[i].cells[j].firstChild.replaceData(0, Rows[i].cells[j].firstChild.nodeValue.length,this.SD[i][j].number);
							this.solvingSD[i][j] = new Token(this.SD[i][j].number,i,j,this.SudokuType);
						}
					}
				}
				TFootRows = this.Table.getElementsByTagName("tfoot")[0].rows;
				TFootRows[0].cells[0].firstChild.replaceData(0, TFootRows[0].cells[0].firstChild.nodeValue.length, "Auflösen");
				TFootRows[0].cells[0].Instanz = this;
				TFootRows[0].cells[0].onclick = function() { 
					if(confirm("Aktuelles Spiel wirklich beenden und Lösung anzeigen?"))
						this.Instanz.getSolution(randomNumbers);
				};
				TFootRows[0].cells[0].title = "Dieses Spiel vorzeitig auflösen...";
				try { TFootRows[0].cells[0].style.cursor = "pointer"; }
				catch(e){ TFootRows[0].cells[0].style.cursor = "hand"; }
				
				TFootRows[0].cells[TFootRows[0].cells.length-1].firstChild.replaceData(0, TFootRows[0].cells[TFootRows[0].cells.length-1].firstChild.nodeValue.length, "Neues Spiel");
				TFootRows[0].cells[TFootRows[0].cells.length-1].Instanz = this;
				TFootRows[0].cells[TFootRows[0].cells.length-1].onclick = function() { this.Instanz.initSudoku(this.Instanz.SudokuType, Math.abs(parseInt(this.Instanz.Controller.style.left))); };
				TFootRows[0].cells[TFootRows[0].cells.length-1].title = "Ein neues Spiel beginnen...";
				try { TFootRows[0].cells[TFootRows[0].cells.length-1].style.cursor = "pointer"; }
				catch(e){ TFootRows[0].cells[TFootRows[0].cells.length-1].style.cursor = "hand"; }
			}
		},

		getSolutionMessage : function(startTime, randomNumbers){
			var Rows = this.Table.getElementsByTagName("tbody")[0].rows;
			for (var i=0; i<randomNumbers.length; i++){
				var r = Math.floor((randomNumbers[i]-1)/this.SudokuType);
				var c = randomNumbers[i] - r*this.SudokuType -1;
				Rows[r].cells[c].getElementsByTagName("input")[0].readOnly = true;
			}
		
			var ss = Math.floor((new Date().getTime() - startTime.getTime())/1000);
			var mm = Math.floor(ss/60);
			var hh = Math.floor(mm/60);
			mm -= hh*60;
			ss -= mm*60;
			var time = (hh>0?hh+" Stunden, ":"") + (hh>0||mm>0?mm+" Minuten, ":"") + (hh>0||mm>0||ss>0?ss+" Sekunden":"")
			var messageStart = "!!!GRATULATION!!!\n\nDu hast das SUDOKU vollständig gelöst!\nBenötigte Zeit: ";
			var messageEnd = ".";
			window.alert( messageStart+time+messageEnd );
		},
		
		getSolution : function(randomNumbers){
			var Rows = this.Table.getElementsByTagName("tbody")[0].rows;
			for (var i=0; i<randomNumbers.length; i++){
				var r = Math.floor((randomNumbers[i]-1)/this.SudokuType);
				var c = randomNumbers[i] - r*this.SudokuType -1;
				Rows[r].cells[c].getElementsByTagName("input")[0].value = Rows[r].cells[c].getElementsByTagName("input")[0].id.replace(/uid_/, "");
				Rows[r].cells[c].getElementsByTagName("input")[0].readOnly = true;
				Rows[r].cells[c].getElementsByTagName("input")[0].sudokuIsSolved = true;
			}
		},
			
		isSolved : function(randomNumbers){
			var Rows = this.Table.getElementsByTagName("tbody")[0].rows;
			for (var i=0; i<randomNumbers.length; i++){
				var r = Math.floor((randomNumbers[i]-1)/this.SudokuType);
				var c = randomNumbers[i] - r*this.SudokuType -1;
				if (trim(Rows[r].cells[c].getElementsByTagName("input")[0].value)=="")
					return false;
				this.solvingSD[r][c] = new Token(0,r,c,this.SudokuType);
			}
			for (var i=0; i<randomNumbers.length; i++){
				var r = Math.floor((randomNumbers[i]-1)/this.SudokuType);
				var c = randomNumbers[i] - r*this.SudokuType -1;
				var userVal = new Number(Rows[r].cells[c].getElementsByTagName("input")[0].value);				
				if (!this.solvingSD.canInstert(new Token(userVal, r, c,this.SudokuType)))
					return false;
			}
			return true;
		},

		
		createSudoku : function(T,isStepForward) {
			this.counter++;
			
			if (this.counter%5==0){
				var thisObject = this;
				window.setTimeout(function() { thisObject.createSudoku(T,isStepForward); } ,1);
			}
			else {
				if (T.col == this.SD[0].length && T.row == this.SD.length-1){
					this.SD.isReady = true;
					return this.setSudoku2Table();
				}
				
				if (T.col>=this.SD[0].length){
					T.col=0;
					T.row++;
				}
				else if (T.col<0){
					T.col=this.SD.length-1;
					T.row--;
				}
				
				if (!isStepForward){
					T = this.SD[T.row][T.col];
					this.SD[T.row][T.col] = new Token(0,T.row,T.col,this.SudokuType);
				}

				for (var l=0; l<this.SD.length; l++){
					if (this.SD.canInstert(T) && !T.isCanceledValues(T.number)){
						T.addCanceledValues(T.number);	
						this.SD[T.row][T.col] = T;
						var StatusCell = this.Table.getElementsByTagName("tfoot")[0].rows[0].cells[this.Table.getElementsByTagName("tfoot")[0].rows[0].cells.length-1];
						StatusCell.firstChild.replaceData(0, StatusCell.firstChild.nodeValue.length, (((T.row) * this.SD.length + (T.col+1))*100/Math.pow(this.SD.length,2)).round(1)+" %" );
						return this.createSudoku(new Token(Math.floor(Math.random()*this.SD.length)+1,T.row, T.col+1,this.SudokuType),true); 
					}
					else {
						T.number++;
						T.number=T.number>this.SD.length?1:T.number;
					}
				}
				this.SD[T.row][T.col] = new Token(0,T.row,T.col,this.SudokuType);
				return this.createSudoku(new Token(0, T.row, T.col-1,this.SudokuType), false);
			}
		},
	
		createTable : function(row,col,defaultValue,id){
			var Table = document.createElement("table");
			var TBody = document.createElement("tbody");
			var TFoot = document.createElement("tfoot");
			Table.appendChild(TBody);
			Table.appendChild(TFoot);
			Table.id = id;
			for (var i=0; i<row; i++){
				var Tr = document.createElement("tr");
				for (var j=0; j<col; j++){
					var Td = document.createElement("td");
					Td.appendChild(document.createTextNode(defaultValue));
					Td.className = "";
					if (i==0)
						Td.className += " topBorder";
					if ((i+1)%Math.sqrt(this.SudokuType) == 0)
						Td.className += " bottomBorder ";
					if (j==0)
						Td.className += " leftBorder ";
					if ((j+1)%Math.sqrt(this.SudokuType) == 0)
						Td.className += " rightBorder ";
					
					Tr.appendChild(Td);
				}
				TBody.appendChild(Tr);
			}
			var Tr = document.createElement("tr");
			for (var j=0; j<3; j++){
				var Td = document.createElement("td");
				switch (j){
					case 0:
						Td.colSpan = Math.sqrt(this.SudokuType);
						Td.className = "leftFootInfo";
						Td.appendChild(document.createTextNode(""));
					break;
					case 1:
						Td.colSpan = Math.sqrt(this.SudokuType);
						Td.className = "centerFootInfo";
						Td.appendChild(document.createTextNode("SUDOKU-Puzzle"));
						try { Td.style.cursor = "pointer"; }
						catch(e){ Td.style.cursor = "hand"; }
						Td.onclick = function() { window.open("http://derletztekick.de", "_blank"); };
						Td.title = "SUDOKU-Puzzle stammt von derletztekick.de - diese Seite besuchen...";
						
					break;
					case 2:
						Td.colSpan = Math.sqrt(this.SudokuType);
						Td.className = "rightFootInfo";
						Td.appendChild(document.createTextNode(defaultValue+" %"));
					break;
				}
				Tr.appendChild(Td);
			}
			TFoot.appendChild(Tr);
			return Table;
		}
	}
	
	var isDOMContentLoaded = false;
	function addContentLoadListener () {
		if (document.addEventListener) {
		var DOMContentLoadFunction = function () {
			isDOMContentLoaded = true;
			Sudoku.initSudoku(9, null);
		};
		document.addEventListener("DOMContentLoaded", DOMContentLoadFunction, false);
	}
	var oldonload = (window.onload || new Function());
		window.onload = function () {
			if (!isDOMContentLoaded) {
				oldonload();
				Sudoku.initSudoku(9, null);
			}
		};
	}
	addContentLoadListener();

	
/*****************************************************************************************************/
	
/***************************************************
 * dom-drag.js                                     *
 * 09.25.2001                                      *
 * www.youngpup.net                                *
 ***************************************************
 * 10.28.2001 - fixed minor bug where events       *
 * sometimes fired off the handle, not the root.   *
 ***************************************************/
                                              
var Drag = {
	obj : null,
	init : function(o, oRoot, minX, maxX, minY, maxY, bSwapHorzRef, bSwapVertRef, fXMapper, fYMapper){
		o.onmousedown	= Drag.start;
		o.hmode			= bSwapHorzRef ? false : true ;
		o.vmode			= bSwapVertRef ? false : true ;
		o.root = oRoot && oRoot != null ? oRoot : o ;

		if (o.hmode  && isNaN(parseInt(o.root.style.left  )))
			o.root.style.left   = "0px";

		if (o.vmode  && isNaN(parseInt(o.root.style.top   ))) 
			o.root.style.top    = "0px";

		if (!o.hmode && isNaN(parseInt(o.root.style.right ))) 
			o.root.style.right  = "0px";

		if (!o.vmode && isNaN(parseInt(o.root.style.bottom))) 
			o.root.style.bottom = "0px";

		o.minX	= typeof minX != 'undefined' ? minX : null;
		o.minY	= typeof minY != 'undefined' ? minY : null;
		o.maxX	= typeof maxX != 'undefined' ? maxX : null;
		o.maxY	= typeof maxY != 'undefined' ? maxY : null;
		
		o.xMapper = fXMapper ? fXMapper : null;
		o.yMapper = fYMapper ? fYMapper : null;

		o.root.onDragStart	= new Function();
		o.root.onDragEnd	= new Function();
		o.root.onDrag		= new Function();
	},

	start : function(e){
		var o = Drag.obj = this;
		e = Drag.fixE(e);
		var y = parseInt(o.vmode ? o.root.style.top  : o.root.style.bottom);
		var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right );
		o.root.onDragStart(x, y);
		o.lastMouseX	= e.clientX;
		o.lastMouseY	= e.clientY;

		if (o.hmode) {
			if (o.minX != null)	
				o.minMouseX	= e.clientX - x + o.minX;

			if (o.maxX != null)	
				o.maxMouseX	= o.minMouseX + o.maxX - o.minX;

		} else {
			if (o.minX != null) 
				o.maxMouseX = -o.minX + e.clientX + x;

			if (o.maxX != null) 
				o.minMouseX = -o.maxX + e.clientX + x;
		}

		if (o.vmode) {
			if (o.minY != null)	o.minMouseY	= e.clientY - y + o.minY;
			if (o.maxY != null)	o.maxMouseY	= o.minMouseY + o.maxY - o.minY;

		} else {
			if (o.minY != null) o.maxMouseY = -o.minY + e.clientY + y;
			if (o.maxY != null) o.minMouseY = -o.maxY + e.clientY + y;
		}
		
		document.onmousemove	= Drag.drag;
		document.onmouseup		= Drag.end;
		return false;
	},

	drag : function(e){
		e = Drag.fixE(e);
		var o = Drag.obj;
		var ey	= e.clientY;
		var ex	= e.clientX;
		var y = parseInt(o.vmode ? o.root.style.top  : o.root.style.bottom);
		var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right );
		var nx, ny;

		if (o.minX != null) 
			ex = o.hmode ? Math.max(ex, o.minMouseX) : Math.min(ex, o.maxMouseX);

		if (o.maxX != null) 
			ex = o.hmode ? Math.min(ex, o.maxMouseX) : Math.max(ex, o.minMouseX);

		if (o.minY != null) 
			ey = o.vmode ? Math.max(ey, o.minMouseY) : Math.min(ey, o.maxMouseY);

		if (o.maxY != null) 
			ey = o.vmode ? Math.min(ey, o.maxMouseY) : Math.max(ey, o.minMouseY);

		nx = x + ((ex - o.lastMouseX) * (o.hmode ? 1 : -1));
		ny = y + ((ey - o.lastMouseY) * (o.vmode ? 1 : -1));

		if (o.xMapper)
			nx = o.xMapper(y)
			
		else if (o.yMapper)	
			ny = o.yMapper(x)

		Drag.obj.root.style[o.hmode ? "left" : "right"] = nx + "px";
		Drag.obj.root.style[o.vmode ? "top" : "bottom"] = ny + "px";
		Drag.obj.lastMouseX	= ex;
		Drag.obj.lastMouseY	= ey;
		Drag.obj.root.onDrag(nx, ny);

		return false;
	},

	end : function(){
		document.onmousemove = null;
		document.onmouseup   = null;
		Drag.obj.root.onDragEnd(parseInt(Drag.obj.root.style[Drag.obj.hmode ? "left" : "right"]), parseInt(Drag.obj.root.style[Drag.obj.vmode ? "top" : "bottom"]));
		Drag.obj = null;
	},

	fixE : function(e){
		if (typeof e == 'undefined') 
			e = window.event;

		if (typeof e.layerX == 'undefined') 
			e.layerX = e.offsetX;

		if (typeof e.layerY == 'undefined') 
			e.layerY = e.offsetY;
		return e;
	}
};