//Written by Paul Stothard, University of Alberta, Canada
//Modified by George Waldon for combined pairwise and coloring DNA alignment

function pairwiseAlignNColorDna (theDocument) {	

	//var MATCH_SCORE = 2;
	//var MISMATCH_SCORE = -1;
	//var GAP_PENALTY = 2;

	//var BEGIN_GAP_PENALTY = 0;
	//var END_GAP_PENALTY = 0;

	var maxInput = 2000;
	
	var newDnaOne = "";
	var titleOne = "";

	var newDnaTwo = "";
	var titleTwo = "";
	
	// Coloring
	var theAlignment = "";
	var alignArray = new Array();
	var groupString = "";
	var arrayOfGroups = new Array();
	var titleArray = new Array();
	var sequenceArray = new Array();
	var longestTitle;
	var startingindex = "0,0";
	var resperline = theDocument.forms[0].elements[12].options[theDocument.forms[0].elements[12].selectedIndex].value;

    //Max lenth of sequence title shown in alignment
    var arraytitlewidth = 20;

	if (testScript() == false) {
		return false;
	}

	if ((checkFormElement (theDocument.forms[0].elements[0]) == false) || (checkSequenceLength(theDocument.forms[0].elements[0].value, maxInput) == false) || (checkFormElement (theDocument.forms[0].elements[2]) == false) || (checkSequenceLength(theDocument.forms[0].elements[2].value, maxInput) == false))	{
		return false;
	}
	
	var MATCH_SCORE = parseInt(theDocument.forms[0].elements[7].options[theDocument.forms[0].elements[7].selectedIndex].value);
	var MISMATCH_SCORE = parseInt(theDocument.forms[0].elements[8].options[theDocument.forms[0].elements[8].selectedIndex].value);
	var BEGIN_GAP_PENALTY = parseInt(theDocument.forms[0].elements[9].options[theDocument.forms[0].elements[9].selectedIndex].value);
	var GAP_PENALTY = parseInt(theDocument.forms[0].elements[10].options[theDocument.forms[0].elements[10].selectedIndex].value);
	var END_GAP_PENALTY = parseInt(theDocument.forms[0].elements[11].options[theDocument.forms[0].elements[11].selectedIndex].value);

	openWindow("Pairwise Align DNA");
	openPre();
	outputWindow.document.write("<title>Pairwise Align DNA</title>");

	newDnaOne = getSequenceFromFasta(theDocument.forms[0].elements[0].value);
	newDnaOne = removeNonDna(newDnaOne);
	titleOne = getTitleFromFasta(theDocument.forms[0].elements[0].value);

	newDnaTwo = getSequenceFromFasta(theDocument.forms[0].elements[2].value);
	newDnaTwo = removeNonDna(newDnaTwo);
	titleTwo = getTitleFromFasta(theDocument.forms[0].elements[2].value);

	outputWindow.document.write(getPairwiseAlignTitle(titleOne, newDnaOne, titleTwo, newDnaTwo,parseInt(resperline)+arraytitlewidth+1));
	
	//change to arrays for pass by reference, so that large sequence isn't copied
	if (newDnaOne.search(/./) != -1)	{
		newDnaOne = newDnaOne.match(/./g);
	}

	if (newDnaTwo.search(/./) != -1)	{
		newDnaTwo = newDnaTwo.match(/./g);
	}

	theAlignment = pairwiseDna (titleOne, newDnaOne, titleTwo, newDnaTwo, MATCH_SCORE, MISMATCH_SCORE, GAP_PENALTY, BEGIN_GAP_PENALTY, END_GAP_PENALTY);
	alignArray = theAlignment.split(/[>%]/);

	if (earlyCheckAlign (alignArray) == false)	{
		return false;
	}

	for (var i = 1; i < alignArray.length; i++)	{
		titleArray[i-1] = alignArray[i].match(/[^\f\n\r]+[\f\n\r]/);
		titleArray[i-1] = filterFastaTitle(titleArray[i-1].toString()).replace(/[\f\n\r]/g, "");
		titleArray[i-1] = titleArray[i-1].substring(0, arraytitlewidth);
		if (i == 1) {
			longestTitle = titleArray[i-1].length;
		}
		else if (titleArray[i-1].length > longestTitle)	{
			longestTitle = titleArray[i-1].length;
		}
		sequenceArray[i-1] = alignArray[i].replace(/[^\f\n\r]+[\f\n\r]/,"");
		sequenceArray[i-1] = filterAlignSeq (sequenceArray[i-1]);
	}

	//make titles equal length
	var spaceString = "                    ";
	for (var i = 0; i < titleArray.length; i++)	{
		if (titleArray[i].length < longestTitle) {
	 		//add spaces
			titleArray[i] = titleArray[i] + spaceString.substring(0, (longestTitle - titleArray[i].length));
		}
	}

	if (checkAlign (titleArray, sequenceArray) == false)	{
		return false;
	}

	arrayOfGroups = groupString.split(/,/);
	if (checkGroupInput (arrayOfGroups) == false)	{
		return false;
	}

    outputWindow.document.write('<div class="dnaalign">\n');
	colorAlign (titleArray, sequenceArray, resperline, "100", arrayOfGroups, startingindex, longestTitle);
    outputWindow.document.write('</div>');
	
	
	closePre();
	closeWindow();
	return true;
}	

