
function Card( q, a, questionTagged, answerTagged ) {
	this.question = new XString(q.str);
	this.answer = new XString(a.str);
	
	this.questionTagged = questionTagged;
	this.answerTagged = answerTagged;
}

function CardManager() {
	
	// if we are editing cards
	this.editingIndex = -1;
	
	// this function will parse our cards and output a nice html stream ...
	var kFQStart = "<question>";
	var kFQEnd = "</question>";
	var kFAStart = "<answer>";
	var kFAEnd = "</answer>";
	
	this.cards = new Array();
	this.fileSize = 0;
	
	this.parseCards = function( stream ) {
		
		var spruceUp = true;
		
		var numQuestions = 0;
		var ind = 0, qend = 0;
		
		var questionNum = 0;
		
		// skip meta headers
		ind = stream.skipWhiteSpace( ind );
		if ( stream.indexOf("<meta>",ind) == ind ) {
			
			ind = stream.indexOf( "</meta>", ind );
			ind += 7;
		}
		
		// classic mac=\r unix-newmac=\n windows \r\n
		// ugly .. but what are you gonna do
		var lineEnd;
		var lineEndLen = 1;
		if ( stream.indexOf("\r\n") != -1 ) {
			lineEnd = "\r\n";
			lineEndLen = 2;
	//console.log( "line end 1" );

		} else if ( stream.indexOf("\r") != -1 ) {
			lineEnd = "\r";
	//console.log( "line end 2" );
		}	else {
			lineEnd = "\n";
	//console.log( "line end 3" );
		}
		
		var possibleQuestion = new XString();
		var possibleAnswer = new XString();
		var questionTagged, answerTagged;
		
		while ( true ) {
	
			questionNum++;
			possibleQuestion.str = "";
			questionTagged = false;
			answerTagged = false;
			
			ind = stream.skipWhiteSpace( ind );
			if ( stream.beginsWithIgnoreCase(ind,kFQStart) ) {
				
				questionTagged = true;
				
				ind = this.searchForTags( stream, ind, possibleQuestion, kFQStart, kFQEnd );
				if ( ind == -1 )
					break;	// no end tag
				
				ind += lineEndLen;
				
				// if the last char before the question end tag is whitespace, strip that
				if ( possibleQuestion.str.substr( possibleQuestion.str.length-lineEndLen, lineEndLen ) == lineEnd )
					possibleQuestion.str = possibleQuestion.str.substr( 0, possibleQuestion.str.length-lineEndLen );
				
				//possibleQuestion.str = possibleQuestion.str.replaceAll( lineEnd, "\n" );
				
			} else {
				
				qend = stream.indexOf( lineEnd, ind );
				if ( qend == -1 ) {
					break;			//  can't end on a question without an answer
				}
				
				if ( qend == ind ) {
					// blank line question
					ind += lineEndLen;
					continue;
				}
				
				possibleQuestion.str += stream.substr( ind, qend-ind );
				ind = qend + lineEndLen;
			}
			
			// add answer
			// skip whitespace
			possibleAnswer.str = "";
			
			if ( stream.beginsWithIgnoreCase(ind,kFAStart) ) {
				
				answerTagged = true;
				
				ind = this.searchForTags( stream, ind, possibleAnswer, kFAStart, kFAEnd );
				if ( ind == -1 )
					break;	// no end tag. bad form
				
				//possibleAnswer.str = possibleAnswer.str.replaceAll( lineEnd, "\n" );
				this.addQuestion( possibleQuestion, possibleAnswer, questionTagged, answerTagged );
				
			} else {
				
				var loopNum = 0;
				var endFile = false;
				while ( true ) {
//	console.log( "loopnum: " + loopNum );
					
					qend = stream.indexOf(lineEnd, ind);
					if ( qend == -1 ) {
						// no end line. just read the last characters
						qend = ind + stream.length-ind;
						endFile = true;
					}
					
					if ( qend == ind ) {
						// blank line. we add what we've got so far
//	console.log ( "blank line: " + possibleQuestion.str );
				//		if ( spruceUp && loopNum >= 2 )
				//			possibleAnswer.str += "</ul>";
							
						this.addQuestion( possibleQuestion, possibleAnswer, questionTagged, answerTagged );
						break;
					
					} else {
						
						if ( loopNum > 0 )
							possibleAnswer.str += "\n";
						
						if ( loopNum > 50 ) 
							break;
						
						possibleAnswer.str += stream.substr( ind, qend-ind );
						
						// if we are sprucing up the answer, we only want to spruce up multiple answerlines
						// this means if we are on line 2, we have to go back and spruce up line 1
				//		if ( spruceUp && loopNum == 1 ) {
				//			this.spruceAnswerLine( spruceUp, possibleAnswer, possibleAnswer.str.length, true );
				//		}
						
				//		if ( spruceUp && loopNum >= 1 ) {
				//			this.spruceAnswerLine( spruceUp, possibleAnswer, qend-ind, false );
				//		}
						
						// if we reached the end of the file, there is no line end, so don't add it
						ind = qend + (endFile?0:lineEndLen);
					}
					
					loopNum++;
				}
			}
		}
	}
	
	// this is _the_ answer. we add <ul> and <il> tags as necessary
	this.spruceAnswer = function( spruceUp, stream ) {
		// answers that start with an <answer> tag are not spruced up
		if ( spruceUp && !stream.beginsWithIgnoreCase(0,kFAStart) ) {
			var loopNum = 0, ind = 0, qend = 0;
			var lineEnd = "\n";
			var lineEndLen = 1;
			var endFile = false;
			var possibleAnswer = new XString();
			
			while ( true ) {
	
				qend = stream.indexOf(lineEnd, ind);
				if ( qend == -1 ) {
					// no end line. just read the last characters
					qend = ind + stream.length-ind;
					endFile = true;
				}
				
				if ( qend == ind ) {
					break;
				
				} else {
					
					if ( loopNum > 0 )
						possibleAnswer.str += "<br>";
					
					// if we are sprucing up the answer, we only want to spruce up multiple answerlines
					// this means if we are on line 2, we have to go back and spruce up line 1
					if ( spruceUp && loopNum == 1 ) {
						this.spruceAnswerLine( spruceUp, possibleAnswer, possibleAnswer.str.length, true );
					}
					
					possibleAnswer.str += stream.substr( ind, qend-ind );
					
					if ( spruceUp && loopNum >= 1 ) {
						this.spruceAnswerLine( spruceUp, possibleAnswer, qend-ind, false );
					}
					
					// if we reached the end of the file, there is no line end, so don't add it
					ind = qend + (endFile?0:lineEndLen);
					
				}
				
				loopNum++;
			}
			
			if ( loopNum > 1 ) {
				possibleAnswer.str += "</ul>";
			}
			
			possibleAnswer.str.replaceAll( "\n", "<br>" );
			
			return possibleAnswer.str;
		}
		return stream;
	}
	
	this.spruceAnswerLine = function( spruce, out, lastAnswerLen, insertUL ) {
		if ( spruce ) {
			
			var answerStartInd = out.str.length - lastAnswerLen;
			
			var found = out.str.findIgnoreCase( answerStartInd, ": " );
			if ( found != -1 ) {
				
				var ind = found - answerStartInd;
				out.str = out.str.insertBefore( answerStartInd+ind, 4, "</b>" );
				out.str = out.str.insertBefore( answerStartInd, 3, "<b>" );
			}
			
			var toInsert = (insertUL ? "<ul><li>" : "<li>");
			out.str = out.str.insertBefore( answerStartInd, toInsert.length, toInsert );
			
		}
	}

	this.searchForTags = function( toTest, toTestInd, out, tagStart, tagEnd ) {
		// search for <question> tags
		if ( toTest.beginsWithIgnoreCase(toTestInd,tagStart) ) {
			
			toTestInd += tagStart.length;
			
			// skip whitespace directly after start tag
			toTestInd = toTest.skipWhiteSpace( toTestInd );
			
			var end = toTest.findIgnoreCase(toTestInd,tagEnd);
			if ( end == -1 )
				return -1;
			
			out.str = "";
			out.str += toTest.substr( toTestInd, end-toTestInd );
	//console.log ( "searched for: " + out.str );
			
			toTestInd = end + tagEnd.length;
		}
		return toTestInd;
	}
	
	this.getFileSize = function() {
		// fileSize contains the exact size of each question and answer. we go through and add some extra padding
		// rather than calc exact padding, just estimate highest padding for each question so they don't go over
		return this.fileSize + (this.cards.length * "<question>\n</question>\n<answer>\n</answer>\n\n".length);
	}
	
	this.validCard = function( i ) {
		return this.cards.length > i && i >=0;
	}
	
	this.getQuestion = function( i ) {
		return this.cards[i].question.str;
	}

	this.getAnswer = function( i ) {
		return this.cards[i].answer.str;
	}
	
	this.setQuestion = function( i, str ) {
		
		this.fileSize -= this.getQuestion(i).length;
		this.fileSize += str.length;
		
		this.cards[i].question.str = str;
	}
	
	this.setAnswer = function( i, str ) {
		
		this.fileSize -= this.getAnswer(i).length;
		this.fileSize += str.length;
		
		this.cards[i].answer.str = str;
	}
	
	this.addQuestion = function( q, a, qtagged, atagged ) {
		//return "<b>"+ind+". </b><font color=darkblue size=+1>" + q.str + "</font><br>"+a.str+"<br>";
		//return "<li><font color=darkblue size=+1>" + q.str + "</font><br>"+a.str+"</li><br>";
		
		this.fileSize += q.str.length;
		this.fileSize += a.str.length;

		this.cards.push( new Card(q,a,qtagged,atagged) );
	}
	
	// insert card before i
	this.insertCardBefore = function( i ) {
		this.cards.splice(i,0, new Card("","",false,false) );
	}
	
	this.deleteCard = function( i ) {
		if ( this.validCard(i) ) {
			
			this.fileSize -= this.getQuestion(i).length;
			this.fileSize -= this.getAnswer(i).length;
			
			this.cards.splice( i, 1 );
		}
	}
	
	this.toHTMLList = function() {
		
		var result = "<ol>";
			
		for (var i=0; i<this.cards.length; i++ ) {
			result += "<li><font color=darkblue size=+1>" +
				this.cards[i].question.str.replaceAll( "\n", "<br>" ) + "</font><br>";
				if ( !this.cards[i].answerTagged )
					result += this.spruceAnswer( true, this.cards[i].answer.str );
				else
					result += this.cards[i].answer.str.replaceAll( "\n", "<br>" );
				
				result += "</li><br>";
		}
		
		result += "</ol>";
		return result;
	}
	
	this.toString = function() {
		
		var result = "";
		
		for ( var i=0; i<this.cards.length; i++ ) {
			if ( this.cards[i].questionTagged )
				result += "<question>\n";
			
			result += this.cards[i].question.str + "\n";

			if ( this.cards[i].questionTagged )
				result += "</question>\n";

			if ( this.cards[i].answerTagged )
				result += "<answer>\n";

			result += this.cards[i].answer.str;
			
			if ( this.cards[i].answerTagged )
				result += "</answer>";

			result += "\n\n";
		}
		
		return result;
	}
	
	this.kModeRunning = 0;
	this.kModeShowingSplash = 1;
	
	this.kStateNoMoreQuestions = 1;
	this.kStateTesting = 2;
	
	
	
	
	// quiz info
	this.myState = this.kStateNoMoreQuestions;
	this.theCurrentQuestion = 0;
	this.answerDisplayed = false;
	
	// test options
	this.inOrder = false;
	this.answersFirst = false;
	
	this.startTest = function() {
		this.myState = this.kStateTesting;
		
		this.theCurrentQuestion = -1;
		this.answerDisplayed = false;
		
		
		this.cards = shuffle( this.cards );
	}
	
	this.showAnswer = function() {
		this.answerDisplayed = true;
		return this.currentAnswer();
	}
	
	this.showHTMLAnswer = function() {
		this.answerDisplayed = true;
		return this.currentHTMLAnswer();
	}
	
	this.getQuestion = function( i ) {
		if ( !this.validCard(i) )
			return "";
		
		if ( this.answersFirst )
			return this.cards[i].answer.str;
		
		return this.cards[i].question.str;
	}
	
	this.getAnswer = function( i ) {
		if ( !this.validCard(i) )
			return "";
		
		if ( this.answersFirst )
			return this.cards[i].question.str;
		
		return this.cards[i].answer.str;
	}
	
	this.getHTMLQuestion = function( i ) {
		if ( !this.validCard(i) )
			return "";
		
		if ( this.answersFirst )
			;
		
		// removed <center> tags
		return "" + this.cards[i].question.str.replaceAll( "\n", "<br>" ) + "";
	}
	
	this.getHTMLAnswer = function( i ) {
		if ( !this.validCard(i) )
			return "";
		
		if ( this.answersFirst )
			;
		
		var result = "";
		
		if ( !this.cards[i].answerTagged )
			result += this.spruceAnswer( true, this.cards[i].answer.str );
		else
			result += this.cards[i].answer.str.replaceAll( "\n", "<br>" );
		
		// removed <center> tags
		return "" + result + "";
	}
	
	this.currentHTMLQuestion = function() {
		return this.getHTMLQuestion( this.theCurrentQuestion );
	}
	
	this.currentHTMLAnswer = function() {
		return this.getHTMLAnswer( this.theCurrentQuestion );
	}
	
	this.currentQuestion = function() {
		return this.getQuestion( this.theCurrentQuestion );
	}
	
	this.currentAnswer = function() {
		return this.getAnswer( this.theCurrentQuestion );
	}
	
	this.nextQuestion = function() {
		
		this.answerDisplayed = false;
		if ( this.theCurrentQuestion >= this.cards.length-1)
			return this.kStateNoMoreQuestions;
		
		this.theCurrentQuestion++;
		return this.kStateTesting;
	}
	
	this.clearCards = function() {
		this.fileSize = 0;
		this.cards = new Array();		// clear the old one
	}
}

	//+ Jonas Raoni Soares Silva
	//@ http://jsfromhell.com/array/shuffle [v1.0]
