// Create Global Namespace, Keeps functions from conflicting.
var AVE = window.AVE || {};

// Watch out for debugging code accidently left in produciton.
 if(!window.console){
    var console = {};
    console.log = Prototype.emptyFunction();
    console.debug = Prototype.emptyFunction();
    console.error = Prototype.emptyFunction();
    console.warn = Prototype.emptyFunction();
    console.info = Prototype.emptyFunction();
 }
 
 
 
/**
 * Delegation Object.
 * Allows you to delegate events to children of an object instead of attaching
 * events to every child. More efficient because less listeners are being created
 * and updates to the DOM will automatically receive the listeners
 *
 * Event.register(document, 'a.external', 'click', doFunction);
 * Event.register('myid', 'a.external', 'click hover', doFunction);
 * Event.unregister('document.body', 'a.external', 'click'); // hover event would remain
 *
 * Dependencies:
 *  prototype.js
 *
 */
 

 
(function() {
  var rules = [];

  // need reference to function. Used so we can stopObserving later
  var eventManagerWrapper = function(e) {
		eventManager(e,this);
  };

  var eventManager = function(event, delegator) {

	var element = event.element();
	var rule = rules[rules.pluck('element').indexOf(delegator)];

    do {
		for (var selector in rule['events'][event.type]) {
			if (matches(rule['events'][event.type][selector]._selector, element)) {
				for (var i=0, handlers=rule['events'][event.type][selector], l=handlers.length; i<l; ++i) {
					handlers[i].call(element, Object.extend(event, { _target: element }));		
				}
			}		
		}
		element = element.parentNode;

    } while (element && element.tagName)
  };

  var matches = function(selectors, element) {
    for (var i=0, l=selectors.length; i<l; ++i) {
        if($(element).match(selectors[i])) return true;
      //if (selectors[i].match(element)) return true;
    }
    return false;
  };

  Event.register = function(element, selector, eventNames, handler) {
  
	var rule = rules[rules.pluck('element').indexOf($(element))];
	var newRule = false;
	if(!rule) {
		rule = {};
		rule['element'] = $(element);
		rule['events']  = {};
		var newRule = true;
	}
	
	var _selector = [ ], expr = selector.strip();
    // instantiate Selector's
    Selector.split(selector).each(function(s) { _selector.push(new Selector(s)); });
	
    // observe event only once
    var eventNames = $w(eventNames);
    eventNames.each(function(eventName) {
        if (!rule['events'][eventName]) {
            rule['events'][eventName] = { };
            $(element).observe(eventName, eventManagerWrapper);
        }
		
		// store instantiated Selector for faster matching
	    // jhummel: selector is instantiated in prototype.js, search for DomQuery to find relevant section
		if (!rule['events'][eventName][expr]) {
             rule['events'][eventName][expr] = Object.extend([ ], { _selector: _selector });
        }

        // associate handler with expression
		// allows for multiple handler to be pushed to the same selector
        rule['events'][eventName][expr].push(handler);
		if(newRule) rules.push(rule);
    });

  };

  Event.unregister = function(element, selector, eventNames) {
	if(!arguments.length){
		rules.each(function(rule){
			for(evt in rule['events']) {
				$(rule['element']).stopObserving(evt, eventManagerWrapper);
			}
		});
	} else {
		var rule = rules[rules.pluck('element').indexOf($(element))];
		
		$w(eventNames).each(function(eventName){
			if (eventName) {
		      rule['events'][eventName][selector] = null;
		      delete rule['events'][eventName][selector];
		    } else {
		      for (var eventName in rule['events']) {
		        selector ? rule['events'][eventName][selector] : rule['events'][eventName] = null;
		        // jhummel: delete was throwing error on IE
		        Try.these(
		            function() { delete (rule['events'][eventName][selector]); },
		            function() { delete (rule['events'][eventName]); }
		        );
		      }
		    }
		});
	}
    
  };

  Event.observe(window, 'unload', function() { Event.unregister(); });
  Event.element = function(event) {
    event = Event.extend(event); var node = event._target || event.target;
    return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node);
   };
})();

 
 