function pairwiseDna (titleOne, newDnaOne, titleTwo, newDnaTwo, matchScore, mismatchScore, gapPenalty, beginGapPenalty, endGapPenalty)	{

	//can use one or both.
	//can compare scores (should be identical)
	var useLinearSpace = true;
	var useQuadraticSpace = false;
	
	var matrix = new Identity();
	matrix.setMatch(matchScore);
	matrix.setMismatch(mismatchScore);

	var scoreSet = new ScoreSet();
	scoreSet.setScoreSetParam(matrix, gapPenalty, beginGapPenalty, endGapPenalty);
	
	var alignment;
	var alignmenttext = "";
	
	if (useLinearSpace) {
		alignment = new AlignPairLinear();
		alignment.setAlignParam(newDnaOne, newDnaTwo, scoreSet);
		alignment.align();

		alignmenttext = alignmenttext + ">" + titleOne + "\n";
		alignmenttext = alignmenttext + addReturns(alignment.getAlignedM());
		alignmenttext = alignmenttext + "\n";
		alignmenttext = alignmenttext + "\n";
		alignmenttext = alignmenttext + ">" + titleTwo + "\n";
		alignmenttext = alignmenttext + addReturns(alignment.getAlignedN());
		alignmenttext = alignmenttext + "\n\n";
		
		outputWindow.document.write("Alignment score: " + alignment.score + "\n\n");
	}

	if (useQuadraticSpace) {

		alignment = new AlignPairQuad();
		alignment.initializeMatrix(newDnaOne, newDnaTwo, scoreSet);
		alignment.fillMatrix();
		//alignment.dumpMatrix();
		alignment.align();	

		alignmenttext = alignmenttext + ">" + titleOne + "\n";
		alignmenttext = alignmenttext + addReturns(alignment.getAlignedM());
		alignmenttext = alignmenttext + "\n";
		alignmenttext = alignmenttext + "\n";
		alignmenttext = alignmenttext + ">" + titleTwo + "\n";
		alignmenttext = alignmenttext + addReturns(alignment.getAlignedN());
		alignmenttext = alignmenttext + "\n\n";
		
		outputWindow.document.write("Alignment score: " + alignment.score + "\n\n");
	}
	return alignmenttext;
}



//------------------------------------ ScoreSet class

//ScoreSet getScore
function getScore (r1, r2) {
	return this.scoringMatrix.scoringMatrix_getScore(r1, r2);	
}

//ScoreSet setScoreSetParam
function setScoreSetParam (scoringMatrix, gapPenalty, beginGapPenalty, endGapPenalty) {
	this.scoringMatrix = scoringMatrix;
	this.gap = gapPenalty;
	this.beginGap = beginGapPenalty;
	this.endGap = endGapPenalty;
}

//ScoreSet class
function ScoreSet () {
	this.scoringMatrix;
	this.gap;
	this.beginGap;
	this.endGap;
	this.useBeginGapTop = true;
	this.useBeginGapLeft = true;
	this.useEndGapBottom = true;
	this.useEndGapRight = true;
}

//create and throw away a prototype object
new ScoreSet();

//define object methods
ScoreSet.prototype.getScore = getScore;
ScoreSet.prototype.setScoreSetParam = setScoreSetParam;

//------------------------------------


//------------------------------------ ScoringMatrix abstract class
//ScoringMatrix getScore method
function scoringMatrix_getScore(r1, r2) {
	r1 = r1.toLowerCase();
	r2 = r2.toLowerCase();
	if (r1 == r2) {
		return this.match;
	}
	else {
		return this.mismatch;
	}
}

//ScoringMatrix class
function ScoringMatrix() {
	this.mismatch;
	this.match;
}

