/* -*- JavaScript -*-
 *
 * Copyright (c) 2006
 * Spoken Language Systems Group
 * MIT Computer Science and Artificial Intelligence Laboratory
 * Massachusetts Institute of Technology
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 **/

function Karaoke (mediaPlayer, karaokeElt){
   this.mediaPlayer = mediaPlayer;
   this.interval = 0;
   this.isStarted = false;
   this.msecs = 1000;		// msecs between probe
   this.karaokeElt = karaokeElt;
   this.bufferCount = 0;	// how long we've been waiting in buffering
   this.lastTime = 0;		// Last time in karaoke
   this.prefetchTime = 120000;
   this.fetchSize = 120000;
   this.gap = 999;
   this.fastPoll = 150;
   this.slowPoll = 1000;
   this.media = null;
   this.nextmedia = null;
   this.editBeforeWidth = 2000; //time in ms added to either end of flagged point
   this.editAfterWidth = 2000;
   this.edits = {};
   this.editMode = false;
   this.startPlay = false; // Start playing
   this.playEnabled = false;
}

// Ensure prototype
new Karaoke(null, null);

Karaoke.prototype.setLecture = function(lecture){
   var lectureid = lecture.getAttribute('lectureid');
   if (!this.lectureid || this.lectureid != lectureid){
	  this.mediaPlayer.stop();
	  this.playEnabled = false;
      this.lectureid = lectureid;
      this.lecture = lecture;
      this.timeslength = 0;
      this.beginTime = 0;
      this.lastTime = 0;
      this.duration = parseInt(lecture.getAttribute('duration'));
      delete this.times;
   }

   var media = nodemedia(lecture);

   if (media == this.media)
      return;

   this.nextmedia = media;
   this.initializeLecture(lecture);
}

// Set a range of times for playing
Karaoke.prototype.setRange = function(beginTime, endTime, startPlay){
   this.mediaPlayer.pause();
   this.beginTime = beginTime;
   this.endTime = endTime;
   this.position = beginTime;
   this.startPlay = startPlay;
}

Karaoke.prototype.fetchText = function(beginTime, endTime){
   //println("fetch: "+beginTime+", "+endTime);
  
   this.fetching = true;
   var outerthis = this;
   var lectureid = this.lectureid;
   var post = {};
   post.lectureid = lectureid;
   post.beginTime = beginTime;
   post.endTime = endTime;
   if (this.highlighter){
      post.highlighter = this.highlighter;
   }
   queue.post(null, 'times.jsp', post,
	      function(xml){outerthis.receiveText(xml, lectureid);});
}

// lecIndex = {beginTime, endTime, par, parIndex}*
// parIndex = {beginTime, endTime, span}*
// document = lecspan->par->text
//
// The lecIndex is complete for the entire lecture; any missing hits
// have ... placeholders and an empty parIndex.

// Initialize a lecture with a lecIndex that spans the entire lecture,
// and "..." for its displayed text.
Karaoke.prototype.initializeLecture = function(lecture){
   var duration = parseInt(lecture.getAttribute('duration'));
   this.lecIndex = [this.newLecRec(0, duration)];
   this.lecspan = document.createElement('span');
   this.lecspan.appendChild(this.lecIndex[0].par);
   this.karaokeElt.innerHTML = '';
   this.karaokeElt.appendChild(this.lecspan);
}

Karaoke.prototype.newLecRec = function(beginTime, endTime){
   var dotpar = document.createElement('p');
   dotpar.appendChild(document.createTextNode('.....'));
   return {'beginTime' : beginTime, 
	   'endTime' : endTime,
	   'par' : dotpar};

}

// Return the position of the largest position <= pos
// Index records must have beginTime and endTime fields
Karaoke.prototype.searchIndex = function(index, t){
   var lo = 0;
   var hi = index.length;
   while(lo+1 < hi){
      var mid = Math.floor((lo+hi)/2);
      if (t < index[mid].beginTime){
	 hi = mid;
      } else if (t <= index[mid].endTime){
	 return mid;
      }
      if (index[mid].endTime <= t){
	 lo = mid;
      }

   }
   return lo;
}

Karaoke.prototype.insertElementAfter = function(oldElement, newElement){
   var parent = oldElement.parentNode;
   var next = oldElement.nextSibling;
   if (next){
      parent.insertBefore(newElement, next);
   } else {
      parent.appendChild(newElement);
   }
}