AVE.Delegation = (function() {
    /* OLD METHOD OF DELEGATION NO LONGER USED */
	/**
	 * Private method to do the actual delegation of events to children
	 * called by the AVE.Delegation.delegate method. Looks at the furthest
	 * descendant and then backs it's way up the dom to look for a match to
	 * the selector.
	 *
	 * _doDelegation(e, 'div', 'mouseover', functionRef);
	 *
	 * $param  {Object}	e									event object passed from parent.
	 * $param  {String} selector							a selector that will match what child to look for
	 * $param  {String} eventName							name of event to listen on the selector for, without the 'on' prefix
	 * $param  {Function} handler							function to execute when an event is matched to a child
	 */
	function _doDelegation(e, selector, eventName, handler) {
		var el = e.element();
		//TODO: can this be shortened into one line?
		while(el.tagName && !el.match(selector))
			el = el.up();

		if(el.tagName && el.match(selector)) {
			handler(e, el);
		}
	};
	
	return {
		/**
		 * Privledged method to setup delegation using the _doDelegation() method
		 *
		 * AVE.Delegation.delegate('myElement','div.className', 'mouseover', functionRef);
		 *
		 * $param  {String || DOM Object} element			parent element of children listening for events
		 * $param  {String} selector						a selector that will match what child to look for
		 * $param  {String} eventNames						name of event to listen on the selector for, without the 'on' prefix
		 * $param  {Function} handler						function to execute when an event is matched to a child
		 */
		delegate: function(element, eventNames, rules) {
			element = $(element);
			rules = $H(rules);
			
			// Not completely sure of this. The browser will end up comparing several functions for every event.
			// Should we strive for only one of each event type on a given element?
            $w(eventNames).each(function(eventName) {
                rules.each(function(rule) {
                    element.observe(eventName, function(e) {
						var key = rule.key.replace(/_/g," ");
                        _doDelegation(e, key, eventName, rule.value);
                    });
                });
		    });
			return element;
		}
	};
})();

Element.addMethods({
  disable: function() { 
    for (var i = 0; i < arguments.length; i++) { 
        var element = $(arguments[i]); 
        if (element.tagName.toLowerCase()=='input') { 
            element.setAttribute('disabled', 'disabled'); 
        } 
    } 
  }, 
  //enable function. by kourge. 
  enable: function() { 
    for (var i = 0; i < arguments.length; i++) { 
        var element = $(arguments[i]); 
        if (element.tagName.toLowerCase()=='input') { 
            element.removeAttribute('disabled'); 
        } 
    } 
  },
  /**
   * Shortcut to AVE.Delegation.delegate() let's you use as a method of extended elements
   * $('myElement').delegate('div.className','mouseover', functionRef); // leave off the first parameter
   * e.g., this.table.delegate('td', 'mouseover', this._mouseover.bind(this));
   */
  delegate: AVE.Delegation.delegate,
  
  /* Redraw method (usually to fix IE rendering bugs */
  redraw: function(element){
    element = $(element);
    var n = document.createTextNode(' ');
    element.appendChild(n);
    (function(){n.parentNode.removeChild(n)}).defer();
    return element;
  },
  
  /* Override prototype because of IE8 issue setting
  /* position of hidden elements. Once bug is solved
  /* in prototype you can remove this code */
  clonePosition: function(element, source) {
    var options = Object.extend({
      setLeft:    true,
      setTop:     true,
      setWidth:   true,
      setHeight:  true,
      offsetTop:  0,
      offsetLeft: 0
    }, arguments[2] || { });

    // find page position of source
    source = $(source);
    var p = source.viewportOffset();

    // check if element is hidden and we're using IE
    var vis = true;
    if(Prototype.Browser.IE && !element.visible()) vis = false;
    element.show();
    // end of first ie hack
    
    // find coordinate system to use
    element = $(element);
    var delta = [0, 0];
    var parent = null;
    // delta [0,0] will do fine with position: fixed elements,
    // position:absolute needs offsetParent deltas
    if (Element.getStyle(element, 'position') == 'absolute') {
      parent = element.getOffsetParent();
      delta = parent.viewportOffset();
    }

    // correct by body offsets (fixes Safari)
    if (parent == document.body) {
      delta[0] -= document.body.offsetLeft;
      delta[1] -= document.body.offsetTop;
    }
    
    // hide the element again if needed
    if(!vis) element.hide();
    
    // set position
    if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
    if (options.setWidth)  element.style.width = source.offsetWidth + 'px';
    if (options.setHeight) element.style.height = source.offsetHeight + 'px';
    return element;
  }
});

