/**
 * This constructor function creates a div element into which a
 * CSS-based figure can be drawn.  Instance methods are defined to draw
 * lines and boxes and to insert the figure into the document.
 *
 * The constructor may be invoked using two different signatures:
 *
 *   new CSSDrawing(x, y, width, height, classname, id)
 *
 * In this case a <div> is created using position:absolute at the
 * specified position and size.
 *
 * The constructor may also be invoked with only a width and height:
 * 
 *   new CSSDrawing(width, height, classname, id)
 * 
 * In this case, the created <div> has the specified width and height
 * and uses position:relative (which is required so that the child
 * elements used to draw lines and boxes can use absolute positioning).
 * 
 * In both cases, the classname and id arguments are optional.  If specified, 
 * they are used as the value of the class and id attributes of the created
 * <div> and can be used to associate CSS styles, such as borders with
 * the figure.
 */
function CSSDrawing(/* variable arguments, see above */) {
    // Create and remember the <div> element for the drawing
    var d = this.div = document.createElement("div");
    var next;

    // Figure out whether we have four numbers or two numbers, sizing and 
    // positioning the div appropriately
    if (arguments.length >= 4 && typeof arguments[3] == "number") {
        d.style.position = "absolute";
        d.style.left = arguments[0] + "px";
        d.style.top = arguments[1] + "px";
        d.style.width = arguments[2] + "px";
        d.style.height = arguments[3] + "px";
        next = 4;
    }
    else {
        d.style.position = "relative"; // This is important
        d.style.width = arguments[0] + "px";
        d.style.height = arguments[1] + "px";
        next = 2;
    }

    // Set class and id attributes if they were specified.
    if (arguments[next]) d.className = arguments[next];
    if (arguments[next+1]) d.id = arguments[next+1];
}

/**
 * Add a box to the drawing.
 * 
 * x, y, w, h:    specify the position and size of the box.
 * content:       a string of text or HTML that to appear in the box
 * classname, id: optional class and id values for the box.  Useful to
 *                associate styles with the box for color, border, etc.
 * Returns: The <div> element created to display the box
 */
CSSDrawing.prototype.box = function(x, y, w, h, content, classname, id) {
    var d = document.createElement("div"); 
    if (classname) d.className = classname;
    if (id) d.id = id;
    d.style.position = "absolute";
    d.style.left = x + "px";
    d.style.top = y + "px";
    d.style.width = w + "px";
    d.style.height = h + "px";
    /* d.cols = 1;
    d.rows = 1; */
    d.innerHTML = content;
    this.div.appendChild(d);
    return d;
};

/**
 * Add a horizontal line to the drawing.
 * 
 * x, y, width:   specify start position and width of the line
 * classname, id: optional class and id values for the box.  At least one
 *                must be present and must specify a border style which
 *                will be used for the line style, color, and thickness.
 * Returns: The <div> element created to display the line
 */
CSSDrawing.prototype.horizontal = function(x, y, width, classname, id) {
    var d = document.createElement("div");
    if (classname) d.className = classname;
    if (id) d.id = id;
    d.style.position = "absolute";
    d.style.left = x + "px";
    d.style.top = y + "px";
    d.style.width = width + "px";
    d.style.height = 1 + "px";
    d.style.borderLeftWidth = d.style.borderRightWidth =
        d.style.borderBottomWidth = "0px";
    this.div.appendChild(d);
    return d;
};

/**
 * Add a vertical line to the drawing.
 * See horizontal() for details.
 */
CSSDrawing.prototype.vertical = function(x, y, height, classname, id) {
    var d = document.createElement("div");
    if (classname) d.className = classname;
    if (id) d.id = id;
    d.style.position = "absolute";
    d.style.left = x + "px";
    d.style.top = y + "px";
    d.style.width = 1 + "px";
    d.style.height = height + "px";
    d.style.borderRightWidth = d.style.borderBottomWidth =
        d.style.borderTopWidth = "0px";
    this.div.appendChild(d);
    return d;
};

/** Add the drawing to the document as a child of the specified container */
CSSDrawing.prototype.insert = function(container) {
    if (typeof container == "string") 
        container = document.getElementById(container);
    container.appendChild(this.div);
}

/** Add the drawing to the document by replacing the specified element */
CSSDrawing.prototype.replace = function(elt) {
    if (typeof elt == "string") elt = document.getElementById(elt);
    elt.parentNode.replaceChild(this.div, elt);
}


//***********************	Event Handling functions	***********
// From Danny Goodman, JavaScript & DHTML Cookbook, Ch.9, pg 244--247

function getPageEventCoords(evt) {
	
	var coords = {left:0, top:0};
	if (evt.pageX) {
		coords.left = evt.pageX;
		coords.top = evt.pageY;
	} else if (evt.clientX) {
		coords.left = 
			evt.clientX + document.body.scrollLeft - document.body.clientLeft;
		coords.top = 
			evt.clientY + document.body.scrollTop - document.body.clientTop;
		// include html element space, if applicable
		if (document.body.parentElement && document.body.parentElement.clientLeft) {
			var bodParent = document.body.parentElement;
			coords.left += bodParent.scrollLeft - bodParent.clientLeft;
			coords.top += bodParent.scrollTop - bodParent.clientTop;
		}
	}
	return coords;
}
//******* end function getPageEventCoords(evt)

function getPositionedEventCoords(evt) {
	
	var elem = (evt.target) ? evt.target : evt.srcElement;
	var coords = {left:0, top:0};
	if (evt.layerX) {
		var borders = {left:parseInt(DHTMLAPI.getComputedStyle("progressBar",
						"border-left-width")),
						top:parseInt(getElementStyle("progressBar",
						"border-top-width"))};
		coords.left = evt.layerX - borders.left;
		coords.top = evt.layerY - borders.top;
	} else if (evt.offsetX) {
		coords.left = evt.offsetX;
		coords.top = evt.offsetY;
	}
	evt.cancelBubble = true;
	return coords;
}
// ************ end function getPositionedEventCoords(evt)

