/**
 * Layout jQuery Tools
 *
 * Extend jQuery with some functions for controlling the layout unobtrusively via Javascript; for instance,
 * stretching the page to fill the viewport, stretching elements to fill their containers.
 *
 * @package pmweb
 * @subpackage layout
 * @since 01/10/2007
 * @copyright Profitmaster Systems Ltd., www.profitmaster.co.uk
 * @author Pete Hurst, <peterh@profitmaster.co.uk>
 * @version $Id: PMWebExtensions.js 6006 2011-11-28 15:51:25Z raff $ v1.0.0.1
 */

/*	
	TopZIndex plugin for jQuery
	Version: 1.1

	http://topzindex.googlecode.com/
	
	Copyright (c) 2009 Todd Northrop
	http://www.speednet.biz/
	
	September 23, 2009
	
	Calculates the highest CSS z-index value in the current document
	or specified set of elements.  Provides ability to push one or more
	elements to the top of the z-index.  Useful for dynamic HTML
	popup windows/panels.
	
	Based on original idea by Rick Strahl
	http://west-wind.com/weblog/posts/876332.aspx

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version, subject to the following conditions:
	
	The above copyright notice and this permission notice shall be
	included in all copies or substantial portions of the Software.
	
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
------------------------------------------------------*/

(function ($) {

$.topZIndex = function (selector) {
	/// <summary>
	/// 	Returns the highest (top-most) zIndex in the document
	/// 	(minimum value returned: 0).
	/// </summary>	
	/// <param name="selector" type="String" optional="true">
	/// 	(optional, default = "body *") jQuery selector specifying
	/// 	the elements to use for calculating the highest zIndex.
	/// </param>
	/// <returns type="Number">
	/// 	The minimum number returned is 0 (zero).
	/// </returns>
	
	return Math.max(0, Math.max.apply(null, $.map($(selector || "body *"), 
		function (v) {
			return parseInt($(v).css("z-index")) || null;
		}
	)));
};

$.fn.topZIndex = function (opt) {
	/// <summary>
	/// 	Increments the CSS z-index of each element in the matched set
	/// 	to a value larger than the highest current zIndex in the document.
	/// 	(i.e., brings all elements in the matched set to the top of the
	/// 	z-index order.)
	/// </summary>	
	/// <param name="opt" type="Object" optional="true">
	/// 	(optional) Options, with the following possible values:
	/// 	increment: (Number, default = 1) increment value added to the
	/// 		highest z-index number to bring an element to the top.
	/// 	selector: (String, default = "body *") jQuery selector specifying
	/// 		the elements to use for calculating the highest zIndex.
	/// </param>
	/// <returns type="jQuery" />
	
	// Do nothing if matched set is empty
	if (this.length === 0) {
		return this;
	}
	
	opt = $.extend({increment: 1, selector: "body *"}, opt);

	// Get the highest current z-index value
	var zmax = $.topZIndex(opt.selector), inc = opt.increment;

	// Increment the z-index of each element in the matched set to the next highest number
	return this.each(function () {
		$(this).css("z-index", zmax += inc);
	});
};

})(jQuery);

/**
 * Either sets min-height in compatible browser, or justplain height in IE6.
 */
jQuery.fn.minHeight = function(height) {
	// Use min-height if other than M$
	if (jQuery.browser.msie && (parseInt(jQuery.browser.version)<7)) {
		$(this).css("height",height);
	}
	else {
		$(this).css("min-height",height);
	}
};
 
/**
 * Cause one element to fill the height of a target (containing) element
 * An offset can be supplied to subtract a certain number of pixels from the new height
 */
jQuery.fn.fillHeight = function(target,offset) {
	// Default no extra offset
	if (offset==undefined) offset = 0;
	// Calculate and set the height
	
	var thisOffset = $(this).offset();
	if (thisOffset!=undefined) {
		var newHeight = $(target).height()
			- (thisOffset.top - $(target).offset().top + offset);
		$(this).minHeight(newHeight);
	}
};

/**
 * Make an element fill the page
 *
 * This looks at the viewport height, and stretches the target element if it isn't already maxed
 */
