/**
*  @author William Malone (www.williammalone.com)
*/

WM.audio = function (GLOBAL, WM, filename, options) {
	
	"use strict";

	var that = WM.dispatcher(),
		audio = document.createElement("audio"),
		paused = true,
		loaded = false,
		loadStartedByUserInteraction = false,
		loadStarted = false,
		playInterval,
		playTimeout,
		loadInterval,
		prevLoadPerc,
		loading = false,
		sounds = [],
		playingSoundName,
		scrubberMoved,
		warningCount,
		scrubberStartTime,
		
		onLoadChange = function () {
		
			var amtLoaded, i;
			
			if (audio.buffered.length) {
				amtLoaded = audio.buffered.end(audio.buffered.length - 1);
			} else {
				amtLoaded = 0;
			}
			
			//WM.debug("Audio loaded: " + amtLoaded.toFixed(2) + " of " + audio.duration.toFixed(2) + " (" + ((amtLoaded / audio.duration) * 100).toFixed(2) + "%)");
			
			if (!loadStarted && amtLoaded > 0 && audio.duration > 0) {
				loadStarted = true;
				that.dispatchEvent("LOAD_STARTED");
			}
			
			if (!loaded) {
				if (GLOBAL.Math.abs(amtLoaded - audio.duration) < 0.001) {

					GLOBAL.clearInterval(loadInterval);
					prevLoadPerc = (amtLoaded / audio.duration) * 100;
					that.dispatchEvent("LOAD_PERCENTAGE_CHANGE");
			
					loaded = true;
					that.dispatchEvent("LOADED");
				}
			}
		},
		
		onPlayTimeout = function () {
			//WM.warning({
			//	name: "SoundTimeout",
			//	message: "Sound Timeout. Audio scrubber at position " + audio.currentTime.toFixed(3) + " but expected " + (sounds[playingSoundName].startTime + sounds[playingSoundName].duration).toFixed(3) + " Sound Details [Name: " + playingSoundName + ", Start Time: " + sounds[playingSoundName].startTime + " Duration: " + sounds[playingSoundName].duration + "]"
			//});
			//soundPlayComplete();
		},
		
		onStalled = function () {
			if (!loadStarted) {
				WM.debug("Media stalled before load started, so loading again.");
				audio.load();
			}
		},
		
		onPageHide = function () {
			that.pause();
		},
		
		onPageShow = function () {
			that.unpause();
		},
		
		soundPlayComplete = function () {

			if (sounds[playingSoundName] && sounds[playingSoundName].loop) {
			
				that.pause();
				
				// Move the scrubber so if the audio autoplays from coming out of focus it will start from the correct point
				audio.currentTime = sounds[playingSoundName].startTime;
				
				// Use setTimeout so it will not work in Safari Mobile if the window is not in focus
				GLOBAL.setTimeout(function () {
					that.play(playingSoundName);
				}, 0);
			} else {
				that.stop();
				that.dispatchEvent("COMPLETE");
			}
		};
		
	that.getAudioElement = function () {
		return audio;
	};
	
	that.mute = function () {
		audio.volume = 0;
	};
	
	that.unmute = function () {
		audio.volume = 1;
	};

	that.pause = function () {
		if (playInterval) {
			GLOBAL.clearInterval(playInterval);
		}
		if (playTimeout) {
			GLOBAL.clearTimeout(playTimeout);
		}
		audio.pause();
		paused = true;
	};
	
	that.unpause = function () {
		if (paused && playingSoundName) {
			that.play(playingSoundName, audio.currentTime);
		}
	};
	
	that.stop = function () {
		that.pause();
		playingSoundName = null;
	};
	
	that.getSound = function (soundName) {
	
		if (soundName === undefined && playingSoundName !== undefined) {
			return sounds[playingSoundName];
		}	
		if (soundName) {
			return sounds[soundName.toUpperCase()];
		}
	};
	
	that.setSound = function (spec) {

		sounds[spec.name.toUpperCase()] = {
			startTime: spec.startTime,
			duration: spec.duration,
			loop: spec.loop
		};
	};
	
	that.clearAllSounds = function (spec) {

		sounds = [];
	};
	
	that.loadByUserInteraction = function () {
		if (!loadStartedByUserInteraction) {
			loadStartedByUserInteraction = true;
			try {
			
				if (loadInterval) {
					GLOBAL.clearInterval(loadInterval);
				}
				
				loadInterval = GLOBAL.setInterval(function () {
					var amtLoaded, amtLoadedPerc;
					
					if (audio.buffered.length) {
						amtLoaded = audio.buffered.end(audio.buffered.length - 1);
					} else {
						amtLoaded = 0;
					}
					
					amtLoadedPerc = amtLoaded / audio.duration * 100;
					if (amtLoaded && amtLoadedPerc !== prevLoadPerc && !isNaN(amtLoadedPerc)) {
						prevLoadPerc = amtLoadedPerc;
						that.dispatchEvent("LOAD_PERCENTAGE_CHANGE");
						//WM.debug("Audio load Percentage: " + amtLoadedPerc.toFixed(2) + "%");
					}
				}, 10);
				
				audio.play();
				//audio.pause();

			} catch (e) {
				//WM.debug("loadByUserInteraction: Audio did not play: " + e.message);
			}
		}
	};
	
	that.getLoadPercentage = function () {
		return prevLoadPerc;
	};
	
	that.play = function (soundName, playStartTime) {
	
		var startTime;

		soundName = soundName.toUpperCase();

		warningCount = 0;

		// Clear timers
		if (playInterval) {
			GLOBAL.clearInterval(playInterval);
		}
		if (playTimeout) {
			GLOBAL.clearTimeout(playTimeout);
		}

		// Ensure sound exists
		if (sounds[soundName] === undefined) {
			
			WM.error({
				name: "SoundUnknown",
				message: "Sound not found. Playing sound '" + soundName + "' has failed."
			});
			return false;	
		}
		
		startTime = (playStartTime !== undefined) ? playStartTime : sounds[soundName].startTime;
		
		// If play start time is out of range for the sound, set play start time beginning of the sound
		if (startTime < sounds[soundName].startTime || startTime > sounds[soundName].startTime + sounds[soundName].duration) {
			WM.warning({
				name: "InvalidPlayStartTime",
				message: "Play start time out of range for the sound: '" + soundName + "'. Setting play start time to the sound start time."
			});
			
			startTime = sounds[soundName].startTime;
		}
		
		try {

			//WM.debug("Play sound: " + soundName + ". audio.currentTime: " + (audio.currentTime).toFixed(2) + ", startTime: " + (sounds[soundName].startTime).toFixed(2) + ", duration: "  + (sounds[soundName].duration).toFixed(2));
			
			scrubberMoved = false;

			// Pause before moving scrubber
			//audio.pause();

			// Save the current scrubber position
			scrubberStartTime = audio.currentTime;
			
			// Move the scrubber to the start time of the sound
			try {
				audio.currentTime = startTime;
			} catch (ex) {
				WM.error({
					name: "CurrentTimeSetFailure",
					message: "Setting the current time has failed: " + ex
				});
			}
			
			playingSoundName = soundName;
			paused = false;
			audio.play();
			
			if (GLOBAL.Math.abs(audio.currentTime - startTime) > 0.1) {
				WM.error({
					name: "ScrubberNotMoving",
					message: "Set the scrubber to " + startTime + " however it is " +  audio.currentTime + "). Playing sound '" + soundName + "' has failed."
				});
				
				//that.stop();

				//GLOBAL.setTimeout(function () {
				//	WM.debug("Sound failed but automatically trying again.");
				//	that.play(soundName);
				//}, 50);
				//return false;
			}
			
			playTimeout = GLOBAL.setTimeout(onPlayTimeout, sounds[playingSoundName].duration * 1000 + 500);
			
			playInterval = GLOBAL.setInterval(function () {
			
				var timeoutAmt;
				
				// Reset the timeout if the scrubber moves
				if (!scrubberMoved && audio.currentTime !== scrubberStartTime) {
				
					scrubberMoved = true;
				
					if (playTimeout) {
						GLOBAL.clearTimeout(playTimeout);
					}
					
					timeoutAmt = (sounds[playingSoundName].duration + sounds[playingSoundName].startTime - audio.currentTime);
					//WM.debug("timeoutAmt - sounds[playingSoundName].duration: " + (timeoutAmt - sounds[playingSoundName].duration));
					if (GLOBAL.Math.abs(timeoutAmt - sounds[playingSoundName].duration) > 0.1) {
						//WM.debug("Timeout amount greater than duration. audio.currentTime: " + (audio.currentTime).toFixed(2) + ", startTime: " + (sounds[playingSoundName].startTime).toFixed(2) + ", duration: "  + (sounds[playingSoundName].duration).toFixed(2) + ", timeoutAmt: " + timeoutAmt.toFixed(2));
						
						timeoutAmt = sounds[playingSoundName].duration;
					}
					
					playTimeout = GLOBAL.setTimeout(onPlayTimeout, timeoutAmt * 1000 + 500);
					
					//WM.debug("Scrubber moved from " + scrubberStartTime.toFixed(2) + " to " + audio.currentTime.toFixed(2) + ". Audio timeout set to: " + timeoutAmt.toFixed(2));
					
				
				} else if (audio.currentTime >= sounds[playingSoundName].startTime + sounds[playingSoundName].duration) {
				
					// If the current audio sprite is done playing then stop the audio playback
				
					//WM.debug("Audio current time is greater than the sound duration plus start time, so sound is complete.");
					soundPlayComplete();
					
				} else if (audio.currentTime < sounds[playingSoundName].startTime - 1) {
					if (!warningCount) {
						warningCount += 1;
						//WM.warning({
						//	name: "ScrubberOutOfRange",
						//	message: "The scrubber is before the sound start time (" + audio.currentTime + " < " +  sounds[playingSoundName].startTime + ")."
						//});
					}
				}
			}, 10);

		} catch (ex) {
			WM.error({
				name: "SoundPlayException",
				message: "Sound Playback has failed: " + ex
			});
			//WM.debug("Audio did not play: " + ex.message);
			return false;
		}
		
		return true;
	};
	
	that.destroy = function () {
	
		if (playInterval) {
			GLOBAL.clearInterval(playInterval);
		}
		if (playTimeout) {
			GLOBAL.clearTimeout(playTimeout);
		}
		if (loadInterval) {
			GLOBAL.clearInterval(loadInterval);
		}
		
		audio.removeEventListener("canplay", onLoadChange);
		audio.removeEventListener("canplaythrough", onLoadChange);
		audio.removeEventListener("loadeddata", onLoadChange);
		audio.removeEventListener("loadedmetadata", onLoadChange);
		audio.removeEventListener("progress", onLoadChange);
		audio.removeEventListener("ended", soundPlayComplete);
		audio.removeEventListener("stalled", onStalled);
		
		GLOBAL.removeEventListener("pagehide", onPageHide);
		GLOBAL.removeEventListener("pageshow", onPageShow);
		
		that.stop();
		that.clearAllSounds();
		audio = null;
	};
	
	that.isLoaded = function () {
		return loaded;
	};
	
	// Listen to media events
	audio.addEventListener("canplay", onLoadChange);
	audio.addEventListener("canplaythrough", onLoadChange);
	audio.addEventListener("loadeddata", onLoadChange);
	audio.addEventListener("loadedmetadata", onLoadChange);
	audio.addEventListener("progress", onLoadChange);
	audio.addEventListener("ended", soundPlayComplete);
	audio.addEventListener("stalled", onStalled);
	
	// Listen for page events (when clicking the home button on iOS)
	GLOBAL.addEventListener("pagehide", onPageHide);
	GLOBAL.addEventListener("pageshow", onPageShow);

	// Load the audio file
	audio.src = filename;
	
	return that;
};