//create and throw away a prototype object
new ScoringMatrix();

//define object methods
ScoringMatrix.prototype.scoringMatrix_getScore = scoringMatrix_getScore;

//------------------------------------ Identity class extends ScoringMatrix Class
//Identity class setMismatch method
function setMismatch(mismatchScore) {
	this.mismatch = mismatchScore;
}

//Identity class setMatch method
function setMatch(matchScore) {
	this.match = matchScore;
}

//Identity class
function Identity () {
}

Identity.prototype = new ScoringMatrix();
Identity.prototype.setMismatch = setMismatch;
Identity.prototype.setMatch = setMatch;

//---------------------------------- Coloring

function colorAlign (arrayOfTitles, arrayOfSequences, basePerLine, consensus, arrayOfGroups, definedStarts, longestTitle)	{
	var positions = new Array (arrayOfSequences.length);
	if (definedStarts.search(/\S/) == -1)	{
		definedStarts = "0,0";
	}
	var definedStartsArray = definedStarts.split(/,/);
	for (var i = 0; i < positions.length; i++)	{
		if (i >= definedStartsArray.length)	{
			positions[i] = 0;
		}
		else	{
			if (definedStartsArray[i].search(/\d/) != -1)	{
				positions[i] = parseInt(definedStartsArray[i].replace(/\D/g, ""));
			}
			else	{
				alert('An incorrect starting position was encountered. It was set to 0.');
				outputWindow.focus();
				positions[i] = 0;
			}
		}
	}
	var totalBasesShown = 0; 
	consensus = (parseInt(consensus)) / 100;
	basePerLine = parseInt(basePerLine);
	var columnCount = 0;
 	var arrayOfColumns = new Array(basePerLine);
	for (var i=0; i < arrayOfColumns.length; i++) {
   		arrayOfColumns[i] = new Array(arrayOfSequences.length);
	}

	var i = 0;
	var columnSeq;
	var re;
	var result;
	var output = "";

	while (totalBasesShown < arrayOfSequences[0].length)	{
		for (var jj = 0; jj < arrayOfSequences.length; jj++)	{		
			output = output + arrayOfTitles[jj] + ' ';
			while ((i < (totalBasesShown + basePerLine)) && (i < arrayOfSequences[0].length))	{
				if (jj == 0) {
					//fill the column
					for (var k = 0; k < arrayOfSequences.length; k++)	{
						arrayOfColumns[columnCount][k] = arrayOfSequences[k].charAt(i);
						
					}
				}
				if ( (arrayOfSequences[jj].charAt(i) == ".") || (arrayOfSequences[jj].charAt(i) == "-") ) {
					output = output + "<span class=\"diff\">" + arrayOfSequences[jj].charAt(i) + "</span>";
					i = i + 1;
					columnCount++;
					continue;
				}

				columnSeq = arrayOfColumns[columnCount].join(",");
				re = new RegExp (arrayOfSequences[jj].charAt(i),"gi");
				
				//GW Intentionally color by similarity
				if ((columnSeq.match(re)).length / arrayOfSequences.length >= consensus)	{
					output = output + "<span class=\"sim\">" + arrayOfSequences[jj].charAt(i) + "</span>";
					i = i + 1;
					columnCount++;
					continue;
				}

				//result = 1;
				//for (var m = 0; m < arrayOfGroups.length; m++)	{
				//	if (arrayOfGroups[m].search(re) != -1)	{
				//		var re = new RegExp ("[" + arrayOfGroups[m] + "]","gi");
				//		result = (columnSeq.match(re)).length;
				//		break;
				//	}
				//}

				//if (result / arrayOfSequences.length >= consensus)	{
                //                   	output = output + "<span class=\"sim\">" + arrayOfSequences[jj].charAt(i) + "</span>";
				//	i = i + 1;
				//	columnCount++;
				//	continue;
				//}
									
				output = output + "<span class=\"diff\">" + arrayOfSequences[jj].charAt(i) + "</span>";
				i = i + 1;
				columnCount++;
			}
			positions[jj] = positions[jj] + ((arrayOfSequences[jj].substring(totalBasesShown,i)).replace(/\.|\-/g,"")).length;
			output = output + ' ' + positions[jj] + '\n';
			outputWindow.document.write(output);
			output = "";
			i = totalBasesShown;
			columnCount = 0;
		}
		totalBasesShown = totalBasesShown + basePerLine;
		i = totalBasesShown;
		outputWindow.document.write('\n');
	}
	return true;
}