/*** extensions to prototype lib 

requires:
/prototype/prototype.js

***/

Loob = {};

/*** extend Element class ***/

Object.extend(Element, {

	get: function(obj) {
		return $(obj);
	},
   
	getByClassName: function(className, parentElement) {
		var children = (parentElement || document).getElementsByTagName('*');
		var elements = new Array();
		for (var i = 0; i < children.length; i++) {
			if( this.hasClassName( children[i], className ) )
				elements.push(child);
		}
		return elements;
	},
   
  toggleClassName: function(element, className) {
		if(this.hasClassName(element, className)) {
			this.removeClassName(element, className);
		} else {
			this.addClassName(element, className);
		}
		return !!this.hasClassName(element, className);
	},
   
	getList: function (el) {
		if( Var.is_element(el) )	
			return [el];
		else if( Var.is_string(el) ) 
			return this.getList(el.split(/\s+/g));
		else if( Var.is_list(el) ) {
			var r = map(el, this.get);
			return filter(r, Var.is_element).length==r.length? r : null;
	 	}
		else 
		return null;
	},

	getStyleWidth: function(elem) {
		return elem.clientWidth;
	},
	
	getStyleHeight: function(elem) {
		return elem.clientHeight;
	},
	
	getBlockLevelChildren: function(elem) {
		var children = elem.childNodes;		
		var elements = new Array();  
		for (var i = 0; i < children.length; i++) {
			if(children[i].nodeType == 1) { elements.push(children[i]); }
		}
		return elements;
	},
	
	getMaxChildWidth: function(elem, ofStyle) {
		var children = this.getBlockLevelChildren(elem);
		var max = 0;
		for (var i = 0; i < children.length; i++) {
			if(ofStyle) {
				var value = this.getStyleWidth(children[i]);
			} else {
				var value = children[i].clientWidth;
			}
			if(value > max) { max = value; }
		}
		return max;
	},
	
	getMaxChildHeight: function(elem, ofStyle) {
		var children = this.getBlockLevelChildren(elem);
		var max = 0;
		for (var i = 0; i < children.length; i++) {
			if(ofStyle) {
				var value = this.getStyleHeight(children[i]);
			} else {
				var value = children[i].clientHeight;
			}
			if(value > max) { max = value; }
		}
		return max;
	},

	applyMaxChildWidth: function(elem, ofStyle) {
		var max = this.getMaxChildWidth(elem, ofStyle);
		var children = this.getBlockLevelChildren(elem);
		for (var i = 0; i < children.length; i++) {
			children[i].style.width = max + 'px';
		}
		return max;
	},

	applyMaxChildHeight: function(elem, ofStyle) {
		var max = this.getMaxChildHeight(elem, ofStyle);
		var children = this.getBlockLevelChildren(elem);
		for (var i = 0; i < children.length; i++) {
			children[i].style.height = max + 'px';
		}
		return max;
	},

	setCSSBackgroundImage: function(element, src_url) {
		Element.Style.setBackgroundImage(arguments[0], arguments[1], arguments[2]);	
	},
	
	setPosition: function(element, new_x, new_y) {
		Element.setStyle(element, { left: new_x + 'px', top: new_y  + 'px' });
	},
	
	setDimensions: function(element, new_width, new_height) {
		Element.setStyle(element, { width: Math.round(new_width) + 'px', height: Math.round(new_height)  + 'px' });
	},
	
	setVisibility: function(element, bool) {
		Element.setStyle(element, { visibility: (bool ? 'visible' : 'hidden') });
	}
	
});

/*** style related methods ***/

