/* Viewer for multiple alignments.
 *
 * Alignment is represented as an array of objects with attributes:
 * - id
 * - name
 * - sequence
 *
 * All sequences in an alignment should be the same length.
 */
var calhoun; 
if (typeof(calhoun) == "undefined") calhoun={};

calhoun.alignment = {};

// AlignmentViewer class
calhoun.alignment.AlignmentViewer = function(elementId,alphabet,sequences) {
   this.elementId = elementId
   this.alphabet = alphabet
   if (sequences) this.setSequences(sequences)
};

(function() {
   var AlignmentViewerClass = calhoun.alignment.AlignmentViewer.prototype

   AlignmentViewerClass.draw = function() {
      if (!this.sequences) return
      var seqNames = []
      var seqStrings = []

      var timer = new calhoun.util.Timer()
      seqNames.push('')
      for (var i=0;i<this.sequences.length;i++) {
         var seq = this.sequences[i]
         if (this.selectedIds[seq.id])
         {
            seqNames.push(seq.name)
            seqStrings.push(seq.sequence)
         }
      }
      timer.report("Finished getting selected sequences",true)

      var summary = new calhoun.alignment.AlignmentSummary(seqStrings)

      timer.report("Finished making summary",true)

      var htmlBuf = new calhoun.util.StringBuilder()
      if (seqStrings.length > 0) {
         htmlBuf.append("<pre><span class='alignment-text' id='sequences'>")
         var blocks = blocks
         len = seqStrings[0].length
         ruler = makeRuler(len)
         htmlBuf.append(ruler)
         htmlBuf.append("<br>")
         for (var i=0;i<seqStrings.length;i++) {
            htmlBuf.append(summary.toHtml(seqStrings[i]))
            htmlBuf.append("<br>")
         }
         htmlBuf.append(ruler)
         htmlBuf.append("<br>")
         htmlBuf.append(numberLine(len))
         htmlBuf.append("<br>")
         htmlBuf.append("<br>" + summary.collapsedConsensus + "<br>")
                htmlBuf.append("</span><span class='alignment-text' id='names'>");
         for (var i=0;i<seqNames.length;i++) {
            htmlBuf.append(seqNames[i] + "<br>");
         }
         htmlBuf.append("<br><br><br></span></pre>")
      }
      else {
         htmlBuf.append("<b>No sequences selected</b>")
      }

      var html = htmlBuf.toString()

      timer.report("Finished making HTML",true)

      calhoun.util.setElementContent(this.elementId,html)
      timer.report("Did set element content",true)
   }
   
   function makeRuler(len) {
   		var ruler = ''
   		for (var i = 0; i < len; i++) {
   			ruler += ((i % 10 == 0) ? "+" : "-")
   		}
   		return ruler
   }
   
   function numberLine(len) {
   		var line = ''
   		for(var i = 0; i < len; i++) {
   			if(i % 10 == 0) {
   				line += i
   				i += (i.toString().length - 1) 
   			}
   			else line += '&nbsp;'
   		}
   		return line
   }

   AlignmentViewerClass.setSequences = function(sequences) {
      this.sequences = sequences
      if (!sequences) return;
      if (!this.selectedIds) {
         this.selectedIds = {}
         for (var i=0;i<sequences.length;i++) {
            this.selectedIds[sequences[i].id] = true
         }
      }
   }

   AlignmentViewerClass.selectIds = function(ids) {
      if (!this.selectedIds) this.selectedIds = {}
      for (var key in this.selectedIds) {
         this.selectedIds[key] = false
      }
      for (var i = 0;i<ids.length;i++) {
         this.selectedIds[ids[i]] = true
      }
   }

   /* Constructs consensus sequence.  I attempted to reverse-engineer the 
    * rules used by jalview, which appear to be, for each position:
    * - If no residue appears more than once, consensus is "-" (doesn't work very well on just two sequences)
    * - Else two or more residues appear in the same number of sequences, consensus is "+"
    * - Else if one residue appears more than any other, consensus is that residue
    * Also constructs a string representing the "character class" of each column of the alignment,
    * where the classes are: 
    * G: gap, 
    * I: 100% identity, 
    * 8: 80% or better identity to consensus, 
    * 6:60% or better identity to consensus
    * 4:40% or better identity to consensus
    * 2:20% or better identity to consensus
    * X: other
    *
    * Return value is an object with properties "consensus" and "classes"
    */
   AlignmentViewerClass.makeConsensus = function(seqStrings) {
   }

   

})()
// end AlignmentViewer class