jQuery.fn.fillPage = function(offset) {
	// Default no extra offset
	if (offset==undefined) offset = 0;

	function fillFunc(target, offset) {
		var maxHeight = $(window).height();
		var newHeight = maxHeight - $(target).offset().top - offset;
		$(target).minHeight(newHeight);
	}

	// Immediately fill the height
	fillFunc(this,offset);
	// Attach the function to window resizing also
	var fillPageTarget = this;
	$(window).resize(function(){fillFunc(fillPageTarget,offset);});
};


jQuery.fn.extend({
   
	/*
	 * Add a watermark to an input box
	 * 
	 * Parameters set via input's attributes:
	 * 
	 * data-watermark="watermark text"
	 * data-watermarkcss="CSS class to add when watermark showing"
	 */
    watermark : function() {
	
        $(this).each(function(){
			var text = $(this).attr('data-watermark');
			var cssClass = $(this).attr('data-watermarkcss');
		
	        // Default CSS class
	        if (cssClass==undefined) cssClass='hasWatermark';
	        // Fill with text value if empty
	        if (!($(this).attr('value')) || $(this).attr('value')==text) {
	            $(this).not(cssClass).attr('value',text).addClass(cssClass);
	        }
	        // Attach focus event (hide text and remove class)
	        $(this).focus(function(evt){
	            // Only blank/remove if it still has the class
	            $(this).filter('.'+cssClass).removeClass(cssClass).attr('value','');
	        });
	        // Blue event (replace text if still empty, add class)
	        $(this).blur(function(evt){
	            // Restore watermark if still blank
	            if (!($(this).attr('value')) || $(this).attr('value')==text) {
	                $(this).attr('value',text).addClass(cssClass);
	            };
	        });
	        var save = this;
	        // Prevent watermark text being passed when button is clicked 
	        $("form").submit(function(evt){
	        	if ($(save).hasClass(cssClass) && $(save).attr('value')==text) {
	        		$(save).attr('value','');
	        	}
	        });
        });
    },

    /*
     * Cause a button click to be simulated when the Enter key is pressed
     * 
     * Parameters set via input's attributes:
     * 
     * data-enterkeyclicks="name of button to be clicked"
     */
    enterKeyClicks : function() {
	
    	$(this).each(function() {
	    	// Attach the key handler
	    	$(this).keypress(function(e){
	    		// Respond to enter key
	    		if (e.which==13) {
	    			// prevent propagation of the event to other handlers,
	    			// to other inputs that might be created in response to the
	    			// click we're about to trigger.
	    			e.stopImmediatePropagation();
	    			// blur input (as would happen if a button was really clicked)
	    			$(this).blur();
	    			// extract name of button to click
	    			buttonName = $(this).attr('data-enterkeyclicks');
	    			// trigger click on named button
	    			$("*[name=" + buttonName + "]", this.form).click();
	    			return false;
	    		}
	    	});
    	});
    },

    /*
     * Cause the containing form to be submitted when the Enter key is pressed
     */
    submitOnEnter : function() {
		
    	$(this).each(function() {
    		// Attach the key handler
    		$(this).keypress(function(e){
    			// Respond to enter key
    			if (e.which==13) {
    				var form = this.form;
    				if (form.onsubmit) {
    					if (form.onsubmit())
    						form.submit();
    				}
    				else {
    					form.submit();
    				}
    				return false;  // needed for IE
    			}
    		});
    	});
    },
    
    /**
     * Resize all text inputs (and textareas) below the target element consistently across browsers
     */
    sizeTextInputs : function() {		
		$("input[type=text], input:not([type]), input[type=password], textarea", this).each(function(){
			// find current width
			currentwidth = $(this).width();			
			// temporarily set width to "auto" to determine browser default width
			$(this).css('width', 'auto');
			defaultwidth = $(this).width();
			
			// set width if it's currently the browser default
			if (currentwidth == defaultwidth) {
				// get size/cols depending on type
				textarea = $(this).is('textarea');
				size = textarea ? $(this).attr('cols') : $(this).attr('size');
				
				// adjust size as best we can to match Firefox 3
				if (!isNaN(size) && size > 0) {
					$(this).css('width', ((size * .5) + 1.2) + 'em');
				} else {
					$(this).css('width', textarea ? '20em' : '11.2em');
				}
			} else {
				// width had already been changed, so put it back to that value
				$(this).css('width', currentwidth + 'px');
			}
		});
    },
	
	preventMultiClick : function() {
		$(this).each(function() {
			$(this).click(function(event) {
				var $input = $(this);
				if ($input.data('pm_preventMultiClick')) {
					return false;  // really do want to prevent everything else
				} else {
					$input.data('pm_preventMultiClick',true);
					return true;
				}
			});
		});
	}
});

