/**
 * CP Application Class
 * takes care of all basic functionality of category pages.
 * 
 * Features : 
 * - tabbed content with optional ajax and optional preloader graphic
 * - flash header using SWFWrapper
 * - omniture tagging
 */

//if set to false, trace will no longer output messages to the console
CP_DEBUG = true;

(function($){

//Global functions
/**
 * trace
 * outputs a message to the console if the browser has one
 */
function trace(){
	if(!CP_DEBUG) return;
	var ar = arguments;
	//the arguments variable is an object in IE so we need to convert it to an array to see all the elements
	if(document.all){
		ar = [];
		for(var i=0,l=arguments.length; i<l ;i++) ar.push(arguments[i]);
	}
	if(typeof console == 'object') 	console.log(ar);
};
window.trace = trace;

/**
 * Fix for missing Array.indexOf method ( missing in IE<=7)
 */
if(!Array.indexOf){
Array.prototype.indexOf = function(obj){
	for(var i=0,l=this.length; i<l; i++) if(this[i]==obj) return i;
	return -1;
}
}

//Array Remove - By John Resig (MIT Licensed)
Array.remove = function(array, from, to) {
  var rest = array.slice((to || from) + 1 || array.length);
  array.length = from < 0 ? array.length + from : from;
  return array.push.apply(array, rest);
};

Array.removeByValue = function(array,v) {
	var i = array.indexOf(v);
	while(i > -1){
		array.splice(i, 1);
		i = array.indexOf(v);
	}
	return array;
}

//Application Class
var Application = {	
	
	version: "1.0",
	
	_config:null,
	
	_$content:null,
	_$header:null,
	_$mainmenu:null,
	
	_loading: false,
	_currentPage:null,
	_pageOptions:null,
	
	//stores the hash of the current page
	_hash:null,
	_assets:null,
	
	_anchorId:null,
	
	run: function(config) {
		//store the config as a class variable
		this._config = config;
		
		//lookup the selectors provided by the config
		this._$content = $(this._config.contentSelector);
		this._$header = $(this._config.headerSelector);
		this._$mainmenu = $(this._config.mainmenuSelector);
		
		//start listening to hash change events
		this._bindHashChange();
		//immediatly trigger a hashchange so the application will evaluate the current url
		$(window).trigger("hashchange");
	},
	
	assets: function(){
		if(!this._assets) {
			this._assets = $.extend(true, {}, AssetManager,EventDispatcher);
			this._assets._init();
		}
		return this._assets;
	},
	
	/**
	 * Starts listening to the hashchange window event.
	 * opens a new page according to the hash of the new url
	 */
	_bindHashChange: function(){
		var self = this;
		$(window).bind('hashchange',function(e){
			//get url fragment (hash) from BBQ
			var url = $.param.fragment();
			//remove trailing slash
			if(url.substr(url.length-1) == "/") url = url.substr(0,url.length-1);
			
			self._hash = url;
			self.openPage(self._hash);
		});
	},
	
	/**
	 * unbinds hashchange events
	 */
	_unbindHashChange: function(){
		$(window).unbind('hashchange');
	},
	
	/**
	 * Scrolls the page to they position provided as the first parameter.
	 * the second parameter time defines how long the animation should take (default 0 - instant)
	 */
	scrollTo: function(y,time){
		if(time==undefined) time = 0;
		$('html').animate({scrollTop:y}, time);
	},
	
	/**
	 * Will start loading a page (html,css and js)
	 */
	openPage: function(id){ 
		
		var self = this;
		//stop if application is currently loading or if the user is requesting a page that is already open.
		if(this._loading) return false;
		if(this._currentPage==id) return false;
		
		var pageId = id;
		
		var n = id.indexOf("-");
		if(n > -1){
			self._anchorId = id.substr(n+1,id.length);
			pageId = id.substr(0,n);
		}else{
			self._anchorId = null;
		}
		
		
		//activate associated mainmenu item
		this._$mainmenu.cp_menu("activateByAttr","href","#"+pageId);
		
		//get the sitemap object associated with this page id
		var mapObj = this._config.sitemap[pageId];
		//send user to notfound page if the sitemap id does not exist
		if(!mapObj){
			//trace("the page you requested is not available");
			//this.loadPageContent(basepath+"notfound.html",this._$content);
			
			//look for anchor
			return false;
		}
		
		if(this._currentPage==pageId){
			return false;
		}
		
		//instant scroll to top of page
		this.scrollTo(0);
		
		this.unbindAnchorLinks();
		
		//hide all tabs
	    $("div.cp_tab").hide();
	    
		//store page id
		this._currentPage = pageId;
		
		var basepath = this._config.assetsPath+"/"+this._config.contentPath+"/";
		
		
		
		if(mapObj["id"]){ // page is an element's id
			//show the element
			$("#" + mapObj["id"]).show();
			
			
			//goto anchor
			var p = $('a[name="'+self._anchorId+'"]');
		    if(p.length) self.scrollTo(p.offset().top);
		    //bind anchor links
			self.bindAnchorLinks();
			
			this.trigger("htmlLoaded",this._currentPage);
			this.trigger("pageLoaded",this._currentPage);
		    
		}else{ // page is an ajax request
			var options = {
				html:basepath+mapObj.html,
				target:this._$content
			}
			
			if(mapObj.css) options.css = basepath+mapObj.css;
			if(mapObj.js) options.js = basepath+mapObj.js;
			
			this.loadPageContent(options);
		}
		
		//push the new page to the BBQ history
		//$.bbq.pushState({ url: id });
		
		this.setHeader(pageId);
		
		//send omniture statistics for the current tab
		if(this._config.useOmniture) this.sendStatistics(pageId);
	},
	
	/**
	 * loadPageContent
	 * loads html content and the css and js files if the sitemap object has one specified.
	 * 
	 * this function dispatches 2 events
	 * htmlLoaded - dispatched when the html is done loading (Cufon should be applied already at this point)
	 * pageLoaded - dispatched when js and css files done loading.
	 */
	loadPageContent: function(options){
		this.unloadPageContent();
		var self = this;
		this._loading = true;
		//store options of this page so unloading can be done later
		this._pageOptions = options;
		$.post(options.html,null,function(data){
			options.target.html(data);
			options.target.show();
			
			//load css and js
			if(options.css) self.assets().queCss(options.css);
			if(options.js) self.assets().queJs(options.js);
			
			self.assets().bind("assetsloaded",function(){
				
				//bug calling through a function does not work
				//self.gotoPageAnchor(self._anchorId);
				var p = $('a[name="'+self._anchorId+'"]');
			    if(p.length) self.scrollTo(p.offset().top);
			    
			    self.bindAnchorLinks();
			    
			    self._loading = false;
				self.trigger("pageLoaded",self._currentPage);
			},true);
			
			self.trigger("htmlLoaded",self._currentPage);
			self.assets().load();
			
		});
		
	},
	
	bindAnchorLinks: function(){
		var self = this;
		$("a[href*=#]").filter("a[href*=-]").click(function(){
			var link = $(this).attr("href");
			var anchorId;
			var n = link.indexOf("-");
			if(n > -1){
				anchorId = link.substr(n+1,link.length);
				var p = $('a[name="'+anchorId+'"]');
			    if(p.length) self.scrollTo(p.offset().top);
			}
		});
	},
	
	unbindAnchorLinks: function(){
		$("a[href*=#]").filter("a[href*=-]").unbind("click");
	},
	
	gotoPageAnchor: function(id){
		var p = $('a[name="'+id+'"]');
	    if(p.length) self.scrollTo(p.offset().top);
	},
	
	/**
	 * unloads the associated content of the currently loaded page (css and js)
	 */
	unloadPageContent: function(){
		if(!this._pageOptions) return;
		var o = this._pageOptions;
		if(o.css) this.assets().removeCSS(o.css);
		
		//js cannot be unloaded (browser specific issue, cannot be fixed)
		if(o.js) this.assets().removeJS(o.js);
	},
	
	/**
	 * loadContent
	 * will load an external html from the url provided in the first parameter
	 * into the element provided in the second parameter (as a jquery object)
	 */
	loadContent: function(file,target){
		var self = this;
		this._loading = true;
		$.post(file,null,function(data){
			target.html(data);
			target.show();
			self._loading = false;
		});
	},
	
	/**
	 * Assigns a background to the header and loads in the associated flash animation
	 */
	setHeader: function(id){
		//stop if no header is to be used
		if(!this._config.useHeader){ 
			this._$header.hide();
			return false;
		}
		//stop if no header element was found
		if(!this._$header.length) return false;
		//stop if no animations have been defined
		if(!this._config.animations) return false;
		
		//get header config
		var headerObj = this._config.animations[id];
		if(!headerObj) {
			trace("no animation found");
			return;
		}
		
		//set header image if the animations has a bg property specified
		if(headerObj.bg){
			var basepath = this._config.assetsPath+"/"+this._config.animationsPath+"/";	
			this._$header.css("background", "url("+basepath+headerObj.bg+")");
		}
		
		//remove previous header animation
		this.removeSWFObject(this._config.headerId);
		//load SWFWrapper for header animation
		this.loadSWFWrapperFlash(id,this._config.flashheaderId);
	},
	
	/**
	 * Load a flash animation created with SWFWrapper
	 */
	loadSWFWrapperFlash: function(id,targetId,width,height,flashversion){
		if(!width) width = 960;
		if(!height) height = 275;
		if(!flashversion) flashversion = "9";
		
		if(!this._config.animations) return false;
		
		var headerObj = this._config.animations[id];
		if(!headerObj) {
			trace("no animation found");
			return;
		}
		
		var basepath = this._config.assetsPath+"/"+this._config.animationsPath+"/";
		
		if(headerObj.playbutton == undefined) headerObj.playbutton = "true";
		if(headerObj.replayButton == undefined) headerObj.replayButton = "true";
		if(headerObj.autoPlay == undefined) headerObj.autoPlay = "true";
		
		//create flashheader with SWFWrapper
		var flashvars = {
			swf:basepath+headerObj.swf,
			wrapperConfig:this._config.assetsPath+"/"+this._config.SWFWrapperConfig,
			playButton:headerObj.playbutton,
			replayButton:headerObj.replayButton,
			autoPlay:headerObj.autoPlay
		};
		if(headerObj.config) flashvars["swfConfig"] = basepath+headerObj.config;
		
		var params = {
			menu: "false",
			seamlesstabbing: "false",
			wmode:"transparent",
			allowFullScreen:"true",
			allowScriptAccess :"always",
			bgColor:"0x000000"
		};		
		var attributes = {
			id:targetId
		};
		
		//normal SWFObject
		swfobject.embedSWF(this._config.assetsPath+"/"+this._config.SWFWrapperPath, targetId, width, height, flashversion, null, flashvars, params, attributes);		
		
		//jQuery SWFObject plugin
		/*
		$('#'+targetId).flash({
			swf: this._config.assetsPath+"/"+this._config.SWFWrapperPath,
			height: height,
			width: width,
			hasVersion: flashversion,
			expressInstaller: this._config.assetsPath+"/flash/expressInstall.swf",
			encodeParams: false,
			wmode:"transparent",
			allowFullScreen:true,
			allowScriptAccess :"always",
			menu: "false",
			seamlesstabbing: "false",
			bgColor:"0x000000",
			flashvars: flashvars,
			hasVersionFail: function (options) {
				console.dir(options); // look at all the useful goodies
				return false; // returning false means the expressInstaller document will not be used
			}
		});*/
		
	},
	
	/**
	 * remove a flash object created with SWFObject.
	 * this will recreate a div with the same id as the flash so it can be recreated.
	 */
	removeSWFObject: function(id){
		var flashMovie = this.getFlashMovieObject(id);
		if(!flashMovie) return;
		var p = flashMovie.parentNode;
		if(!p) return
		swfobject.removeSWF(id);
		var d = document.createElement("div");
		d.id = id;
		p.appendChild(d);
	},
	
	/**
	 * cross browser way to get a flash object.
	 * (should use jquery)
	 */
	getFlashMovieObject: function(id){
	  if (window.document[id]){
		  return window.document[id];
	  }
	  if (document.all){
		  return document.getElementById(id);
	  }else {
		if (document.embeds && document.embeds[id])  return document.embeds[id];
	  } 	
	},
	
	/**
	 * send an omniture tag for this catgory page. 
	 * the first parameter is the name of the tag.
	 * other parts of the omniture tag will be filled in automaticly according to the meta tags in the html head
	 */
	sendStatistics : function(value){
		if (typeof s != 'object') return;
		
		value = value.replace(/_/gi,"[]");
		value = value.replace(/\\/gi,":");
		value = value.replace(/\//gi,":");
		value = value.replace(/\[\]/gi,"_");
		value = "_"+value.substr(1,value.length);
		
	    if (!this.statsSent) {
		    var PRODUCTSUBCATEGORY = this.getMetrics('PRODUCTSUBCATEGORY');
		    var DIVISION = this.getMetrics('DIVISION');
		    var SECTION = this.getMetrics('SECTION');
		    var CATALOGTYPE = this.getMetrics('CATALOGTYPE');
		    var PRODUCTGROUP = this.getMetrics('PRODUCTGROUP');
		    var PRODUCTCATEGORY = this.getMetrics('PRODUCTCATEGORY');
		    
	        if (PRODUCTSUBCATEGORY != "") {
	            var TABSECTION = PRODUCTSUBCATEGORY + value;
	            s.pageName = DIVISION + ":" + SECTION + ":" + TABSECTION + ":" + CATALOGTYPE;
	        } else if (PRODUCTCATEGORY != "") {
	            var TABSECTION = PRODUCTCATEGORY + value;
	            s.pageName = DIVISION + ":" + SECTION + ":" + TABSECTION + ":" + CATALOGTYPE;
	        }
	        
	        s.t();
	    }
	    this.statsSent = false;
	},
	
	/**
	 * get an omniture meta tag from the head
	 */
	getMetrics : function(metric) {
		var content = $("meta[name='PHILIPS.METRICS."+metric+"']").attr("content");
		if (typeof content == 'undefined' || content == null){
			return '';
		}else{
			return content;
		}
	}
}

/**
 * Basic eventdispacher class that will allow the extended class to dispatch events.
 * other scripts can bind and listen to these events to react to certain actions.
 */
var EventDispatcher = {
		
	_eventListeners: [],
    
	bind: function(event,func,once)  {
    	if(once==undefined) once = false;
    	
    	var targetListener;
    	for(var i=0,l=this._eventListeners.length; i<l ;i++){
    		var obj = this._eventListeners[i];
    		if(obj.event==event){
    			targetListener = obj;
    		}
    	}
    	
    	if(targetListener){
    		targetListener.funcs.push({func:func,once:once})
    	}else{
    		this._eventListeners.push({funcs:[{func:func,once:once}] ,event:event});
    	}
    },
    
    unbind: function(event)  {
    	if(!this._eventListeners) return;
    	for(var i=0,l= this._eventListeners.length; i<l ;i++){
    		if(this._eventListeners[i].event==event){
    			this._eventListeners.splice(i);
    			return;
    		}
    	}
    },
    
    isBound: function(event)  {
        for(var i=0,l=this._eventListeners.length; i<l ;i++){
        	var obj = this._eventListeners[i];
        	if(obj.event!=event) return true;     
        }
        return false;
    },
    
    trigger: function( evt, eventArgs )  {
    	var i=0;
        while(i<this._eventListeners.length){
        	var obj = this._eventListeners[i];
        	
        	if(obj.event!=evt) {
        		i++;
        		continue;
        	}        		
        	
    		var eventRemoved = false;
        	for(var i2=0,l2=obj.funcs.length; i2<l2 ;i2++){
        		if(!obj.funcs[i2]) continue;
        		obj.funcs[i2].func(eventArgs);
        		
        		if(obj.funcs[i2].once){
        			obj.funcs.splice(i2,1);
        			if(!obj.funcs.length) {
        				delete obj;
        				eventRemoved = true;
        			}
        		}
        	}
        	
        	if(eventRemoved){
        		this._eventListeners.splice(i,1);
			}else{
				i++;
			}
        }
    }
};

/**
 * The AssetManager takes care of loading unloading external files such as 
 * css and js files. 
 */
var AssetManager = {
	_js: null,
	_css: null,
	_js_queued: null,
	_css_queued: null,
	
	_func: null,
	
	_init: function() {
		this._js = [];
		this._css = [];
		this._js_queued = [];
		this._css_queued = [];
	},
	
	destroy: function() {
		this._js = null;
		this._css = null;
		this._js_queued = null;
		this._css_queued = null;
	},
	
	queCss: function(url) {
		if(this._css_queued.indexOf(url)>-1) return;
		if(this._css.indexOf(url)>-1) return;
		this._css_queued.push(url);
	},
	
	queJs: function(url) {
		if(this._js_queued.indexOf(url)>-1) return;
		if(this._js.indexOf(url)>-1) return;
		this._js_queued.push(url);
	},
	
	load: function(){
		if(this._css_queued.length){
			this._addCss(this._css_queued.shift());
			return;
		}
		if(this._js_queued.length){
			this._addJs(this._js_queued.shift());
			return;
		}
		
		this.trigger('assetsloaded');
	},
	
	_addCss: function(file){
	   if($.browser.msie && (jQuery.browser.version==7 || jQuery.browser.version<=8)) {
	      document.createStyleSheet(file);
	   } else {
   		$( document.createElement('link') ).attr({
   			href: file,
   			media: 'screen',
   			type: 'text/css',
   			rel: 'stylesheet'
   		}).appendTo('head');		
		}
		
		this._css.push(file);
		this.load();
	},
	
	_addJs: function(file){
		var self = this;
		$.getScript(file,function (data){
			self._js.push(file);
			self.load();
		});
	},
	
	removeCSS: function( url, media ) {
		if(!media) media = "screen";
		$("link").each(function(){
			if(this.href.indexOf(url)>-1){
				$(this).remove();
			}
		});
		this._css = Array.removeByValue(this._css,url);
		this._css_queued = Array.removeByValue(this._css_queued,url);
	},
	
	removeJS: function(url) {
		$("script").each(function(){
			if(this.src.indexOf(url)>-1){
				$(this).remove();				
			}
		});
		//this._js = Array.removeByValue(this._js,url);
		//this._js_queued = Array.removeByValue(this._js_queued,url);
	},
	
	/**
	 * removes all JS tags from the html, the javascript code stays in memory.
	 */
	removeAllJS: function(url) {
		$("script").remove();
		//this._js = Array.removeByValue(this._js,url);
		//this._js_queued = Array.removeByValue(this._js_queued,url);
	}
};

//if client is IE , Replace the normal jQuery getScript function with one that supports
//debugging and which references the script files as external resources rather than inline.
if(document.all){
	jQuery.extend({
		getScript: function(url, callback) {
		   var head = document.getElementsByTagName("head")[0] || document.documentElement;
		   var script = document.createElement("script");
		   //if ( s.scriptCharset ) script.charset = s.scriptCharset;
		   script.src = url;
		
		   // Handle Script loading
		   {
		      var done = false;
		
		      // Attach handlers for all browsers
		      script.onload = script.onreadystatechange = function(){
		         if ( !done && (!this.readyState ||
		               this.readyState == "loaded" || this.readyState == "complete") ) {
		            done = true;
		            if (callback)
		               callback();
		
		            // Handle memory leak in IE
		            script.onload = script.onreadystatechange = null;
		            
		            if ( head && script.parentNode ) {
						head.removeChild( script );
					}
		         }
		      };
		   }
		   
		   // Use insertBefore instead of appendChild  to circumvent an IE6 bug.
		   // This arises when a base node is used (#2709 and #4378).	
		   head.insertBefore( script, head.firstChild );
			
		  // head.appendChild(script);
		
		   // We handle everything using the script element injection
		   return undefined;
		}
	});
}

/**
 * extensions for jQuery selector filters
 */
$.extend(
	$.expr[":"], {
		
		/**
		 * checks if an element is set to display:block
		 */
		block: function(elem) {
			return $(elem).css("display") === "block";
		},
		
		/**
		 * checks if an element has data associated with it.
		 */
		hasData : function(elem) {
			return !$.isEmptyObject( $(elem).data() );
		},
		
		/**
		 * checks if an element is disabled
		 */
		disabled: function(elem){
			if ( $.browser.msie ) {
				if($(elem).attr("disabled")) return true;
			}else{
				
				if($(elem).attr("disabled")!=undefined) return true;
			}
			return false;
		}
	}
);

//extend application with eventdispatcher
var Application = $.extend(true, Application,EventDispatcher);
//expose the Application
window.CP = Application;

})(jQuery);
