// Cantaloupe Web Commons
// Copyright (C) 2009 Darien Brown (dobrown AT webdesignery DOT com)
// All rights reserved.

var CWC = {
	version: 2.2
};

// modify prototypes
Array.prototype.find = function(field, value) {
	var idx = -1;
	for(var i = 0; i < this.length; i++) {
		if(typeof this[i][field] == 'undefined') continue;
		if(this[i][field] == value) idx = i;
	}
	return idx;
};

/* In-place modification of elements */
Array.prototype.modify = function(fn, bind) {
	for(var i = 0; i < this.length; i++) {
		this[i] = fn.call(bind, this[i], i, this);
	}
};


// Template is adapted from RND at amix.dk
CWC.Template = new Class({
	initialize: function(tmpl, defaults, scope) {
		this.tmpl = tmpl;
		this.defaults = defaults;
		this.scope = scope || window;
	},
	exec: function(obj) {
		var output = '';
		
		var rxEVAL = /\{%=((.|\n)*?)\}/g;
		var rxVAR = /$\{[A-Za-z0-9_.]*\}/g;
		var rxFIELD = /%\(([A-Za-z0-9,_|.]*)\)/g;
		var rxIF = /@IF\(([A-Za-z0-9,_|.]*)\)((.|\n)*?)@ENDIF/g;
		var rxEACH = /@EACH\(([A-Za-z0-9_]*)\)((.|\n)*?)@END/g;
		
		var fnEVAL = (function(w, g) {
			var result = '';
			try { result = eval(g); }
			catch(e){}
			return result;
		}).bind(this);
		
		var fnFIELD = (function(w, g) {
			g = g.split('|');
			f = (g[0]).split(',');
			var cnt = null;
			var fields = [];
			if(f.length == 1) cnt = (typeof obj[g[0]] != 'undefined' && obj[g[0]] != null) ? obj[g[0]] : this.defaults[g[0]];
			else for(var i = 0; i < f.length; i++) fields[i] = (obj[f[i]]) ? obj[f[i]] : this.defaults[f[i]];
			for(var i = 1; i < g.length; i++) {
				if(f.length == 1) cnt = this.scope[g[i]](cnt);
				else cnt = this.scope[g[i]](fields);
			}
			if(cnt === 0 || cnt === -1 || cnt === false) cnt += '';
			return cnt ? cnt : (this.defaults[g[0]] ? this.defaults[g[0]] : (this.blanks ? '' : w));
		}).bind(this);
		
		var fnIF = (function(w, g, txt) {
			g = g.split('|');
			var fields = (g[0]).split(',');
			var result = true;
			var data;
			
			if(fields.length == 1) {
				data = (typeof obj[g[0]] != 'undefined' && obj[g[0]] != null) ? obj[g[0]] : this.defaults[g[0]];
				if(typeof data == 'undefined' || data == null) result = false;
			} else {
				data = [];
				for(var i = 0; i < fields.length; i++) data[i] = (obj[fields[i]]) ? obj[fields[i]] : this.defaults[fields[i]];
			}
			
			for(var i = 1; i < g.length; i++) result = this.scope[g[i]](data) && result;
			
			return result ? txt : '';
		}).bind(this);
		
		var fnEACH = (function(w, g, txt) {
			if(!obj[g] || obj[g] == null || !obj[g].length) return '';
			var tmpl2 = new Template(txt, this.defaults, this.scope);
			
			var result = '';
			for(var i = 0; i < obj[g].length; i++)
				result += tmpl2.exec(obj[g][i]);
			
			return result;
		}).bind(this);
		
		
		output = this.tmpl.replace(rxEVAL,fnEVAL);
		output = output.replace(rxVAR,fnEVAL);
		
		output = output.replace(rxEACH,fnEACH);
		output = output.replace(rxIF,fnIF);
		
		output = output.replace(rxFIELD,fnFIELD);
		return output;
		/*
		var fn = (function(w, g) {
			g = g.split('|');
			var cnt = (obj[g[0]]) ? obj[g[0]] : this.defaults[g[0]];
			for(var i = 1; i < g.length; i++) cnt = this.scope[g[i]](cnt);
			if(cnt == 0 || cnt == -1) cnt += '';
			return cnt ? cnt : (this.defaults[g[0]] ? this.defaults[g[0]] : w);
		}).bind(this);
		return this.tmpl.replace(/%\(([A-Za-z0-9_|.]*)\)/g,fn);
		*/
	}
});