Karaoke.prototype.insertLecRec = function(insRec){
   var index = this.lecIndex;
   // Highest index <= insert point
   var i = this.searchIndex(index, insRec.beginTime);
   var rec = index[i];
   var recPar = rec.par;
   var insPar = insRec.par;
   if (rec.parIndex){
      // Already there
      //println("Double insert"+insRec);
      return;
   }

   if (Math.abs(insRec.beginTime-rec.beginTime) < this.gap){
      insRec.beginTime = rec.beginTime;
      // Replace start
      if (Math.abs(insRec.endTime-rec.endTime) < this.gap){
		insRec.endTime = rec.endTime;
		// Replace all
		index[i] = insRec;
	 
        // Replace recPar with insPar
        recPar.parentNode.replaceChild(insPar, recPar);	 
	    //println("Replace: "+i+' '+insRec.beginTime+", "+insRec.endTime);
      } else {
	    rec.beginTime = insRec.endTime+1;
	    index.splice(i, 0, insRec);
	    // Insert insPar before recPar
        recPar.parentNode.insertBefore(insPar, recPar);
	    //println("Prepend: "+i+' '+insRec.beginTime+", "+insRec.endTime);
      }
   } else if (Math.abs(insRec.endTime-rec.endTime) < this.gap){
      insRec.endTime = rec.endTime;
      // Replace end
      rec.endTime = insRec.beginTime-1;
      index.splice(i+1, 0, insRec);
      this.insertElementAfter(recPar, insPar);
      //println("Append: "+(i+1)+' '+insRec.beginTime+", "+insRec.endTime);
   } else {
      // Split the rec and insert between
      var newRec = this.newLecRec(insRec.endTime+1, rec.endTime);
      rec.endTime = insRec.beginTime-1;
      index.splice(i+1, 0, insRec);
      index.splice(i+2, 0, newRec);
      this.insertElementAfter(recPar, insPar);
      this.insertElementAfter(insPar, newRec.par);
      //println("Split: "+(i+1)+' '+insRec.beginTime+", "+insRec.endTime);
   }
   //this.dumpIndex(index);
}

Karaoke.prototype.dumpIndex = function(index){
   var i;
   var s = '';
   for(i=0; i<index.length; i++){
      var rec = index[i];
      s+='['+rec.beginTime+','+rec.endTime+']';
   }
   println(s);
}

// Return the span to under t or null if there is none
Karaoke.prototype.getSpan = function(t){
   if (!this.lecIndex)
      return;

   var lecRec = this.lecIndex[this.searchIndex(this.lecIndex, t)];
   var parIndex = lecRec.parIndex;
   if (parIndex){
      var wordRec = parIndex[this.searchIndex(parIndex, t)];
      if (!wordRec)
	 return;
      return wordRec.elt;
   } else {
      // Ask for some text
      if (!this.fetching){
	 var beginTime;
	 var endTime;
	 if (t-lecRec.beginTime > 30000){
	    beginTime = t;
	 } else {
	    beginTime = lecRec.beginTime;
	 }
	 if (lecRec.endTime-t > this.fetchSize){
	    endTime = beginTime+this.fetchSize;
	 } else {
	    endTime = lecRec.endTime;
	 }
	 this.fetchText(beginTime, endTime);
      }

      return lecRec.par;
   }
}