Element.Style = {

	setBackgroundImage: function(element, src_url) {
		var element = $(element);
		var options = Object.extend({
			top: 0, left: 0,
			repeat: 'no-repeat'
		}, arguments[2] || {});
		if(options.className) element.className = options.className;
		options.top = (options.top != 0) ? options.top + 'px' : 0;
		options.left = (options.left != 0) ? options.left + 'px' : 0;
		element.style.backgroundImage = 'url(' + src_url + ')';
		element.style.backgroundPosition =  options.left + ' ' + options.top;
		element.style.backgroundRepeat = options.repeat;		
	},

	clearBackgroundImage: function(element) {
		element.style.backgroundImage = 'none';
	},

	getBackgroundPosition: function(element) {
		// from inline or javascript declaration
		return Element.Style.split(Element.getStyle(element, 'background-position'), arguments[1] || false);	
	},
	
	getBackgroundImage: function(element) {
		var prop = Element.getStyle(element, 'background-image') || '';
		var matches = prop.match(/url\(([^\)]+)\)/);
		var url = matches ? matches[1] : null;
		if(url && arguments[1]) {
			var img = new Image(); img.src = url;
			return img;
		} else {
			return url;
		}
	},
	
	setBackgroundPosition: function(element, left, top) {
		Element.setStyle(element, { backgroundPosition: Math.round(left) + 'px ' + Math.round(top) + 'px' });
	},
	
	centerBackground: function(element, img_dim) {
		var dim = Element.getDimensions(element);
		var offset_left = Var.is_number(arguments[2]) ? arguments[2]/100 : 0.5;
		var offset_top = Var.is_number(arguments[3]) ? arguments[3]/100 : 0.5;
		var new_left = Math.round((dim.width - img_dim.width) * offset_left);
		var new_top = Math.round((dim.height - img_dim.height) * offset_top);
		Element.Style.setBackgroundPosition(element, new_left, new_top);		
	},

	getOutOfViewOffset: function(elem_dim, img_dim) {
		var side = arguments[2] || 'n';
		var offset_x = arguments[3] || 0;
		var offset_y = arguments[4] || 0;
		switch(side) {
			case 'n':
				return [0, - img_dim.height - offset_y];
			case 'ne':
				return [elem_dim.width + offset_x, - img_dim.height - offset_y];
			case 'e':
				return [elem_dim.width + offset_x, 0];
			case 'se':
				return [elem_dim.width + offset_x, elem_dim.height + offset_y];
			case 's':
				return [0, elem_dim.height + offset_y];
			case 'sw':
				return [- img_dim.width - offset_x, elem_dim.height + offset_y];
			case 'w':
				return [- img_dim.width - offset_x, 0];
			case 'nw':
				return [- img_dim.width - offset_x, - img_dim.height - offset_y];
			default:
				return [0, - img_dim.height - offset_y];
		}		 
	},

	getCenterPointOffset: function(elem_dim, img_dim) {
		var aspect_x = Var.is_number(arguments[2]) ? arguments[2] : 0.5;
		var aspect_y = Var.is_number(arguments[3]) ? arguments[3] : 0.5;
		
		var new_center_x = aspect_x * img_dim.width;
  	var new_center_y = aspect_y * img_dim.height;
		
		var crop_point_x = (new_center_x - (elem_dim.width/2))
  	var crop_point_y = (new_center_y - (elem_dim.height/2))
		
		if((crop_point_x + elem_dim.width) >= img_dim.width) { 
			crop_point_x = img_dim.width - elem_dim.width; 
		} else if(crop_point_x < 0) {
			crop_point_x = 0;
		}
  	if((crop_point_y + elem_dim.height) >= img_dim.height) { 
			crop_point_y = img_dim.height - elem_dim.height; 
		} else if(crop_point_y < 0) {
			crop_point_y = 0;
		}
		return [- crop_point_x, - crop_point_y];
	},
	
	centerBackgroundOnPoint: function(element, img_dim) {
		var offset = Element.Style.getCenterPointOffset(element, img_dim. arguments[2], arguments[3]);
		Element.Style.setBackgroundPosition(Element.getDimensions(element), offset[0], offset[1]);
	},
	
	getPositionOffset: function(elem, value) {
		var element = $(elem);
		return { left: parseInt(element.style.left || '0'), top: parseInt(element.style.top || '0') }
	}, 
	
	split: function(value) {
		if(value && value.indexOf(' ')) { 
			var parts = value.split(' ');
			if(arguments[1]) { return [ parseInt(parts[0]), parseInt(parts[1]) ]; }
			return [ parts[0], parts[1] ];
		}
		return null;
	}
	
}

Element.Window = {
	
	getDimensions: function() {
		if (self.innerHeight) {	// all except Explorer
			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;
		}	else {
			windowWidth = 0; windowHeight = 0;
		}
		return { width: windowWidth, height: windowHeight };
	},
	
	open: function(uri, name) {
		var params = [];
		var options = Object.extend({
			width: 800, height: 600,
			status: 0, menubar: 0, toolbar: 0,
			scrollbars: 1, resizable: 1
		}, arguments[2] || {})		
		options.left 	= (options.left || Math.round((screen.width/2) - (options.width/2)));
		options.top 	= (options.top || Math.round((screen.height/2) - (options.height/2)));
		for(var k in options) { params.push(k + '=' + options[k]); }
		window.open(uri, (name || 'window'), params.join(','));
	}
	
}