// From Danny Goodman, JavaScript & DHTML Cookbook, Ch.9, pg 238--239

function addEvent(elem, evtType, func, capture)	{
	
	capture = (capture) ? capture : false;
	if (elem.addEventListener)	{
		elem.addEventListener(evtType, func, capture);
	} else if (elem.attachEvent)	{
		elem.attachEvent("on" + evtType, func);
	} else {
		// for IE/Mac, NN4, and older
		elem["on" + evtType] = func;
	}
}
//********  end function addEvent(elem, evtType, func, capture)

function removeEvent(elem, evtType, func, capture) {
	
	capture = (capture) ? capture : false;
	if (elem.removeEventListener) {
		elem.removeEventListener(evtType, func, capture);
	} else if (elem.attachEvent) {
		elem.detachEvent("on" + evtType, func);
	} else {
		// for IE/Mac, NN4 and older
		elem["on" + evtType] = null;
	}
}
//******** end function removeEvent(elem, evtType, func, capture) 

function addOnLoadEvent(func) {
	
	if (window.addEventListener || window.attachEvent) {
		addEvent(window, "load", func, false);
	} else {
		var oldQueue = (window.onload) ? window.onload : function() {};
		window.onload = function() {
			oldQueue();
			func();
		}
	}
}
//******** end function addOnLoadEvent(func)
//****************************************************************
//  Cookie functions
//****************************************************************
// Utility function to retrieve an expiration date in proper format;
// pass three integer parameters for the number of days, hours, and minutes
// from now that you want the cookie to expire (or negative values for a past date)
// All three parameters are required, so use zeros where appropriate.

function getExpDate(days, hours, minutes) {
	
	var expDate = new Date;
	if (typeof days == "number" && typeof hours == "number" && typeof minutes == "number" ) {
		expDate.setDate(expDate.getDate() + parseInt(days));
		expDate.setHours(expDate.getHours() + parseInt(hours));
		expDate.setMinutes(expDate.getMinutes() + parseInt(minutes));
		return expDate.toUTCString();
	}
} // end function getExpDate(days, hours, minutes)
//****************************************************************
// Utility function called by getCookie()

function getCookieVal(offset) {
	var endstr = document.cookie.indexOf (";", offset);
	if (endstr == -1) {
		endstr = document.cookie.length;
	}
	return decodeURI(document.cookie.substring(offset, endstr));
}
//****************************************************************
// Primary function to retrieve cookie by name

function getCookie(name) {
	var arg = name + "=";
	var alen = arg.length;
	var clen = document.cookie.length;
	var i = 0;
	while (i < clen) {
		var j = i + alen;
		if (document.cookie.substring(i, j) == arg) {
			return getCookieVal(j);
		}
		i = document.cookie.indexOf(" ", i) + 1;
		if (i == 0) break;
	}
	return "";
}
//****************************************************************
// Store cookie value with optional details as needed

function setCookie(name, value, expires, path, domain, secure) {
	
	document.cookie = name + "=" + encodeURI(value) +
		((expires) ? "; expires=" + expires : "") +
		((path) ? "; path=" + path : "") +
		((domain) ? "; domain=" + domain : "") +
		((secure) ? "; secure" : "");
}
//****************************************************************
// Remove cookie by setting an ancient expiration date

function deleteCookie(name, path, domain) {
	if (getCookie(name)) {
		document.cookie = name + "=" +
			((path) ? "; path=" + path : "") +
			((domain) ? "; domain=" + domain : "") +
			"; expires=Thu, 01-Jan-70 00:00:01 GMT";
	}
}
// End cookie functions  *****************************************
//****************************************************************
//	Acrostic utility functions...
//****************************************************************

// Non-puzzle-specific globals

var isIE = true;
var bHintRequested = false;
var iHintSelection = -1;
var bShowMistakes = false;
var bSolution = false;
var bGridSelected = false;
var bPuzzleSelected = false;
var bBadPuzzle = false; // If error condition is detected in puzzle-specific globals this is set to true.
var sInstructionString = "Instructions: Start by filling in as many of the words on the left as you can, based on the clues given. When complete, the grid will contain a quote. "
							+ "The first letters of the words on the left, reading down, will spell out the author of the quote and the title"
							+ " of the work that the quote was taken from.\n Keyboard navigation: Arrow keys, square brackets, semicolon and minus sign "
							+ "will move the cursor. Space, backslash, and underscore will erase letters, and the equal sign will move between the words and the grid.";

//***************************************************************************
function savePuzzle() {
	
	var sSaveString = sPuzzleCode + aGridAttempt.join(""); // Builds a string consisting of the unique puzzle identifier and the gridAttempt array.
															// All sPuzzleCodes have six characters.
	var name = 'savestring'; // variable name for cookie
	
	setCookie(name, sSaveString, getExpDate(14,0,0)); // Saves for fourteen days 
}
//***************************************************************************
function restoreSaved() {
	
	var name = 'savestring';
	var sRecoverString = getCookie(name);
	var iLength = aGridAttempt.length + 6;
	
	if (sRecoverString) {	// true if getCookie returned a non-empty string, false if it returned the empty string (we hope).
		if (sRecoverString.substring(0, 6) == sPuzzleCode) {	// Is this the right puzzle?
			if (sRecoverString.length == iLength ) {	// An additional check.
				for (var i=6; i<iLength; i++) {
					aGridAttempt[i-6] = sRecoverString.charAt(i);	// Put the character into the array.
					if (sRecoverString.charAt(i) == " ") {
						aWurdzAttempt[aGridToWurdz[i-6]] = "_";	// Replace spaces with underscores.
					} else {
					aWurdzAttempt[aGridToWurdz[i-6]] = sRecoverString.charAt(i);
					}
				}
			}
		}
	}
}
//***************************************************************************
function cluesDraw(cluDrawing) {	// Draw clues.
	
	var iCurrYPos = 0;
	
	for (var y0 = 0; y0 < iCluNum; y0++) {
		cluDrawing.box(0, iCurrYPos, iCluWidth-1, (iLineHeight*aClueLines[y0]-8), aClues[y0], "cluestyle"); // x, y, width, height
		iCurrYPos += iLineHeight * aClueLines[y0] - ((aClueLines[y0]-1)*iPadFactor);
		// Calculates how far down to draw the next box based on the number of lines in current clue.
		// Subtracts out a padding factor for multiple lines, otherwise multi-line clues take up
		// too much space. Probably this could be handled in the CSS padding or border or something...
	}
}