Karaoke.prototype.receiveText = function(xml, lectureid){
   this.fetching = false;
   if (this.lectureid != lectureid){
      // Active lecture has changed
      //println("lecid changed"+this.lectureid+" "+lectureid);
      return;
   }

   var frags = xml.getElementsByTagName('fragment');
   var i;
   var j;
   var k = 0;
   var nwords = 0;

   for(i=0; i<frags.length; i++){
      var frag = frags[i];
      var children = frag.childNodes;
      for(j=0; j < children.length; j++){
	 if (children[j].nodeName == "word")
	    nwords++;
      }
   }
	
   var times = new Array(nwords);

   var par = document.createElement("span");
   var outerThis = this;
   par.onclick = function(e){outerThis.playFromWord(eventTarget(e), times);}
   for(i=0; i<frags.length; i++){
      var frag = frags[i];
      for(j=0; j<frag.childNodes.length;j++){
	 var word = frag.childNodes[j];
	 if (word.nodeName != "word")
	    continue;
	 var span = document.createElement("span");
	 var tnode = document.createTextNode(word.getAttribute("text"));

	 if (word.getAttribute('term') == 'true'){
	    span.style.backgroundColor = '#FFFF84';
	 }
	 span.onmouseover = function(e){eventSpan(e).className = "hover";};
	 span.onmouseout = function(e){eventSpan(e).className = "";}

	 span.appendChild(tnode);
	
	 times[k++] = {'beginTime' : parseInt(word.getAttribute('beginTime')),
		       'endTime' :   parseInt(word.getAttribute('endTime')),
		       'elt' : span};
	 par.appendChild(span);
	 // Always append a space -- we could be joined with the next
	 // chunk.
	 par.appendChild(document.createTextNode(' '));
      }
   }
   if (times.length == 0)
      return;
   this.insertLecRec({'beginTime' : times[0].beginTime,
		      'endTime' : times[times.length-1].endTime,
		      'parIndex' : times,
		      'par' : par});
}

Karaoke.prototype.playFromWord = function(span, times){
   for(var i=0; i<times.length; i++){
      if (times[i].elt == span){
	 this.setRange(times[i].beginTime, this.duration, true);
	 return;
      }
   }
}

Karaoke.prototype.playFromLectureWord = function(span, lecture, highlight, times){
   for(var i=0; i<times.length; i++){
      if (times[i].elt == span){
	 this.setLecture(lecture);
	 this.highlighter = highlight ? nodeQuery(lecture) : '';
	 this.setRange(times[i].beginTime, this.duration, true);
	 return;
      }
   }
}

Karaoke.prototype.play = function(lecture, highlighter, beginTime, endTime, startPlay){
   this.setLecture(lecture);
   this.highlighter = highlighter;
   this.setRange(beginTime, endTime, startPlay);
}

Karaoke.prototype.searchSpan = function(pos){
   var times = this.times;
   var lo = 0;
   var hi = this.timeslength;
   while(lo < hi){
      var mid = Math.floor((lo+hi)/2);
      if (pos < this.times[mid][0]){
	 hi = mid;
      } else if (this.times[mid][1] < pos){
	 lo = mid+1;
      } else {
	 this.lastSpani = mid;
	 return times[mid][2];
      }
   }
   return null;
}

Karaoke.prototype.adjustInterval = function(msecs){
   if (this.msecs == msecs)
      return;

   if (this.isStarted){
      clearInterval(this.interval);
      this.isStarted = false;
   }

   this.msecs = msecs;
   this.start();
}

Karaoke.prototype.start = function(){
   if (this.isStarted)
      return;
   var outerthis = this;
   this.isStarted = true;
   this.interval = setInterval(function(){outerthis.intervalFunction();},
			       this.msecs);
}

Karaoke.prototype.playTutorial = function(startPlay){
   var outerthis = this;
   var post = {};
   post.lectureid = 1;
   getIndexedLectureHits(post, function(xml){
			    var lecture=xml.getElementsByTagName('lecture')[0];
			    if (lecture){
				    var duration=parseInt(lecture.getAttribute('duration'));
				    outerthis.play(lecture, "", 0,duration, startPlay);
				}
			 });
}

Karaoke.prototype.playSegment = function(segment){
   var beginTime = parseInt(segment.getAttribute("beginTime"));
   var endTime =  parseInt(segment.getAttribute("endTime"));
   var lecture = nodeLecture(segment);
   var highlighter = nodeQuery(segment);
   this.play(lecture, highlighter, beginTime, endTime, true);
   return false;
}

Karaoke.prototype.playFromFragment = function(fragment, highlight){
   var segment = nodeSegment(fragment);
   var lecture = nodeLecture(segment);
   var beginTime = parseInt(fragment.getAttribute("beginTime"));
   var endTime = parseInt(segment.getAttribute("endTime"));
   var highlighter = highlight ? nodeQuery(segment) : '';
   this.play(lecture, highlighter, beginTime, endTime, true);
   return false;
}

Karaoke.prototype.stop = function(){
   this.mediaPlayer.stop();
   this.playEnabled = false;
}