/*** event handling ***/

Object.extend(Event, {

	relativePointerX: function(evt) {
		var pointerX = Event.pointerX(evt);
		var elemPosition = Position.cumulativeOffset(Event.element(evt));
		return pointerX - elemPosition[0];
	},
	
	relativePointerY: function(evt) {
		var pointerY = Event.pointerY(evt);
		var elemPosition = Position.cumulativeOffset(Event.element(evt));
		return pointerY - elemPosition[1];
	}

});

/*** Math ***/

Math.roundTo = function(val, by) {
	// round down to nearest divisable by
	if (val < 0) {
		val += -val % by;
	} else {
		val -= val % by;
	}
	return val;
}

/*** extend Array object ***/

Object.extend(Array.prototype, {
	
	toQueryString: function(name) {
		return this.collect(function(item) { return name + "[]=" + encodeURIComponent(item) }).join('&');
	},
	
	sum: function(initial) {
		return this.inject(initial || 0, function(x, y) {return x + y;});
	},

	random: function(range) {
		var i = 0;
		if (!range) range = this.length;
		else if (range > 0) range %= 1;
		else i = range; range = this.length + range % this.length;
		return this[Math.floor(range * Math.random() - i)];
	},

	shuffle: function(deep) {
		var i = this.length;
		var clone = this.toArray();
		while (i) {
			j = Math.floor((i--) * Math.random());
			t = deep && clone[i].shuffle === undefined ? clone[i].shuffle() : clone[i];
			clone[i] = clone[j];
			clone[j] = t;
		}
		return clone;
	},

	unique: function() {
		var result = [];
		for (i = 0; i < this.length; i++) {
			if (result.indexOf(this[i]) < 0) result.push(this[i]);
		}
		return result;
	}

});

/*** extend String object ***/

Object.extend(String.prototype, {
	
	int_chars_lower: ['à','á','â','ã','ä','å','æ','ç','è','é','ê','ë','ì','í','î','ï','ñ','ò','ó','ô','õ','ö','ø','œ','ù','ú','û','ü','ÿ'],	
	int_chars_upper: ['À','Á','Â','Ã','Ä','Å','Æ','Ç','È','É','Ê','Ë','Ì','Í','Î','Ï','Ñ','Ò','Ó','Ô','Õ','Ö','Ø','Œ','Ù','Ú','Û','Ü','Ÿ'],
	int_chars_lower_ascii: ['a','a','a','a','a','a','ae','c','e','e','e','e','i','i','i','i','n','o','o','o','o','o','o','oe','u','u','u','u','y'],
	int_chars_upper_ascii: ['A','A','A','A','A','A','AE','C','E','E','E','E','I','I','I','I','N','O','O','O','O','O','O','OE','U','U','U','U','Y'],
	
	translit: function() {
		newstr = this
		for(var i = 0; i < this.int_chars_lower.length; i++) { newstr = newstr.replace(new RegExp(this.int_chars_lower[i], 'g'), this.int_chars_lower_ascii[i]); }
		for(var i = 0; i < this.int_chars_upper.length; i++) { newstr = newstr.replace(new RegExp(this.int_chars_upper[i], 'g'), this.int_chars_upper_ascii[i]); }	
		return newstr;	
	},
	
	urlify: function() {
		var options = Object.extend({ delimiter: '-', allow: '', cutoff: 0 }, arguments[0] || {});
		var newstr = this.translit().toLowerCase();		
		esc_delimiter = options.delimiter.escape_for_regexp();
		esc_allow = options.allow.escape_for_regexp();
		if(options.cutoff > 0) newstr = newstr.truncate(options.cutoff)
		newstr = newstr.replace(new RegExp('[^a-z0-9' + esc_allow + esc_delimiter + ']', 'g'), ' ');
		newstr = newstr.replace(new RegExp('^([\s' + esc_allow + esc_delimiter + ']+)', 'g'), '');
		newstr = newstr.replace(new RegExp('([' + esc_allow + esc_delimiter + ']+)$', 'g'), '');
		if(options.delimiter == '') newstr = newstr.camelize();
		newstr = newstr.strip().replace(/\b\s+/g, options.delimiter);
		if(options.delimiter != '') newstr = newstr.replace(new RegExp(esc_delimiter + '{2,}', 'g'), options.delimiter)
		return newstr;
	},
	
	escape_for_regexp: function() {
		return this.replace(/[.*+?^${}()|[\]\/\\]/g, '\\$0');
	}
	
});

