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

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

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

com.aboone.Timer = function(config){
	return {
		EVENT_TYPE: ['','start','stop']
		,updateTitle: config.updateTitle || false
		,showControls: config.showControls || false
		,ajax:config.ajax || false
		,displayFormat: config.displayFormat
		,displayPrefix: config.displayPrefix || ''
		,displaySuffix: config.displaySuffix || ''
		,logTimeFormat: config.logTimeFormat || 'hms'
		,logPageSize: config.logPageSize || 10
		,logCurPage: config.logCurPage || 0
		,onSwap : config.onSwap || function(){}  //TODO: rm?
		,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.periodEl = config.periodEl || false;
			
			this.curElapsed = 0;
			this.elapsedTime = this.timerEl.attr('rel') ? this.timerEl.attr('rel') : 0;
			
			this.startTime = this.getAdjustedStartTime();
			
			if(this.showControls) this.initControls();
			
			this.display();
			
			if(this.isRunning()){
				this.start();
			}
		}
		
		,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.ajax){
				var that = this;
				var te = function(){
					var el = $(this);
					$.post($(this).attr('href'),{},function(){
						if(act == 'start') that.start();
						that.timerEl[act == 'start' ? 'addClass' : 'removeClass']('running');
						el.addClass(act == 'start' ? 'stop' : 'start').removeClass(act).html(act == 'start' ? 'stop' : 'start');
					});
					return false;
				}
				this.timerEl.next('.timer-controls').click(te);
			}
		}

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

		,addListener: function(event, fn){
			if (!this.listeners[event]){ this.listeners[event] = []; }
			this.listeners[event].push(fn);
		}

		,search: function(slice){
			var that = this;
			
			slice = typeof slice == 'string' ? {search: slice} : slice;
			if(this.id) slice.timerId = this.id;

			$.get('/slice.php', slice, function(data){
				that.periods = data.period;
				//that.meta = data.timer[0].meta; //TODO: summarize!
				that.initLog();
			},'json');
		}
		
		,refresh: function(){
			var that = this;

			$.get('/rest/timer.php', {id: this.id, periods: true}, function(data){
				that.periods = data.timer[0].periods;
				that.meta = data.timer[0].meta;
				that.display();
				that.initLog();
			},'json');
		}
		
		,initLog: function(){
			var that = this;
			
			if(this.periods){
				if (!$('ol#log').length) {
					// initialize the log container
					$('#logWrap').append('<ol id="log"></ol>');
					$('#logWrap').append('<div class="log-links">'
									+'<a class="log-prev" href="javascript:;">&lt; newer</a>'
									+' <a class="log-next" href="javascript:;">older &gt;</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.logCurPage = that.logCurPage + ($(this).hasClass('log-next') ? 1 : -1);
							that.initLog();	
						}
					})
				}

				var map = this.map(this.periods, function(o){
					return o.startTime.replace(/ .*/, '');
				}, function(o){
					var parts = o.startTime.replace(/ .*/, '').split(/-/);
					return months[parts[1]-1]+' '+parts[2]/1+', '+parts[0];
				});
				
				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.logPageSize) - 1;
				var minPage = 0;
				
				this.logCurPage = Math.max(minPage, Math.min(this.logCurPage, maxPage));
				
				var html = '';
				var startIndex = this.logPageSize * this.logCurPage;
				
				for(var j=startIndex; j<sortedMap.length && j<startIndex+this.logPageSize; j++){
					var tot = 0;
					var running = false;
					var curHtml = '';
					sortedMap[j].data.sort(function(a,b){
						return a.startTime < b.startTime ? 1 : -1;
					});
					for(var i=0; i<sortedMap[j].data.length; i++){
						var p = sortedMap[j].data[i];
						running = running || p.running;
						var unpaid = !p.lineItemId || p.lineItemId == "0";
						tot += p.elapsed;
					 	curHtml += '<li class="event '+(unpaid?'unpaid':'paid')+'" id="period-'+p.id+'">';
						curHtml += '<a href="javascript:;" class="delete"><span>delete</span></a>';
						curHtml += '<span class="start" title="click to edit">'+p.startTime.replace(/.* /,'')+'</span>'
						if(p.stopTime){
							curHtml += '<span class="stop" title="click to edit">'+p.stopTime.replace(/.* /,'')+'</span>'
						}
							
						var et = new com.aboone.ElapsedTime(p.elapsed);
						curHtml += '<span class="meta">';
						curHtml += '<span class="summary'+(p.running?' running':'')+'" rel="'+p.elapsed+'">'+et.display({format: that.logTimeFormat})+'</span>';
						curHtml += '<span class="period"><input class="periodName" value="'+p.name+'" rel="'+p.id+'"/></span>'
						curHtml += '</span>';
						curHtml += '</li>';
					}
					
					var total = new com.aboone.ElapsedTime(tot);
					var displayTime = total.display({format: that.logTimeFormat});
					
					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.logTimeFormat
					});
					it.init();
				});
			}

			$('.log-prev')[this.logCurPage == minPage ? 'hide' : 'show']();
			$('.log-next')[this.logCurPage == maxPage ? 'hide' : 'show']();
			
			$('.periodName')
				.focus(function(){ $(this).addClass('focused'); })
				.blur(function(){ $(this).removeClass('focused')})
				.change(function(){ that.savePeriod(this); });
				
			$('li.event .start, li.event .stop').click(function(){
				that.togglePeriodEdit($(this).parent('.event').attr('id').replace(/^period-/,''), this)
			});
			$('li.event')
				.mouseover(function(){ 	$(this).addClass('over'); 		})
				.mouseout(function(){ 	$(this).removeClass('over'); 	});
				
			$('li.event .delete').click(function(){
				var period = $(this).parent('li.event');
				var id = period.attr('id').replace(/period-/,'');
				if(id && !isNaN(id) && window.confirm("Are you sure you want to delete this entry? It cannot be recovered!")){
					$.post('/rest/period.php', {'delete': '{"id":'+id+'}'}, function(){
						that.refresh();
					});
				}
			});
		}

		,savePeriod : function(el){
			$(el).addClass('saving');
			var t = $('#timerId').val();
			var periodId = $(el).attr('rel');
		
			$.post('/updatePeriod.php', {name:$(el).val().replace(/"/g, '\"'),id:periodId}, function(data){
				$(el).addClass('saved');
				$(el).removeClass('saving');
				window.setTimeout(function(){
					$(el).removeClass('saved');
				},5000);
			},'json');
		}
		
		,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]);
		}
		
		,togglePeriodEdit: function(pid,el,on){
			if(on == undefined){
				on = !$(el).find('select').length;
			}
			if(on){
				var v = $(el).text();
				var that = this;
				var foo = v.replace(/([0-9]{2}):([0-9]{2}):([0-9]{2})/, function(o,g1,g2,g3){
					return that.sel([],0,23,1,g1/1,function(v){ return v > 9 ? v : '0'+v; })
							+':'+that.sel([],0,59,1,g2/1,function(v){ return v > 9 ? v : '0'+v; })
							+':'+that.sel([],0,59,1,g3/1,function(v){ return v > 9 ? v : '0'+v; })
							+'<input type="button" value="Save" class="saveButton"/>';
				});
				$(el).html(foo);
				$(el).find('input.saveButton').click(function(){
					var ts = '';
					$(this).parent().find('select').each(function(){
						if(ts) ts += ':';
						ts += $(this).val();
					})
					
					var params = {id:pid};
					params[$(el).hasClass('start') ? 'startTime' : 'stopTime'] = ts;
					$.post('/updatePeriod.php', params, function(data){
						$(el).html(ts);
						that.refresh();
					},'json');
				})
			} else {
				//$(el).html($(el).find('input').text());
			}
		}
		
		,sel:function(attrs,min,max,step,val,renderer){
			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' : '';
				html += '<option'+sel+'>'+(renderer ? renderer(i) : i)+'</option>';
			}
			html += '</select>';
			return html;
		}
		
		,getAdjustedStartTime : function(et){
			if(!et) et = this.getElapsedTime();
			return Math.floor(new Date()/1000-et);
		}
		
		,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();
			}
			var that = this
			this.timer = window.setInterval(function(){
				that.update();
				that.display();
			}, 1000);
		}
		
		,stop : function(){
			window.clearInterval(this.timer);
			delete this.timer;
		}
		
		,update: function(){
			this.curElapsed++;
		}
		
		,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#mode_'+mode.replace(' ','_')).addClass('active');

			this.fire('changemode', [this.mode]);
		}
		
		,save : function(){
			$.post('/rest/timer.php',{
				json: '{"id":'+this.id+',"name":"'+this.name+'","color":"'+this.color+'","mode":"'+this.mode+'","rate":'+this.rate+'}'
			});
		}
		
		,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;
		}
		
		,display: function(){
			if(this.rate && this.mode && this.mode.charAt(0) == '$'){
				var value = '$'+this.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+')');
			}
		}
		
		,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');
			}
		}
	};
}

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');
		}
	}
}
