/*
 * @fileOverview
 * @name Notifier.js
 * @Author Hans Hillen hhillen@paciellogroup.com
 * 
 * Copyright (c) 2008 The Paciello Group, Inc
 * Licensed under GPL (http://www.opensource.org/licenses/gpl-license.php)
 */


/*global $, TPG, swfobject */
//tpg namespace
var TPG;
if (!TPG) {
	TPG = {};
}

/**
 * @class Manages different types of notifications 
 * @param metod {string}the chosen method for displaying notifications
 * @param params {object} object containing optional parameters appropriate for the specified notification method 
 * @constructor
 */
TPG.Notifier = function(method, params) { 
	this._method = this.verifyMethod(method);
	this._notifications = [];  	
	this._sndObjs = {}; 
	this._sndObjs._index = 0; // index used to create unique IDs for each sound object
	if (this._method == "flash" && !swfobject.hasFlashPlayerVersion("8")) {
		TPG.log("we don't have proper flash support, fallback to something else");
		this._method = this.verifyMethod("liveregion");
	}
	this._params = !params ? {} : params;
	this._setup();
	TPG.log("TPG Notifier Object Loaded");
};

//Class Properties
TPG.Notifier._listeners = []; // array of objects which should be notified when flash is reachable

//CONFIG VARIABLES (can be overridden through params object:
TPG.Notifier.swfId = "soundHandlerSwf"; // ID to use  for the Flash object
TPG.Notifier.swfPath = "flash/tpgnotifier.swf"; //path used to locate the flash movie

//Class Methods 

/**
 * Attempt to reference flash object. Called from within Flash code to ensure the flash is properly loaded;
 */
TPG.Notifier.locateSwf = function() {
	var swf = swfobject.getObjectById(TPG.Notifier.swfId);
	for (var i = 0 ; i < TPG.Notifier._listeners.length; i++) {
		TPG.Notifier._listeners[i].setSwf(swf);
	}
};

/**
 * used by flash movie to check if the container (i.e. the HTML DOM) has finished loading
 */
TPG.Notifier.pingContainer = function() {
	return true;
};

/**
 * add object which should be notified once the flash movie is ready.
 * @param obj {object} Object that must have a setSwf method 
 */
TPG.Notifier.addSwfListener = function(obj) {
	TPG.Notifier._listeners.push(obj);
};

//Instance Methods

/**
 * Prepares chosen notification method.
 * @params {object} optional parameters
 */
TPG.Notifier.prototype._setup = function() {
	var params = this._params;
	switch (this._method) {
		case "flash":
			this._basePath = params.basePath ? params.basePath : "";
			this._swfPath = params.swfPath ? params.swfPath : TPG.Notifier.swfPath;
			this._swfId = params.swfId ? params.swfId : TPG.Notifier.swfId;
			TPG.Notifier.addSwfListener(this);
			//add flash movie object to the dom using swfobject
			var fallbackDiv = document.createElement('div');
			fallbackDiv.id = this._swfId;
			document.body.appendChild(fallbackDiv);
			TPG.log($(this._swfId).id + "!!");
			var att = { data:this._swfPath, width:"1", height:"1" };
			var par = {'allowscriptaccess' : 'always'};
			swfobject.createSWF(att, par, this._swfId);
		break;
		case "alerts":
		break;
		case "liveregion":
			//gather live region specific parameters
			var liveRegionId = TPG.checkStringVar(params.id, "");
			var appendLiveRegionToId = TPG.checkStringVar(params.parentId, "");
			var appendLiveRegionTo = appendLiveRegionToId === "" ? document.body : $(appendLiveRegionToId);
			var liveRegionTag = TPG.checkStringVar(params.tag, "span");
			var hideLiveRegion = TPG.checkBoolVar(params.hide, false);
			var liveRegionClass = TPG.checkStringVar(params.className, "");
			var makeParentLive = TPG.checkBoolVar(params.makeParentLive, false);
			var suffix = TPG.checkStringVar(params.triggerSuffix, "");
			this._liveRegionCallFunction = params.callFunction ? params.callFunction : null;
			this.ignoreAlerts = TPG.checkBoolVar(params.ignoreAlerts, false);
			
			this.initLiveRegion(liveRegionId, appendLiveRegionTo, liveRegionTag, liveRegionClass, hideLiveRegion, makeParentLive, suffix);	
		break;
	}	
};

/**
 * Creates a new instance of the TPG.Notifier.SoundProfile class.
 * @param url {string} path to mp3 file 
 * @param ignoreBasePath {bool} used for occasions where a basepath exists but it should not be used (e.g. for absolute remote filepaths). 
 **/