Karaoke.prototype.intervalFunction = function(){
   	if (!this.counter)
    	this.counter = 0;

    this.mediaPlayer.checkState();
   	if (this.mediaPlayer.wasDead()){
    	// TBD: Reload RealPlayer when it crashes
      	clearInterval(this.interval);
      	return;
   	}
   	if (this.nextmedia){
		if (!this.mediaPlayer.wasStopped()){
			this.adjustInterval(this.slowPoll);
	    	this.mediaPlayer.stop();
	    	this.playEnabled = false;
			return;
    	}
	}

	if ((!this.mediaPlayer.wasPlaying() || !this.lecIndex) && this.msecs != this.slowPoll){
		this.adjustInterval(this.slowPoll);
   	} else if (this.mediaPlayer.wasPlaying() && this.msecs != this.fastPoll){
    	this.adjustInterval(this.fastPoll);
   	}

	if (!this.mediaPlayer.wasBuffering()){
    	this.bufferCount = 0;
   	}
   
   	if (this.mediaPlayer.wasStopped()){
      	if (this.nextmedia){
			if (this.media != this.nextmedia){
        	    this.media = this.nextmedia;	
				this.mediaPlayer.setSource(this.nextmedia);
			}

			this.nextmedia = null;
			this.playEnabled = this.startPlay;
			return;
		}
    	if (this.startPlay){
			this.mediaPlayer.play();
			this.playEnabled = true;
			this.startPlay = false;
		}
	} else if (this.mediaPlayer.wasBuffering()){
		// Buffering
		if (!this.playEnabled){
			this.mediaPlayer.pause();
		} else {
	        this.bufferCount += this.msecs;
    	    if (this.bufferCount > 1000){
        		this.bufferCount = 0;
        		this.mediaPlayer.play();
        	}
     	}
	} else if (this.mediaPlayer.wasPlaying()){
		this.startPlay = false;

      	if (this.position){
			this.mediaPlayer.setPosition(this.position);
		 	this.getSpan(this.position+this.prefetchTime);
		 	delete this.position;
	     	return;
      	}

      	var pos = this.mediaPlayer.getPosition();
      	var span = this.getSpan(pos);
      	this.getSpan(pos+this.prefetchTime);
      	if (span != this.span){
      		if (this.span){
				this.span.className = '';
			}	
		    if (span){
			    span.className = 'playing';
		    	span.scrollIntoView(true);
			}
			this.span = span;
		}
      	if (pos > this.endTime){
			this.mediaPlayer.stop();
			this.playEnabled = false;
      	}
	} else if (this.mediaPlayer.wasPaused()){
	    if (this.startPlay){
			this.mediaPlayer.play();
			this.playEnabled = true;
			this.startPlay = false;
		}
	}
};

Karaoke.prototype.flagEdit = function (){
	
	var flaggedPos = this.mediaPlayer.getPosition();
	//alert('Flag at: '+flaggedPos+'ms, lectureid: '+this.lectureid);
	// Make sure we don't overlap
	var width = this.editBeforeWidth+this.editAfterWidth;
	for(var i in this.edits){
		if(Math.abs(flaggedPos-i) < width){
			return;
		}
	}

	this.edits[flaggedPos]={};
	this.fetchEdits(flaggedPos);
}

//TODO: overlapping edits (user flags 2-4 sec and 3-5 sec. Displays two edits, one is 2-4, second is 4-5)
//      extending underline to highlight current edit window? -won't do for now
//      change color of edit region when flagged - done
//      possibly increase size of flag window before and decrease after (3 sec - flag - 1.5 sec)?
//		present flagged segments in left panel like search results
//      	possibly put tabs below the query box but above the search results for search and edit
//grabs the xml corresponding to the flagged region
Karaoke.prototype.fetchEdits = function(flagTime){	
	var editfloor = Math.max(0,flagTime-this.editBeforeWidth);
	var editceil = Math.min(this.duration,flagTime+this.editAfterWidth);
    var post = {};
    var lectureid = this.lectureid;
	post.lectureid = lectureid;
    post.beginTime = editfloor;
    post.endTime = editceil;
    var outerThis = this;
    queue.post(null, timesURL, post,
	      function(xml){
	      	 outerThis.receiveEdits(xml, lectureid, flagTime);
	      }); 
}