/**
 * Make element(s) wide enough to contain all the sub-elements identified by
 * the given selector.  Width is set using CSS min-width property.
 */
jQuery.fn.extend({
	containWidth : function(selector) {
		// for each element to which the function is being applied
		$(this).each(function() {
			var widest = 0;
			// for each contained element identified by the selector
			$(this).find(selector).each(function() {
				width = $(this).outerWidth(true);
				if (width > widest) {
					widest = width;
				}
			});
			if (widest > 0) {
				$(this).css("min-width", "" + widest + "px");
			}
			
		});
	}
});

 
$.fn.restoreOpenyCloseys = function() {
	var i;
	
	for (i = 0; i < this.length; i++) {
		frame = this.eq(i);
		title = frame.children(".title").text();
		cookieName = "Frame_" + title;
		
		if (Get_Cookie(cookieName) == "closed") {
			frame.children(".inner").hide();
			frame.removeClass("openy").addClass("closey");
		} else {
			frame.removeClass("closey").addClass("openy");
		}
	}	
};

$.fn.toggleOpenyClosey = function() {
	frame = this.parent();
	inner = frame.children(".inner");
	title = this.text();
	cookieName = "Frame_" + title;
	
	if (frame.hasClass("closey")) {
		if (jQuery.browser.msie) {
			inner.show("fast");
		} else {
			inner.slideDown("fast");
		}
		frame.removeClass("closey").addClass("openy");
		Delete_Cookie(cookieName, "/", "");
	} else {
		inner.slideUp("fast");
		frame.removeClass("openy").addClass("closey");
		Set_Cookie(cookieName, "closed" , 1, "/", "", "");
	}
	
	// 22/01/09 pns
	// IE6 fix: hide and show any open openycloseys after this one
	// When IE6 opens or closes an openyclosey it also moves the content
	// of any following openycloseys.  We correct this by
	// closing and reopening them.
	// This fix was needed for Dowding's template.  The cause of the problem
	// is IE6-obscure, so might not be a problem with other templates.
	if (jQuery.browser.msie && (parseInt(jQuery.browser.version) < 7)) {
		frame.nextAll(".openy").children(".inner").hide();
		frame.nextAll(".openy").children(".inner").show("fast");
	}
	
	return false;
}

function Set_Cookie( name, value, expires, path, domain, secure ) 
{
	// set time, it's in milliseconds
	var today = new Date();
	today.setTime( today.getTime() );
	
	/*
	if the expires variable is set, make the correct 
	expires time, the current script below will set 
	it for x number of days, to make it for hours, 
	delete * 24, for minutes, delete * 60 * 24
	*/
	if ( expires ) {
		expires = expires * 1000 * 60 * 60 * 24;
	}
	var expires_date = new Date( today.getTime() + (expires) );
	
	document.cookie = name + "=" +escape( value ) +
	( ( expires ) ? ";expires=" + expires_date.toGMTString() : "" ) + 
	( ( path ) ? ";path=" + path : "" ) + 
	( ( domain ) ? ";domain=" + domain : "" ) +
	( ( secure ) ? ";secure" : "" );
}

function Get_Cookie( check_name ) {
	// first we'll split this cookie up into name/value pairs
	// note: document.cookie only returns name=value, not the other components
	var a_all_cookies = document.cookie.split( ';' );
	var a_temp_cookie = '';
	var cookie_name = '';
	var cookie_value = '';
	var b_cookie_found = false; // set boolean t/f default f
	
	for ( i = 0; i < a_all_cookies.length; i++ )
	{
		// now we'll split apart each name=value pair
		a_temp_cookie = a_all_cookies[i].split( '=' );
		
		
		// and trim left/right whitespace while we're at it
		cookie_name = a_temp_cookie[0].replace(/^\s+|\s+$/g, '');
	
		// if the extracted name matches passed check_name
		if ( cookie_name == check_name )
		{
			b_cookie_found = true;
			// we need to handle case where cookie has no value but exists (no = sign, that is):
			if ( a_temp_cookie.length > 1 )
			{
				cookie_value = unescape( a_temp_cookie[1].replace(/^\s+|\s+$/g, '') );
			}
			// note that in cases where cookie is initialized but no value, null is returned
			return cookie_value;
			break;
		}
		a_temp_cookie = null;
		cookie_name = '';
	}
	if ( !b_cookie_found )
	{
		return null;
	}
}