var GetParams = (function() {
	var result = new Array();
	var locvartemp = ( document.location.href.indexOf( "?" ) + 1 ) ? document.location.href.substr( document.location.href.indexOf( "?" ) + 1 ) : "";
	locvartemp = locvartemp.split( "&" );
	for( var x = 0; x < locvartemp.length; x++ ) {
		var lvTempVar = locvartemp[x].split( "=" );
		result[ unescape( lvTempVar[0] ) ] = unescape( lvTempVar[1] );
	}
	return result;
})();

CWC.Formats = {
	currency: function(num) {
		if($type(num) == 'undefined' || num == null) num = "0";
		num = num.toString().replace(/\$|\,/g,'');
		if(isNaN(num)) num = "0";
		sign = (num == (num = Math.abs(num)));
		num = Math.floor(num*100+0.50000000001);
		cents = num%100;
		num = Math.floor(num/100).toString();
		if(cents<10) cents = "0" + cents;
		for (var i = 0; i < Math.floor((num.length-(1+i))/3); i++)
			num = num.substring(0,num.length-(4*i+3))+','+
		num.substring(num.length-(4*i+3));
		return (((sign)?'':'-') + '$' + num + '.' + cents);
	},
	hex: function(num) {
		var digits = ['0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'];
		var exp = 65536;
		var result = '0x';
		for(var exp = 65536; exp >= 1; exp = exp/16) {
			if(num >= exp) {
				result += digits[Math.floor(num/exp)];
				num -= exp * Math.floor(num/exp);
			} else {
				result += '0';
			}
		}
		return result;
	},
	
	date: function(d, format) {
		format = format.toUpperCase();
		var re = /^(M|MM|D|DD|YYYY)([\-\/]{1})(M|MM|D|DD|YYYY)(\2)(M|MM|D|DD|YYYY)$/;
		if (!re.test(format)) { format = "MM/DD/YYYY"; }
		if (format.indexOf("M") == -1) { format = "MM/DD/YYYY"; }
		if (format.indexOf("D") == -1) { format = "MM/DD/YYYY"; }
		if (format.indexOf("YYYY") == -1) { format = "MM/DD/YYYY"; }
		
		var M = "" + (d.getMonth()+1);
		var MM = "0" + M;
		MM = MM.substring(MM.length-2, MM.length);
		var D = "" + (d.getDate());
		var DD = "0" + D;
		DD = DD.substring(DD.length-2, DD.length);
		var YYYY = "" + (d.getFullYear()); 
		
		var sep = "/";
		if (format.indexOf("-") != -1) { sep = "-"; }
		var pieces = format.split(sep);
		var result = "";
		
		switch (pieces[0]) {
			case "M" : result += M + sep; break;
			case "MM" : result += MM + sep; break;
			case "D" : result += D + sep; break;
			case "DD" : result += DD + sep; break;
			case "YYYY" : result += YYYY + sep; break;
		}
		switch (pieces[1]) {
			case "M" : result += M + sep; break;
			case "MM" : result += MM + sep; break;
			case "D" : result += D + sep; break;
			case "DD" : result += DD + sep; break;
			case "YYYY" : result += YYYY + sep; break;
		}
		switch (pieces[2]) {
			case "M" : result += M; break;
			case "MM" : result += MM; break;
			case "D" : result += D; break;
			case "DD" : result += DD; break;
			case "YYYY" : result += YYYY; break;
		}
		return result;
	},
	
	byteSize: function(num) {
		if($type(num) == 'undefined' || num == null) num = "0";
		if (num >= 1073741824) {
		     num = this.integer(num / 1073741824, 2, '.', '') + ' Gb';
		} else if (num >= 1048576) {
			num = this.integer(num / 1048576, 2, '.', '') + ' Mb';
		} else if (num >= 1024) {
			num = this.integer(num / 1024, 0) + ' Kb';
		} else {
			num = this.integer(num, 0) + ' bytes';
		}
		
		return num;
	},
	
	dateEpoch: function(num) {
		var d = new Date(num);
		return this.date(d, 'YYYY/MM/DD');
	},
	
	deAmp: function(str) { return str.replace(/&/g, '&amp;'); },
	integer: function(num) { return parseInt(num); },
	error: function(data) { return '<p style="#990000">'+data+'</p>'; }
};