//receives the xml and extracts the words
Karaoke.prototype.receiveEdits = function(xml, lectureid, flagTime){

	if (this.lectureid != lectureid){
		// Lecture was changed while waiting for query results
		return;
	}

	var fragments = xml.getElementsByTagName('fragment');
	var words = fragments[0].getElementsByTagName('word');
   	var tmp = '';
   	var t;
   	var tmpspan;
   	var espans = [];
   	for(var j = 0; j < words.length; j++){
   	  t = parseInt(words[j].getAttribute('endTime'))-1;
   	  var lecRec = this.lecIndex[this.searchIndex(this.lecIndex, t)];
  	  var parIndex = lecRec.parIndex;
      var wordRec = parIndex[this.searchIndex(parIndex, t)];
	  tmpspan = wordRec.elt;
      tmpspan.style.color = 'red';
      espans.push(tmpspan);
   	  //tmp +=span.firstChild.nodeValue;
   	  tmp+= words[j].getAttribute('text')+' ';
	}
	if (tmp==''){
      delete this.edits[flagTime];
	} else {
	   var edit = this.edits[flagTime];
       edit.text = tmp;
       edit.span = espans;
	}
}

Karaoke.prototype.finishEdit = function (){
	this.mediaPlayer.pause();

	var editPane = resultsManager.selectPane(false, 'edit');
	editPane.innerHTML = '';
	this.editContainer = makeelt(editPane, 'div', 'edit-container');
	this.editContainer.innerHTML = '<div width="100%" id="edit-row"><span id="edit-title"></span></div>';
	var edittable = document.createElement('table');
	edittable.style.width = '100%';
	var outerThis = this;
	var indices = [];
	for(var k in this.edits) indices.push(parseInt(k));
	indices.sort();
	for (var k = 0; k < indices.length; k++){
		(function(k){
        var time = indices[k];
        var lecture = outerThis.lecture;
        var edit = outerThis.edits[time];
		var tbody = edittable;
		var tr = tbody.insertRow(tbody.rows.length);
		tr.style.width = '100%';
	  	var td = tr.insertCell(tr.cells.length);
	  	var tip = makeelt(td, 'div', 'tooltip-contents');
	  	var beginTime = Math.max(0,time-outerThis.editBeforeWidth);
	  	var endTime = Math.min(time+outerThis.editAfterWidth,outerThis.duration);
	  	tip.innerHTML = "Start playing from "+timeStr(beginTime);
	  	registerEvent(td, "mouseover", onMouseOverShowTooltip);
	  	registerEvent(td, "mouseout", onMouseOutHideTooltip);
	  	
	  	makeelt(td, 'div', 'small-play-button');
	  	td.vAlign = 'middle';
	  	td.style.width = '25px';
	  	td.style.height = '100%';
	  	td.onclick = function(){
	  	   outerThis.play(outerThis.lecture, false, beginTime, endTime, true);
	  	};
	  	
	  	td = tr.insertCell(tr.cells.length);
	  	td.width = '1*';
	  	
	    var editText = makeelt(td, 'textarea', 'edit-box');
		editText.value = edit.text;
		editText.rows = 3;
	    editText.cols = 30;
		td = tr.insertCell(tr.cells.length);
		var editSubmit = makeelt(td, 'input', 'submit-button');
        editSubmit.setAttribute('TYPE', 'button');
        editSubmit.value = 'Submit';
        editSubmit.onclick = function(){
          var post = {};
          post.beginTime = beginTime;
          post.endTime = endTime;
          post.lectureid = outerThis.lectureid;
          post.transcription = editText.value;
          queue.post("Sending Edit", submitEditURL, post, function(xml){
			// Flush index cache to get edits
            outerThis.initializeLecture(lecture);
			edit.text = editText.value;
          });
        };
        
	    td.appendChild(document.createElement('br'));
	})(k)};
	this.editContainer.appendChild(edittable);
	document.getElementById("edit-title").innerHTML = '<strong>'+k+'</strong>'+' Segment'+((k != 1) ? 's' : '')+' Flagged for Editing';
	//var queryText = document.getElementById('query-text');
	//queryText.innerHTML = '';
	this.editMode = true;
}

Karaoke.prototype.clearEdits = function(){
	for(var i in this.edits){
		var spans = this.edits[i].span;
		for(var j=0; j<spans.length; j++){
			spans[j].style.color = 'black';
		}
	}

    this.edits = {};
	resultsManager.selectPane(false, 'query-results');
    this.editMode = false;
}