//***************************************************************************
function wurdzDraw(wurdzFigure) {	// Draw underscores or letters. Display aWurdAttempt (or solution) with underscores for blanks.
	
	var iNumUnderScore = 0;
	var iCounter = 0;
	var iCurrYPos = 4;
	
	
	for (var y0 = 0; y0 < iCluNum; y0++) {	// For each clue...
		iNumUnderScore = aWurdz[y0].length;
		for (var x0 = 0; x0 < iNumUnderScore; x0++) {	// ...for each letter of the Wurd...
		
			// Draw solution
			if (bSolution) {
				wurdzFigure.box((x0*iUnderscoreWidth)+2, iCurrYPos, iUnderscoreWidth-2, iWurdzHeight-1, aWurdz[y0].charAt(x0), "wurdzstyle");
			
			// Draw hint
			} else if ( (aWurdzToGrid[iCounter] == aGridNumbers[iBoxSelected]) && (bHintRequested) ) {
				wurdzFigure.box((x0*iUnderscoreWidth)+2, iCurrYPos, iUnderscoreWidth-2, iWurdzHeight-1, sWurdzString.charAt([iCounter]), "hintwurdzstyle");
				
			// Highlight errors if requested.
			} else if (bShowMistakes) {
				if (sWurdzString.charAt(iCounter) != aWurdzAttempt[iCounter])
					wurdzFigure.box((x0*iUnderscoreWidth)+2, iCurrYPos, iUnderscoreWidth-2, iWurdzHeight-1, " ", "errwurdzstyle");
				else
					wurdzFigure.box((x0*iUnderscoreWidth)+2, iCurrYPos, iUnderscoreWidth-2, iWurdzHeight-1, aWurdzAttempt[iCounter], "wurdzstyle");
			
			//Draw in gray if grid selected, but no highlight if puzzle not selected.
			} else if  ( (aWurdzToGrid[iCounter] == aGridNumbers[iBoxSelected]) && (bGridSelected) ) { 
				if (bPuzzleSelected)
					wurdzFigure.box((x0*iUnderscoreWidth)+2, iCurrYPos, iUnderscoreWidth-2, iWurdzHeight-1, aWurdzAttempt[iCounter], "selectedwurdzstyle");
				else wurdzFigure.box((x0*iUnderscoreWidth)+2, iCurrYPos, iUnderscoreWidth-2, iWurdzHeight-1, aWurdzAttempt[iCounter], "wurdzstyle"); 
			
			// Highlight in red if wurdz selected, but no highlight if puzzle not selected.
			} else if ( (aWurdzToGrid[iCounter] == aGridNumbers[iBoxSelected]) && (!bGridSelected) ) {
				if (bPuzzleSelected)
					wurdzFigure.box((x0*iUnderscoreWidth)+2, iCurrYPos, iUnderscoreWidth-2, iWurdzHeight-1, aWurdzAttempt[iCounter], "selectedwurdzstyle2");
				else
					wurdzFigure.box((x0*iUnderscoreWidth)+2, iCurrYPos, iUnderscoreWidth-2, iWurdzHeight-1, aWurdzAttempt[iCounter], "wurdzstyle");
			
			//Otherwise draw current attempt.
			} else
				wurdzFigure.box((x0*iUnderscoreWidth)+2, iCurrYPos, iUnderscoreWidth-2, iWurdzHeight-1, aWurdzAttempt[iCounter], "wurdzstyle");
				
			iCounter++;
		}
		iCurrYPos += iLineHeight * aClueLines[y0] - ((aClueLines[y0]-1)*iPadFactor);
	}
	
}
//***************************************************************************/
function drawGrid(drawing) {	// Draw puzzle grid with blanks or letters.

// Draw a series of boxes for the puzzle grid. Insert letter from solution or solution attempt as appropriate.
	var iCounter = 0;
	for (var y0 = 0; y0 < iBoxDown; y0++)
		for (var x0 = 0; x0 < iBoxAcross; x0 ++) {
			
			// Draw filled rect for space or padding at end of quote.
			if ( (sAcrostiString.charAt(iCounter)==" ") || (iCounter >= iASLen) ) {	
				drawing.box((x0*iBoxWidth), (y0*iBoxHeight), iBoxWidth-1, iBoxHeight-2, " ", "spacestyle");
			
			// Draw hyphen.
			} else if (sAcrostiString.charAt(iCounter)=="-") { // Hyphen
				drawing.box((x0*iBoxWidth), (y0*iBoxHeight), iBoxWidth-1, iBoxHeight-2, "-", "boxstyle");
			
			// Draw solution.
			} else if (bSolution) {	
				drawing.box((x0*iBoxWidth), (y0*iBoxHeight), iBoxWidth-1, iBoxHeight-2, sAcrostiString.charAt(iCounter), "solutionstyle");
			
			// Give us a hint.
			} else if ((iCounter == iBoxSelected) && (bHintRequested)) { 
				drawing.box((x0*iBoxWidth), (y0*iBoxHeight), iBoxWidth-1, iBoxHeight-2, sSolutionString.charAt([aGridNumbers[iCounter]]), "hintboxstyle");
			
			// Highlight errors if requested.
			} else if (bShowMistakes) {
				if (aGridAttempt[aGridNumbers[iCounter]] != sSolutionString.charAt(aGridNumbers[iCounter])) 
					drawing.box((x0*iBoxWidth), (y0*iBoxHeight), iBoxWidth-1, iBoxHeight-2, " ", "errorboxstyle");
				else
					drawing.box((x0*iBoxWidth), (y0*iBoxHeight), iBoxWidth-1, iBoxHeight-2, aGridAttempt[aGridNumbers[iCounter]], "boxstyle");
			
			// Draw selected box in grid as gray if Wurdz is selected or no highlight if puzzle not selected.
			} else if  ((iCounter == iBoxSelected) && (!bGridSelected))   {	
				if (bPuzzleSelected)
					drawing.box((x0*iBoxWidth), (y0*iBoxHeight), iBoxWidth-1, iBoxHeight-2, aGridAttempt[aGridNumbers[iCounter]], "selectedboxstyle");
				else drawing.box((x0*iBoxWidth), (y0*iBoxHeight), iBoxWidth-1, iBoxHeight-2, aGridAttempt[aGridNumbers[iCounter]], "boxstyle");
			
			// Draw selected box in red if grid is selected, no highlight if puzzle not selected.
			} else if ((iCounter == iBoxSelected) && (bGridSelected) ) {	
				if (bPuzzleSelected)
					drawing.box((x0*iBoxWidth), (y0*iBoxHeight), iBoxWidth-1, iBoxHeight-2, aGridAttempt[aGridNumbers[iCounter]], "selectedboxstyle2");
				else
					drawing.box((x0*iBoxWidth), (y0*iBoxHeight), iBoxWidth-1, iBoxHeight-2, aGridAttempt[aGridNumbers[iCounter]], "boxstyle");
			
			// Otherwise draw current attempt at solution.
			} else {		
				drawing.box((x0*iBoxWidth), (y0*iBoxHeight), iBoxWidth-1, iBoxHeight-2, aGridAttempt[aGridNumbers[iCounter]], "boxstyle");
			}			 
			 iCounter++;
		}
		//alert(iBoxSelected);
}	// end function drawGrid(drawing)
//***********************************************************************
function initPuzzle() {	// Draws the complete blank puzzle (with clues). Performs other initializations as needed.
	
	// First, some data validation...
	if (aWurdz.length != aClues.length)
		alert ("aWurdz length not equal to aClues length");
		
	if (aWurdz.length != aClueLines.length) {
		alert ("aWurdz length not equal to aClueLines length");
		alert(iCluNum);
	}	
	if (sSolutionString.length != sWurdzString.length)
		alert ("sSolutionString length not equal to sWurdzString length");
		
	if (aWurdzToGrid.length != sSolutionString.length)
		alert ("sSolutionString length not equal to aWurdzToGrid length");

	for (var i = 0; i < iCluNum; i++) 
		iTotalCluesHeight += aClueLines[i]; 
		// Calculate total lines of clues based on number of clues and number of lines
	
	
	var iCurrClue = 0; // Initialize the aWhichClue array. Used mainly for keyboard navigation of the puzzle.
	var iCount = 0;
	
	for (i=0; i< iTotalLetters; i++) {
		if (iCount < aWurdz[iCurrClue].length) {
			aWhichClue[i] = iCurrClue;
			iCount++;
			aWhichLetter[i] = iCount;
		} else { // Adjust counters and go to next clue.
			iCount = 1; // This assignment is approved by the Society for the Promotion of Confusing Index Variables.
			iCurrClue++;
			aWhichClue[i] = iCurrClue;
			aWhichLetter[i] = iCount;
		}
	}
		
	iTotalCluesHeight *= iLineHeight;
	iWurdzBottom = iWurdzTop + iTotalCluesHeight; 
	iCount = 0;
	
	for (i=0; i < iASLen; i++) {
		if ( (sAcrostiString.charAt(i) == " ") || (sAcrostiString.charAt(i) == "-") )
			aGridNumbers[i] = -1
		else {
			aGridNumbers[i] = iCount;
			iCount++;
		}
	}
	if (iCount == 0) {
		alert("WARNING: No letters In puzzle!");
		bBadPuzzle = true;
	}
	
	iCount = 0;
	for (i=0; i<iASLen; i++) {
		while ((sAcrostiString.charAt(iCount) == " ") || (sAcrostiString.charAt(iCount) == "-") )
			iCount++;
		aGridAttemptToGaps[i] = iCount;
		iCount++;
	}
	
	iCount = aWurdzToGrid.length;
	for (i=0; i<iCount;i++) {
		aGridToWurdz[aWurdzToGrid[i]] = i; // Reverse mapping.
	}
	
	// Initialize solution attempt.
	for (i=0; i < iTotalLetters; i++) {
		aGridAttempt[i] = " ";
		aWurdzAttempt[i] = "_";
	}
	
	restoreSaved();	// Load any saved data from cookie.
		
	iBoxSelected = aGridAttemptToGaps[aWurdzToGrid[0]];
		
	if (!bBadPuzzle) { // Continue if no error was found.
	
		// Correct top of puzzle position if browser is IE.
		// 
		//If isIE is true, 40 pixels are added to the puzzle position. This should bring the puzzle 
		// down to the proper place for IE.
		
		isIE = (navigator.appname == "Microsoft Internet Explorer");
	
		if (isIE) {
			iCluesTopCoord += 40;
		}
		
	
		// Create a figure for the clues.
		var cluFigure = new CSSDrawing(iCluesLeftCoord, iCluesTopCoord, iCluWidth+1, iTotalCluesHeight, "clustyle", "clufigure");
		
		cluesDraw(cluFigure);
		
		cluFigure.replace("cluplaceholder");
		// The clues don't change, so no further action is needed for them.
		
		// Create figure for the wurdz.
		var wurdzFigure = new CSSDrawing(iWurdzLeftCoord, iCluesTopCoord, (iUnderscoreWidth * iMaxWurdzLength), iTotalCluesHeight, "wurdz", "wurdzfigure");
		
		wurdzDraw(wurdzFigure);
		wurdzFigure.replace("wurdzplaceholder");
		
		// Create a figure for the puzzle grid
		var figure = new CSSDrawing(iPuzzleLeftCoord, iPuzzleTopCoord, (iBoxWidth*iBoxAcross + 1), (iBoxDown*iBoxHeight + 1), "figure", "puzzle");
				
		// Add boxes to the drawing
		drawGrid(figure);
	
		// Now insert the grid into the document, replacing the grid placeholder 
		figure.replace("gridplaceholder");
		
		// Create a figure for the instructions/solution and draw them/it.
		var InstructionsFigure =  new CSSDrawing(iInstructionsLeft, iInstructionsTop, iInstructionsWidth, iInstructionsHeight, "instructionstyle", "instructions");
		InstructionsFigure.box(0, 0, iInstructionsWidth-1, iInstructionsHeight-1, sInstructionString, "instructionstyle");
		InstructionsFigure.replace("solplaceholder");
		
		document.onclick = handlePuzzleClick;
		document.onkeypress = handlePuzzleKeypress; 
		document.onkeydown = handlePuzzleKeydown; // Need both since keypress misses some keys in some browsers.
		// Other possibilities:
		//addEvent(document, "click", handlePuzzleClick, false);
		//addEvent(document, "mousedown", handlePuzzleClick, false);
		//addEvent(document, "keypress", handlePuzzleKeypress, false);
		//addEvent(document, "keydown", handlePuzzleKeydown, false);
	}
}	// End function initPuzzle()