CWC.Events = {	
	addEvent: function(obj, type, fn, name) {
		name = name ? name : 'default';
		obj[name] = fn;
		if(obj.addEventListener) {
			obj.addEventListener(type,fn,false);
		} else if(obj.attachEvent) {
			obj.attachEvent('on'+type, obj[name]);
		}
	},
	removeEvent: function(obj, type, fn, name) {
		name = name ? name : 'default';
		if(obj.removeEventListener) {
			obj.removeEventListener(type, fn, false);
		} else if(obj.detachEvent) {
			obj.detachEvent('on'+type, obj[name]);
		}
		obj[name] = null;
	},
	loadEvents: [],
	addLoadEvent: function(fn, uid) {
		if(typeof window.onload != 'function') {
			window.onload = CWC.Events.execLoadEvents;
		}
		CWC.Events.loadEvents[uid] = fn;
	},
	removeLoadEvent: function(uid) {
		CWC.Events.loadEvents[uid] = null;
	},
	execLoadEvents: function() {
		for(var uid in CWC.Events.loadEvents) {
			if(CWC.Events.loadEvents[uid] != null && typeof CWC.Events.loadEvents[uid] == 'function' && !Array.prototype[uid])
				(CWC.Events.loadEvents[uid])();
		}
	}
};

CWC.History = {
	handlers: [],
	addListener: function(fn, uid) {
		CWC.History.handlers[uid] = fn;
	},
	removeListener: function(uid) {
		CWC.History.handlers[uid] = null;
	},
	proc: function(uid, data) {
		if(CWC.History.handlers[uid] != null && typeof CWC.History.handlers[uid] == 'function' && !Array.prototype[uid])
			(CWC.History.handlers[uid])(data);
	},
	bcast: function(data) {
		for(var uid in CWC.History.handlers) {
			CWC.History.proc(uid, data);
		}
	},
	update: function(uid, data, name) {
		document.getElementById('history').src = "http://b2b.monroehardware.com/history.asp?uid="+uid+"&data="+data+"&name="+name;
	}
};



// simple history function...
// entry point from history frame
function doHistory(uid, data) {window.setTimeout('CWC.History.proc("'+uid+'","'+data+'")',0);}



CWC.UI = {};
CWC.UI.Loading = new Class({
	Binds: ['destroy','hide'],
	Implements: [Events, Options],
	options: {
		onShow: function(stick) {
			if(this.options.fade) this.stick.setStyle('opacity', 0);
			this.stick.setStyle('visibility', 'visible');
			if(this.options.fade) this.stick.fade('in');
		},
		onHide: function(stick) {
			if(this.options.fade) this.stick.fade('out');
			else this.stick.setStyle('visibility', 'hidden');
		},
		width: 200,
		height: 100,
		title: '',
		text: '',
		className: null,
		fade: true,
		relativeTo: document.body,
		position: 'center'
	},
	
	initialize: function(opts) {
		if(opts) this.setOptions(opts);
		this.stick = this.getStick();
	},
	
	getStick: function() {
		var outer = new Element('div', {
				'class': 'tip',
				'styles': {
					'visibility': 'hidden',
					'display': 'none',
					'position': 'absolute',
					'top': 0,
					'left': 0,
					'z-index': 10000
				},
				'width': this.options.width,
				'height': this.options.height
		});
		
		outer.adopt(new Element('div', {'class': 'tip-title'}).appendText('Loading...'));
		outer.adopt(new Element('div', {'class': 'tip-text'}).appendText('Please wait while information is retrieved from the server.'));
		outer.inject(document.body);
		
		return outer;
	},
	
	position: function() {
		this.stick.setStyle('display', 'block');
		this.stick.position({
			relativeTo: this.options.relativeTo,
			position: this.options.position
		});
	},
	
	show: function() {
		this.position();
		//this.fireEvent('show', [this.stick]);
		if(this.options.fade) this.stick.setStyle('opacity', 0);
		this.stick.setStyle('visibility', 'visible');
		if(this.options.fade) this.stick.fade('in');
	},
	
	hide: function() {
		//this.fireEvent('hide', [this.stick]);
		if(this.options.fade) this.stick.fade('out');
		else this.stick.setStyle('visibility', 'hidden');
	},
	
	destroy: function() {}
});

