// public

var width = 400;

function initTime() {
	ctx = document.getElementById("drawBox").getContext("2d");
	updateTimeline();
	timePos = time();
	if (!ordered(minTime, timePos, maxTime)) {
		timePos = date.getTime();
	}
	reloadTimenodes();
	mediaHandlers.push(setBitMask);
	cumulativeOffset = $("drawBox").cumulativeOffset();

	setInterval(function() {
		if (document.OwlPlayer && document.OwlPlayer.getLog) {
			//var msg = document.OwlPlayer.getLog();
			//if (msg && typeof(msg) == "string" && msg.match(/\S/)) { GLog.write("OwlPlayer: " + msg); }
		}
	}, 1000);
}

////////////////////////////////////////////////////////////////////////////////
// private

// basic time stuff 
var seconds = 1000;
var minutes = 60 * seconds;
var hours = 60 * minutes;
var days = 24 * hours;
function time() { return (new Date()).getTime(); }
function ordered(x,y,z) { return (x < y && y < z); }

// constant
var baseTimeRange = 4 * 365.24 * days;
var zoomScale = Math.pow(5 * minutes / baseTimeRange, 1/9);
var monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
var cumulativeOffset; // position of timeline

// independent
var date = new Date(projTime ? 1000 * projTime : time() - baseTimeRange*Math.pow(zoomScale,projTZoom)/4);
var zoom = projTZoom;

var timePos;
var playSpeed = 0;

// dependent variables [on date and zoom]
var timeRange;
var minTime;
var maxTime;
function updateTimeline() {
	timeRange = baseTimeRange * Math.pow(zoomScale, zoom);
	minTime = date.getTime() - timeRange / 2;
	maxTime = minTime + timeRange;
}
function timeToPx(t) { return width*(t-minTime)/timeRange; }

// some utility functions for date format

function copyDate(d) { return new Date(d.getFullYear(),d.getMonth(),d.getDate()); }
function floorTime(d, minutes, seconds, millis) {
	var c = new Date(d);
	if (minutes) {
		c.setSeconds(0);
		c.setMinutes(minutes * Math.floor(d.getMinutes() / minutes));
	}
	else if (seconds) {
		c.setSeconds(seconds * Math.floor(d.getSeconds() / seconds));
	}
	else if (millis) {
		c.setMilliseconds(millis * Math.floor(d.getMilliseconds() / millis));
	}
	return c;
}
function abbrMo(d) { return monthNames[d.getMonth()]; }
function getHour12(d, showMinutes, showSeconds, showMillis) {
	var min = (showMinutes ? ":" + padZero(d.getMinutes()) : "");
	var sec = (showSeconds ? ":" + padZero(d.getSeconds()) : "");
	var millis = (showMillis ? (d.getMilliseconds()/1000+1+"").substr(1) : "");
	var baseTime = ((d.getHours()+11) % 12 + 1) + min + sec + millis;
	if (!showSeconds) {
		baseTime += (d.getHours() < 12 ? " am":" pm");
	}
	return baseTime;
}
function padZero(x) { return ("0"+String(x)).substr(-2); }

////////////////////////////////////////////////////////////////////////////////

// text-draw utility stuff

var ctx; // 2D drawing context for waveform canvas

function createText(parentNode, text, x, y) {
	var div = document.createElement("div");
	div.className = "timelineText";
	div.innerHTML = text;
	if (x) { setX(div, x); }
	if (y) { setX(div, y); }
	parentNode.appendChild(div);
	return div;
}
function setX(node, x) {
	node.xpos = x;
	node.style.left = x - 50 + "px";
}
function drawLines(firstVert) {
	ctx.beginPath();
	ctx.moveTo(firstVert[0], firstVert[1]);
	for (var i = 1; i < arguments.length; i++) {
		ctx.lineTo(arguments[i][0], arguments[i][1]);
	}
	ctx.stroke();
}
function vertBar(x) { drawLines([x,0], [x,50]); }

////////////////////////////////////////////////////////////////////////////////

timeScales = [];
timeScales[0] = { // 4 years
	"floor": function(d) { return new Date(d.getFullYear(), 0, 1); },
	"label": function(d) { return String(d.getFullYear()); },
	"step": 366 * days, "bias": 0.5 * 365.24 * days };
timeScales[1] = { // 4 seasons
	"floor": function(d) { return new Date(d.getFullYear(), d.getMonth(), 1); },
	"label": function(d) { if (!(d.getMonth() % 3))
		return abbrMo(d) + " '" + padZero(d.getYear()); return ""; },
	"step": 32 * days, "bias": 0.5 * 365.24 / 12 * days };
timeScales[2] = { // 3 months
	"label": function(d) { return abbrMo(d) + " '" + padZero(d.getYear()); },
	"floor": timeScales[1].floor, "step": 32 * days, "bias": timeScales[1].bias };