/*
* Orginal: http://adomas.org/javascript-mouse-wheel/
* prototype extension by "Frank Monnerjahn" themonnie @gmail.com
*/
Object.extend(Event, {
    wheel:function (event){
        var delta = 0;
        if (!event) event = window.event;
        if (event.wheelDelta) {
            delta = event.wheelDelta/120;
            if (window.opera) delta = -delta;
        } else if (event.detail) { delta = -event.detail/3;     }
        return Math.round(delta); //Safari Round
    }
});
/*
* end of extension
*/

AVE.BehaviorOptions = {};

AVE.runBehavior = (function(){
	var defaults = {
		remove: {
			message: "Are you sure? This action cannot be undone."
		}
	};
	
	var behaviors = {
		dialog: function(e,el) {

			var dialog = AVE.Dialog.Uniq.createInstance;
			var options = {};
			
			if(el.readAttribute('id') && AVE.BehaviorOptions[el.readAttribute('id')])
				Object.extend(options, AVE.BehaviorOptions[el.readAttribute('id')]);
				
			if(el.readAttribute('aveoptions') && AVE.BehaviorOptions[el.readAttribute('aveoptions')]) {
				Object.extend(options, AVE.BehaviorOptions[el.readAttribute('aveoptions')]);
			}
		    
			if(el.readAttribute('avedialogclass')) {
				Object.extend(options, {className: el.readAttribute('avedialogclass')});
			
			}
		    
			dialog(options).getContent(el.readAttribute('href'));
			e.stop();
		},
		
		// Seems silly, but can be useful for replicating anchors on other elements. Like in table.js
		link: function(e,el) {
			window.location = el.readAttribute('href');
			e.stop();
		},
		
		'dialog-link': Prototype.emptyFunction,
		
		remove: function(e, el) {
			var str = (AVE.BehaviorOptions[el.readAttribute('id')]) ? AVE.BehaviorOptions[el.readAttribute('id')].message : defaults.remove.message;
			var ans = confirm(str);
			if(!ans){ e.stop(); }
		}
		
	};
	
	return function(e, el) {
		//e.stop();
		
		el = (el.readAttribute('avebehavior')) ? el : el.up('[avebehavior]');
		behaviors[el.readAttribute('avebehavior')](e, el);
	};
})();

/**
 * Hooking up behaviors on the body level. Shouldn't have to ever rehook
 * new content that's loaded into the page via Ajax calls
 */

document.observe('dom:loaded', function(evt) {

    var delegate = function(e) {
        var el = e.element();
        var trigger = el.readAttribute('aveevent') || 'click';
            
		if(e.type.toLowerCase() == trigger.toLowerCase())
        AVE.runBehavior(e, el);
    };
    
   Event.register(document.body, "[avebehavior]", "click", delegate);
});

/**
 * WindowProperties Object.
 * Quick way of getting content or viewport size
 *
 * Dependencies:
 *  prototype.js
 *
 */