/*** Var class for type checking ***/

Var = {
	is_array:			function(o) { return (this.is_object(o) && o.constructor == Array); },
	is_bool:			function(o) { return (typeof o == 'boolean'); },
	is_function:	function(o) { return (typeof o == 'function'); },   
	is_null:			function(o) { return (typeof o == 'object' && !o); },
	is_number:		function(o) { return (typeof o == 'number' && isFinite(o)); },
	is_object:		function(o) { return (o && typeof o == 'object') || this.is_function(o) },
	is_regexp:		function(o) { return (o && o.constructor == RegExp); },
	is_string:		function(o) { return (typeof o == 'string'); },
	is_undef:			function(o) { return (typeof o == 'undefined'); },
	is_element:		function(o, strict) { return o && this.is_object(o) && ((!strict && (o==window || o==document)) || o.nodeType == 1); },
	is_list: 			function(o) { return o && this.is_object(o) && (this.is_array(o) || o.item); },
	clone: function(obj) {
		var o = new Object(), p; 
		for(p in obj) { if('object' == typeof obj[p] && obj[p]) { o[p] = obj[p].clone(); } else { o[p] = obj[p]; } } 
		return o; 
	},
	serialize: function(o) {
		var queryComponents = new Array();
		for(var key in o) {
			if (typeof o[key] == 'object' && o[key].serialize) {
				queryComponents.push(o[key].serialize());
			} else if(o[key] != undefined) {			
				var match = o[key].toString().match( /\s*function (.*)\(/ );
	      if ( match == null ) {
					queryComponents.push(encodeURIComponent(key) + '=' + 
					encodeURIComponent(o[key]));
				}
			}
		}
		return queryComponents.join('&');
	}
};

/*** Element.Class handling ***/

Element.Class = {
    // Element.Class.toggle(element, className) toggles the class being on/off
    // Element.Class.toggle(element, className1, className2) toggles between both classes,
    //   defaulting to className1 if neither exist
    toggle: function(element, className) {
      if(Element.Class.has(element, className)) {
        Element.Class.remove(element, className);
        if(arguments.length == 3) Element.Class.add(element, arguments[2]);
      } else {
        Element.Class.add(element, className);
        if(arguments.length == 3) Element.Class.remove(element, arguments[2]);
      }
    },

    // gets space-delimited classnames of an element as an array
    get: function(element) {
      return $(element).className.split(' ');
    },

    // functions adapted from original functions by Gavin Kistner
    remove: function(element) {
      element = $(element);
      var removeClasses = arguments;
      $R(1,arguments.length-1).each( function(index) {
        element.className = 
          element.className.split(' ').reject( 
            function(klass) { return (klass == removeClasses[index]) } ).join(' ');
      });
    },

    add: function(element) {
      element = $(element);
      for(var i = 1; i < arguments.length; i++) {
        Element.Class.remove(element, arguments[i]);
        element.className += (element.className.length > 0 ? ' ' : '') + arguments[i];
      }
    },

    // returns true if all given classes exist in said element
    has: function(element) {
      element = $(element);
      if(!element || !element.className) return false;
      var regEx;
      for(var i = 1; i < arguments.length; i++) {
        if((typeof arguments[i] == 'object') && 
          (arguments[i].constructor == Array)) {
          for(var j = 0; j < arguments[i].length; j++) {
            regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)");
            if(!regEx.test(element.className)) return false;
          }
        } else {
          regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)");
          if(!regEx.test(element.className)) return false;
        }
      }
      return true;
    },

    // expects arrays of strings and/or strings as optional paramters
    // Element.Class.has_any(element, ['classA','classB','classC'], 'classD')
    has_any: function(element) {
      element = $(element);
      if(!element || !element.className) return false;
      var regEx;
      for(var i = 1; i < arguments.length; i++) {
        if((typeof arguments[i] == 'object') && 
          (arguments[i].constructor == Array)) {
          for(var j = 0; j < arguments[i].length; j++) {
            regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)");
            if(regEx.test(element.className)) return true;
          }
        } else {
          regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)");
          if(regEx.test(element.className)) return true;
        }
      }
      return false;
    },

    childrenWith: function(element, className) {
      var children = $(element).getElementsByTagName('*');
      var elements = new Array();

      for (var i = 0; i < children.length; i++)
        if (Element.Class.has(children[i], className))
          elements.push(children[i]);

      return elements;
    }
}