timeScales[3] = { // 3 weeks
	"floor": function(d) { var c = copyDate(d);
		c.setDate(d.getDate() - d.getDay()); return c; },
	"label": function(d) { return abbrMo(d) + " " + d.getDate(); },
	"step": 8 * days, "bias": 0 };
timeScales[4] = { // 5 days
	"floor": function(d) { return copyDate(d); },
	"label": timeScales[3].label, "step": 25 * hours, "bias": 12 * hours };
timeScales[5] = { // 2 half-days
	"floor": function(d) { var c = copyDate(d);
		c.setHours(6 * Math.floor(d.getHours() / 6)); return c; },
	"label": function(d) {
		var suffix = " " + getHour12(d);
		return (d.getHours()?"":(d.getMonth()+1)+"/"+d.getDate()+":") + suffix;
	},
	"step": 7 * hours, "bias": 0 };
timeScales[6] = { // 6 hours
	"floor": function(d) { var c=copyDate(d); c.setHours(d.getHours()); return c;},
	"label": getHour12, "step": hours, "bias": 20 * minutes };
timeScales[7] = { // 3 half-hours
	"floor": function(d) { return floorTime(d, 15); },
	"label": function(d) { if (!(d.getMinutes() % 30)) return getHour12(d, true);
		return ""; },
	"step": 16 * minutes, "bias": 0 };
timeScales[8] = { // 4 5-min intervals
	"floor": function(d) { return floorTime(d, 5); },
	"label": function(d) { return getHour12(d, true); },
	"step": 6 * minutes, "bias": 0 };
timeScales[9] = { // 5 minutes
	"floor": function(d) { return floorTime(d, 1); }, "label": timeScales[8].label,
	"step": 61 * seconds, "bias": 20 * seconds };
timeScales[10] = { // 4 quarter-mins
	"floor": function(d) { return floorTime(d, 0, 15); },
	"label": function(d) { return getHour12(d, true, true); },
	"step": 16 * seconds, "bias": 0 };
timeScales[11] = { // 4 5-sec intervals
	"floor": function(d) { return floorTime(d, 0, 5); }, "label": timeScales[10].label,
	"step": 6 * seconds, "bias": 0 };
timeScales[12] = { // 5 seconds
	floor: function(d) { return floorTime(d, 0, 1); }, label: timeScales[11].label,
	step: 1001, bias: 0 };
timeScales[13] = { // 5 0.2-seconds
	floor: function(d) { return floorTime(d, 0, 0, 200); },
	label: function(d) { return getHour12(d, true, true, true); },
	step: 201, bias: 0 };
timeScales[14] = { // 4 0.05-seconds
	floor: function(d) { return floorTime(d, 0, 0, 50); },
	label: function(d) { return (d.getTime() % 100 ? "" : getHour12(d,true,true,true)); },
	step: 51, bias: 0 };
	

var timenodes = [];
function createTimenode(d, scale) {
	var x  = timeToPx(d.getTime()             );
	var bx = timeToPx(d.getTime() + scale.bias);
	var timenode = createText($("textBox"), scale.label(d), bx);
	timenode.bias = bx - x;
	timenode.date = d;
	return timenode;
}

function setBitMask(data) {
	bitMask = data.timeData;
	leadingZeroes = nBuckets - bitMask.length;
	redraw();
}

function reloadTimenodes() {
	for (var i = 0; i < timenodes.length; i++) {
		timenodes[i].parentNode.removeChild(timenodes[i]);
	}
	timenodes = [];
	var scale = timeScales[zoom];
	var alive = 1; // extend for-loop by 1 iter. beyond primary failure
	for (var d = scale.floor(new Date(minTime));
	     d.getTime()<maxTime || (alive--); d = scale.floor(new Date(d.getTime() + scale.step)) ) {
		timenodes.push(createTimenode(d, scale));
	}
	redraw();
}