function shuffle( o ) { //v1.0
	for(var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
	return o;
}

String.prototype.findIgnoreCase = function( ind, toTest ) {
	return this.indexOf(toTest,ind);
};

// insert len characters from string, str, before index, ind
String.prototype.insertBefore = function( ind, len, str ) {
	//console.log( this.substring(0,ind) + str.substr(0,len) + this.substring(ind) );
	return this.substring(0,ind) + str.substr(0,len) + this.substring(ind);	
};

 // Replaces all instances of the given substring.
String.prototype.replaceAll = function(
		strTarget, // The substring you want to replace
		strSubString // The string you want to replace in.
	 ) {
	 
	 var strText = this;
	 var intIndexOfMatch = strText.indexOf( strTarget );
	  
	 // Keep looping while an instance of the target string
	 // still exists in the string.
	 while (intIndexOfMatch != -1){
		 // Relace out the current instance.
		 strText = strText.replace( strTarget, strSubString )
		  
		 // Get the index of any next matching substring.
		 intIndexOfMatch = strText.indexOf( strTarget );
	 }
	  
	 // Return the updated string with ALL the target strings
	 // replaced out with the new substring.
	 return( strText );
}

String.prototype.beginsWithIgnoreCase = function( ind, toTest ) {
	if ( this.substr(ind,toTest.length) == toTest )
		return true;
	
	return false;
};

String.prototype.skipWhiteSpace = function ( ind ) {
	// skip whitespace
    while ( ind < this.length && (
			this.charAt(ind) == '\n' ||
			this.charAt(ind) == '\r' ||
			this.charAt(ind) == '\t' ||
			this.charAt(ind) == ' ' ) ) {
		ind++;
	}
	
	return ind;
};

function XString( str ) {
	if ( typeof(str) == "undefined" )
		this.str = "";
	else 
		this.str = str;
}