// AlignmentSummary class
/* This class captures:
 * - A consensus character for each column of the alignment.  I use the following rules,
 *   which are my best guess at the logic used by jalview:
 *   - If no residue appears more than once, consensus is "-" (doesn't work very well on just two sequences)
 *   - Else two or more residues appear in the same number of sequences, consensus is "+"
 *   - Else if one residue appears more than any other, consensus is that residue
 *
 * - A "character class" for each column of the alignment, one of:
 *    G: gap, 
 *    I: 100% identity, 
 *    8: 80% or better identity to consensus, 
 *    6:60% or better identity to consensus
 *    4:40% or better identity to consensus
 *    2:20% or better identity to consensus
 *    X: other
 * - A collapsed consensus sequence for the alignment, i.e. with the gaps removed.  We might 
 *   have gaps all the way through the alignment because we may be operating on a subset of sequences.
 */
calhoun.alignment.AlignmentSummary = function(seqStrings) {
   this.init(seqStrings)
};

(function() {
   var AlignmentSummaryClass = calhoun.alignment.AlignmentSummary.prototype

   AlignmentSummaryClass.init = function(seqStrings) {
      YAHOO.log("AlignmentSummary.init(" + seqStrings + ")")
      var seqLength = seqStrings.length > 0 ? seqStrings[0].length : 0;
      YAHOO.log("seqLength = " + seqLength)
      var consBuf = new calhoun.util.StringBuilder()
      var classBuf = new calhoun.util.StringBuilder()
      var collBuf = new calhoun.util.StringBuilder()
      var seqArrays = []
      
      for (var i=0;i<seqStrings.length;i++) {
         seqArrays[i] = seqStrings[i].split("")
      }

      for (var i=0;i<seqLength;i++) {
         var counts = {}
         for (var j=0;j<seqArrays.length;j++) {
            var c = seqArrays[j][i]
            if (c != '-') {
               if (counts[c]) counts[c]++
               else counts[c] = 1
            }
         }

         var bestChar
         var bestCount = 0
         var ambiguous = false
         var uniqCharCnt = 0
         var totalCount = 0
         for (var c in counts) {
            uniqCharCnt++
            totalCount += counts[c]
            if (counts[c] > bestCount) {
               bestChar = c
               bestCount = counts[c]
               ambiguous = false
            }
            else if (counts[c] == bestCount) {
               ambiguous = true
            }
         }

         if (bestCount > 1) {
            if (ambiguous) {
               consBuf.append("+")
               collBuf.append("+")
            }
            else { 
               consBuf.append(bestChar)
               collBuf.append(bestChar)
            }
         }
         else {
            consBuf.append("-")
            if (bestCount > 0) {
               collBuf.append("-")
            }
         }

         if (bestCount == 0) {
            classBuf.append('G')
         }
         else if (uniqCharCnt == 1) {
            classBuf.append('I')
         }
         else {
            var pct = bestCount*100/totalCount
            if (pct >= 80) {
               classBuf.append('8')
            }
            else if (pct >= 60) {
               classBuf.append('6')
            }
            else if (pct >= 40) {
               classBuf.append('4')
            }
            else if (pct >= 20) {
               classBuf.append('2')
            }
            else {
               classBuf.append('X')
            }
         }
      }

      this.consensusChars = consBuf.toArray()
      this.charClasses = classBuf.toArray()
      this.collapsedConsensus = collBuf.toString()
   }

   AlignmentSummaryClass.toHtml = function(sequence) {
      var seqChars = sequence.split("")
      var htmlBuf = new calhoun.util.StringBuilder()
      var prevColor = null
      var currColor = null
      for (var i=0;i<seqChars.length;i++) {
         if (this.charClasses[i] != 'G') {
            if (seqChars[i] == this.consensusChars[i] && seqChars[i] != '-') {
               currColor = this.chooseColor(this.charClasses[i])
            } 
            else {
               currColor = null
            }
            if (currColor != prevColor) {
               if (prevColor != null) {
                  htmlBuf.append("</span>")
               }
               if (currColor != null) {
                  htmlBuf.append("<span style='background-color:"+currColor+"'>")
               }
            }
            htmlBuf.append(seqChars[i])

            prevColor = currColor
         }
      }

      if (prevColor != null) htmlBuf.append("</span>")

      return htmlBuf.toString()
   }

   AlignmentSummaryClass.chooseColor = function(charClass) {
      switch (charClass) {
      case 'I': return "#6464FF"
      case '8': return "#6464FF"
      case '6': return "#9999FF"
      case '4': return "#CCCCFF"
      }
      return null
   }
})()
// end AlignmentSummary class