TPG.Notifier.prototype.createSound = function(filePath, ignoreBasePath) {
	var basePath = ignoreBasePath ? "" : this._basePath;
	var soundObjId = "sound" + this._sndObjs._index++;
	this._sndObjs[soundObjId] = new TPG.Notifier.SoundProfile(this._swf, basePath, soundObjId, filePath); 
	return soundObjId;
};

/**
 * Store reference to flash movie as an object property
 * @param swfobj Reference to Flash Object
 */
TPG.Notifier.prototype.setSwf = function(swfObj) {
	this._swf = swfObj;
};

/**
 * Set or change the notification method (currently this can be either 'flash', 'alerts', or 'liveregion'
 * @param method {string} 
 **/
TPG.Notifier.prototype.verifyMethod = function(method) {
	return TPG.checkStringVar(method, "liveregion", ["flash", "alerts", "liveregion"]);
};

/**
 * Creates a new notification based on text. To add sound use setNotificationForSound(). 
 * @param text {string} Textual message associated with the alert.
 * @param id {string} Identier for easy referencing. If this is omitted a numeric index will have to be used for referencing.
**/
TPG.Notifier.prototype.addNotification = function(text, id) {
	this._notifications[id ? id : this._notifications.length] = {'text' : text};
	return id ? id : this._notifications.length - 1;
};

/**
 * Associates an existing sound object with an existing notification
 * @param notificationId {string} ID of notification to set the sound to
 * @param soundId {string} ID of sound object to set to the notification
 * @param loops {number} Number of times the sound should play. Defaults to 1/
 * @param volume {number} Volume level the sound should play with
 * @param pan {number} Number indicating the sound's spatial positioning 
 * @param offset {number} Offset in seconds
 */
TPG.Notifier.prototype.setNotificationSound = function(notificationId, soundId, loops, vol, pan, offset) {
	this._notifications[notificationId].sndId = soundId;
	this._notifications[notificationId].sndLoops = TPG.checkNumberVar(loops, 1, 0);
	this._notifications[notificationId].sndVolume = TPG.checkNumberVar(vol, 100, 0,  100);
	this._notifications[notificationId].sndPan = TPG.checkNumberVar(pan, 0, -100, 100);
	this._notifications[notificationId].sndOffset = TPG.checkNumberVar(offset, 0, 0);	
};

/**
 * Fires a notification according to the chosen method
 * @param id {string} the id of the notification to fire
 **/
TPG.Notifier.prototype.notify = function(id) {
	var n = this._notifications[id];
	switch(this._method) {
		case "flash":
			if (!this._swf) {
				//there's no flash to talk to, skip to other method
				this._method = "liveregion";
				TPG.log('Could not find Flash object to onteract with. Skipping to fallback method');
				this._setup();
				this.notify(id);
				return;
			}
			this._sndObjs[n.sndId].play(n.sndLoops, n.sndVolume, n.sndPan, n.sndOffset); //play the sound 
		break;
		case "alerts":
			alert(this._notifications[id].text);
		break;
		case "liveregion":
		default:
		if (!this.ignoreAlerts) {
			//either update a suffix element (requires a modal parent live region) to trigger an update, or update actual text. 
			this.updateLiveRegion(this._liveRegionSuffix !== "" ? this._liveRegionSuffix : this._notifications[id].text);
		}
		if (this._liveRegionCallFunction) {
			this._liveRegionCallFunction();
		}
		break;
	}
};

/**
 * creates and / or sets a live region element
 * @param id {string} ID of the live region. If this element does not exist it will be created.
 * @param parentNode Element the live region should be a child of
 * @param className {string} CSS class to give the live region
 * @param hide {bool} Wether or not to visually hide the liveregion element
 * @param makeParentLive {bool} Whether or not to make the element's parent live instead.
 * @param suffix {string} text to add to trigger a modal parent update 
 **/
TPG.Notifier.prototype.initLiveRegion = function(id, parentNode, tag, className, hide, makeParentLive, suffix) {
	if (!id) {
		return;
	}
	 if ($(id)) { //element already exists. Make it live
		this._liveRegion = $(id); 
		TPG.Aria.setProperty(this._liveRegion, 'live', 'polite');
		TPG.Aria.setProperty(this._liveRegion, 'atomic', 'true');
		TPG.Aria.setRolesAndStates(this._liveRegion, true, 1, true);	
	}
	else { //create from scratch
		if (!parentNode) {
			parentNode = document.body;
		} 
		
		var liveNode = document.createElement(tag);
		liveNode.id = id;
		liveNode.className = className;
		TPG.Aria.setProperty(makeParentLive ? parentNode : liveNode, 'live', 'assertive');
		if (makeParentLive) {
			this._liveRegionSuffix = suffix;
		}
		if (hide) {
			liveNode.setAttribute("style", "position: absolute; top: -10000px; left: -10000px; width: 1px; height: 1px; overflow: hidden");	
		}
		this._liveRegion = parentNode.appendChild(liveNode);
	}
};