//***************************************************************************

function drawNewGrid() {
	
	// Create a new figure for grid, draw and replace old grid
	var figure = new CSSDrawing(iPuzzleLeftCoord,iPuzzleTopCoord,(iBoxWidth*iBoxAcross + 1), (iBoxDown*iBoxHeight + 1), "figure", "puzzle");
	drawGrid(figure);
	figure.replace("puzzle");
	
	// Create figure for the wurdz, draw and replace old Wurdz.
	var wurdzFigure = new CSSDrawing(iWurdzLeftCoord, iCluesTopCoord, (iUnderscoreWidth * iMaxWurdzLength), iTotalCluesHeight, "wurdz", "wurdzfigure");
	wurdzDraw(wurdzFigure);
	wurdzFigure.replace("wurdzfigure");
	if (bSolution) {
		var InstructionsFigure =  new CSSDrawing(iInstructionsLeft, iInstructionsTop, iInstructionsWidth, iInstructionsHeight, "instructionstyle", "instructions");
		InstructionsFigure.box(0, 0, iInstructionsWidth-1, iInstructionsHeight-1, sAuthorQuote, "instructionstyle");
		InstructionsFigure.replace("instructions");
	}

}	// End function drawFigure() 
//***************************************************************************
function showHint() {
	
	if (bSolution) // Don't waste time trying to find a hint if the solution is already displayed.
		return;
	bShowMistakes = false;
	var bFoundHint = false;	// Flag to indicate whether it's necessary to wrap the search to the top.
	var iLastLetter = sSolutionString.length; 
	
	 if (bGridSelected) {
		var iTheCurrLetter = aGridNumbers[iBoxSelected];
		
		for (var i=iTheCurrLetter; i<iLastLetter; i++) { // First look at current selection forward to the end of the puzzle.
			if (sSolutionString.charAt(i) != aGridAttempt[i]) {
				bFoundHint = true;
				iBoxSelected = aGridAttemptToGaps[i]; // Move selection to first discrepancy.
				break;
			}
		}
		if (!bFoundHint)  // GridAttempt string is correct from current selection to end; start over at begining.
			for (i=0; i<iTheCurrLetter; i++)
				if (sSolutionString.charAt(i) != aGridAttempt[i]) {
					bFoundHint = true;
					iBoxSelected = aGridAttemptToGaps[i]; // Move selection to first discrepancy.
					break;
				}
	} else { // Wurdz are selected
		var iTheCurrLetter = aGridToWurdz[aGridNumbers[iBoxSelected]];
		for (var i=iTheCurrLetter; i<iLastLetter; i++) {
			if (sWurdzString.charAt(i) != aWurdzAttempt[i]) {
				bFoundHint = true;
				iBoxSelected = aGridAttemptToGaps[aWurdzToGrid[i]]; // Move selection to first discrepancy.
				break;
			}
		}
		if (!bFoundHint) 
			for (i=0; i<iTheCurrLetter; i++)
				if (sWurdzString.charAt(i) != aWurdzAttempt[i]) {
					bFoundHint = true;
					iBoxSelected = aGridAttemptToGaps[aWurdzToGrid[i]]; // Move selection to first discrepancy.
					break;
				}
	}
	if (bFoundHint) {
		bHintRequested = true; // Change flag.
		drawNewGrid();
	}
}
//***************************************************************************
function showMistakes() {
	
	if (bSolution) // Don't waste time trying to find mistakes if the solution is already displayed.
		return;
	
	bHintRequested = false;
	bShowMistakes = true;
	
	// If bSolution is false then there is at least one mistake, and there might be many, so let the draw routines find them.
	drawNewGrid();
}
//***************************************************************************
function showSolution() {
	
	var sMessage = "\n" + 
		"___________________________________________\n\n" + 
		"Are you sure you want to see the solution?\n" + 
		"___________________________________________\n\n" + 
		"Click OK to see the solution or Cancel to go back.";
	
	if (!confirm(sMessage)) return;
	
	bSolution = true;
	bShowMistakes = false;
	bHintRequested = false;
	drawNewGrid();
	iBoxSelected = -1;
}
//***************************************************************************
function erasePuzzle() {

	var sMessage = "\n" +
		"Are you sure you want to erase the puzzle?"
		+ "\n";
		
	if (!confirm(sMessage)) return;
	
	bSolution = false;
	bHintRequested = false;
	bGridSelected = false;
	for (i=0;i<iASLen; i++)	{
		aGridAttempt[i] = " ";
		aWurdzAttempt[i] = "_";
	}
	iBoxSelected = aGridAttemptToGaps[aWurdzToGrid[0]];
	drawNewGrid();
	
	var InstructionsFigure =  new CSSDrawing(iInstructionsLeft, iInstructionsTop, iInstructionsWidth, iInstructionsHeight, "instructionstyle", "instructions");
	InstructionsFigure.box(0, 0, iInstructionsWidth-1, iInstructionsHeight-1, sInstructionString, "instructionstyle");
	InstructionsFigure.replace("instructions");

}
//***************************************************************************
function advanceSelection() {
	
	if (bGridSelected) {
		iBoxSelected++ ;
		//alert(iBoxSelected);
		if (iBoxSelected == iASLen)
			iBoxSelected = 0;
			
		while ((sAcrostiString.charAt(iBoxSelected) == " ") || (sAcrostiString.charAt(iBoxSelected) == "-") ) {
		iBoxSelected++ ;
		if (iBoxSelected == iASLen)
			iBoxSelected = 0;
		}
	} else {
		var iUnderScoreSel = aGridToWurdz[aGridNumbers[iBoxSelected]];
		iUnderScoreSel++;
		if (iUnderScoreSel==iTotalLetters)
			iUnderScoreSel = 0;
		iBoxSelected = aGridAttemptToGaps[aWurdzToGrid[iUnderScoreSel]];
	}
}
//***************************************************************************
function retreatSelection() {

	if (bGridSelected) {
	
		iBoxSelected-- ;
		if (iBoxSelected < 0) {
			iBoxSelected = iASLen-1; // Resets iASLen to zero-based length.
		}
		while ((sAcrostiString.charAt(iBoxSelected) == " ") || (sAcrostiString.charAt(iBoxSelected) == "-") ) {
			// Skip over spaces and hyphens.
			iBoxSelected-- ;
			if (iBoxSelected < 0 )
				iBoxSelected = iASLen; // Note that this will result in an infinite loop if there are no letters in the puzzle.
										// However, in that case the program shouldn't get here anyway.
		}
	} else {
		var iUnderScoreSel = aGridToWurdz[aGridNumbers[iBoxSelected]];
		iUnderScoreSel--;
		if (iUnderScoreSel < 0)
			iUnderScoreSel = iTotalLetters - 1;
		iBoxSelected = aGridAttemptToGaps[aWurdzToGrid[iUnderScoreSel]];
		//alert(iBoxSelected);
	}
	//alert(iBoxSelected);
}
//***************************************************************************
function addLetter(charCode) {
	aGridAttempt[aGridNumbers[iBoxSelected]] = String.fromCharCode(charCode);
	aWurdzAttempt[aGridToWurdz[aGridNumbers[iBoxSelected]]] = String.fromCharCode(charCode);
}
//***************************************************************************
function deleteLetter() {
	aGridAttempt[aGridNumbers[iBoxSelected]] = " ";
	aWurdzAttempt[aGridToWurdz[aGridNumbers[iBoxSelected]]] = "_";
}
//***************************************************************************
function checkForSolve() {
	var testString = aGridAttempt.join("");
	if (sSolutionString == testString) {
		bSolution = true;
		//alert("You did it!");
	} else {
		bSolution = false;
	}
}
//***************************************************************************
function upRow(){
	if (bGridSelected) {
	
		var bAtEnd = (((iBoxSelected + 1) % iBoxAcross) == 0); // True if selection is at the end of a line.
		
		iBoxSelected -= iBoxAcross; // Subtract one row from selection. Now test for error conditions.
		
		if (iBoxSelected < 0) { // If selection was in the top row, selection will now be negative, so...
			iBoxSelected += (iBoxDown * iBoxAcross); // ...add an entire puzzle length.
			if (iBoxSelected >= iASLen) // This guards against going off the end.
				iBoxSelected = iASLen - 1;
		}
		// If any of the above code puts the selection in a space, try advancing but stop if we come to the end of the puzzle.
		// However, if we the selection started at the end of a line, retreat instead, since advancing will go to the next line
		// (the one we started on).
		while ((sAcrostiString.charAt(iBoxSelected) == " ") || (sAcrostiString.charAt(iBoxSelected) == "-") ) {
			if (bAtEnd) {
				iBoxSelected-- ; // This should avoid wrapping around to the same line it started on.
			} else {
				iBoxSelected++ ;
				if (iBoxSelected >= iASLen) { // Past end of puzzle string.
					iBoxSelected =  iASLen -1;// Subtract one to get back within string and leave loop.
					break;
				} // if (iBoxSelected >= iASLen)
			} // else
		} // while
		
		// If the selection is still in a space, then we are a space at the end of the puzzle, so go back.
		while ((sAcrostiString.charAt(iBoxSelected) == " ") || (sAcrostiString.charAt(iBoxSelected) == "-") ) 
			iBoxSelected-- ;
		
	} else { // bGridSelected is false, so the Wurdz are selected.
		
		var iUnderScoreSel = aGridToWurdz[aGridNumbers[iBoxSelected]];
		var iCurrClue = aWhichClue[iUnderScoreSel];
		var iCurrLetter = aWhichLetter[iUnderScoreSel];
		// Now we know which clue and which letter of the clue is currently selected.
		if (iCurrClue > 0) { // Just go up to the clue above (most common case)
			if (iCurrLetter <= aWurdz[iCurrClue-1].length) {
				iUnderScoreSel -= aWurdz[iCurrClue-1].length;
			} else { // Clue above is shorter than the current clue...
				iUnderScoreSel -=  iCurrLetter; // ...go to the end of the clue above.
			}
		} else { // Selection is at the top already, go to bottom clue.
			if (aWurdz[iCluNum-1].length < iCurrLetter) { // Selection is past end of bottom clue
				iUnderScoreSel = iTotalLetters-1;
			} else {
				iUnderScoreSel = iTotalLetters-1 - (aWurdz[iCluNum-1].length) + iCurrLetter;
			}
		}
	iBoxSelected = aGridAttemptToGaps[aWurdzToGrid[iUnderScoreSel]];
	} //  else { // bGridSelected is false
} // End function upRow()
//***************************************************************************
function downRow() {
	
	if (bGridSelected) {
		var bAtStart = (((iBoxSelected + 1) % iBoxAcross) == 1); // True if selection is at the start of a line.
		
		iBoxSelected += iBoxAcross; // Add one row to selection. Now test for error conditions.
		
		if (iBoxSelected >= iASLen)  // Past end of puzzle, go up to top...
			iBoxSelected -= (iBoxDown * iBoxAcross); // ...by subtracting an entire puzzle length.
		
		// If the code above results in the selection of a space or hyphen, retreat one space unless bAtStart is true.
		while ((sAcrostiString.charAt(iBoxSelected) == " ") || (sAcrostiString.charAt(iBoxSelected) == "-") ) {
			if (bAtStart) {
				iBoxSelected++ ; // This should avoid wrapping around to the same line it started on.
			} else {
				iBoxSelected-- ;
				if (iBoxSelected < 0) // Insurance against weirdities such as the puzzle beginning with spaces.
					while ((sAcrostiString.charAt(iBoxSelected) == " ") || (sAcrostiString.charAt(iBoxSelected) == "-") )
						iBoxSelected++ ;
			}
		} // while
	} else { //if (bGridSelected) ...the Wurdz are selected.
		var iUnderScoreSel = aGridToWurdz[aGridNumbers[iBoxSelected]];
		var iCurrClue = aWhichClue[iUnderScoreSel];
		var iCurrLetter = aWhichLetter[iUnderScoreSel];
		
		if (iCurrClue < iCluNum-1) { // Above bottom, just go down to the clue below (most common case)
			if (iCurrLetter <= aWurdz[iCurrClue+1].length) {
				iUnderScoreSel += aWurdz[iCurrClue].length; // If next wurd is equal to or longer than the current position, just add current wurd length.
			} else { // Otherwise go to end of next wurd.
				iUnderScoreSel += aWurdz[iCurrClue].length - iCurrLetter + aWurdz[iCurrClue+1].length;
			}
		} else { // At bottom, must go to top...
			if (aWurdz[0].length >= iCurrLetter) { // Wurd at top is equal to or longer than selected letter position.
				iUnderScoreSel = iCurrLetter;
			} else { // Wurd at top is shorter
				iUnderScoreSel = aWurdz[0].length - 1;
			}
		}
		iBoxSelected = aGridAttemptToGaps[aWurdzToGrid[iUnderScoreSel]];
	} // End else { //if (bGridSelected)
	
}
//***************************************************************************
function handlePuzzleKeypress(evt) {  // Mainly for letters...
	if (bPuzzleSelected) {
		evt = (evt) ? evt : ((window.event) ? event : null);
		if (evt) {
			var charCode = (evt.charCode) ? evt.charCode : ((evt.keyCode) ? evt.keyCode :
							((evt.which) ? evt.which : 0));
			if ((charCode > 96) && (charCode < 123)) // Lowercase letter: convert to uppercase.
				charCode -= 32;
				
			bHintRequested = false;	
			bShowMistakes = false;
				
			if ( ((charCode > 64) && (charCode < 91)) || ( (charCode > 47) && (charCode < 58) )  ) { // Add uppercase letter if letter key pressed.
				addLetter(charCode);
				advanceSelection();
				checkForSolve();
				
				drawNewGrid();
			}
			
			switch (charCode) {
			
				case 8: // Backspace key: delete letter and retreat.
					deleteLetter();
					retreatSelection();
					drawNewGrid();
					if (evt.cancelBubble) {
						evt.cancelBubble = true;
						evt.returnValue =false;
					}
					return false;
					
					
				case 127: // Delete key: delete letter and retreat.
					deleteLetter();
					retreatSelection();
					drawNewGrid();
					if (evt.cancelBubble) {
						evt.cancelBubble = true;
						evt.returnValue =false;
					}
					return false;
					
				case 32: // Spacebar: delete letter and advance.
					deleteLetter();
					advanceSelection();
					drawNewGrid();
					if (evt.cancelBubble) {
						evt.cancelBubble = true;
						evt.returnValue =false;
					}
					return false;
					
				case 45: // Minus: like up arrow, go up a row.
					upRow();
					drawNewGrid();
					return;
	
				case 46: // Delete key: delete letter and retreat.
					deleteLetter();
					retreatSelection();
					drawNewGrid();
					if (evt.cancelBubble) {
						evt.cancelBubble = true;
						evt.returnValue =false;
					}
					return false;
	
				case 59: // Semi-colon: like arrow down, go down a row.
					downRow();
					drawNewGrid();
					return;
				
				case 61: // Equal sign: switch from grid to wurdz or vice versa.
					bGridSelected = !bGridSelected;
					drawNewGrid();
					return;
					
				case 91: //  Right square bracket: retreat selection.
					retreatSelection();
					drawNewGrid();
					return;
								
				case 92: // Backslash: delete letter and retreat.
					deleteLetter();
					retreatSelection();
					drawNewGrid();
					return;
					
				case 93: // Left square bracket: advance selection.
					advanceSelection();
					drawNewGrid();
					return;
					
				case 95: // Underscore: treat like spacebar.
					deleteLetter();
					advanceSelection();
					drawNewGrid();
					return;
					
				default: // Do nothing
					//alert(charCode); // Display charCode.
					return;
			} // end switch
		} // End if (evt)
	} // End if (bPuzzleSelected)
}
//***************************************************************************
function handlePuzzleKeydown(evt) { // Mainly for arrow keys, etc.
	if (bPuzzleSelected) {
		evt = (evt) ? evt : ((window.event) ? event : null);
		if (evt) {
			var charCode = (evt.keyCode) ? evt.keyCode : ((evt.charCode) ? evt.charCode :
							((evt.which) ? evt.which : 0));
							
			bHintRequested = false;
			bShowMistakes = false;
			
			switch (charCode) {
					
				case 37: // Arrow left: retreat selection.
					retreatSelection();
					// Cancel hint on any keypress.
					
					drawNewGrid();
					if (evt.cancelBubble) {
						evt.cancelBubble = true;
						evt.returnValue =false;
					}
					return false;

					//return;
					
				case 38: // Arrow up: go up a row.
					upRow();
					 // Cancel hint on any keypress.
					drawNewGrid();
					if (evt.cancelBubble) {
						evt.cancelBubble = true;
						evt.returnValue =false;
					}
					return false;
					//return;
		
				case 39: // Arrow right: advance selection.
					advanceSelection();
					 // Cancel hint on any keypress.
					drawNewGrid();
					if (evt.cancelBubble) {
						evt.cancelBubble = true;
						evt.returnValue =false;
					}
					return false;
					//return;
		
				case 40: // Arrow down: go down a row.
					downRow();
					 // Cancel hint on any keypress.
					drawNewGrid();
					if (evt.cancelBubble) {
						evt.cancelBubble = true;
						evt.returnValue =false;
					}
					return false;
					//return;
				
				default: // Do nothing
					//alert(charCode); // Display charCode.
					return;
			}
		
		}// End if (evt)
	} // End if (bPuzzleSelected)
}
//***************************************************************************
function handlePuzzleClick(evt) {
	
	evt = (evt) ? evt : event;
	var coords = getPageEventCoords(evt);
	//alert(coords.top);
	//alert(coords.left);
	
	if ( (coords.left > iGridLeft) && (coords.left < iGridRight) ) {
		if ( (coords.top > iGridTop) && (coords.top < iGridBottom) ) { // Then click is in grid
		
			bGridSelected = true;
			bHintRequested = false;
			bShowMistakes = false;
			bPuzzleSelected = true;
			
			var iGridRow = Math.ceil( (coords.top - iGridTop + 1) / iBoxHeight);
			var iGridCol = Math.ceil((coords.left - iGridLeft + 1) / iBoxWidth);
			var iClickBox = ((iGridRow - 1) * iBoxAcross) + iGridCol - 1;
			if ( (sAcrostiString.charAt(iClickBox) != " ") && (sAcrostiString.charAt(iClickBox) != "-") ) {
			    iBoxSelected = iClickBox;
			    
				drawNewGrid();
			}
		}
	} else if ((coords.left > iWurdzLeftCoord) && (coords.left < iWurdzRightCoord)) {
		if ( (coords.top > iWurdzTop) && (coords.top < iWurdzBottom) ) { // Then click might be on wurd
			var iClickY = coords.top - iWurdzTop + 1;
			var iTheClu = 1;
			var iCurrY = 0;
						
			for (var i=0; i<iCluNum; i++) {
				if ((iClickY >= iCurrY) && (iClickY <= iCurrY+iWurdzHeight)) {	// Find the number of the clue.
					break;
				} else { // Increment
					iTheClu++;
					iCurrY += iLineHeight * aClueLines[i] - ((aClueLines[i]-1)*iPadFactor);
				}
			}
			// NOT QUITE RIGHT, IT CONTINUES TO INCREMENT COUNT PAST NUMBER OF LETTERS IN WURD
			if (iTheClu < iCluNum + 1) {  // If click was not past the end...
			    var iClickX = Math.ceil((coords.left - iWurdzLeftCoord) / iUnderscoreWidth); // Letter in wurd.
			    
			    // If click was off the end of the wurd, exit without changing the cursor.
			    if (iClickX > aWurdz[(iTheClu - 1)].length) 
			    	return;
			    
			    // Otherwise, figure out which letter to highlight.
			    var iCount = 0;
			    for (i = 0; i < (iTheClu - 1); i++) {
			        iCount += aWurdz[i].length; // Add length of each word to current count.
			    }
			    iCount += iClickX - 1;
			    iBoxSelected = aGridAttemptToGaps[aWurdzToGrid[iCount]];
			    bGridSelected = false;
			    bPuzzleSelected = true;
				bHintRequested = false;// Cancel hint (or showError) on any puzzleclick.
				bShowMistakes = false;
			    drawNewGrid();
			} //if (iTheClu
		}//else {alert(coords.top); alert(coords.left);alert(iTheClu);}
	} else { // Deselect puzzle.
		if (bPuzzleSelected) {
			bPuzzleSelected = false;
			drawNewGrid();
		}
	}// } else 
	// evt.cancelBubble = true;
} // end function handlePuzzleClick(evt)
//***************************************************************************

addOnLoadEvent(initPuzzle);



