/**
 * base.js
 * author:	Jackson Tomlinson
 * brief:	A common set of functions for the ACM app
 */

var ACM = {}

ACM.color = function(str){
	this.str = str;
	this.RGB = RGBcolor(str);
	this.HSV = HSVfromRGB(this.RGB);
	return this;
}

function RGBcolor(str){
	this.r = hexToR(str);
	this.g = hexToG(str);
	this.b = hexToB(str);
	return this;
}

function HSVfromRGB(rgb){
	var min = Math.min(Math.min(rgb.r, rgb.g), rgb.b);
	this.v = Math.max(Math.max(rgb.r, rgb.g), rgb.b);
	
	var delta = this.v - min;
	
	if(this.v != 0){
		this.s = delta / this.v;
	}else{
		this.s = 0;
		this.h = -1;
		return this;
	}
	
	if (rgb.r == this.v){
		this.h = (rgb.g - rgb.b) / delta;
	}else if (rgb.g == this.v){
		this.h = 2 + (rgb.b - rgb.r) / delta;
	}else{
		this.h = 4 + (rgb.r - rgb.g) / delta;
	}
	
	this.h *= 60;
	if(this.h < 0){
		this.h += 360;
	}
		
	return this;
}

function HSVtoRGB(c){
	var i;
	var f, p, q, t;

	if( c.s == 0 ) {
		// achromatic (grey)
		this.r = c.v;
		this.g = c.v;
		this.b = c.v;
		return this;
	}

	c.h /= 60;			// sector 0 to 5
	i = Math.floor( c.h );
	f = c.h - i;			// factorial part of h
	p = c.v * ( 1 - c.s );
	q = c.v * ( 1 - c.s * f );
	t = c.v * ( 1 - c.s * ( 1 - f ) );

	switch( i ) {
		case 0:
			this.r = c.v;
			this.g = t;
			this.b = p;
			break;
		case 1:
			this.r = q;
			this.g = c.v;
			this.b = p;
			break;
		case 2:
			this.r = p;
			this.g = c.v;
			this.b = t;
			break;
		case 3:
			this.r = p;
			this.g = q;
			this.b = c.v;
			break;
		case 4:
			this.r = t;
			this.g = p;
			this.b = c.v;
			break;
		default:		// case 5:
			this.r = c.v;
			this.g = p;
			this.b = q;
			break;
	}
	this.r = Math.round(this.r);
	this.g = Math.round(this.g);
	this.b = Math.round(this.b);
	return this;
}
	
function RGBtoHex(rgb) {return toHex(rgb.r)+toHex(rgb.g)+toHex(rgb.b)}

function toHex(N) {
	if (N==null) return "00";
	N=parseInt(N); if (N==0 || isNaN(N)) return "00";
	N=Math.max(0,N); N=Math.min(N,255); N=Math.round(N);
	return "0123456789ABCDEF".charAt((N-N%16)/16) + "0123456789ABCDEF".charAt(N%16);
}
	
function hexToR(h) {return parseInt((cutHex(h)).substring(0,2),16)}
function hexToG(h) {return parseInt((cutHex(h)).substring(2,4),16)}
function hexToB(h) {return parseInt((cutHex(h)).substring(4,6),16)}
function cutHex(h) {return (h.charAt(0)=="#") ? h.substring(1,7):h}

ACM.mysqlTimeStampToDate = function (timestamp) {
	if (timestamp) {
		//function parses mysql datetime string and returns javascript Date object
		//input has to be in this format: 2007-06-05 15:26:02
		var regex = /^([0-9]{2,4})\/([0-1][0-9])\/([0-3][0-9]) (?:([0-2][0-9]):([0-5][0-9]):([0-5][0-9]))?/;
		var parts = timestamp.replace(regex, "$1 $2 $3 $4 $5 $6").split(' ');
		return new Date(parts[0], parts[1] - 1, parts[2], parts[3], parts[4], parts[5]);
	}
	return new Date(timestamp);
}

ACM.mysqlDateToDate = function (timestamp) {
	if (timestamp) {
		//function parses mysql datetime string and returns javascript Date object
		//input has to be in this format: 200-/06-05
		var regex = /^([0-9]{2,4})\/([0-1][0-9])\/([0-3][0-9])?/;
		var parts = timestamp.replace(regex, "$1 $2 $3").split(' ');
		return new Date(parts[0], parts[1] - 1, parts[2]);
	}
	return new Date(timestamp);
}