var nBuckets = 56;
var bitMask = "0";
var leadingZeroes;
function redraw() {
	ctx.fillStyle = "rgb(255, 233, 211)";
	ctx.fillRect(0, 0, width, 50);
	ctx.strokeStyle = "rgb(192, 192, 192)";
	for (var i = 1; i < timenodes.length - 1; i++) {
		vertBar(0.5 + Math.round(timenodes[i].xpos - timenodes[i].bias));
	}
	drawLines([0,15.5], [width,15.5]);
	drawLines([0,35.5], [width,35.5]);

	ctx.fillStyle = "rgb(224, 160, 128)";
	for (var i = leadingZeroes; i < nBuckets; i++) {
		if (bitMask[i - leadingZeroes] == "1") {
			ctx.fillRect(width/nBuckets * i, 15, width/nBuckets + 1, 20);
		}
	}
	if (selectedSound) {
		ctx.fillStyle = "rgb(32, 160, 32)";
		var barStart = timeToPx(selectedSound.start);
		var barEnd = timeToPx(selectedSound.end);
		ctx.fillRect(barStart - 0.5, 15, barEnd - barStart + 1, 20);
	}
	if (hoveredAnnotation) {
		ctx.fillStyle = "rgb(0,80,0)";
		var lineStart = timeToPx(hoveredAnnotation.media.start);
		var lineEnd = timeToPx(hoveredAnnotation.media.end);
		if (hoveredAnnotation.start) {
			lineStart = timeToPx(hoveredAnnotation.start);
			lineEnd = timeToPx(hoveredAnnotation.end);
		}
		ctx.fillRect(lineStart - 0.5, 20, lineEnd - lineStart + 1, 10);
	}

	var timePosPx = Math.round(width * (timePos - minTime) / timeRange) + 0.5;
	ctx.fillStyle = ctx.strokeStyle = "rgb(0, 0, 0)";
	ctx.beginPath();
	ctx.moveTo(timePosPx - 5, 0);
	ctx.lineTo(timePosPx, 10);
	ctx.lineTo(timePosPx + 5, 0);
	ctx.fill();
	vertBar(timePosPx);

	if (startX != null) { drawLines([startX+0.5,15], [startX+0.5,35]); }
}

////////////////////////////////////////////////////////////////////////////////
// HTML interface

var newDate; // used for panning, etc.


var startX = null;
function timelineDown(e) {
	if (window.event) { e = window.event; }
	var x = e.pageX - cumulativeOffset[0];
	var y = e.pageY - cumulativeOffset[1];
	if (selectedSound && 20 < y && y <= 30) {
		var clickTime = minTime + timeRange * x / width;
		if (ordered(selectedSound.start, clickTime, selectedSound.end)) {
			startX = x;
			document.body.onmouseup = timelineUp; // temporary
			$("timeClickTarget").onclick = null;
			redraw();
		}
	}
}
function timelineUp(e) {
	if (window.event) { e = window.event; }
	var x = e.pageX - cumulativeOffset[0];
	var y = e.pageY - cumulativeOffset[1];
	var startTime = minTime + timeRange * startX / width;
	var len = 0;
	if (startX != x) {
		var tmpEndTime = minTime + timeRange * x / width;
		var endTime = Math.min(Math.max(startTime, tmpEndTime), selectedSound.end);
		startTime = Math.max(Math.min(startTime, tmpEndTime), selectedSound.start);
		var len = endTime - startTime;
	}
	//GLog.write(new String(new Date(startTime)) + " len: " + len);
	document.body.onmouseup = null;
	$("timeClickTarget").onclick = timelineClick;
	var input = buildElement(["input", {
		value: newTagPrompt,
		onblur: synchBlur,
		startTime: startTime,
		len: len
	}]);
	input.style.left = (startX - width/2) + "px";
	$("synchForm").appendChild(input);
	input.activate();
	startX = null;
}
function synchBlur() {
	var input = this;
	var data = input.value;
	var start = Math.round(input.startTime);
	var len = Math.round(input.len);
	if (data != "" && data != newTagPrompt) {
		addAnnotation(selectedSound, 0, start, len, data);
	}
	$("synchForm").removeChild(input);
	redraw();
}
function abortSynnotation() {
	var form = $("synchForm");
	while (form.hasChildNodes()) { form.removeChild(form.firstChild); }
}

var lastClick = 0; // function-static
function timelineClick(e) {
	if (window.event) { e = window.event; }
	var x = e.pageX - cumulativeOffset[0];
	var y = e.pageY - cumulativeOffset[1];
	var now = time();
	if (lastClick + 500 < now) {
		lastClick = now;
		timelineSingleClick(x, y);
	}
	else {
		lastClick = 0;
		timelineDoubleClick(x, y);
	}
}
function timelineSingleClick(x, y) {
	var clickTime = minTime + timeRange * x / width;
	if (15 < y && y <= 35) { // weird
		if (selectedSound && selectedSound.start < clickTime && // already selected
		    clickTime < selectedSound.end) {
			return;
		}
		var clickSounds = [];
		var closestSound = null;
		for (var i in mediaInstances) {
			var sound = mediaInstances[i];
			if (!sound) { continue; } // jic
			if (ordered(sound.start, clickTime, sound.end)) { clickSounds.push(sound); }
			else {
				var distToClick = Math.max(sound.start - clickTime,
				                           clickTime - sound.end);
				if (!closestSound || distToClick < closestSound.distToClick) {
					if (closestSound) { closestSound.distToClick = undefined; }
					closestSound = sound;
					sound.distToClick = distToClick;
				}
			}
		}
		if (closestSound) { closestSound.distToClick = undefined; }
		if (clickSounds.length > 0) {
			showSounds(clickSounds);
		}
		else if (closestSound && // we clicked a sloppy boundary
		         bitMask[Math.floor(nBuckets * x / width) - leadingZeroes] == "1") {
			showSounds([closestSound]);
		}
		else if (selectedSound) {
			selectedSound.deselect();
		}
	}
	else { // outside: just move the timePos
		timePos = clickTime;
		redraw();
	}
}
function timelineDoubleClick(x, y) { // single-click may need to be undone
	if (20 < y && y <= 30) { return; }
	newDate = new Date(minTime + timeRange * x / width);
	if (zoom < timeScales.length - 1) {
		date = newDate;
		zoom++;
		updateTimeline();
		timePos = date.getTime();
		rangeChanged();
		reloadTimenodes();
	}
	else {
		panTimeline();
	}
}