(function() {
	var contentDiv = null;
	var errorDiv = null;
	var ajax = null;
CWC.ClientPlug = new Class({
	Implements: [Options, Events],
	Binds: ['go','callback','error'],
	
	options: {
		guid: 'undefined',
		version: 0,
		content: 'plug_content',
		error: 'plug_error',
		
		dataSource: 'data',
		bindGo: false,
		
		errorMessage: '<b>Error Loading Data</b><br/>There was a problem loading the data you selected. Your session may have timed out. Please log out and log back in.'
	},
	
	url: null,
	data: null,
	history: {
		current: null,
		last: null
	},
	
	initialize: function(options) {
		this.setOptions(options);
		
		contentDiv = $(this.options.content);
		errorDiv = $(this.options.error);
		
		CWC.Events.removeLoadEvent(this.options.guid);
		if(this.options.bindGo)
			CWC.History.addListener(this.go, this.options.guid);
		
		this.fireEvent('init');
		
		this.start();
	},
	start: function() {
		this.fireEvent('url');
		var url = this.url ? this.url : 'ajax.asp?proc='+this.options.dataSource+'&id='+this.history.current;
		ajax = new Request({
			url: url,
			method: 'get',
			onComplete: this.callback,
			onFailure: this.error
		}).send();
		this.showDialog();
	},
	go: function(id) {
		if(id == this.history.current) return;
		
		this.history.last = this.history.current;
		this.history.current = id;
		
		this.start();
		
		CWC.update(this.options.guid, id, "");
	},
	callback: function(result) {
		try {
			this.data = eval('('+result+')');
		} catch(e) {
			this.error();
		}
		
		ajax = null;
		this.hideDialog();
		this.fireEvent('render');
	},
	error: function() {
		try {
			this.hideDialog();
			if(this.ajax != null) {
				alert(this.ajax.transport.readyState+" "+this.ajax.transport.responseText);
				this.ajax = null;
			}
			
			errorDiv.set('html', errMsg);
		} catch(e) {}
	},
	
	dialog: null,
	showDialog: function() {
		if(this.dialog == null) this.dialog = new CWC.UI.Loading();
		this.dialog.show();
	},
	hideDialog: function() {
		if(this.dialog != null) this.dialog.hide();
	}
});
})();

/****************************************************************************************
NEW TABLE UI ADDITIONS BETA
****************************************************************************************/
CWC.ColumnInfo = new Hash({
	id: 'id',
	title: 'Column',
	type: 'string',
	width: 50,
	format: null,
	proc: null
});

CWC.TableModel = new Class({
  Implements: [Options, Events],
  Binds: ['fetchSuccess','fetchFailure','error'],
  
  options: {
	url: '/ajax.asp',
	dataSource: 'data',
	method: 'get'
  },
  
  ajax: null,
  url: null,
  args: null,
  
  schema: null,
  data: null,
  meta: null,
  
  initialize: function(options) {
	this.setOptions(options);
	
	this.args = $H();
	
	this.fireEvent('init');
  },
  
  fetch: function() {
	// fetchInit to setup args and url
	this.fireEvent('fetchInit');
	
	this.mkURL();
	
	// fetchStart to display loading message, etc.
	this.fireEvent('fetchStart');
	
	this.ajax = new Request({
	  url: this.url,
	  method: this.options.method,
	  onSuccess: this.fetchSuccess,
	  onException: this.error,
	  onFailure: this.fetchFailure
	});
	
	
	try {
	  this.ajax.send(this.postData);
	} catch(e) {
	  this.error(e.name, e.message);
	}
  },
  
  fetchSuccess: function(response) {
	try {
	  var json = eval('(' + response + ')');
	  
	  this.schema = json['schema'] ? $A(json['schema']) : $A();
	  this.data = json['data'] ? $A(json['data']) : $A();
	  this.meta = json['meta'] ? $H(json['meta']) : $H();
	  
	  // merge with default values for columnInfo
	  if(this.data) this.schema.modify(function(col, i, schema) {
		return $H(col).combine(CWC.ColumnInfo);
	  });
	  
	} catch(e) {
	  this.error(e.name, e.message);
	}
	
	// fetchEnd to remove loading message, render, etc.
	this.fireEvent('fetchEnd');
  },
  
  fetchFailure: function(xhr) {
	this.error('XHR Failure', 'Error fetching data from server.');
  },
  
  // generate the url for request
  mkURL: function() {
	this.url = this.options.url;
	this.postData = 'proc=' + this.options.dataSource;
	this.args.each(function(value, key, hash) {
	  this.postData += '&' + key + '=' + escape(value);
	}, this);
  },
  
  columnInfo: function(id, info) {
	var x = -1;
	if($type(id) == 'number') x = id;
	else x = this.schema.find('id', id);
	return (x < 0) ? null : this.schema[x].get(info);
  },
  
  addColumn: function(info) {
	this.schema.push($H(info).combine(CWC.ColumnInfo));
  },
  
  delColumn: function(id) {
	var x = -1;
	if($type(id) == 'number') x = id;
	else x = this.schema.find('id', id);
	if(x > 0) this.schema.splice(x, 1);
  },
  
  columnCount: function() {
	return this.schema.length;
  },
  
  rowCount: function() {
	return this.data.length;
  },
  
  getRow: function(x) {
	if(x < 0 || x >= this.data.length) return null;
	return this.data[x];
  },
  
  error: function(name, message) {
	name = name ? name : 'Error';
	message = message ? message : 'An unknown error has occurred.';
	this.fireEvent('error', [name, message]);
  }
});

