/*

ActiveArchives
AASlider.js
All code for ActiveArchives is released under a GPL2 license

*/

var AASLIDER_ELTIDGEN = 0;
var AASLIDER_INTERVAL_TIME = 1000;
//
// Title => SliderElement
// .text => .item
//
// SliderElement in effect "holds" an item on the slider's timeline,
// positioning it between a start and end time
//
var SliderElement = Class.create({
	initialize: function (start, end, item) {
		AASLIDER_ELTIDGEN += 1;
		this.id = "slider_elt" + AASLIDER_ELTIDGEN;
		
		if (typeof(start) == "string")
			start = timecode_tosecs(start);
		if (typeof(end) == "string")
			end = timecode_tosecs(end);

		this.start = start;
		this.end = end;
		this.item = item;
		/*
		item should/can implement the following... 
			show : function () {},
			hide : function () {},
			setCurrentTime: function (t) {},
			play: function () {},
			pause: function () {}
			getCurrentTime: function () {},
			getPlaying: function () {}
		*/
	},
	show: function () {
		if (this.item.show) this.item.show();
	},
	hide: function () {
		if (this.item.hide) this.item.hide();
	}
});

var SliderManager = Class.create({
	initialize: function () {
		this.titlesByStart = [];
		this.titlesByEnd = [];
		this.lastTime = undefined;
		this.startIndex = -1;
		this.endIndex = -1;
		this.toShow = {};
		this.toHide = {};
		this.activeItems = {};
	},
	addTitleByStart: function (newtitle) {
		/* insert annotation in the correct (sorted) location */
		for (var i=0; i<this.titlesByStart.length; i++) {
			if (this.titlesByStart[i].start > newtitle.start) {
				// insert before this index
				this.titlesByStart.splice(i, 0, newtitle);
				return;
			}
		}
		// otherwise simply append
		this.titlesByStart.push(newtitle);
	},
	addTitleByEnd: function (newtitle) {
		/* insert annotation in the correct (sorted) location */
		for (var i=0; i<this.titlesByEnd.length; i++) {
			if (this.titlesByEnd[i].end > newtitle.end) {
				// insert before this index
				this.titlesByEnd.splice(i, 0, newtitle);
				return;
			}
		}
		// otherwise simply append
		this.titlesByEnd.push(newtitle);
	},
	addTitle: function (newtitle) {
		this.addTitleByStart(newtitle);
		this.addTitleByEnd(newtitle);
	},
	markToShow: function (t) {
		if (this.toHide[t.id]) {
			delete this.toHide[t.id];
		} else {
			this.toShow[t.id] = t;
		}
	},
	markToHide: function (t) {
		if (this.toShow[t.id]) {
			delete this.toShow[t.id];
		} else {
			this.toHide[t.id] = t;
		}
	},
	updateForTime: function (time) {
		if (this.titlesByStart.length == 0) return;
		// console.log("updateForTime");

		/* check against lastTime to optimize search */
		// valid range for i: -1            (pre first title)
		//   			  to  titles.length-1 (last title), can't be bigger, as this isn't defined (when would it go last -> post-last)
		if (time < this.lastTime) {
			// SLIDE BACKWARD
			//
			var n = this.titlesByStart.length;
			// [start: 50, start: 70]
			// [end: 55, end: 75]
			// time = 80
			// startIndex = 1 (at end)
			// process ends first! (as shows of same element will override!! when going backwards, dus)
			while (this.endIndex >= 0 && time < this.titlesByEnd[this.endIndex].end) {
				this.markToShow(this.titlesByEnd[this.endIndex]);
				this.endIndex--;
			}
			while (this.startIndex >= 0 && time < this.titlesByStart[this.startIndex].start) {
				this.markToHide(this.titlesByStart[this.startIndex]);
				this.startIndex--;
			}
		} else {
			// SLIDE FORWARD
			// 
			// process starts first! (as hides of same element will override!!)
			var n = this.titlesByStart.length;
			while ((this.startIndex+1) < n && time >= this.titlesByStart[this.startIndex+1].start) {
				this.startIndex++;
				if (this.startIndex < n) this.markToShow(this.titlesByStart[this.startIndex]);
			}	
			n = this.titlesByEnd.length;
			while ((this.endIndex+1) < n && time >= this.titlesByEnd[this.endIndex+1].end) {
				this.endIndex++;
				if (this.endIndex < n) this.markToHide(this.titlesByEnd[this.endIndex]);
			}	
		}
		// if (this.startIndex != si) this.setStartIndex(si);
		this.lastTime = time;

		// perform show/hides
		var clearFlag = false;
		for (var tid in this.toShow) {
			this.toShow[tid].show();
			this.activeItems[tid] = this.toShow[tid];
			clearFlag = true;
		}
		if (clearFlag) this.toShow = {};
		clearFlag = false;
		for (var tid in this.toHide) {
			this.toHide[tid].hide();
			delete this.activeItems[tid];
			clearFlag = true;
		}
		if (clearFlag) this.toHide = {};

		return;
	}
});