Element.Image = {
	
	toBackground: function(element) {
		var element = $(element);
		if(!Var.is_undef(element.parentNode) && element.nodeName.toLowerCase() == 'img' && !Var.is_undef(element.src)) {
			var parent = element.parentNode;
			
			var options = Object.extend({
				top: 0,
				left: 0,
				repeat: 'no-repeat',
				tag_element: 'div',
				class_name: 'img_bg'
			}, arguments[1] || {});
			
			var width = options.width || element.width; 
			var height = options.height || element.height;
			
			if(width && height) {
				var replacement = document.createElement(options.tag_element);
				Element.Style.setBackgroundImage(replacement, element.src, options);
				Element.Class.add(replacement, options.class_name);
				replacement.style.width = width + 'px'; replacement.style.height = height + 'px';
				replacement.width = width; replacement.height = height;
				replacement.min_width = 0; replacement.min_height = 0;
				replacement.max_width = width; replacement.max_height = height;
				parent.replaceChild(replacement, element);
				if(element.id != undefined) replacement.id = element.id;
				return replacement;
			}
		}
		return element;		
	}
	
}

var Preload = new Object();

Preload.Images = Class.create();
Preload.Images.prototype = {
	
	/*** preload images async ***/
	
	source_values: [],
	num_loaded: 0,
	options: {},
	
	initialize: function(source_values) {
		this.options = Object.extend({
			loaded: null,
			completed: null,
			failed: null,
			delay: 0
		}, arguments[1] || {});		
		
		this.initialized = Var.is_function(this.options.initialized) ? this.options.initialized : this.null_func;
		this.image_loaded = Var.is_function(this.options.loaded) ? this.options.loaded : this.null_func;
		this.load_error = Var.is_function(this.options.failed) ? this.options.failed : this.null_func;
		this.load_completed = Var.is_function(this.options.completed) ? this.options.completed : this.null_func;
		this.reset();
		this.source_values = source_values;
		this.initialized(this);
		this.preload();
	},
	
	preload: function() {	
		for(var i = 0; i < this.source_values.length; i++) {
			var value = this.source_values[i];
			var image = new Image();
			image.onerror = this.load_error.bind(this, image, value);
			image.onload = this.loaded.bind(this, image, value);
			if(Var.is_string(value)) {				
				image.src = value;
			} else if(Var.is_object(value) && Var.is_string(value.src)) {
				image.src = value.src;	
			} else if(Var.is_object(value) && Var.is_string(value.path)) {
				image.src = value.path;				
			} else {
				this.loaded(image, value);
			}
		}
	},
	
	loaded: function(image, value) {
		this.image_loaded(image, value);
		this.num_loaded++;
	},
	
	completed: function(preloader) { },	
	load: function(idx) { },	
	load_next: function(image) { },		
	null_func: function() { },
	
	reset: function() {
		this.source_values = [];
		this.num_loaded = 0;
	}	

}

Preload.ImagesSync = Class.create();
Object.extend(Object.extend(Preload.ImagesSync.prototype, Preload.Images.prototype), {
		
	preload: function() {
		if(this.source_values.length > 0) { this.load(this.num_loaded); }
	},
	
	load: function(idx) {
		var delay = arguments[1] || this.options.delay;
		var value = this.source_values[idx];
		var image = new Image();
		image.onerror = this.load_error.bind(this, image, value);
		if(delay == 0) {
			image.onload = this.load_next.bind(this, image, value);	
		} else {
			image.onload = setTimeout(this.load_next.bind(this, image, value), (delay * 1000));
		}				
		if(Var.is_string(value)) {
			image.src = value;
		} else if(Var.is_object(value) && Var.is_string(value.src)) {
			image.src = value.src;
		} else if(Var.is_object(value) && Var.is_string(value.path)) {
			image.src = value.path;
		} else {
			this.load_next(image, value);
		}
	},
	
	load_next: function(image, value) {
		this.loaded(image, value);
		if(this.num_loaded < this.source_values.length) {
			this.load(this.num_loaded, this.options.delay);
		} else {
			this.load_completed(this);
		}
	}
	
});


