if(!window['com']){ com = {}; }
if(!window['com.aboone']){ com.aboone = {}; }

String.format = function (B) {var A=Array.prototype.slice.call(arguments,1);return B.replace(/\{(\d+)\}/g,function(C,D){return A[D]});};

var months = ['January','February','March','April','May','June','July','August','September','October','November','December'];

com.aboone.isIPhoneUserAgent = window.navigator.userAgent.match(/iPhone/);

var DateTimeUtils = (function(){
	return {
		parseDateTime : function(ts) {
			var d = ts ? new Date(ts*1000) : new Date();
			var parts = [
				d.getFullYear(),
				d.getMonth() + 1,
				d.getDate(),
				d.getHours(),
				d.getMinutes(),
				d.getSeconds()
			];
			return parts;
		},
		format : function(ts, fmt) {
			var d = ts ? new Date(ts*1000) : new Date();
			
			var utils = this;
			var r = fmt.replace(/./g, function(m){
				switch(m) {
					case 'A': return d.getHours() < 12 ? 'AM' : 'PM';
					case 'G': return d.getHours();
					case 'H': return utils.pad(d.getHours());
					case 'F': return months[d.getMonth()];
					case 'Y': return d.getFullYear();
					case 'a': return d.getHours() < 12 ? 'am' : 'pm';
					case 'd': return utils.pad(d.getDate());
					case 'g': return d.getHours() == 0 ? 12 : d.getHours() % 12;
					case 'h': return utils.pad(d.getHours() == 0 ? 12 : d.getHours() % 12);
					case 'i': return utils.pad(d.getMinutes());
					case 'j': return d.getDate();
					case 'm': return utils.pad(d.getMonth());
					case 's': return utils.pad(d.getSeconds());
					default:  return m;
				}
			});
			return r;
		},
		pad : function(num, len) {
			if (len === undefined) {
				len = 2;
			}
			var str = num + '';
			if (num < Math.pow(10, len-1)) {
				var n = len - str.length;
				for (var i=0; i<n; i++) {
					str = '0' + str;
				}
			}
			
			return str;
		}
	};
})()

var PrivateUtils = (function() {
	return {
		map: function(arr, maskFn, labelFn){
			var map = [];
			if(!labelFn) labelFn = maskFn;
			for(var i=0; i<arr.length; i++){
				var mask = maskFn(arr[i]);
				if(map[mask] == undefined){ map[mask] = {label:labelFn(arr[i]), data:[]}; }
				map[mask].data.push(arr[i]);
			}
			return map;
		}
	
		,numberFormat: function(n){
			n = n.toFixed(2);
			var rgx = /(\d+)(\d{3})/;
			while (rgx.test(n)) {
				n = n.replace(rgx, '$1' + ',' + '$2');
			}
			return n;
		}

		,sel:function(attrs,min,max,step,val,renderer,valueRenderer){
			var html = '';
			for(var x in attrs) if(attrs.hasOwnProperty(x)){ attrHtml+=' '+x+'="'+attrs[x]+'"'; }
			html = '<select'+html+'>';
			for(var i=min; i<=max; i=i+step){
				var sel = val == i ? ' selected' : '';
				var valAttr = '';
				if (valueRenderer) {
					valAttr = String.format(' value="{0}"', valueRenderer(i));
				}
				html += '<option'+valAttr+sel+'>'+(renderer ? renderer(i) : i)+'</option>';
			}
			html += '</select>';
			return html;
		}
	};
})()

/*
 * Ideal class hierarchy:
 *  Timer => Simple start/stop/update functions
 *  |_ PersistentTimer => Connected to web service
 *      - show log (T/F)
 *      - show slices (T/F)
 *      - show charts (T/F)
 *     |_ CompleteTimer => has the stuff above enabled
 */

