/*
---
script: element.media.js

decription: Element.Media is a Mootools plugin that provides easier handling of media interaction via a JavaScript interface.

license: MIT-style license.

authors:
 - Robin Grass (http://kodlabbet.net/)
 
requires:
  core:1.2.3:
  - Array
  - Function
  - Number
  - String
  - Hash
  - Event
  - Class
  - Class.Extras
  - Element
  - Element.Event

provides: [InternalTimer, Element.Media]

...
*/


// Add media events to the native element events
var ElementMediaEvents = new Hash({
	'abort': 2,
	'canplay': 2,
	'canplaythrough': 2,
	'dataunavailable': 2,
	'durationchange': 2,
	'error': 2,
	'empty': 2,
	'loadstart': 2,
	'pause': 2,
	'play': 2,
	'progress': 2,
	'ratechange': 2,
	'timeupdate': 2,
	'volumechange': 2,
	'ended': 2
});

$extend(Element.NativeEvents, ElementMediaEvents);
$extend(Element.Events, ElementMediaEvents.each(function(name, type, hash) {
	hash.set(name, $H({
		base: name,
		condition: function(event) {
			var tag = event.target.tagName;
			if(tag == 'audio' || tag == 'video')
				return false;
			this.fireEvent(name, event);
		return false;	
		}
	}));
}));


/**
 *	InternalTimer
 *
 *	A class used for an internal timer.
 */
var InternalTimer = new Class({
	
	Implements: [Options, Events],
	
	options: {
		onTick: $empty
	},

	time: 0,
	
	initialize: function(time, options) {
		this.setOptions(options);
		this.set(time);
	},
	
	set: function(time) {
		this.time = (time) ? time.toInt() : 0;
	},
	
	get: function() {
		return this.time;
	},
	
	start: function() {
		var self = this;
		this.timer = (function(){
			self.time++;
			self.fireEvent('onTick');
		}).periodical(1000);
	},
	
	stop: function() {
		$clear(this.timer);
	},
	
	reset: function() {
		this.stop();
		this.set();
	}

});


/**
 *	Element.Media
 */
