/*
 *	Wimbomedia Playlist Class	
 *	Copyright 2007 - 2008 Petro Salema
 *	www.petrosalema.com
 *
 *	Done: Create a timer management function
 *	TODO: What on earth is going on with IE and the animateResizing function?!? Bugs out when you set the target to 0!!!
 *
 * --------------------------------------------- */


var Playlist = Class({


	TIMER_STEP: 75,
	INIT_ATTEMPTS: 20,	// No. of times to attempt initialization before diving up
	DIV_ID: 'playlist',	// ID of the playlist div
	PL_ID_PREFIX: 'track_',

	playlist_id: null,
	playlist_name: null,
	playlist_tracks: [],

	currently_playing: null,

	is_opened: true,
	init_attempts_count: 0,	// No. of attempts to intialize the playlist
	attention: null,	// The ID of the track which the Playlist menu buttons will effect
	drop_target: null,	// The ID of the track underwhich to drop our dragging object
	allow_drop: false,	// True if we are in a area where we are allowed to drop something
	pickup_offset: 0,	// From where within the picked up object did we grab it.
	preoccupied: false,	// True where we are in the process of animating something of "attention"
	poof: new Array(5),	// Array containing poof images
	poof_image: new Image(64,64), // Actual image which we will animate by changing source
	poof_count: 0,		// Used to keep track of where we are in the animation
	timers: [],		// An Array containing objects { fingerprint:fn, timer:setInterval }
	pl_height: 0,
	marker_height: 0,

	div: null,		// The playlist's actual div element
	offsets: null,		// Object containing offset- top, left of the playlist div
	menu: null,		// The div that holds all the menu buttons
	grip: null,		// A div within the "menu" div which we use to grap and dbclick playlist items
	marker: null,		// A div which acts as indicator of where our dragged element will be dropped off
	playhead: null,
	ghost: null,		// The image of the playlist item we are dragging
	ghost_offsets: null,	// Object containing offset- top and left coords of the menu_ghost's parent
	dragging: null,		// Where we store the obj we're dragging
	poof_div: null,
	body: null,


	init: function (args)
	{/* args is an Object containing preferences */

		if(args)
		{
			this.playlist_id = args.id;
			this.opened = args.opened || true;
			this.fixed = args.fixed || false;
		}

		// Attempt to hook onto a playlist div
		this.createTimer(this.hook, 100);
	},


	createTimer: function()
	{
		var args = Util.argsToArray(arguments);
		var call = args.shift();
		var interval = args.shift();
		var parent = this;

		var fn = function(){ call.apply(parent, args); }

		this.removeTimer(call);
		this.timers.push({ fingerprint: call, timer: setInterval(fn, interval) });
	},


	removeTimer: function(fingerprint)
	{
		var t = this.timers;

		for(var i in t)
		{
			if(t[i].fingerprint == fingerprint)
			{
				clearInterval(t[i].timer);
				t[i].timer = null;
				t.splice(i, 1);
			}
		}
	},


	hook: function()
	{/* Attempt to access Playlist's div element */

		if(this.init_attempts_count == this.init_attempts_max)
		{
			this.removeTimer(arguments.callee);
			return this.failed();
		}

		this.init_attempts_count++;

		if(this.div = document.getElementById(this.DIV_ID))
		{
			this.removeTimer(arguments.callee);
			this.build();
		}
	},


	failed: function()
	{/* Called by the "hook" function when init_attempts_max is reached */

		return false;
	},


	build: function()
	{/* Called by the "hook" function when successful */

		this.body = document.getElementsByTagName('body')[0];

		// Get the menu and grip  divs
		this.menu = document.getElementById('pl_menu');
		this.grip = document.getElementById('pl_menu_grip');

		// Create a marker div inside the playlist div
		this.marker = document.createElement('div');
		this.marker.setAttribute('id', 'pl_marker');
		this.div.insertBefore(this.marker, document.getElementById('pl_breaker'));

		// Create a playhead div but do not insert yet
		this.playhead = document.createElement('div');
		this.playhead.setAttribute('id', 'pl_playhead');
		this.playhead.innerHTML = "<img src=\"images/playhead.gif\" alt=\"\" />";

		// Create a ghost div outside the playlist div
		this.ghost = document.createElement('div');
		this.ghost.setAttribute('id', 'pl_ghost');
		this.div.parentNode.appendChild(this.ghost);

		var pls = Util.getElementsByClassName('pl', this.div);
		if(pls.length > 0)
		{
			var p = pls[0];
			this.pl_height = p.offsetHeight + parseInt(Util.getStyle(p, 'margin-top')) + parseInt(Util.getStyle(p, 'margin-bottom'));
		}
		else
		{
			this.pl_height = 0;
		}

		this.updateOffsets();
		this.attachEvents();

		// Poof clouds
		var poof_src = 'images/';
		for(var i=1, j=this.poof.length+1; i<j; i++)
		{
			var p = this.poof[i] = { src: poof_src + 'poof-' + i + '.png', image: new Image(64,64) }
			p.image.src = p.src;
		}

		var pd = this.poof_div = document.createElement('div');
		pd.setAttribute('id', 'poof');
		pd.appendChild(this.poof_image);
		this.body.insertBefore(pd, this.body.firstChild);
	},


	attachEvents: function(obj)
	{
		var objs = new Array();

		if(!obj) { objs = Util.getElementsByClassName('pl', this.div); } else { objs[0] = obj; }

		var count = objs.length;

		for(var i=0; i<count; i++)
		{
			var o = objs[i];
			var o_t = Util.getElementsByClassName('pl_title', o)[0];
			Util.addEvent(o,   'mouseover', this.pl_onMouseOver.bindWithEventTo(this));
			Util.addEvent(o_t, 'mouseover', this.pl_onMouseOver.bindWithEventTo(this)); // We need to attach the mouseover here as well for IE
		}

		Util.addEvent(this.grip, 'mousedown', this.menu_onMouseDown.bindWithEventTo(this));
		Util.addEvent(this.grip, 'mouseup',   this.menu_onMouseUp.bindWithEventTo(this));
		Util.addEvent(this.grip, 'dblclick',  this.menu_onDBClick.bindWithEventTo(this));

		window.onresize = this.updateOffsets.bindTo(this);
		window.onscroll = this.updateOffsets.bindTo(this);

		if(Wimbomedia.Handle)
		{
			Wimbomedia.Handle.addEvent('onMouseUp', this.updateOffsets.bindTo(this));
		}
	},


	updateOffsets: function()
	{
		this.ghost_offsets = Util.globalOffset(this.ghost.parentNode);
		this.offsets = Util.globalOffset(this.div);

		if(Util.isIE())
		{
			var sl = parseInt(document.body.scrollLeft);
			this.offsets.l -= sl;
			this.ghost_offsets.l += parseInt(Util.getStyle(this.ghost.parentNode, 'padding-left'));
		}
	},


	setMenuY: function(y)
	{
		this.menu.style.top = y + 'px';
	},


	pl_onMouseOver: function(e)
	{/* If we are dragging, then use this function to position and display drag marker
   	    otherwise we use this to focus the menu's attention on to a playlist item */

		if(this.preoccupied) return;

		var src = e.target || window.event.srcElement;
		while(src.className != 'pl') { src = src.parentNode; } // We need the the actual "pl" element
		var id = src.getAttribute('id');

		if(this.dragging && id != this.attention)
		{
			this.showMarker(src);
			this.drop_target = id;
			return;
		}

		this.attention = id;
		this.setMenuY(src.offsetTop);
		this.menu.style.display = 'block';
	},


	menu_onDBClick: function(e)
	{/* TODO: Make this Event load track info */

		var src = e.target || window.event.srcElement;
	},


	menu_onMouseDown: function()
	{
		Util.disableSelecting();

		document.onmousemove = this.menu_onMouseMove.bindWithEventTo(this);
		document.onmouseup = this.menu_onMouseUp.bindWithEventTo(this);
	},


	menu_onMouseUp: function(e)
	{
		// Util.enableSelecting(); Defer enableSelecting to the "release" function

		document.onmousemove = null;

		this.body.style.cursor = 'auto';

		if(this.dragging) { this.drop(e); }
	},


	menu_onMouseMove: function(e)
	{/* Only called during dragging */

		this.body.style.cursor = 'pointer';
		this.menu.style.display = 'none';

		var mouse = Util.getMouse(e);
		var x = mouse.x;
		var y = mouse.y;

		var grip = this.grip;
		var ghost = this.ghost;
		var marker =  this.marker;

		// If we haven't yet, let's pick up the object to drag
		this.dragging = this.dragging || this.pickup(this.attention, e);

		ghost.style.left = (x - this.ghost_offsets.l - this.pickup_offset) + 'px';
		ghost.style.top = (y - this.ghost_offsets.t) + 10 + 'px';
		ghost.style.display = 'block';

		// Only allow drop within the confines of the playlist area
		this.allow_drop = Util.hitTest(x, y, this.div.parentNode);

		if(this.allow_drop)
		{
			if(y < this.offsets.t)
			{
				this.showMarker();
				this.drop_target = null;
			}
			marker.style.background = '#08c';
			marker.innerHTML = "<div id='pl_marker_msg'>Drop track here</div>";
		}
		else
		{
			marker.style.background = '#d66';
			marker.innerHTML = "<div id='pl_marker_msg'>Remove this track</div>";
		}

		return false;
	},


	pickup: function(id, e)
	{/* cuts an Element and pastes it into the ghost div for dragging */

		var el = document.getElementById(id);
		var g = this.ghost;

		this.pickup_offset = Util.getMouse(e).x - Util.globalOffset(el).l;
		g.style.width = el.offsetWidth + 'px';

		this.showMarker(el);

		el = Util.removeNode(el);
		g.appendChild(el);

		return el;
	},


	drop: function(e)
	{
		// Disable switching of "attention" while animating the drop
		this.preoccupied = true;
		this.pickup_offset = 0;

		if(this.allow_drop)
		{
			var pos = Util.globalOffset(this.marker);
			this.createTimer(this.animateEasing, this.TIMER_STEP, this.ghost, pos.l, pos.t, this.release.bindTo(this));
		}
		else
		{
			this.createTimer(this.animateResizing, this.TIMER_STEP, this.marker, null, 0, this.hideMarker.bindTo(this));
			this.trash(e);
		}
	},


	release: function()
	{
		if(this.drop_target != this.attention)
		{
			var node = Util.removeNode(this.attention);

			if(this.drop_target == null)
			{
				Util.insertAfter(node, this.marker);
			}
			else
			{
				Util.insertAfter(node, document.getElementById(this.drop_target));
			}

			this.hideMarker();
		}

		this.preoccupied = false;
		this.drop_target = null;
		this.dragging = null;
		Util.enableSelecting();
	},


	trash: function(e)
	{
		Util.removeNode(this.attention);
		this.preoccupied = false;
		this.drop_target = null;
		this.dragging = null;

		var pos = Util.getMouse(e);
		var pd = this.poof_div;
		pd.style.left = (pos.x - (parseInt(Util.getStyle(pd, 'width')) / 2)) + 'px';
		pd.style.top =  (pos.y - (parseInt(Util.getStyle(pd, 'height')) / 2)) + 'px';
		pd.style.display = 'block';

		this.createTimer(this.animatePoof, this.TIMER_STEP);

		Util.enableSelecting();

		this.removeTrackFromList(this.attention);
	},


	showMarker: function(node)
	{
		this.marker = Util.removeNode(this.marker);
		this.marker.style.display = 'block';
		this.marker.style.height = this.marker_height + 'px';

		if(node)
		{
			Util.insertAfter(this.marker, node);
		}
		else
	 	{
			this.div.insertBefore(this.marker, this.div.firstChild);
		}
	},


	showPlayhead: function(node)
	{
		var playhead = document.getElementById('pl_playhead');
		if(playhead) { Util.removeNode(playhead); }
		node.appendChild(this.playhead);
	},


	hideMarker: function()
	{
		this.marker.style.display = 'none';
		this.marker = Util.removeNode(this.marker);
		this.div.insertBefore(this.marker, this.div.firstChild);

		if(this.is_opened)
		{
			var pl_count = Util.getElementsByClassName('pl', this.div).length;
			this.createTimer(this.animateResizing, this.TIMER_STEP, this.div, 'auto', pl_count * this.pl_height);
		}
	},


	animateEasing: function(el, target_x, target_y, callback)
	{/* Animates Element el to target_ positions x and y. Then fires callback */

		var offsets = Util.globalOffset(el);
		var offset_l = offsets.l;
		var offset_t = offsets.t;

		var step_x = Math.round((target_x - offset_l) / 1.5);
		var step_y = Math.round((target_y - offset_t) / 1.5);

		trace(step_x + " " + offset_l + " " + target_x + " " + "<br />", false);

		if(Math.abs(step_x) < 2)
		{
			el.style.left = target_x + 'px';
			el.style.top = target_y + 'px';
			this.removeTimer(arguments.callee);
			if(callback) { callback(); }
		}

		var x = offset_l + step_x;
		var y = offset_t + step_y;

		/* TODO: Unconscionably hacky! Figure out another way to do this...  */
		if(el == this.ghost)
		{
			x -= this.ghost_offsets.l;
			y -= this.ghost_offsets.t;
		}

		el.style.left = x + 'px';
		el.style.top = y + 'px';
	},


	animateResizing: function(el, target_w, target_h, callback)
	{
		var damp = 1.5;

		var offset_w = el.offsetWidth;
		var offset_h = el.offsetHeight;

		var tw = (target_w == null) ? offset_w : target_w;
		var th = (target_h == null) ? offset_h : target_h;

		var step_w = Math.round((tw - offset_w) / damp);
		var step_h = Math.round((th - offset_h) / damp);

		var measure = (target_w == null || target_w == 'auto') ? step_h : step_w;


		var w, h;

		if(Math.abs(measure) <= 2)
		{
			if(target_w == 'auto') { w = 'auto'; } else if(target_w != null) { w = tw + 'px'; }
			if(target_h == 'auto') { h = 'auto'; } else if(target_h != null) { h = th + 'px'; }

			this.removeTimer(arguments.callee);
			if(callback) { callback(); }
		}
		else
		{
			if(target_w == 'auto') { w = 'auto'; } else if(target_w != null) { w = (offset_w + step_w) + 'px'; }
			if(target_h == 'auto') { h = 'auto'; } else if(target_h != null) { h = (offset_h + step_h) + 'px'; }
		}

		if(target_w != null) el.style.width = w;
		if(target_h != null) el.style.height = h;
	},


	animatePoof: function()
	{
		this.poof_count++;
		this.poof_image.src = this.poof[this.poof_count].src;

		if(this.poof_count + 1 == this.poof.length)
		{
			this.removeTimer(arguments.callee);
			this.poof_count = 0;
			this.poof_div.style.display = 'none';
		}
	},


	toggleExpand: function(force_open)
	{
		if(this.is_opened && !force_open)
		{
			this.createTimer(this.animateResizing, this.TIMER_STEP, this.div, null, 1, (function(){this.div.className = 'hide'}).bindTo(this));
			this.is_opened = false;
		}
		else
		{
			var pl_count = Util.getElementsByClassName('pl', this.div).length;
			this.div.className = '';
			this.createTimer(this.animateResizing, this.TIMER_STEP, this.div, 'auto', pl_count * this.pl_height);
			this.is_opened = true;
		}

		document.getElementById('btn_toggle').innerHTML = this.is_opened ? '-' : '+';
	},


	addTrack: function(id)
	{
		if(this.playlist_tracks.indexOf(id) != -1) { return; }

		var search_track = document.getElementById('search_track_' + id);

		var track_name = search_track.getAttribute('title');

		/* Create a new playlist item  */
		var new_pl = document.createElement('div');
		new_pl.setAttribute('id', this.PL_ID_PREFIX + id);
		new_pl.className = 'pl';
		this.attachEvents(new_pl);
		new_pl.innerHTML = "<div class=\"pl_title\">" + track_name + "</div><div class=\"pl_rounder_end\"></div>";

		/* Attach it to the end of the list   */
		var items = Util.getElementsByClassName('pl', this.div);

		this.div.insertBefore(new_pl, document.getElementById('pl_breaker'));

		if(this.pl_height == 0)
		{
			this.pl_height = new_pl.offsetHeight + parseInt(Util.getStyle(new_pl, 'margin-top')) + parseInt(Util.getStyle(new_pl, 'margin-bottom'));
			
			this.marker_height = new_pl.offsetHeight;
		}

		if(this.is_opened) { this.toggleExpand(true); }

		this.playlist_tracks.push(id);
	},


	playTrack: function(track)
	{/* If the track is not on the playlist, this function will pass on the request to the addTrack function.
	    Repositions the playhead, sets currently_playing to a new id. */

		var id = track || this.attention.match(/_(\d+)$/)[1];

		if(this.playlist_tracks.indexOf(id) == -1) { this.addTrack(id); }

		var pl = document.getElementById(this.PL_ID_PREFIX + id);

		this.showPlayhead(pl);

		this.currently_playing = id;

		if(id == this.currently_playing) { return; }
		/* Otherwise lets tell the player to play this track TODO... */
	},


	favorTrack: function()
	{
		alert('Favor track: ' + this.attention);
	},


	clipTrack: function()
	{
		alert('Clip track: ' + this.attention);
	},


	trashEverything: function()
	{
		var yes = confirm("Are you sure you want to empty the entire playlist?");

		if(!yes) return;

		var items = Util.getElementsByClassName('pl', this.div);

		while(item = items.shift())
		{
			Util.removeNode(item);
		}

		this.playlist_tracks.splice(0, this.playlist_tracks.length);

		if(this.is_opened) { this.toggleExpand(true); }
	},


	removeTrackFromList: function(str)
	{
		var list = this.playlist_tracks;

		var track_id = /^\d+$/.test(str) ? str : str.match(/_(\d+)$/)[1];

		var index = list.indexOf(track_id);

		list.splice(index, 1);
	}


}); /* ---------- END OF Playlist Class ---------- */