AVE.WindowProperties = {
	/**
	 * var dims = AVE.WindowProperties.getContentSize([$$('.myClass'),'myDOMID','$('anotherID')']);
	 * var height = dims.height; var width = dims.width;
	 *
	 * $param  {String || DOM Object || Array} ignore			Items not to consider in calculating size of content
	 * @return {Object} browserSize								Object with a width and height value
	 */
getContentSize: function(ignore) {
   /* if(ignore) {
		if(ignore.constructor != Array) {
			ignore = [$(ignore)];
	    } else {
			ignore = ignore.flatten().collect(function(el) {
				// arrrrg, why can't I use invoke for this?
				return $(el);
			});
		}
	}
	
	if(ignore) ignore.invoke('hide');
    var bodyDims = $(document.body).getDimensions();
    var browserSize = document.viewport.getDimensions();

    if(bodyDims.height > browserSize.height) browserSize.height = bodyDims.height;
    if(bodyDims.width > browserSize.width) browserSize.width = bodyDims.width;

    if(ignore) (function(){ignore.invoke('show');}).defer();
    return browserSize; */

	  var xScroll, yScroll;
		
		if (window.innerHeight && window.scrollMaxY) {	
			xScroll = window.innerWidth + window.scrollMaxX;
			yScroll = window.innerHeight + window.scrollMaxY;
		} else if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac
			xScroll = document.body.scrollWidth;
			yScroll = document.body.scrollHeight;
		} else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
			xScroll = document.body.offsetWidth;
			yScroll = document.body.offsetHeight;
		}
		
		var windowWidth, windowHeight;
		
		if (self.innerHeight) {	// all except Explorer
			if(document.documentElement.clientWidth){
				windowWidth = document.documentElement.clientWidth; 
			} else {
				windowWidth = self.innerWidth;
			}
			windowHeight = self.innerHeight;
		} else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
			windowWidth = document.documentElement.clientWidth;
			windowHeight = document.documentElement.clientHeight;
		} else if (document.body) { // other Explorers
			windowWidth = document.body.clientWidth;
			windowHeight = document.body.clientHeight;
		}	
		
		// for small pages with total height less then height of the viewport
		if(yScroll < windowHeight){
			pageHeight = windowHeight;
		} else { 
			pageHeight = yScroll;
		}
	
		// for small pages with total width less then width of the viewport
		if(xScroll < windowWidth){	
			pageWidth = xScroll;		
		} else {
			pageWidth = windowWidth;
		}

		return {width: pageWidth, height: pageHeight};
  },
  
  getScrollbarWidth: function() {
    var outer = new Element('div', {style: "width: 50px; height: 50px; overflow: hidden;"});
    var inner = new Element('div', {style: "height: 100px; border: 4px solid black;"});
    outer.insert(inner);
    $(document.body).insert(outer);
    var w1 = inner.getWidth();
    outer.setStyle({overflow: "auto"});
    var w2 = inner.getWidth();
    if (w1 == w2) w2 = outer.clientWidth; 
    outer.remove();
    return w1 - w2;
  }


};


/**
 * New Effect For Counting
 *
 */

Effect.Count = Class.create(
	Object.extend(Effect.Base.prototype, {
	  initialize: function(element, count) {
	    var options = arguments[2] || {};
	    this.element = $(element);
	    this.startCount = parseFloat(this.element.innerHTML);
	    this.finishCount = count;
	    this.delta = (this.finishCount - this.startCount);
	    this.start(options);
	  },
	  update: function(position) {
	    var value = (this.startCount + (this.delta*position)).toString().split('.');
	    Element.update(this.element, value[0]);
	  }
	})
);

/**
 * Format Currency
 *
 */

AVE.Utils = {
	formatCurrency: function(amount) {
		var i = parseFloat(amount);
		if(isNaN(i)) { i = 0.00; }
		var minus = '';
		if(i < 0) { minus = '-'; }
		i = Math.abs(i);
		i = parseInt((i + .005) * 100);
		i = i / 100;
		s = new String(i);
		if(s.indexOf('.') < 0) { s += '.00'; }
		if(s.indexOf('.') == (s.length - 2)) { s += '0'; }
		s = minus + s;
		return this.formatWithCommas(s);
	},
	
	formatWithCommas: function(amount) {
		var delimiter = ","; // replace comma if desired
		var a = amount.split('.',2);
		var d = a[1];
		var i = parseInt(a[0]);
		if(isNaN(i)) { return ''; }
		var minus = '';
		if(i < 0) { minus = '-'; }
		i = Math.abs(i);
		var n = new String(i);
		var a = [];
		while(n.length > 3)
		{
			var nn = n.substr(n.length-3);
			a.unshift(nn);
			n = n.substr(0,n.length-3);
		}
		if(n.length > 0) { a.unshift(n); }
		n = a.join(delimiter);
		if(d.length < 1) { amount = n; }
		else { amount = n + '.' + d; }
		amount = minus + amount;
		return amount;
	},
	
	formatRequired: function() {
	    $$('.required').each(function(el) {
	        el.insert({bottom: '<small style="position: relative; top: -.2em;">*</small>'});
	        el.removeClassName('required').addClassName('styled-required');
	    });
	}
};

/**
 * Text Area Counter
 * Allows for maxlength type functionality on textareas
 *
 * Dependencies:
 *  prototype.js
 *
 */