/* A wrapper for 'event delegation' */
/* usage: ACM.delegate('click', '#id P.class', someFunction, someArguments) */
ACM.delegateTypes = {}; /* used to limit event testing to certain event types */
ACM.delegateFuncs = {}; /* used to store functions and reference them at a later time (to minimisze YUI's Event.addListener) */
ACM.delegateArgs = {}; /* used to store the argument list for each delegate */
ACM.delegate = function(type, css, func, args) {
	ACM.delegateFuncs[css+type] = func;
	ACM.delegateArgs[css+type] = args;
	if (!ACM.delegateTypes[type]) { /* has a, I don't know, "click" listener been added yet? */
		ACM.delegateTypes[type] = [];
		$J(document).bind(type, function(e) {
			e = e? e : window.event; /* remind IE of what an event is */
			var node = e.srcElement || e.target; /* get the actual node acted upon from the event object */
			var trunk = node; /* make a reference to 'node', used later to climb the parentNode tree of 'node' */
			var i = 0;
			var tree = {}; /* this will be filled with a bunch of values, with a string as the key, and the current parentNode as the value */
			do {
				i++;
				if (trunk.nodeType != 9) { /* we don't care about 'document' - we only want to go as far as <BODY> */
					tree[i] = trunk;
					tree[trunk.nodeName] = i;
					if (trunk.className) {
						var classNames = trunk.className.split(' ');
						for (var c in classNames) {
							tree[trunk.nodeName + '.' + classNames[c]] = i;
							tree['.' + classNames[c]] = i;
						}
					}
					if (trunk.id) {
						tree[trunk.nodeName + '#' + trunk.id] = i;
						tree['#' + trunk.id] = i;
					}
				}
			} while ((trunk = trunk.parentNode)); /* keep climing until there are no more parentNodes */
			for (var _css in ACM.delegateTypes[type]) { /* loop through all possible css strings that belong to, say, "click" */
				var obj = false; /* this will be passed through as an argument to a function that survives the gauntlet of tests below */
				var tests = 0; /* if this gets bumped up to match the value of parts.length, the function is allowed to execute */
				var order = 0;
				var parts = _css.split(' ').reverse(); /* "#doc A.someClass" will turn into Array("A.someClass" "#doc") to reflect the backwards nature of climbing the parentNode tree */
				var partsLength = parts.length;
				for (var p in parts) { /* go through all the parts */
					if (tree[parts[p]] && tree[parts[p]] > order) {
						if (!obj) {
							obj = node;
							var findParent = tree[parts[p]];
							for (var n = 1; n < findParent; n++) {
								obj = obj.parentNode;
							}
						}
						tests++; /* if the hash key has been filled, up the passed tests by 1 */
						order = tree[parts[p]];
					}
				}
				if (tests == partsLength) { /* have enough test passed? */
					if (!ACM.delegateFuncs[_css + type]) {
						alert('DEV WARN: Delegated function stored at ACM.delegateFuncs[' + (_css + type) + '] isn\'t defined on this page!');
						//ACM.event.preventDefault(e);
						e.preventDefault();
						return false;
					}
					ACM.delegateFuncs[_css+type](obj, ACM.delegateArgs[_css+type], e); /* then this function may be allowed to execute */
				}
			}
		});
	}
	ACM.delegateTypes[type][css] = true; /* sticks in an associative reference to this css string */
};


/**********************************/
/*		Prototype functions       */
/**********************************/
Date.prototype.month = function(){
	var str = String(this.getMonth()+1);
	if(str.length < 2){
		str = "0" + str;
	}
	return str;
}

Date.prototype.date = function(){
	var str = String(this.getDate());
	if(str.length < 2){
		str = "0" + str;
	}
	return str;
}

/**
 * Returns the week number for this date.  dowOffset is the day of week the week
 * "starts" on for your locale - it can be from 0 to 6. If dowOffset is 1 (Monday),
 * the week returned is the ISO 8601 week number.
 * @param int dowOffset
 * @return int
 */
Date.prototype.getWeek = function (dowOffset) {
/*getWeek() was developed by Nick Baicoianu at MeanFreePath: http://www.meanfreepath.com */

	dowOffset = typeof(dowOffset) == 'int' ? dowOffset : 0; //default dowOffset to zero
	var newYear = new Date(this.getFullYear(),0,1);
	var day = newYear.getDay() - dowOffset; //the day of week the year begins on
	day = (day >= 0 ? day : day + 7);
	var daynum = Math.floor((this.getTime() - newYear.getTime() - 
	(this.getTimezoneOffset()-newYear.getTimezoneOffset())*60000)/86400000) + 1;
	var weeknum;
	//if the year starts before the middle of a week
	if(day < 4) {
		weeknum = Math.floor((daynum+day-1)/7) + 1;
		if(weeknum > 52) {
			nYear = new Date(this.getFullYear() + 1,0,1);
			nday = nYear.getDay() - dowOffset;
			nday = nday >= 0 ? nday : nday + 7;
			/*if the next year starts before the middle of
 			  the week, it is week #1 of that year*/
			weeknum = nday < 4 ? 1 : 53;
		}
	}
	else {
		weeknum = Math.floor((daynum+day-1)/7);
	}
	return weeknum;
};

//Object.prototype.cloneJSON = function(o){
//	return eval(uneval(o));
//}

function serialize(_obj)
{
   // Let Gecko browsers do this the easy way
   if (typeof _obj.toSource !== 'undefined' && typeof _obj.callee === 'undefined')
   {
      return _obj.toSource();
   }
   // Other browsers must do it the hard way
   switch (typeof _obj)
   {
      // numbers, booleans, and functions are trivial:
      // just return the object itself since its default .toString()
      // gives us exactly what we want
      case 'number':
      case 'boolean':
      case 'function':
         return _obj;
         break;

      // for JSON format, strings need to be wrapped in quotes
      case 'string':
         return '\'' + _obj + '\'';
         break;

      case 'object':
         var str;
         if (_obj.constructor === Array || typeof _obj.callee !== 'undefined')
         {
            str = '[';
            var i, len = _obj.length;
            for (i = 0; i < len-1; i++) { str += serialize(_obj[i]) + ','; }
            str += serialize(_obj[i]) + ']';
         }
         else
         {
            str = '{';
            var key;
            for (key in _obj) { str += key + ':' + serialize(_obj[key]) + ','; }
            str = str.replace(/\,$/, '') + '}';
         }
         return str;
         break;

      default:
         return 'UNKNOWN';
         break;
   }
}