/*
 * Changes the text value of a live element, triggering an update
 * @param  msg {string} 
 */
TPG.Notifier.prototype.updateLiveRegion = function(msg) {
	TPG.Dom.innerText(this._liveRegion, msg);
};

/**
 * @class Represents a Flash sound object. What makes a profile unique is it's filepath to an mp3 file. One profile can have one or more schemes, which determine how the sound should be played. This function is generally called through the TPG.Notifier.createSound method.
 * @param swf Reference to swf object 
 * @param basePath common directory path shared by all sound objects
 * @param identifier, 
 * @param filePath {string} Path to mp3 file, not including basePath
 * @constructor 
**/
TPG.Notifier.SoundProfile = function(swf, basePath, identifier, fileName) {
	this._basePath = basePath ? basePath : "";
	this._swf = swf; 
	/*The swf object is sent by the calling Notifier object. If it's undefined it can mean two things: 
	1. Flash is still loading in which case we should register our current object to be notified when the swf is ready. 
	2. The swf couldn't be found or loaded and the entire thing won't work. Attempts to interact with it (e.g. loading or playing a sound) yield a log an warning.
	*/
	if (swf === undefined || !swf.pingFlash()) {//Safari will sometimes enable the swf object while it's not really ready. Hence the pingFlash() check
		TPG.Notifier.addSwfListener(this); // will notify this object when flash movie is ready
	}
	this._identifier = identifier; // TODO: add check for valid identifier string (no spaces, etc.)
	this._presets = [];
	if (fileName === undefined || identifier === undefined) {
		TPG.log("Error: No filepath or identifier specified. Cannot create sound profile without those");
		return false;
	}
	this._fileName = fileName;
	if (this._swf) {// the flash is ready load the sound. Otherwise it will be loaded later by the Queue handler
		this.createSoundInFlash();
	}
};

/**
 * Will be called when Flash is ready
 * @param swfObject
 */
TPG.Notifier.SoundProfile.prototype.setSwf = function(swfObj) {
	this._swf = swfObj;
	//now that we now the flash movie is ready, we can load the sound for this profile 
	this.createSoundInFlash();
};

/**
 * Call the flash function responsible for creating a new sound object
**/
TPG.Notifier.SoundProfile.prototype.createSoundInFlash = function () {
	if (this._swf) {
		this._swf.addSound(this._identifier, this._fileName,  this._basePath);
	}
	else {
		TPG.log("warn", "Error: attempted to interact with flash but it isn't ready yet");
	}
};


/**
Add a new scheme to the current profile. 
**/
TPG.Notifier.SoundProfile.prototype.addScheme = function(loops, volume, pan, offset) {
	this._presets.push([loops, volume, pan, offset]);
	var returnIndex = this._presets.length - 1;
	return returnIndex;
};

/**
 * Add multiple schemes at once, by passing sets of parameters as objects. each object must have parameters specified in the following order:
 * 0: loops, 1: volume, 2: pan, 3: offset
**/
TPG.Notifier.SoundProfile.prototype.addSchemes = function(schemeObjects) {
	var params;
	var indices = [];
	for (var i = 0; i < schemeObjects.length; i++) {
		params = schemeObjects[i];
		indices.push(this.addScheme(params[0], params[1], params[2], params[3]));		
	}
	return indices;
};

/**
 * Change an already existing scheme
**/
TPG.Notifier.SoundProfile.prototype.updateScheme = function(index, loops, volume, pan, offset) {
	if (!index) {
		index = 0;
	}
	this._presets[index] = [loops, volume, pan, offset];
	TPG.log("Updated scheme at index %s (loops: %s, volume: %s, pan: %s, offset: %s) for profile %s", index, loops, volume, pan, offset, this._identifier);
	return index;
};

/**
 * Plays the actual sound according to the specified scheme index, which defaults to 0
**/
TPG.Notifier.SoundProfile.prototype.playScheme = function(index) {
	TPG.log("Attempting to play sound at index %s of profile %s", index, this._identifier);
	if (this._swf === undefined) {
		TPG.log("can't play sound, no flash movie to play with");
		return; 
	}
	if (!index) {
		index = 0;
	}	
	var params = this._presets[index];
	this.play(params[0], params[1], params[2], params[3]);
};
/*
 * Tells Flash to play the sound 
 */
TPG.Notifier.SoundProfile.prototype.play = function(loops, volume, pan, offset) {
	this._swf.playSound(this._identifier, offset, loops, volume, pan);
};

/**
Stop the sound at the specified schemeindex if it's currently playing
**/
TPG.Notifier.SoundProfile.prototype.stop = function(index) {
	if (!index) {
		index = 0;
	}	
	this._swf.stopSound(this._identifier);
	TPG.log("Stopped playing sound at index %s of profile %s", index, this._identifier);
};