AVE.MaxLength = Class.create({
	defaults: {
		insertion: 'after',
		max: 2000
	},

	initialize: function(el, options) {
      
		this.options = Object.extend(Object.clone(this.defaults), options || {});
		this.el = $(el);
		if(!el || !el.tagName.toUpperCase() == "TEXTAREA") return;

		this.onkeyup = this._onkeyup.bindAsEventListener(this);
		this.createCounter();
		this.onkeyup();
		el.observe('keyup', this.onkeyup);
	},
	
	createCounter: function() {
		this.counter = new Element('div').addClassName('maxlength-counter');
		var ins = {};
		ins[this.options.insertion] = this.counter;
		this.el.insert(ins);
	},
	
	_onkeyup: function(e) {
		if(this.el.value.length >= this.options.max) {
			this.el.value = this.el.value.substring(0, this.options.max);
			this.counter.update("0 characters remaining (text truncated)");
		} else {
			var remaining = this.options.max - this.el.value.length;
			this.counter.update(remaining + ' characters remaining.');	
		}
	}
});

/**
 * Ribbon Box
 * Author: JD Hendrickson
 * Date: 12/16/2009
 * 
 * Dependencies:
 * 	prototype.js
 * 	ave.js
 */
AVE.RibbonBox = Class.create({
	initialize : function(_el){
		this.self	= _el;
		this.type	= this.getType(_el);

		this.render();
	},

	render : function(){
		var _ribbon		= new Element('div', { 'class' : 'ribbon' });
		var _ribbonBox	= new Element('div', { 'class' : 'ribbon-box' });
		var _content	= new Element('div', { 'class' : 'content clearfix' });
		var _message	= this.self.innerHTML;

		_content.update(this.self.innerHTML);

		this.self.update('');
		this.self.removeClassName('ave-ribbon-box');
		this.self.addClassName('ribbon-box-wrapper clearfix');

		this.self.insert(_ribbon);
		_content.wrap(_ribbonBox);
		this.self.insert(_ribbonBox);
	},

	getType : function(_el){
		var _type;
		
		// You can add additional types here
		if(_el.hasClassName('information-box')) _type = 'information-box';
		else _type = 'plain';

		return _type;
	}
});

document.observe('dom:loaded', function(){
    AVE.Utils.formatRequired();
	$$('textarea, .showmaxlength').each(function(ta){
		if(ta.readAttribute('maxlength')) {
			new AVE.MaxLength(ta, {max: ta.readAttribute('maxlength')});
		}
	});
	
	// RIBBON BOX INIT
	$$('.ave-ribbon-box').each(function(el){ new AVE.RibbonBox(el);	});
});

/**
 * Auto Format Dialog Content
 * Automatically applies some formatting objects to dialog content
 *
 * Dependencies:
 *  prototype.js
 *
 */
(function() {
    var cleanupCals = function(e){

		var div = e.memo.dialog.dialog;
		div.select('.date input').each(function(inp){
			var id, dp, created;

			id = inp.id;
			dp = datePickerController.getDatePicker(id);

			// dp = (id && datePickerController.datePickers[id]) ? datePickerController.datePickers[id] : null;
			//created = (dp && dp.created) ? true : false;
			if(dp) {
				datePickerController.datePickers[id].destroy();
				datePickerController.datePickers[id] = null;
				delete datePickerController.datePickers[id];
			    if($(dp.div)) { dp.destroy(); $(dp.div).remove(); };
			}
		});
	};
	
	$(document).observe('dialog:render', function(e){
	    // format required fields
	    AVE.Utils.formatRequired();
	    
	    $$('.rounded').invoke('round');
		var div = e.memo.dialog.dialog;
		
        
		div.select('.date input').each(function(inp){
			datePickerController.create(inp);
		});
		
		// catch server validation errors and highlight fields
		AVE.Validation.onPageLoad();
	});

	
	
	$(document).observe('dialog:cleanup', cleanupCals);
	$(document).observe('dialog:request', cleanupCals);
})();

// Sets the cursor position for a textarea
// defaults to end of textarea
var setCursor = function(textarea, options){
    
    var newOptions = options || {};
    var len = newOptions.cursorPos || textarea.value.length;
    
    if(textarea.setSelectionRange) { 
        textarea.focus(); 
        textarea.setSelectionRange(len, len); 
    }
    else { 
        textarea.focus();
        textarea.onfocus = textarea.value = textarea.value;
    } 
}