Element.Media = new Class({
	
	Implements: [Options, Events],
	
	options: {
		autoBuffer: false,
		autoPlay: false,
		controls: false,
		transcript: null,
		element: {
			create: false,
			type: null,
			properties: null,
			sources: null
		},
		onAbort: $empty,
		onError: $empty,
		onLoadStart: $empty,
		onLoading: $empty,
		onPause: $empty,
		onPlay: $empty,
		onPlayback: $empty,
		onStop: $empty,
		onEnded: $empty,
		onVolumeChange: $empty,
		onTranscriptKey: $empty
	},
	
	initialize: function(context, options) {
		this.setOptions(options);
		this.setContext(context);
		
		this.setup();
	},
	
	setContext: function(context) {
		var element = this.options.element;
		
		if($type(context) == 'element')
			this.media = context;
		
		if($type(context) == 'string')
			this.media = $(context);
		
		if(element.create) {
			var $media = new Element(element.type, element.properties);
			$A(element.sources).each(function(source) {
				new Element('source', {
					'src': source.url,
					'type': source.type
				}).inject($media);
			});
			$media.inject(this.media, 'top');
			this.media = $media;
		}
	},
	
	setup: function() {
		var self = this;

		$A(['autoBuffer', 'autoPlay', 'controls']).each(function(option) {
			if(self.options[option])
				self.media[option.toLowerCase()] = self.options[option];
		});
		
		// A pretty damn long if statement...
		if(this.options.trascript && $type(this.options.transcript) == 'object' || this.options.transcript && $type(this.options.transcript) == 'hash')
			this.transcript = ($type(this.options.transcript) == 'object') ? new Hash(this.options.transcript) : this.options.transcript;
		
		this.$timer = new InternalTimer(null, {
			onTick: function() {
				if(self.transcript)
					self.observeTranscriptChanges();
			}
		});
		
		// Seriously, there's gotta be a better way to do this...
		new Hash({
			'onAbort': { name: 'abort', pass: null },
			'onLoadStart': { name: 'loadstart', pass: 'event' },
			'onLoading': { name: 'progress', pass: 'event' },
			'onPlay': { name: 'play', pass: self.media },
			'onPause': { name: 'pause', pass: self.media },
			'onEnded': { name: 'ended', pass: self.media },
			'onTimeChange': { name: 'timeupdate', pass: self.media },
			'onVolumeChange': { name: 'volumechange', pass: self.media }
		}).each(function(fn, name) {
			self.media.addEvent(fn.name, function(event) {
				self.fireEvent(name, (fn.pass == 'event') ? event : fn.pass);
				
				if(fn.name == 'play') self.$timer.start();
				if(fn.name == 'pause') self.$timer.stop();
				if(fn.name == 'ended') {
					self.$timer.stop();
					self.$timer.set(0);
				}
				if(fn.name == 'timeupdate') {
					(function() {
						self.$timer.set(self.media.currentTime);
					}).run();
				}
			});
		});
	},
	
	observeTranscriptChanges: function() {
		var self = this;
		if(this.transcript) {
			this.transcript.each(function(data, key) {
				var transcriptKey = self.convertTimestampToSeconds(key).toInt();
				var timeKey = self.convertTimestampToSeconds(self.getTime()).toInt();				
				if(timeKey == transcriptKey) {
					if(data.callback)
						data.callback.run(self);
					self.fireEvent('onTranscriptKey', [(data.text) ? data.text : data, key]);
				}
			});
		}
	},
	
	convertTimestampToSeconds: function(timestamp) {
		var segments = timestamp.split(':');
		var hundreds = (segments.length > 3) ? segments.splice(3, 1) : '00';
		segments = segments.splice(0, 3);
		
		var seconds = segments.map(function(item, key) {
			var modifier = (key == 0) ? 3600 : (key == 1) ? 60 : (key == 2) ? 1 : 1;
			return item.toInt() * modifier;
		}).sum();
		
	return '{seconds}.{hundreds}'.substitute({ seconds: seconds.toString(), hundreds: hundreds }).toFloat();
	},
	
	convertSecondsToTimestamp: function(seconds) {
		var segments = seconds.toString().split('.');
		var seconds = segments[0].toInt();
		var hundreds = segments[1];
		var timestamp = $A([Math.floor(seconds / 3600), Math.floor(seconds / 60), (seconds % 60)]).map(function(item, key) {
			var item = item.toString();	
			if(item.length == 1)
				item = item.pad(2, '0', 'left').toString();
		return item;
		}).join(':');
	
	return timestamp + ':' + hundreds;
	},
	
	getDuration: function() {
		return this.convertSecondsToTimestamp(this.media.duration);
	},
	
	getTime: function() {
		return this.convertSecondsToTimestamp(this.media.currentTime);
	},
	
	setTime: function(timestamp) {
		var seconds = this.convertTimestampToSeconds(timestamp);
		var duration = this.convertTimestampToSeconds(this.getDuration());
		var time = (seconds <= duration) ? seconds : duration;
		this.media.currentTime = timestamp;
	},
	
	getVolume: function() {
		return ((this.media.volume).limit(0, 1) * 100);
	},
	
	setVolume: function(volume) {
		var volume = (volume.limit(0, 100) / 100);
		this.media.volume = volume;
	},
	
	mute: function() {
		this.lastVolume = this.getVolume();
		this.setVolume(0);
	},
	
	unmute: function() {
		this.setVolume(this.lastVolume);
		this.lastVolume = 0;
	},
	
	play: function() {
		this.media.play();
		this.$timer.start();
	},
	
	pause: function() {
		this.media.pause();
	},
	
	stop: function() {
		this.media.pause();
		this.media.currentTime = 0;
		this.fireEvent('onStop');
	}

});