// html/dom interface

function panTimeline(dir) {
	if (typeof(dir) == "number") {
		newDate = new Date(date.getTime() + dir * timeRange / 4);
	}
	var scale = timeScales[zoom];
	if (newDate.getTime() < date.getTime()) {
		var startTime = timenodes[0].date.getTime();
		var newMin = newDate.getTime() - 0.5 * timeRange;
		var alive = (startTime < newMin ? 0 : 1);
		for (var d = scale.floor(new Date(startTime - 0.5*scale.step));
		     newMin < d.getTime() || (alive--);
		     d = scale.floor(new Date(d.getTime() - 0.5*scale.step)) ) {
			timenodes.unshift(createTimenode(d, scale));
		}
	}
	else {
		var startTime = timenodes[timenodes.length - 1].date.getTime();
		var newMax = newDate.getTime() + 0.5 * timeRange;
		var alive = (newMax < startTime ? 0 : 1);
		for (var d = scale.floor(new Date(startTime + scale.step));
		     d.getTime() < newMax || (alive--);
		     d = scale.floor(new Date(d.getTime() + scale.step)) ) {
			timenodes.push(createTimenode(d, scale));
		}
	}
	animatePan.prevCall = animatePan.startTime = time();
	animatePan.endTime = animatePan.startTime +
		4 * (width * Math.abs(newDate.getTime() - date.getTime()) / timeRange);
	animatePan.interval = setInterval(animatePan, 50);
}


function chZoom(diff) {
	var newZoom = Math.min(Math.max(zoom + diff, 0), timeScales.length - 1);
	if (zoom != newZoom) {
		var wasTimePosIn = (minTime < timePos && timePos < maxTime);
		if (wasTimePosIn) { var timePosFrac = (timePos - minTime) / timeRange; }
		zoom = newZoom;
		updateTimeline();
		if (wasTimePosIn && (timePos < minTime || maxTime < timePos)) {
			date = new Date(timePos - timePosFrac * timeRange + timeRange / 2);
			updateTimeline();
		}
		rangeChanged();
		reloadTimenodes();
	}
}
function zoomToFit(sound) {
	date = new Date(sound.start + sound.len / 2);
	var logScale = 1 / Math.log(zoomScale);
	zoom = Math.min(Math.floor(Math.log(sound.len / baseTimeRange) * logScale),
	                timeScales.length - 1);
	updateTimeline();
	rangeChanged();
	reloadTimenodes();
}

// helper

function animatePan() {
	var now = time();
	var dx = (newDate.getTime() < date.getTime() ? -1 : 1) *
		(Math.min(now, animatePan.endTime) - animatePan.prevCall) / 4;
	for (var i = 0; i < timenodes.length; i++) {
		setX(timenodes[i], timenodes[i].xpos - dx);
	}
	if (now < animatePan.endTime) {
		animatePan.prevCall = now;
	}
	else {
		date = newDate;
		updateTimeline();
		while (timenodes[1].date.getTime() < minTime) { timenodes.shift(); }
		while (maxTime < timenodes[timenodes.length - 2].date.getTime()) {
			timenodes.pop();
		}
		clearInterval(animatePan.interval);
	}
	rangeChanged();
	//redraw();
}


// for playback

var startTimePos;
var playStarted = 0;

function playStart() {
	startTimePos = timePos;
	playAnimate.step = Math.round(0.9 * timeRange / width);
	playAnimate.interval = setInterval(playAnimate, playAnimate.step);
	playStarted = time();
	if (document.OwlPlayer) {
		document.OwlPlayer.play(selectedSound.id,
		        Math.round(timePos - selectedSound.start),
			Math.round(0) ); 
	}
}
function playAnimate() {
	timePos += playAnimate.step;
	redraw();
}
function playStop() {
	clearInterval(playAnimate.interval);
	timePos = startTimePos + (time() - playStarted);
	if (document.OwlPlayer) {
		document.OwlPlayer.stop(selectedSound.id);
	}
}