/////////////////////////
// AASlider
//
// Requires: Scriptaculous (Control.Slider), (& Prototype)
//

var AASlider = Class.create({
	/*
	does the slider have a notion of playstate (or simply inherited from attached items that "pull" the slider along when playing!?
	however, playstate sould have to trigger other elements to play if overlapping
	slider would have its own TitlesManager to know what's active at any given time,
	including the videos themselves!?
	when two audio/video elements are active -- should only follow one (at a time!)
	say the first added get priority? (or maybe better for last added?)
	(but still set both on a scrub)
	*/
	initialize: function (elt, slider_opts) {
		this.tm = new SliderManager();
		/* create Scriptaculous slider */
		this.elt = Object.extend(elt);
		this.thumb = this.elt.down(".aa_slider_thumb");
		if (!this.thumb) log("couldn't find elt with class aa_slider_thumb");
		this.track = this.elt.down(".aa_slider_track");
		if (!this.track) log("couldn't find elt with class aa_slider_track");

		this.label = this.elt.down(".aa_slider_label");
		this.play_control = this.elt.down(".aa_slider_play");
		this.pause_control = this.elt.down(".aa_slider_pause");
		if (this.pause_control) {
			this.pause_control.observe("click", this.on_pause_control.bindAsEventListener(this));
		}
		if (this.play_control) {
			this.play_control.observe("click", this.on_play_control.bindAsEventListener(this));
		}

		if (!slider_opts) { slider_opts = {} };
		slider_opts.onSlide = this.onSlide.bind(this);
		slider_opts.onChange = this.onChange.bind(this);
		this.slider = new Control.Slider(this.thumb, this.track, slider_opts);

		this.currentTime = 0;
		this.duration = 3600; // 1 hour default duration
		this.currentSliderValue = 0; // slider value 0-1
		// this.timedisplay = create("div.slider_timedisplay");
		// this.timedisplay.style.position = "absolute";
		this.paused = true;
		this.updatePlayState();
		this.showTimeDisplay = true;
		this.updateTimeDisplay(0);
		window.setInterval(this.interval.bind(this), AASLIDER_INTERVAL_TIME);
		// this.changingPlayState = false;
		this.sliderDragging = false;
		this.pauseOnDrag = true;
	},
	updatePlayState: function () {
		if (this.paused) {
			if (this.play_control) this.play_control.show();
			if (this.pause_control) this.pause_control.hide();
		} else {
			if (this.pause_control) this.pause_control.show();
			if (this.play_control) this.play_control.hide();
		}
	},
	pause : function () {
		if (this.paused) return;
		// this.changingPlayState = true;
		this.paused = true;
		for (var key in this.tm.activeItems) {
			var selt = this.tm.activeItems[key];
			selt.item.pause();
		}
		this.updatePlayState();
		// this.waitForAllPlaying(false);
		// this.changingPlayState = false;
	},
	play : function () {
		if (!this.paused) return;
		// this.changingPlayState = true;
		this.paused = false;
		for (var key in this.tm.activeItems) {
			var selt = this.tm.activeItems[key];
			// log("attempting to play", selt.item, selt.item.play);
			selt.item.play();
		}
		this.updatePlayState();
		// this.waitForCommonPlayState(true);
		// this.changingPlayState = false;
	},
/*	waitForCommonPlayState: function (playing) {
		// not used at the moment
		// (as trying to detect/set global playstate from individual items maybe not a good idea)
		var done = true;
		while (!done) {
			for (var key in this.tm.activeItems) {
				var selt = this.tm.activeItems[key]
				var seltIsPlaying = (selt.item.isPlaying || (selt.item.getIsPlaying && selt.item.getIsPlaying()));
				if ((playing && !seltIsPlaying) || (!playing && seltIsPlaying))
					done = false;
 			}
		}
	},*/
	on_pause_control: function (evt) {
		// log("AASlider.pause");
		this.pause();
	},
	on_play_control: function (evt) {
		// log("AASlider.play");
		this.play();
	},
	setCurrentTime: function (time) {
		// log("AASlider.setCurrentTime", time);
		this.slider.setValue(time / this.duration);
	},
	interval : function () {
		// log("AASlider.interval", this);
		//
		// follow the time of the first item encountered in a "playing" state
		// maybe good to have an option to reverse the search order (last->first)

		// ooei, actually the order is rather random in this case, not so good maybe...

		if (this.sliderDragging) return;
		var activeItemsCount = 0;
		var playingItemsCount = 0;
		var setSliderValue = false;
		var followingPlayingElement = false;
		for (var key in this.tm.activeItems) {
			activeItemsCount += 1;
			var selt = this.tm.activeItems[key]
			// log(selt, selt.item.getIsPlaying);
			var seltIsPlaying = (selt.item.isPlaying || (selt.item.getIsPlaying && selt.item.getIsPlaying()));
			if (seltIsPlaying) {
				playingItemsCount += 1;
				if (followingPlayingElement && !setSliderValue) {
					// this element will "drive" the timeline
					var itemTime = selt.item.currentTime || selt.item.getCurrentTime();
					// log("following temporal item");
					var time = selt.start + itemTime;
					this.setCurrentTime(time);
					setSliderValue = true;
				}
			} else if (!this.paused) {
				// log("force play", selt.item, selt.item.play);
				if (selt.item.play) selt.item.play();
			}
		}
		// log("AASlider.interval, playing:", playingItemsCount+"/"+activeItemsCount);
		// otherwise, if playing, (but no temporal items are playing)
		if ((playingItemsCount == 0 || !followingPlayingElement) && !this.paused) {
			var curTime = this.currentTime;
			curTime += (AASLIDER_INTERVAL_TIME / 1000.0);
			var newSliderValue = curTime / this.duration;
			// log("autoIncrement", this.currentTime, curTime, this.currentSliderValue, newSliderValue);
			this.slider.setValue(curTime / this.duration);
		}
	},
	setDuration : function (time) {
		if (typeof(time) == "string")
			time = timecode_tosecs(time);
		var oldValueSecs = this.currentSliderValue * this.duration;
		this.duration = time;
		// recalculate slider value
		var newValue = (oldValueSecs / this.duration);
		this.slider.setValue(newValue); // this will trigger an onChange
	},
	onSlide: function (value) {
		// log("onSlide", this);
		this.draggingSlider = true;
		var time = (value * this.duration);
		this.currentTime = time;
		// log("onSlide", time);
		this.currentSliderValue = value;
		this.tm.updateForTime(time);
		if (this.showTimeDisplay) this.updateTimeDisplay(value);
		// set (relative) time on active items
		for (var key in this.tm.activeItems) {
			var selt = this.tm.activeItems[key];
			var itemTime = (time - selt.start);
			if (selt.item.setCurrentTime) selt.item.setCurrentTime(itemTime);
		}
	},
	onChange: function (value) {
		// log("onChange", this, value);
		var time = (value * this.duration);
		this.currentTime = time;
		this.currentSliderValue = value;
		if (this.showTimeDisplay) this.updateTimeDisplay(value);
		this.tm.updateForTime(time);		
/*		if (!this.draggingSlider) {
			// set (relative) time on active items
			for (var key in this.tm.activeItems) {
				var selt = this.tm.activeItems[key];
				var itemTime = (time - selt.start);
				if (selt.item.setCurrentTime) selt.item.setCurrentTime(itemTime);
			}
		}*/
		this.draggingSlider = false;
	},
	updateTimeDisplay: function (value) {
		if (!this.label) return;
		this.label.innerHTML = timecode_fromsecs(this.duration * value);
		return;
		if (!this.timedisplay.parentNode) {
			// log("Adding timedisplay");
			document.body.appendChild(this.timedisplay);
		}
		// log(this.duration, value);
		this.timedisplay.innerHTML = timecode_fromsecs(this.duration * value);
		this.timedisplay.clonePosition($(this.thumb), {offsetTop: 20, setWidth: false, setHeight: false});
	},
	attach: function(item, start, end) {
		// end is optional, will attempt to get duration from item
		if (end == undefined) {
			var duration = item.getDuration ? item.getDuration() : item.duration;
			if (duration == null) duration = 10; // worst case, assume 10 seconds
			end = start + duration;
		}
		// sanity check
		if (end < start) {
			log("ERROR: AASlider.attach end < start, rejecting.");
			return;
		}
		var elt = new SliderElement(start, end, item);
		this.tm.addTitle(elt);
		// force tm update via setting the slider
		this.slider.setValue(this.currentSliderValue);
	}
});
