/**********************************************************************************
 *                        Mutant Primer Designer 1.0.                             *
 *       Copyright (C) 2006  ZIJUAN LIU, YUBO DONG. yubodong@gmail.com            *
 **********************************************************************************
 * 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA     *
 *                                                                                *
 **********************************************************************************/
//---------------------------------------------------------------------------------
//Update History
//---------------------------------------------------------------------------------
//    Date     |    Author    |  Reason
//---------------------------------------------------------------------------------
// 02/26/2006  |   Yubo Dong  | Created the first version of Mutant Primer Designer
//---------------------------------------------------------------------------------
 
//
//Class for Mutant Primer Designer
//

function MutantPrimerDesigner(){
     this.process_step1 = process_step1;
	 this.process_step2 = process_step2;
	 this.getPrimer     = getPrimer;
	 this.getStartingCodon = getStartingCodon;
	 this.getCmdString     = getCmdString;


	 //
     //<letter><number<letter> | <letter><letter><letter><number><letter><letter><letter>
     //
     var m_strCmdString = null; 
     
     //
     //User entered sequence
     //
     var m_strDNASequence = null;
	 //
	 //
	 //
	 var m_SEQTranslateResult = null;

	 var m_objStartingCodon = null, m_arrRplCodons = null;

	 var m_strStartAcid = null, m_strEndAcid = null, m_iLocation = null;

     function getStartingCodon() { return m_objStartingCodon; } 
	 function getCmdString() { return m_strCmdString; }
	 function process_step1(cmd,seq){
		 m_strCmdString = cmd.toUpperCase();
		 m_strDNASequence = seq;
		 var translator = new SEQTranslator();
		 //
		 //Parsing command string, the starting amino acid stores in m_strStartAcid
		 //ending amino acid stores in m_strEndAcid, location information stores in m_iLocation
		 //For example: R153L -> m_strStartAcid = R, m_strEndAcid = L, m_iLocation = 153
		 //Return true if the amino acid is single letter, false if it is three letters
		 //If there is syntax error, it will return null
		 //
		 var bOneOrThree = parseCmdString();
		 if ( bOneOrThree == null ) return null;
		 //Translate input DNA sequence
		 translator.process(seq,bOneOrThree);

		 m_SEQTranslateResult = translator.getResult();

		 //Get codon by given amino acid and location
		 m_objStartingCodon = getCodonByAminoAcid(m_strStartAcid, m_iLocation);
		 if ( m_objStartingCodon != null ){
		    //Get all codons by ending amino acid
		    m_arrRplCodons = getAllCodonByAcidFromCodonTable(m_strEndAcid,translator.getCodonTable(),bOneOrThree);

            var sim_codon = getSimilarCodon();
			return sim_codon;
		 }
		 return null;
	 }
	 function process_step2(sim_codon){
         //
     	 //Replace the starting codon in original DNA sequence with selected similar codon
		 //
		 var left = m_SEQTranslateResult.original.substr(0,m_objStartingCodon.iIndex);
		 var right = m_SEQTranslateResult.original.substr(m_objStartingCodon.iIndex + 3, 
		                                                 m_SEQTranslateResult.original.length - m_objStartingCodon.iIndex);
		 m_SEQTranslateResult.original = left + sim_codon + right;

		 return getInitialPrimer(m_objStartingCodon);
	 }

	 function getPrimer(objPrimer){
	    var arrPrimers = new Array();
		objPrimer = checkLeadingEndingChar(objPrimer);
		var ATRatio = getATRatio(objPrimer);
		objPrimer.ATRatio = ATRatio;

		var obj = new Object(); 
		obj.Start = objPrimer.Start; obj.End = objPrimer.End; obj.Primer = objPrimer.Primer; obj.ATRatio = ATRatio;
		obj.Location = objPrimer.Location;
		arrPrimers[arrPrimers.length] = obj;
		var flag = -1;
		var strHistory = objPrimer.Primer + "(" + ATRatio + ") Len = " + objPrimer.Primer.length + "\n";
		while ( ATRatio < 0.4 || ATRatio > 0.6 ){
			  var retPrimer = movePrimer(objPrimer,flag);
			  if ( retPrimer == null ){
			     retPrimer = movePrimer(objPrimer,flag * (-1));
				 flag *= -1;
			  }
			  flag *= -1;
			  if ( retPrimer == null ){
				 break;
			  }
			  retPrimer = checkLeadingEndingChar(retPrimer);
			  ATRatio = getATRatio(retPrimer);

		      var obj = new Object(); 
		      obj.Start = retPrimer.Start; obj.End = retPrimer.End; obj.Primer = retPrimer.Primer; obj.ATRatio = ATRatio;
			  obj.Location = retPrimer.Location;
   	          arrPrimers[arrPrimers.length] = obj;

			  if ( retPrimer.Primer.length > 40 ){
				 break;
			  }
		}
		return arrPrimers;
	 }

	 function getInitialPrimer(firstCodon){
	     var iStart = firstCodon.iIndex - 12;
		 if ( iStart < 0 ) iStart = 0;
		 var iEnd = firstCodon.iIndex + 12 + 3;
		 if (iEnd > m_SEQTranslateResult.original.length - 1)
		    iEnd = m_SEQTranslateResult.original.length - 1;
		 var objPrimer = new Object();
		 objPrimer.Start = iStart;
		 objPrimer.End   = iEnd;
		 objPrimer.Primer = m_SEQTranslateResult.original.substring(iStart,iEnd);
		 objPrimer.Location = 13;
		 return objPrimer;
	 }

	 function movePrimer(objPrimer,flag){
	     if ( flag == -1 ){ //Move backward
			if ( objPrimer.Start == 0 ) return null; 
		    objPrimer.Start --;
			objPrimer.Primer = m_SEQTranslateResult.original.charAt(objPrimer.Start) + objPrimer.Primer;
			objPrimer.Location ++;
			return objPrimer;
		 }
	     if ( flag == 1 ){ //Move forward
			if ( objPrimer.End == m_SEQTranslateResult.original.length-1 ) return null; 
			objPrimer.Primer = objPrimer.Primer + m_SEQTranslateResult.original.charAt(objPrimer.End);
		    objPrimer.End ++;
			return objPrimer;
		 }
		 return null;
	 }

	 function checkLeadingEndingChar(objPrimer){
	     var chFirst = objPrimer.Primer.charAt(0);
		 var chLast  = objPrimer.Primer.charAt(objPrimer.Primer.length-1);
         var bNoValidLeading = false;
		 var bNoValidEnding  = false;
		 //check leading character
		 while ( true ){
		     if ( chFirst == 'C' || chFirst == 'G' ) break;
			 if ( objPrimer.Start == 0 ){bNoValidLeading = true;break;}
			 objPrimer.Start --;
			 objPrimer.Location ++;
			 chFirst = m_SEQTranslateResult.original.charAt(objPrimer.Start);
			 objPrimer.Primer = chFirst + objPrimer.Primer;
		 }

		 //check ending character
		 while ( true ){
		     if ( chLast == 'C' || chLast == 'G' ) break;
			 if ( objPrimer.End == m_SEQTranslateResult.original.length-1 ){bNoValidEnding = true;break;}
			 chLast = m_SEQTranslateResult.original.charAt(objPrimer.End);
			 objPrimer.End ++;
			 objPrimer.Primer = objPrimer.Primer + chLast;
		 }

		 if ( !bNoValidLeading && !bNoValidEnding )
		    return objPrimer;
		 
		 return null;
	 }

	 function getATRatio(objPrimer){
	     var iNum = 0;
		 for ( var i = 0; i < objPrimer.Primer.length; i ++ ){
		     var ch = objPrimer.Primer.charAt(i);
			 if ( ch == 'A' || ch == 'T' ) iNum ++;
		 }
		 return iNum / objPrimer.Primer.length;
	 }

     //
	 //Get all similar codons, stores in an array
     //
	 function getSimilarCodon(){
	     var ch1 = m_objStartingCodon.Codon.charAt(0), ch2 = m_objStartingCodon.Codon.charAt(1);
		 var ch3 = m_objStartingCodon.Codon.charAt(2); 
		 var arrSim = new Array();
		 for ( var i = 0; i < m_arrRplCodons.length; i ++ ){
		     var c1 = m_arrRplCodons[i].charAt(0),c2 = m_arrRplCodons[i].charAt(1),c3 = m_arrRplCodons[i].charAt(2);
			 arrSim[arrSim.length] = getSimilarity(ch1,ch2,ch3,c1,c2,c3);
		 }
         var sim_codon = new Array();
		 for ( var i = 0; i < arrSim.length; i ++ ){
		     if ( arrSim[i] == 2 ){
			    sim_codon[sim_codon.length] = m_arrRplCodons[i];
			 }
		 }
		 if ( sim_codon.length == 0 ){
		    for ( var i = 0; i < arrSim.length; i ++ ){
		        if ( arrSim[i] == 1 ){
			       sim_codon[sim_codon.length] = m_arrRplCodons[i];
			    }
		    }
		 }
		 if ( sim_codon.length == 0 )
		    sim_codon = m_arrRplCodons;
		 return sim_codon;
	 }

	 function getSimilarity(ch1,ch2,ch3,c1,c2,c3){
	     var iSimilarity = 0;
		 if ( ch1 == c1 ) iSimilarity ++;
		 if ( ch2 == c2 ) iSimilarity ++;
		 if ( ch3 == c3 ) iSimilarity ++;
		 return iSimilarity;
	 }

	 function getAllCodonByAcidFromCodonTable(acid,codontable,one_or_three){
	     var arrCodon = new Array();
		 for ( var i = 0; i < codontable.length; i += 3 ){
		     var bEqual = false;
		     if ( one_or_three ) bEqual = (acid == codontable[i+1]);
			 else  bEqual = (acid == codontable[i+2]);
			 if ( bEqual ){
			    arrCodon[arrCodon.length] = codontable[i];
			 }
		 }
		 return arrCodon;
	 }
	 function getCodonByAminoAcid(acid,iLocation){
	     if ( m_SEQTranslateResult != null ){
		    var iIndex = 0;
		    for ( var i = 0; i < m_SEQTranslateResult.processed.length; i += 3 ){
			    var ch = m_SEQTranslateResult.processed.substr(i,3);

				if (ch.charAt(0) == " " && ch.charAt(2) == " ")
				   ch = ch.charAt(1);
				iIndex ++;
				if ( iIndex == iLocation ){
				   if ( ch != acid ){
				      alert("This position has a different residue");
					  return null;
				   }
				   var obj = new Object();
				   obj.iIndex = i;
				   obj.Codon  = m_SEQTranslateResult.original.substr(i,3); 
				   return obj;
				}
			}
		 }
		 return null;
	 }

	 function parseCmdString(){
		 var bOneOrThree = true; //Single amino acid
		 if ( m_strCmdString == null )
		 {
			 alert("Enter command string first");
			 return null;
		 }
		 if ( m_strCmdString.length < 3 )
		 {
			 alert("Invalid command string");
			 return null;
		 }

         var iIndex = 0;
		 var chStartAcid="",chEndAcid="", chLocation = "";
		 var strAlpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
		 var strNumber = "0123456789";
		 var ch = m_strCmdString.charAt(iIndex);
		 while ( true )
		 {
			 if ( iIndex == m_strCmdString.length ) break;
			 if ( ch == " " || ch == "\t" || ch == "\n" )
			 {
				 iIndex ++; ch = m_strCmdString.charAt(iIndex);
				 continue;
			 }
			 if ( strAlpha.indexOf(ch) >= 0 )
				 chStartAcid += ch;
			 else break;
			 iIndex ++; ch = m_strCmdString.charAt(iIndex);
		 }

		 while ( true )
		 {
			 if ( iIndex == m_strCmdString.length ) break;
			 if ( ch == " " || ch == "\t" || ch == "\n" )
			 {
				 iIndex ++; ch = m_strCmdString.charAt(iIndex);
				 continue;
			 }
			 if ( strNumber.indexOf(ch) >= 0 )
				 chLocation += ch;
			 else break;
			 iIndex ++; ch = m_strCmdString.charAt(iIndex);
		 }
         
		 while ( true )
		 {
			 if ( iIndex == m_strCmdString.length ) break;
			 if ( ch == " " || ch == "\t" || ch == "\n" )
			 {
				 iIndex ++; ch = m_strCmdString.charAt(iIndex);
				 continue;
			 }
			 if ( strAlpha.indexOf(ch) >= 0 )
				 chEndAcid += ch;
			 else break;
			 iIndex ++; ch = m_strCmdString.charAt(iIndex);
		 }
		 if ( chStartAcid.length != chEndAcid.length || chLocation.length == 0 )
		 {
			 alert("Invalid command string");
			 return null;
		 }

		 if ( chStartAcid.length == 1 )
			 bOneOrThree = true;
		 else if ( chStartAcid.length == 3 )
			 bOneOrThree = false;
		 else
		 {
			 alert("Invalid command string");
			 return null;
		 }
		 m_strStartAcid = chStartAcid;
		 m_strEndAcid   = chEndAcid;
		 m_iLocation    = chLocation * 1;
		 return bOneOrThree;
	 }
}