com.aboone.Timer = function(config){
	return {
		updateTitle: config.updateTitle || false
		,showSlices: config.showSlices || false
		,displayFormat: config.displayFormat
		,displayPrefix: config.displayPrefix || ''
		,displaySuffix: config.displaySuffix || ''
		,listeners: config.listeners || []
		
		// TODO: timer props - clean this up
		,name: config && config.name
		,id: config && config.id
		,mode: config.mode || 'total'
		,color: config.color || 'FFFFFF'
		,rate: config.rate ? config.rate/1 : 0
		,meta: config.meta || []
		,periods: config.periods || []
		
		,init: function(){
			this.timerEl = config.timerEl || $('#timer');
			
			this.curElapsed = 0;
			this.elapsedTime = this.timerEl.attr('rel') ? this.timerEl.attr('rel') : 0;
			
			// set start time, adjusted if necessary
			this.startTime = Math.floor(new Date()/1000 - this.getElapsedTime());
			
			this.display();
			
			if(this.isRunning()){
				this.start();
			}
			
			if (this.showSlices) this.initSlices();
			
			if (config.showControls || typeof config.controlConfig == 'object') {
				// copy over config for now, TODO: clean up
				if (config.controlConfig) {
					this.ajaxControls = config.controlConfig.ajax || false;
				}
				this.initControls();
			}
			
			if (config.showLog || typeof config.logConfig == 'object') {
				this.log = new com.aboone.TimerLog(this, config.logConfig || {});
				this.log.init();
			}
		}
		
		,getElapsedTime : function(){
			var mode = this.mode.replace(/^\$/, '');
			if(this.meta && this.meta[mode] !== undefined){
				return this.meta[mode] + this.curElapsed;
			} else if(this.startTime && this.isRunning()){
				return Math.floor(new Date()/1000) - this.startTime;
			} else {
				return this.elapsedTime;
			}
		}
		
		,isRunning: function(){
			return this.timerEl.hasClass('running') || this.timerEl.parent().hasClass('running');
		}
		
		,start : function(){
			if(this.timer){
				this.stop();
			}
			
			this.timerEl.addClass('running');
			
			var that = this
			this.timer = window.setInterval(function(){
				that.update();
				that.display();
			}, 1000);
		}
		
		,stop : function(){
			window.clearInterval(this.timer);
			delete this.timer;
			this.timerEl.removeClass('running');
		}
		
		,update: function(){
			this.curElapsed++;
		}
		
		,display: function(){
			if(this.rate && this.mode && this.mode.charAt(0) == '$'){
				var value = '$'+PrivateUtils.numberFormat(this.getElapsedTime()/3600*this.rate);
			} else {
				var et = new com.aboone.ElapsedTime(this.getElapsedTime());
				var time = et.display({
					format: this.displayFormat,
					zero: "0",
					showFractions: false
				});
				var value = this.displayPrefix+time+this.displaySuffix;
			}
			
			this.timerEl.html(value);
			if(this.updateTitle){
				$('title').html(this.name + ': ' + value + ' ('+this.mode+')');
			}
		}
		
		// controls
		,initControls: function(){
			var act = this.isRunning() ? 'stop' : 'start';
			var t = ' <a href="/{name}/{act}" class="timer-controls {act}">{act}</a>';
			this.timerEl.after(t.replace(/\{name\}/g,this.name).replace(/\{act\}/g, act));
			
			if (this.ajaxControls){
				var that = this;
				this.timerEl.next('.timer-controls').click(function(){
					var el = $(this);
					var fn = $(this).attr('href').replace(/.*\//, '');
					$.get($(this).attr('href'), {noredirect: true}, function(){
						el.removeClass(fn);
						that[fn]();
						
						fn = fn == 'start' ? 'stop' : 'start';
						el.addClass(fn).html(fn).attr('href', '/' + that.name + '/' + fn);
					});
					return false;
				});
			}
		}

		// preference logic
		,setPref : function(key, value) {
			var that = this;
			$.post('/pref.php', {name: key, value: value, timerId: this.id}, function(pref){
				if (pref) {
					if (!that.prefs) {
						that.prefs = {};
					}
					that.prefs[pref.name] = pref.value;
				}
			}, 'json');
		}

		// event handlers - utility?
		,fire: function(event, args){
			if (this.listeners[event]){
				for (var i=0; i<this.listeners[event].length; i++){
					this.listeners[event][i](args);
				}
			}
		}

		// event handlers - utility?
		,addListener: function(event, fn){
			if (!this.listeners[event]){ this.listeners[event] = []; }
			this.listeners[event].push(fn);
		}
		
		,refresh: function(){
			var that = this;

			$.get('/rest/timer.php', {id: this.id}, function(data){
				that.meta = data.timer[0].meta;
				that.display();
				if (that.log) {
					that.log.refresh();
				}
			},'json');
		}
		
		,save : function(){
			$.post('/rest/timer.php', {
				_delta : true,
				id: this.id,
				mode: this.mode,
				rate: this.rate || 0
			});
		}
		
		// clean up concept of mode
		,toggleMode: function(){
			if (this.mode && this.mode.charAt(0) == '$'){
				this.mode = this.mode.substr(1); 
				$('#modeToggleLink').addClass("timeModeLink").removeClass('moneyModeLink');
			} else {
				this.mode = '$'+this.mode;
				$('#modeToggleLink').removeClass("timeModeLink").addClass('moneyModeLink');
				
				//TODO: improve this solution
				if(!this.rate){
					this.rate = window.prompt("Enter an hourly rate");
				}
			}
			this.save();
			this.display();

			this.fire('changemode', [this.mode]);
		}
		
		,updateMode: function(mode){
			if(this.mode && this.mode.charAt(0) == '$'){
				this.mode = '$'+mode;
			} else {
				this.mode = mode;
			}
			this.display();
			this.save();
				
			$('h4 a.active').removeClass("active");
			$('h4 a[title="' + mode + '"]').addClass('active');

			this.fire('changemode', [this.mode]);
		}

		/* begin slices */
		,initSlices : function() {
			var that = this;
			
			$.get('/slices.php', {timerId: this.id}, function(data){
				var custom = false;
				var html = '';
				
				for (var i=0; i<data.slice.length; i++) {
					var s = data.slice[i];
					
					if (!custom && s.id) {
						html += '<br/>';
						custom = true;
					}
					
					html += that.sliceMarkup(s, custom);
				}
				
				html += '<a id="slice-edit" href="javascript:timer.newSlice()"><span>edit</span></a>';
				
				$('#slices').html(html).click(function(e){
					if (e.target.nodeName == "A" && e.target.id && e.target.id.match(/^slice-/)) {
						that.updateMode($(e.target).text());
					}
				});
				
				$('a.slice[title="' + that.currentSlice() + '"]').addClass('active');
				
				// slice deletion
				$('#slices').dblclick(function(e){
					if ($(e.target).is('a.custom-slice') && !$(e.target).next('.delete-slice').length) {
						$(e.target).after('<a class="delete-slice" href="javascript:timer.deleteSlice(\'' + e.target.id.replace('slice-','') + '\')">x</a>');
					}
				})
			}, 'json');
		}
		
		,currentSlice : function() {
			if (this.mode && this.mode.charAt(0) == '$') {
				return this.mode.substr(1);
			} else {
				return this.mode;
			}
		}
		
		,sliceMarkup : function(slice, custom) {
			var link = '<a class="slice ~" id="slice-%" title="@" href="javascript:;">@</a>';
			var id = slice.id ? slice.id : slice.name;
			
			return link.replace(/~/, custom ? 'custom-slice' : '').replace(/%/, id).replace(/@/g, slice.name);
		}
		
		,newSlice : function() {
			var that = this;
			
			if (!$('.custom-slice').length) {
				$('#slice-edit').before('<br/>');
			}
			
			$('#slice-edit').before('<input class="new-slice" />');
			$('.new-slice:last').focus(function(){
				$(this).addClass('focused');
			}).blur(function(){
				var val = $(this).val();
				
				if (val) {
					var input = $(this);
					var slice = {name: val, search: val, timerId: that.id};
					$.post('/rest/slice.php', slice, function(data){
						that.refresh();
						input.before(that.sliceMarkup(data, true)).remove();
						that.updateMode(val);
					}, 'json');
				}
				
				$(this).removeClass('focused');
			}).keyup(function(e){
				if (e.keyCode == 13) {
					$(this).blur();
				}
			}).focus();
		}
		
		,deleteSlice : function(id) {
			if (!id) {
				return;
			}
			
			var that = this;
			
			var el = $('#slice-' + id);
			var del = el.next();
			var slice = el.attr("title");
			
			$.post('/rest/slice.php', {'delete' : '{"timerId":' + that.id + ', "id":' + id + '}'}, function(data){	
				el.remove();
				del.remove();
				
				that.updateMode('current');
			});
		}
		/* end slices */
	};
}

com.aboone.TimerLog = function(timer, config) {
	return {
		elapsedTimeFormat: config.elapsedTimeFormat || 'hms',
		timeFormat: config.timeFormat || 'H:i:s',
		dateHeaderFormat: config.dateHeaderFormat || 'F j, Y',
		pageSize: config.pageSize || 10,
		curPage: config.curPage || 0,
		timer: timer,
		periods: timer.periods || [],
		
		init: function(){
			var that = this;
			if (!$('ol#log').length) {
				// initialize the log container
				$('#logWrap').append('<div class="addPeriod">Add Time</div>');
				$('#logWrap').append('<ol id="log"></ol>');
				$('#logWrap').append('<div class="log-links">'
								+'<a class="log-prev" href="javascript:;">&laquo; newer</a>'
								+' <a class="log-next" href="javascript:;">older &raquo;</a>'
								+' <a class="log-toggle" href="javascript:;">hide log</a>'
							+'</div>');

				$('#logWrap .log-links a').click(function(){
					if ($(this).hasClass('log-toggle')) {
						that.toggleLog();
					} else {
						that.curPage = that.curPage + ($(this).hasClass('log-next') ? 1 : -1);
						that.draw();
					}
				})
				
				$('#logWrap .addPeriod').click(function(){
					that.addNewPeriod();
				});
				
				$('#log').click(function(e) {
					var tgt = $(e.target);
					if (tgt.hasClass('.start') || tgt.hasClass('.stop')) {
						var periodId = tgt.parents('.event').attr('id').replace('period-','');
						that.togglePeriodEdit(periodId, true);
					} else if (tgt.is('.saveButton')) {
						var wrap = tgt.parents('.event');
						var periodId = wrap.attr('id').replace('period-','');
						
						var ts = function(el){
							var s = [];
							if (el) {
								el.find('select').each(function(){
									s.push($(this).val());
								});
								if (s.length) {
									var d = new Date(s[2], s[0] - 1, s[1], s[3], s[4], s[5], 0);
									return d/1000;
								}
							}
						}
						
						var start = ts(wrap.find('.start'));
						var stop = ts(wrap.find('.stop'));
						var name = wrap.find('.periodName').val();
						
						var params = {id: periodId};
						params.start = start
						if (stop) {
							params.stop = stop;
						}
						
						params.timerId = that.timer.id;
						params.name = name;
						
						if (periodId == 'new') {
							delete params.id;
						} else {
							params._delta = true;
						}

						$.post('/rest/period.php', params, function(data){
							that.timer.refresh();
						},'json');
					} else if (tgt.is('.cancelLink')) {
						var periodId = tgt.parents('.event').attr('id').replace('period-','');
						that.togglePeriodEdit(periodId, false);
					} else if (tgt.hasClass('delete')) {
						var periodId = tgt.parents('.event').attr('id').replace('period-','');
						if(periodId && !isNaN(periodId) && window.confirm("Are you sure you want to delete this entry? It cannot be recovered!")){
							$.post('/rest/period.php', {'delete': '{"id":'+periodId+'}'}, function(){
								that.timer.refresh();
							});
						}
					} else if (tgt.hasClass('editDate')) {
						tgt.parents('.event').addClass('event-edit-dates');
						tgt.hide();
					}
				});
			}
			
			this.refresh();
		}
		
		,draw : function() {
			var that = this;
			var map = PrivateUtils.map(this.periods, function(o){
				return DateTimeUtils.format(o.start, 'Y-m-d');
			}, function(o){
				return DateTimeUtils.format(o.start, that.dateHeaderFormat)
			});
			
			var sortedMap = [];
			for(var d in map){
				map[d].sortRef = d;
				sortedMap.push(map[d]);
			}
			sortedMap.sort(function(a, b){
				return (a.sortRef < b.sortRef) ? 1 : -1;
			});
			
			var maxPage = Math.ceil(sortedMap.length / this.pageSize) - 1;
			var minPage = 0;
			
			this.curPage = Math.max(minPage, Math.min(this.curPage, maxPage));
			
			var html = '';
			var startIndex = this.pageSize * this.curPage;
			
			for(var j=startIndex; j<sortedMap.length && j<startIndex+this.pageSize; j++){
				var tot = 0;
				var running = false;
				var curHtml = '';
				sortedMap[j].data.sort(function(a,b){
					return a.start < b.start ? 1 : -1;
				});
				for(var i=0; i<sortedMap[j].data.length; i++){
					var p = sortedMap[j].data[i];
					
					running = running || p.running;
					tot += p.elapsed;
					
					curHtml += this.logEntry(p);
				}
				
				var total = new com.aboone.ElapsedTime(tot);
				var displayTime = total.display({format: this.elapsedTimeFormat});
				
				html += '<li class="dayLabel">'+sortedMap[j].label+' <span class="summary'+(running?' running':'')+'" rel="'+tot+'">'+displayTime+'</span></li>';
				html += curHtml;
			}
			$('ol#log').html(html);
			
			$('.summary.running').each(function(){
				var it = new com.aboone.Timer({
					name: '',
					timerEl: $(this),
					displayFormat: that.elapsedTimeFormat
				});
				it.init();
			});

			$('.log-prev')[this.curPage == minPage ? 'hide' : 'show']();
			$('.log-next')[this.curPage == maxPage ? 'hide' : 'show']();
			
			this.addPeriodEventListeners($('li.event'));
		}
		
		,refresh : function() {
			var that = this;
			$.get('/rest/period.php', {timerId: this.timer.id}, function(data){
				that.periods = data.period;
				that.draw();
			}, 'json');
		}
		
		,toggleLog: function(){
			if($('#logWrap.hidden').length){
				$('#logWrap .log-toggle').text('hide log');
				$('#logWrap').removeClass('hidden');
			} else {
				$('#logWrap .log-toggle').text('show log');
				$('#logWrap').addClass('hidden');
			}
		}
		
		,addPeriodEventListeners : function(el) {
			var that = this;
			
			el.mouseover(function(){ $(this).addClass('over'); })
				.mouseout(function(){ $(this).removeClass('over'); });
			el.find('.periodName')
				.focus(function(){ $(this).addClass('focused'); })
				.blur(function(){ $(this).removeClass('focused')})
				.change(function(){ that.savePeriodName(this); });
		}
		
		,logEntry : function(p) {
			var unpaid = !p.lineItemId || p.lineItemId == "0";
			var et = new com.aboone.ElapsedTime(p.elapsed);
			
			var html = '';
		 	html += '<li class="event '+(unpaid?'unpaid':'paid')+'" id="period-'+p.id+'">';
			html += '<a href="javascript:;" class="delete"><span>delete</span></a>';
			html += '<span class="start" title="click to edit">'+DateTimeUtils.format(p.start, this.timeFormat)+'</span>'
			if (p.stop != 0) {
				html += '<span class="stop" title="click to edit">'+DateTimeUtils.format(p.stop, this.timeFormat)+'</span>'
			}
			html += '<span class="meta">';
			html += '<span class="summary'+(p.running?' running':'')+'" rel="'+p.elapsed+'">'+et.display({format: this.elapsedTimeFormat})+'</span>';
			html += '<span class="period"><input class="periodName" value="'+p.name+'" rel="'+p.id+'"/></span>'
			html += '</span>';
			html += '</li>';
			return html;
		}
		
		,editLogEntry : function(p) {
			if (!p) {
				var now = new Date() / 1000;
				var p = {
					name: '',
					start : now,
					stop : now
				};
			}
			
			var unpaid = !p.lineItemId || p.lineItemId == "0";
			var et = new com.aboone.ElapsedTime(p.elapsed);
			
			var id = p.id || 'new';
			var extraCls = p.id ? '' : ' event-edit-dates';
			
		 	var html = '<li class="event event-edit'+ extraCls +'" id="period-'+id+'">';
			if (p.id) {
				html += '<a href="javascript:;" class="delete" style="display: block"><span>delete</span></a>';
			}
			html += '<a class="editDate" title="Edit Dates"><span>edit dates</span></a> ';
			
			html += '<span class="start"><span class="dateEditor">' + this.dateEditor(p.start) + "</span> "+ this.timeEditor(p.start) + '</span>';
			
			if (p.stop != 0) {
				html += '<span class="stop"><span class="dateEditor">' + this.dateEditor(p.stop) + "</span> "+ this.timeEditor(p.stop) + '</span>';
			}
			html += '<input type="button" value="save" class="saveButton" />';
			html += ' <a class="cancelLink" href="javascript:;">cancel</a>';
			html += '<span class="meta">';
			//html += '<span class="summary'+(p.running?' running':'')+'" rel="'+p.elapsed+'">'+et.display({format: this.elapsedTimeFormat})+'</span>';
			html += '<span class="period"><br/><input class="periodName" value="' + p.name + '"/></span>'
			html += '</span>';
			html += '</li>';
			return html;
		}
		
		,dateEditor: function(datetime) {
			var parts = DateTimeUtils.parseDateTime(datetime);
			var yr = parts[0]/1;
			
			var fnPad = function(v){ return v > 9 ? v : '0'+v; };
			
			var dateEditor = PrivateUtils.sel([],1,12,1,parts[1],function(v){ return months[v/1 - 1]; },fnPad)
					+' '+PrivateUtils.sel([],1,31,1,parts[2],undefined,fnPad)
					+', '+PrivateUtils.sel([],yr-1,yr+1,1,yr);
					
			return dateEditor;
		}
		
		,timeEditor : function(datetime) {
			var parts = DateTimeUtils.parseDateTime(datetime);
			
			var fnPad = function(v){ return v > 9 ? v : '0'+v; };
			
			var timeEditor = PrivateUtils.sel([],0,23,1,parts[3],fnPad)
					+':'+PrivateUtils.sel([],0,59,1,parts[4],fnPad)
					+':'+PrivateUtils.sel([],0,59,1,parts[5],fnPad);
					
			return timeEditor;
		}

		,savePeriodName : function(el){
			$(el).addClass('saving');
			var t = $('#timerId').val();
			var periodId = $(el).attr('rel');
			var p = {
				_delta: true,
				name: $(el).val().replace(/"/g, '\"'),
				id: periodId
			};
		
			$.post('/rest/period.php', p, function(data){
				$(el).addClass('saved');
				$(el).removeClass('saving');
				window.setTimeout(function(){
					$(el).removeClass('saved');
				},5000);
			},'json');
		}
		
		,getPeriod : function(id) {
			if (!this.periods) return;
			for (var i=0; i<this.periods.length; i++) {
				if (this.periods[i].id == id) {
					return this.periods[i];
				}
			}
		}
		
		,addNewPeriod : function() {
			var html = this.editLogEntry();
			$('#log').prepend(html);
		}
		
		,togglePeriodEdit: function(periodId, on){
			var el = $('#period-' + periodId);
			var p = this.getPeriod(periodId);

			if (on == el.hasClass('event-edit')) {
				return;
			}
			
			if (on){
				el.replaceWith(this.editLogEntry(p));
			} else if(periodId == 'new') {
				el.remove();
			} else {
				el.replaceWith(this.logEntry(p));
			}
			
			this.addPeriodEventListeners($('#period-' + periodId));
		}
		/* / log stuff */
	};
}

com.aboone.ElapsedTime = function(secs){
	var chunks = [
		{singular: 'day', plural: 'days', abbrev: 'd', secs: 86400},
		{singular: 'hour', plural: 'hours', abbrev: 'h', secs: 3600},
		{singular: 'minute', plural: 'minutes', abbrev: 'm', secs: 60},
		{singular: 'second', plural: 'seconds', abbrev: 's', secs: 1}
	];
	return {
		time: secs,
		display: function(config){
			if(config === true) config = {}; // backwards compat
			if(!config) config = {};
			if(!config.format) config.format = 'hms';
			if(config.showFractions !== false) config.showFractions = true;
			if(!config.zero) config.zero = "";
			var displayParts = [];
			
			var chunk = 0;
			while(secs > 0 && chunk < chunks.length){
				if(config.format.toLowerCase().indexOf(chunks[chunk].abbrev) != -1){
					var ct = Math.floor(secs/chunks[chunk].secs);
					
					if(ct > 0){
						var disp = ct;
						if(config.format.indexOf(chunks[chunk].abbrev) == -1){ // uppercase format match, use full
							disp += ' '+chunks[chunk][ct == 1 ? 'singular' : 'plural'];
						} else {	
							disp += chunks[chunk].abbrev;
						}
						displayParts.push(disp);
					}
					secs = secs % chunks[chunk].secs;
				}
				chunk++;
			}
			if(displayParts.length){
				return displayParts.join(' ');
			} else if(config.showFractions){
				var smallest = '';
				var i=chunks.length-1;
				while(i >= 0 && config.format.toLowerCase().indexOf(chunks[i].abbrev) == -1) i--;
				smallest = chunks[i].abbrev;
				return smallest ? "< 1"+smallest : "";
			} else return config.zero;
		}
	}
}

com.aboone.ColorPicker = function(config){
	var defaultPalette = function(){
		var steps = ['00','33','66','99','CC','FF'];
		var colors = [];
		for(var i=0; i<steps.length; i++)
		for(var j=0; j<steps.length; j++)
		for(var k=0; k<steps.length; k++){
			colors.push(steps[i]+steps[j]+steps[k]);
		}
		return colors;
	};
	return {
		container: config.container,
		value: config.value,
		colors: config.colors || defaultPalette(),
		onChange: config.onChange || function(v){},
		init : function(){
			this.render();
			this.el = $('#'+this.container);
			this.input = this.el.find('input[name=color]');
			var that = this;
			$('#'+this.container+' li.swatch').click(function(){
				that.input.val($(this).attr('rel'));
				that.updateDisplayColor();
				that.el.find('.colorPalette').hide();
				that.onChange(that.getValue());
			});
			this.el.find(' .colorSelection').click(function(){
				that.el.find('.colorPalette').toggle();
			})
			this.updateDisplayColor();
		},
		getValue: function(){
			return this.input.val();
		},
		updateDisplayColor: function(){
			this.el.find('.colorSelection').css('backgroundColor','#'+this.getValue());
		},
		render: function(){
			var html = '<input type="hidden" name="color" value=\"'+(this.value ? this.value : '')+'\"/>';
			html += '<span class="colorSelection"></span>';
			html += '<ul class="colorPalette">';
			for(var i=0; i<this.colors.length; i++){
				html += '<li class="swatch" style="background-color: #'+this.colors[i]+'" rel="'+this.colors[i]+'" title="'+this.colors[i]+'"></li>'
			}
			html += '<li style="clear: both;"></li>';
			html += '</ul>';
			$('#'+this.container).html(html).addClass('colorPicker');
		}
	}
}