function Delete_Cookie( name, path, domain ) {
	if ( Get_Cookie( name ) ) {
		document.cookie = name + "=" +
		( ( path ) ? ";path=" + path : "") +
		( ( domain ) ? ";domain=" + domain : "" ) +
		";expires=Thu, 01-Jan-1970 00:00:01 GMT";
	}
}

var PMWeb = {

	// Pops up a traditional browser window
	PopupBrowserWindow: function (url, name, options) {
		if(name === undefined) name = "_blank";
		if(options == undefined ) options = "status = 1, height = 384, width = 640, resizable = 1, scrollbars=yes";
		var pop = window.open( url, name, options);
		return pop;
	},


	// If this is true, prevents SizeTextInputs being called which can be slow
	// for large DOMs.
	noSizeTextInputs : false,

	lastFocus : null,

	// If true, will show errors as an alert.
	popupErrors:false,


	PopupErrors : function() {
		var errors = '';
		$('.error.message ul li').each( function(idx, ele) {
			errors += $(this).text() +"\n\r\n\r";
		});
		if(errors !== '' ) alert(errors);
	},

	/**
	 * Bind an event handler to the document's pmReady event
	 * 
	 * By calling PMWeb.Ready(), all handlers bound in this way can be called.
	 * 
	 * Each handler should have two parameters: the event and a context.
	 * Calling PMWeb.Ready(context) allows whatever the handlers do to be
	 * applied in the given context.  AJAXFormUpdate.js uses this mechanism
	 * to reapply jQuery to new HTML returned from AJAX calls.
	 * 
	 * In general, use PMWeb.OnReady() rather than $(document).ready().
	 */
	OnReady : function(func) {
		$(document).bind('pmReady', func);
	},
	
	/**
	 * Trigger all the handlers for the pmReady event, passing the given context to each
	 */
	Ready : function(context) {
		$(document).triggerHandler('pmReady', context);
	},
	
	/**
	 * Standard function called on page load to implement some PMWeb UI
	 */
	OnLoad : function(context) {
		$("input", context).focus(function(){
			PMWeb.lastFocus = this;
		});

		// The pmAutoPostback class is used to cause a form to be POSTed when
		// a certain event occurs on an input.  In the case of select inputs,
		// the postback occurs when the value is changed.
		$('.pmAutoPostback', context).change(
			function() {
				this.form.submit();
			}
		);

		// Watermark anything with .pmWatermark class
		$('.pmWatermark', context).watermark();
		
		// Call enterKeyClicks() for anything with .pmEnterKeyClicks
		$('.pmEnterKeyClicks', context).enterKeyClicks();
		
		// Call submitOnEnter() for anything with .pmSubmitOnEnter
		$('.pmSubmitOnEnter', context).submitOnEnter();
		
		// Call autocomplete() for anything with .pmAutoComplete
		$('.pmAutoComplete', context).each(function() {
			var url = $(this).attr('data-autocomplete');
			$(this).autocomplete({'source': url});
		});
		
		// Prevent multiple clicks on buttons
		$('.pmPreventMultiClick', context).preventMultiClick();
		
		// Expand any pmFitGrids elements so they're wide enough for any data
		// grids they contain
		$('.pmFitGrids', context).containWidth('.dataGrid');
		
		// pmScrollPane class uses jScrollPane plugin to add scrollbar
		// NB This must be done before closing any containing pmOpenyClosey or the
		//    jScrollPane plugin will think all the dimensions are zero and mess up.
		$(".pmScrollPane", context).jScrollPane({showArrows:true, autoHeight:true});
				
		// The pmOpenyClosey class is used to allow the user to toggle a frame
		// open/closed and remember the state between sessions using Cookies
		$(".pmOpenyClosey .title", context).click(
			function() {
				$(this).toggleOpenyClosey();
			}
		);
		$(".pmOpenyClosey", context).restoreOpenyCloseys();

		// resize text inputs consistently across browsers
		if (PMWeb.noSizeTextInputs == false ) { $(context).sizeTextInputs();}
		
		// prevent default enter key submit action for inputs on designated forms
		$("form.pmNoDefaultSubmit input[type=text]", context).keypress(function(event){
			if (event.which == 13) return false;
			return true;
		});
		
		// finally, set focus on the first marked element
		$(".pmAutoFocus", context).first().each(function() {
			//cConsole.log(".pmAutoFocus id == '" + this.id + "', name == '" + this.name + "'")
			PMWeb.SetCurrentFocus(this);
		});
	},
	
	GetCurrentFocus : function() {
		return PMWeb.lastFocus;
	},

	SetCurrentFocus : function(elem) {
		if (elem) {
			//cConsole.log("SetCurrentFocus to id == '" + elem.id + "', name == '" + elem.name + "'");
			if ($.browser.msie) {
				try {
					// see http://dev.jquery.com/ticket/6374
					elem.focus();
					$(elem).select();
					PMWeb.lastFocus = elem;
				} catch(e){
					//cConsole.log("caught exception in SetCurrentFocus");
				}
			} else {
				$(elem).focus().select();
				PMWeb.lastFocus = elem;
			}
		}
	},

		// START soundManager.
	// A basic sound manager.
	soundManager : function()
	{
		var	that = {}, soundChannels = {}, sc, audioSupport, basicAudioSupport, hasOgg, hasMp3, audioObj;
		var	pausedList = [];
		var	names = [];
		// Test for audio capabilities.
		try {
			audioObj = new Audio();
			//audioObj = document.createElement('audio');
			audioSupport = !!(audioObj.canPlayType);

			basicAudioSupport = !!(!audioSupport ? audioObj.play : false);
			if (audioSupport) {
			  // Currently canPlayType(type) returns: "no", "maybe" or "probably"
			  hasOgg = ("no" != audioObj.canPlayType("audio/ogg")) && (audioObj.canPlayType("audio/ogg") != "" );
			  hasMp3 = ("no" != audioObj.canPlayType("audio/mpeg")) && (audioObj.canPlayType("audio/mpeg") != "");

			 }
		} catch (e) {
		  audioSupport = false;
		  basicAudioSupport = false;

		}

		that.allocateChannelsToSound = function( name, url, numChannels, loop )
		{
			if ( !audioSupport ) { return that; }
			if(hasOgg) { url += '.ogg'; }
			else { url += '.mp3'; }
			soundChannels[name] = { channels:[], url:url, count: 0, maxCount:numChannels };
			names.push(name);
			for (var i = 0; i < numChannels; i++ )
			{
				sc = soundChannels[name].channels[i] = new Audio(url);
				sc.preload = 'auto';
				if ( loop ) { sc.loop = true; }
			}
			return that;
		};

		that.play = function(name, vol)
		{
			if(!audioSupport) { return; }
			if(soundChannels[name] !== undefined) {
				sc = soundChannels[name];
				if ( vol === undefined ) { vol = 1; }
				sc.channels[sc.count].volume = vol;
				sc.channels[sc.count].play();
				sc.count++;
				if (sc.count === sc.maxCount ) {
						sc.count = 0;
				}
			}
		};

		// Loop through all channels that are currently playing and pause them.
		// Make a note of which channels we have paused so we can unpause them later.
		that.pauseAll = function() {
			for ( var i=0;i<names.length;i++) {
				for (var c=0;c<soundChannels[names[i]].channels.length;c++) {
					var	sc = soundChannels[names[i]].channels[c];
					// If sound is not paused and sound has not already ended, then pause it.
					if ( !sc.paused && !sc.ended ) {
						sc.pause();
						pausedList.push(sc);
					}
				}
			}
		}

		that.unPauseAll = function() {
			for ( var i=0;i<pausedList.length;i++) {
				pausedList[i].play();
			}
			pausedList =[];
		}
		// I would expect that removing all references to a sound channel should stop it and remove it.
		// This doesn't happen. According to the HTML5 specs, media elements will only be garbage collected
		// after they have been paused or reached the end of their source.
		that.destroy = function() {
				that.pauseAll();
				for ( var i=0;i<names.length;i++) {
					for (var c=0;c<soundChannels[names[i]].channels.length;c++) {
							delete soundChannels[names[i]].channels[c];
					}
			}
		}

		return that;
	}()// END soundManager;
};

$(function() {
	// Call PMWeb standard setup each time the pmReady event is triggered
	PMWeb.OnReady( function(event, context){ PMWeb.OnLoad(context); });
	// Trigger the pmReady event for the document
	PMWeb.Ready(document);
});