CWC.UI.DataGrid = new Class({
	Implements: [Options, Events],
	Binds: ['render', 'onScroll'],
	
	options: {
		title: 'Table Name',
		place: 'grid',
		showTitle: true,
		showToolbar: false,
		showGuides: false,
		showHeader: true,
		showFooter: false
	},
	
	id: null,
	components: null,
	model: null,
	
	initialize: function(options, model) {
		this.setOptions(options);
		this.setModel(model);
		
		this.id = (new Date()).getTime();
		this.components = $H();
		
		this.components['grid'] = new Element('div', {'class': 'table'});
		this.components['toolbar'] = new Element('div', {'class': 'tableTools'});
		this.components['guides'] = new Element('div', {'id': 'tg' + this.id, 'class': 'tableGuide'});
		this.components['footer'] = new Element('div', {'id': 'tf' + this.id, 'class': 'tableFoot'});
		
		this.components['title'] = new Element('div', {'class': 'tableTitle'});
		this.components['titleTxt'] = new Element('div', {'class': 'tableTitleTxt', text: this.options.title});
		this.components['toggle'] = new Element('div', {'class': 'tableToggle'});
		this.components['title'].adopt(
			this.components['titleTxt'],
			this.components['toggle'],
			'span'
		);
		
		/* HEADER CONSTRUCTION */
		this.components['header'] = new Element('div', {id: 'th' + this.id, 'class': 'tableHead'});
		this.components['headTable'] = new Element('table', {
			border: 0,
			cellpadding: 0,
			cellspacing: 0
		});
		this.components['headRow'] = new Element('thead');
		this.components['headTable'].grab(this.components['headRow']);
		this.components['header'].grab(this.components['headTable']);
		
		
		/* BODY CONSTRUCTION */
		this.components['body'] = new Element('div', {
			id: 'tb' + this.id,
			'class': 'tableBody',
			events: {scroll: this.onScroll}
		});
		this.components['bodyTable'] = new Element('table', {
			border: 0,
			cellpadding: 0,
			cellspacing: 0
		});
		this.components['body'].grab(this.components['bodyTable']);
		
		/* FINAL ASSEMBLY */
		this.components['grid'].adopt(
			this.components['title'],
			this.components['toolbar'],
			//this.components['guides'],
			this.components['header'],
			this.components['body'],
			this.components['footer']
		);
		
		$(this.options.place).grab(this.components['grid']);
		
		//this.components['guides'].setStyle('top', this.components['header'].getSize().y);
	},
	
	setModel: function(model) {
		if(this.model != null) {
			this.model.removeEvent('fetchEnd', this.render);
		}
		if(model != null) {
			model.addEvent('fetchEnd', this.render);
		}
		this.model = model;
	},

	render: function() {
		/* HEADERS */
		this.components['headRow'].empty();
		this.model.schema.each(function(item) {
			var th = new Element('th', {
				'class': (item['id'] == this.model.meta.sort ? 'sorted' : '')
			});
						
			var div = new Element('div', {
				'class': (item['id'] == this.model.meta.sort ? 'sasc' : ''),
				styles: {width: item['width']},
				html: item['title']
			});

			th.grab(div);
			this.components['headRow'].grab(th);
		}, this);
		
		/* GUIDES */
		var left = 2;
		var height = this.components['body'].getSize().y + this.components['header'].getSize().y;
		var heads = this.components['headRow'].getElements('th');
		this.components['guides'].empty();
		this.model.schema.each(function(item, x) {
			left = heads[x].offsetLeft + heads[x].offsetWidth - 2;
			//left += item['width'];
			this.components['guides'].grab(new Element('div', {
				styles: {
					display: 'block', 
					left: left,
					height: height
				}
			}));
		}, this);
		
		/* BODY */
		this.components['bodyTable'].empty();
		this.model.data.each(function(item, rec) {
			var tr = new Element('tr', {
				'class': (rec%2==1 ? 'odd' : 'even')
			});
			this.model.schema.each(function(field) {
				var td = new Element('td');
				var div = new Element('div', {
					styles: {width: field['width']},
					html: item[field['id']]
				});
				td.grab(div);
				tr.grab(td);
			});
			this.components['bodyTable'].grab(tr);
		}, this);
	},
	
	onScroll: function() {
		this.components['header'].scrollLeft = this.components['body'].scrollLeft;
		this.components['guides'].scrollLeft = this.components['body'].scrollLeft;
	}
});
