/**
*	@fileoverview :
* 	Main JS file for yourlodestar.com, by inUse Lab1 2009
*	@author Emil Björklund
*	@requires jQuery
*	@requires Flot
*	Contents are packaged in the jQuery object, to initialize after page
*	load is finished
*/

if (typeof gettext === "undefined") {
	// mock gettext funcction, in case gettext is not available
	// @returns string
	// with original text.
	function gettext(str) {
		return str;
	};
}


// Mock console function, so that stuff won't break if debug statements are forgotten.
if(!window.console) {
  window.console = new function() {
    this.log = function(str) {};
	this.warn = function(str) {};
    this.dir = function(str) {};
  };
};

// Steven Levithan's date library:
/*
 * Date Format 1.2.3
 * (c) 2007-2009 Steven Levithan <stevenlevithan.com>
 * MIT license
 *
 * Includes enhancements by Scott Trenda <scott.trenda.net>
 * and Kris Kowal <cixar.com/~kris.kowal/>
 *
 * Accepts a date, a mask, or a date and a mask.
 * Returns a formatted version of the given date.
 * The date defaults to the current date/time.
 * The mask defaults to dateFormat.masks.default.
 */

var dateFormat = function () {
	var	token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,
		timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
		timezoneClip = /[^-+\dA-Z]/g,
		pad = function (val, len) {
			val = String(val);
			len = len || 2;
			while (val.length < len) val = "0" + val;
			return val;
		};

	// Regexes and supporting functions are cached through closure
	return function (date, mask, utc) {
		var dF = dateFormat;

		// You can't provide utc if you skip other args (use the "UTC:" mask prefix)
		if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !(/\d/.test(date))) {
			mask = date;
			date = undefined;
		}

		// Passing date through Date applies Date.parse, if necessary
		date = date ? new Date(date) : new Date;
		if (isNaN(date)) throw SyntaxError("invalid date");

		mask = String(dF.masks[mask] || mask || dF.masks["default"]);

		// Allow setting the utc argument via the mask
		if (mask.slice(0, 4) == "UTC:") {
			mask = mask.slice(4);
			utc = true;
		}

		var	_ = utc ? "getUTC" : "get",
			d = date[_ + "Date"](),
			D = date[_ + "Day"](),
			m = date[_ + "Month"](),
			y = date[_ + "FullYear"](),
			H = date[_ + "Hours"](),
			M = date[_ + "Minutes"](),
			s = date[_ + "Seconds"](),
			L = date[_ + "Milliseconds"](),
			o = utc ? 0 : date.getTimezoneOffset(),
			flags = {
				d:    d,
				dd:   pad(d),
				ddd:  dF.i18n.dayNames[D],
				dddd: dF.i18n.dayNames[D + 7],
				m:    m + 1,
				mm:   pad(m + 1),
				mmm:  dF.i18n.monthNames[m],
				mmmm: dF.i18n.monthNames[m + 12],
				yy:   String(y).slice(2),
				yyyy: y,
				h:    H % 12 || 12,
				hh:   pad(H % 12 || 12),
				H:    H,
				HH:   pad(H),
				M:    M,
				MM:   pad(M),
				s:    s,
				ss:   pad(s),
				l:    pad(L, 3),
				L:    pad(L > 99 ? Math.round(L / 10) : L),
				t:    H < 12 ? "a"  : "p",
				tt:   H < 12 ? "am" : "pm",
				T:    H < 12 ? "A"  : "P",
				TT:   H < 12 ? "AM" : "PM",
				Z:    utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""),
				o:    (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4),
				S:    ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10]
			};

		return mask.replace(token, function ($0) {
			return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
		});
	};
}();

// Some common format strings
dateFormat.masks = {
	"default":      "ddd mmm dd yyyy HH:MM:ss",
	shortDate:      "m/d/yy",
	mediumDate:     "mmm d, yyyy",
	longDate:       "mmmm d, yyyy",
	fullDate:       "dddd, mmmm d, yyyy",
	shortTime:      "h:MM TT",
	mediumTime:     "h:MM:ss TT",
	longTime:       "h:MM:ss TT Z",
	isoDate:        "yyyy-mm-dd",
	isoTime:        "HH:MM:ss",
	isoDateTime:    "yyyy-mm-dd'T'HH:MM:ss",
	isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"
};

// Internationalization strings
dateFormat.i18n = {
	dayNames: [
		"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
		"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
	],
	monthNames: [
		"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
		"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
	]
};

// For convenience...
Date.prototype.format = function (mask, utc) {
	return dateFormat(this, mask, utc);
};

$(function() {
	
	/**
	*	The "ldstr" object, to ensure no pollution of 
	*	the global namespace.
	* 	@constructor
	*/
	var ldstr = {
			// variable definitions
		chart: $("#viz"),
		zoomer: $('#zoom_graph'),
		
		API_URL: "http://lodestarproject.appspot.com/eko/",
		community_id: "community",
		
		t: {}, // timeout variable holder
		current_scope: 'hour',
		series: {},
		toggler: $('#intervalFormToggler'),
		interval_selector: $('#intervalSelector'),
		
		// UTC dates for today...
		// to: new Date(Date.UTC(new Date().getFullYear(), new Date().getMonth(), new Date().getDay())),
		// from: new Date(Date.UTC(new Date().getFullYear(), new Date().getMonth(), new Date().getDay())),
		from: null,
		to: null,
		markings_color: "#fef7f7",
		// "Libraries"
		scopes: {
			detail: {
				opts: {
					lines: {
						show: true
					},
					xaxis: {
						mode: "time",
						timeformat: "%H:%M",
						minTickSize: [5, "minute"]
					}
				},
				tip_tpl: gettext('<p>On {from}, your usage was {amount} <abbr title="Watt">W</abbr>.<p>'),
				format: "W"
			},
			hour: {
				opts: {
					bars: {
						show: true,
						barWidth: (3600 * 1000), // one hour, in milliseconds
						lineWidth: 0
						
					},
					xaxis: {
						mode: "time",
						timeformat: "%d %b, %H:%M",
						minTickSize: [3, "hour"]
					}
				},
				
				tip_tpl: gettext('<p>Between {from} and {to} you used {amount} <abbr title="Watt">W</abbr>.</p><p>That is {diff}% {more_less} than you usually use during this time, and represents a difference of {diff_w} <abbr title="Watt">W</abbr>.</p>'),
				format: 'Wh' // format for axis,
				
			},
			day: {
				opts: {
					bars: {
						show: true,
						barWidth: (24 * 3600 * 1000), // one day, milliseconds
						lineWidth: 0,
						align: "center"
					},
					xaxis: {
						mode: "time",
						timeformat: "%d %b",
						minTickSize: [1, "day"]
					}
				},
				tip_tpl: gettext('<p>On {from} you used {amount} <abbr title="Watt hours">Wh</abbr>.</p><p>That is {diff}% {more_less} than you usually use on that day, and represents a difference of {diff_w} <abbr title="Watt hours">Wh</abbr>.</p>'),
				format: "kWh",
				grid: {
					// markings: ldstr.etc.markings.weekendAreas
				}
				
				
			},
			month: {
				opts: {
					bars: {
						show: true,
						barWidth: (28 * 24 * 3600 * 1000), // 28 days, in milliseconds. TODO: flexible bar width?
						lineWidth: 0,
						align: "center"
					},
					xaxis: {
						mode: "time",
						timeformat: "%d %b",
						minTickSize: [1, "day"]
					}
					 
				},
				format: "kWh"
				
			}
		},
		opts: {
			/**
			*	Global options for the plot.
			*/
			grid: {
				borderWidth: 0,
				borderColor: null,
				backgroundColor: null,
				hoverable: true,
				clickable: true
			},
			colors: ["#fee901", '#c2272d'],
			yaxis: { min: 0 },
			xaxis: {
				mode: "time"
			},
			selection: { 
				mode: "x",
				color: "#cbd4db" 
			}
		},
		// templates for snippets, date formats for example.
		templates: {
			singleDate: '<span class="from"><span class="dayNumber">{fdd}</span> <span class="monthName">{fmonth}</span> <span class="year">{fyyyy}</span></span>',
			betweenDays: '<span class="from"><span class="dayNumber">{fdd}</span> <span class="monthName">{fmonth}</span> <span class="year">{fyyyy}</span></span> &ndash; <span class="to"><span class="dayNumber">{tdd}</span> <span class="monthName">{tmonth}</span> <span class="year">{tyyyy}</span></span>',
			betweenHours: '<span class="from"><span class="dayNumber">{fdd}</span> <span class="monthName">{fmonth}</span> <span class="year">{fyyyy}</span>, <span class="hourMinutes">{fhhmm}</span></span> &ndash; <span class="to"><span class="dayNumber">{tdd}</span> <span class="monthName">{tmonth}</span> <span class="year">{tyyyy}</span>, <span class="hourMinutes">{thhmm}</span></span>',
			insideDayHours: '<span><span class="dayNumber">{fdd}</span> <span class="monthName">{fmonth}</span> <span class="year">{fyyyy}</span>, <span class="hourMinutes from">{fhhmm}</span>&nbsp;&ndash;&nbsp;<span class="hourMinutes to">{thhmm}</span>'
		},
		etc: {
			/**
			*	Helper functions for math, formatting etc.
			*/
			
			date: {
				daysInMonth: function(month,year) {
					var dd = new Date(year, month, 0);
					return dd.getDate();
				},
				getMonthName: function(index) {
					var monthNames = [
						"January",
						"February",
						"March",
						"April",
						"May",
						"June",
						"July",
						"August",
						"September",
						"October",
						"November",
						"December"
					];
					
					return gettext(monthNames[index]);
				},
				fromString: function(dstr) {
					// console.log('Running date.fromString! String is %s', (dstr || undefined)); // Debug
					// console.log('Caller is %s', ldstr.etc.date.fromString.caller); // Debug
					dstr = dstr.split('-').join("");
					if (dstr.length == 6) {
						// console.log('Date string is 6 digits, %s', dstr);
						// date formats can be weird from the API (i.e. 091001 instead of 20091001)
						dstr = ""+"20" + dstr;
						// console.log("Datestring is now fixed to %s", dstr); // Debug
					};
					var year,month,day;
					// var date = new Date();

					year = dstr.substring(0,4);
					month = dstr.substring(4,6);
					day = dstr.substring(6,8);
					date = new Date(parseInt(year, 10), parseInt(month, 10)-1, parseInt(day, 10), 0, 0, 0, 0);
					// console.log(date); // Debug
					return date;
				},
				normalize: function(date) {
					// Convenience function to set a date to 0 hours, minutes, seconds and ms.
					// @returns Date
					date.setHours(0);
					date.setMinutes(0);
					date.setSeconds(0);
					date.setMilliseconds(0);
					return date;
				}
			},
			
			format: {
	            Wh: function(val, axis) {
	                return val.toFixed(axis.tickDecimals) + "&nbsp;Wh";
	            },
	            W: function(val, axis) {
	                return val.toFixed(axis.tickDecimals) + "&nbsp;W";
	            },
	            kWh: function(val, axis) {
	                return (val.toFixed(axis.tickDecimals)/1000) + "&nbsp;kWh";
	            },
				TZDay: function(val, axis) {

					return val+(2 * 3600 * 1000);
				}
	        },
			markings: {
				weekendAreas: function(axes) {
					// from the Flot examples, found at http://people.iola.dk/olau/flot/examples/visitors.html
			        var markings = [];
			        var d = new Date(axes.xaxis.min);
			        // go to the first Saturday
			        d.setUTCDate(d.getUTCDate() - ((d.getUTCDay() + 1) % 7));
			        d.setUTCSeconds(0);
			        d.setUTCMinutes(0);
			        d.setUTCHours(0);
			        var i = d.getTime();
			        do {
			            // when we don't set yaxis, the rectangle automatically
			            // extends to infinity upwards and downwards
			            markings.push({ xaxis: { from: i, to: i + 2 * 24 * 60 * 60 * 1000 }, color: ldstr.markings_color });
			            i += 7 * 24 * 60 * 60 * 1000;
			        } while (i < axes.xaxis.max);

			        return markings;
			    }
				
			},
			data: {
				getSeriesDetails: function(series, avgs) {
					/* 
					*	Takes a data series and picks out specific values based on from- and to- points.
					*	@argument series Array
					*	@argument from Date
					* 	@argument to Date
					*/
					// console.log('Running getSeriesDetails.'); // Debug
					// console.log(series); // Debug
					
					var s = {
						min: 999999999,
						max: 0,
						total: 0
					};
					var valid_points = [];
					var l = series.length;
					// console.log("length is %s", l); // Debug
					// Loop over the data, pick out the relevant interval.
					// if (ldstr.current_scope == "detail") {
					// 						// console.log('Details mode: area is 0.');
					// 						var area = 0;
					// 					};
					var now = new Date().getTime();
					// console.log((now)) // Debug
	                for(var i=0; i<l; i++) {
						if (series[i][1] > 0) {
							// add to total unless value is below 0 (in which case there is something wrong with the meter...)
							if (ldstr.current_scope == "detail") {
								if(i==0) {
									continue;
								};
								try {
									s.total += avgs[i];
								} catch(e) {
									
								};
								
							} else {
								s.total += series[i][1];
							};
						};
						if (series[i][1] > 0 && series[i][1] < now) {
								valid_points.push(series[i]);
						}

						if (series[i][1] > s['max']) {
							s['max'] = series[i][1];
						};
						if (series[i][1] !== 0 && series[i][1] < s['min']) {
							s['min'] = series[i][1];
						};
						
	                };

					if (ldstr.current_scope == "detail") {
						// different calculation of average in details mode...
						var time_length = ((series[series.length-1][0] - series[0][0])/(3600*1000));
						s['avg'] = (s['total']/time_length).toFixed(1);
					} else {
						s['avg'] = (s['total']/valid_points.length).toFixed(0);
					}
					
					// difference
					// console.log(s.max, s.min); // Debug
					
					s['diff'] = s['max'] - s['min'];
					return s; 
				},
				
				getPointDetails: function(point) {
					// Gets the details of a specific point: its average and diff in watts compared to average.
					var obj = {
						avg: 0,
						diff: 0
					};
					try {
						for (i = 0, l = ldstr.series.original.avg.length; i<l; i++) {
							if (ldstr.series.original.avg[i][0] == point[0]) {
								// console.log(ldstr.series.original.avg[i]);
								obj.avg = ldstr.series.original.avg[i][1];
								obj.diff = point[1] - obj.avg;
								break;
							};
						};
					} catch(e) {
						// console.log(e);
					};
					
					return obj;
				},
				
				split: function(response) {
					// Splits data series in over/under averages:
					
					// truncate/reset current series:
					series = { over: [], under: [] };
					
					// loop over data/averages and split/push.
					for (var i = 0, l = response['data'].length; i<l; i++) {
						if (response['data'][i][1] < response['avg'][i][1]) {
							series.under.push(response['data'][i]);
						} else {
							series.over.push(response['data'][i]);
						};
					};
					return series;
				}
			},
			env: {
				parseFragment: function(hash) {
					var fragment, rstring, re, m;
					var params = {}; // the object we plan on returning
					fragment = hash.replace('#', "") || undefined;
					// console.log('Fragment is %s', fragment || "not defined"); // Debug
					
					if (typeof fragment == "undefined") {
						return false;
					}
					// pick out scope, first date, then either the string ends (with or without slash) or it has an end date.
					rstring = /^\/(hour|day|month)\/(\d{4})-?(\d{2})-?(\d{2})(\/(\d{4})-?(\d{2})-?(\d{2})|\/)?/;
					re = new RegExp(rstring);
					m = re.exec(fragment);
					if(m) {
						// console.log(('Match for dates + scope!')) // Debug // Debug
						// console.log(m); // Debug
						params['scope'] = m[1];
						params['from'] = new Date(m[2], parseInt(m[3], 10)-1, parseInt(m[4], 10), 0, 0, 0, 0);
						// console.log(("From is %s", params.from)) // Debug
						
						if (m[5] !=="/") {
							// console.log('Two dates.'); // Debug
							params['to'] = new Date(m[6], parseInt(m[7], 10)-1, parseInt(m[8], 10), 0, 0, 0, 0);
						} else {
							params['to'] = params['from']; // "to" is same as "from".
						}
					} else {
						// test for keywords defining various set intervals
						rstring = /^\/(detail|today|this-week|this-month)\/?$/;
						re = new RegExp(rstring);
						m = re.exec(fragment);
						if (m) {
							// console.log(m[1]); // Debug
							var today = ldstr.etc.date.normalize(new Date());
							switch (m[1]) {
								case "detail":
									// console.log('fragment is details'); // Debug
									params.from = new Date;
									params.to = new Date;
									params['scope'] = "detail";
									break;
								case "today":
									// console.log('/today'); // Debug
									params['scope'] = "hour";
									params['from'] = today;
									params['to'] = new Date(params.from.getTime() + ((24 * 3600 * 1000)-1));
									
									break;
								case "this-week":
									params['scope'] = "day";
									// console.log("%s", today); // Debug
									var dayNumber = today.getDate();
									var weekDay = today.getDay();
									params['from'] = new Date(today.getFullYear(), today.getMonth(), today.getDate()-(today.getDay()-1));
									// console.log("Start of week is %s", params['from']); // Debug
									params['to'] = new Date(today.getFullYear(), today.getMonth(), today.getDate()+(7-today.getDay()));
									// console.log("End of week is %s", params['to']); // Debug
									// console.log(params); // Debug
									break;
								case "this-month":
									// console.log('/this-month'); // Debug
									params['scope'] = "day";
									var thisMonth = today;
									thisMonth.setDate(1);
									params['from'] = new Date(today.getFullYear(), today.getMonth(), 1);
									// console.log("Start of month is %s", params['from']); // Debug
									params.to = new Date(params.from);
									params.to.setDate(ldstr.etc.date.daysInMonth(params.from.getFullYear(), params.from.getMonth()));
									// console.log("End of month is %s", params['to']); // Debug
									// console.log(params); // Debug
									break;
								default:
									break;
							}
							
						} else {
							// console.log(('Strange.')) // Debug // Debug
							return false;
						};
					};
					if (params.from > params.to) {
						// switch from/to if from is greater.
						var tmp = params.from;
						params.from = params.to;
						params.to = params.tmp;
					};
					return params;
				}
			},
			str: {
				supplant: function (str, o) {
					// modified version of Douglas Crockfords supplant, from http://javascript.crockford.com/remedial.html
				    return str.replace(/{([^{}]*)}/g,
				        function (a, b) {
				            var r = o[b];
				            return typeof r === 'string' || typeof r === 'number' ? r : a;
				        }
				    );
				},
				pad: function(num, len) {
					var str = "" + num;
					while (str.length < len) {
						str = "0" + str;
					};
					return str;
				},

				
				capfirst: function(str) {
					return str.substring(0,1).toUpperCase() + str.substring(1);
				}
			}
		},
		gui: {
			// Handle GUI stuff here, e.g. calendar, records, detail table etc.
			
			showTooltip: function(x, y, scope, data) {
				/**
				*	Shows a tooltip next to the hovered bar, 
				*	with contents based on the scope 
				*	that should be set by the function responsible for
				*	setting upp the plot and gui (default is hours)
				*/
				// console.log('Showing tooltip'); // Debug
				console.log(scope); // Debug
				console.log(data);
				var template = ldstr.scopes[scope].tip_tpl || "No info";
				switch (scope) {
					case "detail":
						//TODO
						data.from = data.from.format("dd mmmm yyyy 'at' HH:MM:ss");
						break;
					case "hour":
						data.from = ldstr.etc.str.pad(data.from.getHours()-1, 2) + ":00";
						data.to = ldstr.etc.str.pad(data.to.getHours(), 2) + ":00";
						break;
					case "day":
						data.from = data.from.format("dddd dd mmmm yyyy");
						break;
					case "month":
						// TODO
						break;
					default:
						break;
				}
				
				
				var content = ldstr.etc.str.supplant(template, data);
	            $('<div id="tooltip" class="' + scope + '">' + content + '<p class="close"><a href="#">'+gettext('Close')+'</a></p><div class="presentational tip"></div>').css( {
	                position: 'absolute',
	                display: 'none',
	                top: y - 38,
	                left: x + 19
	            }).appendTo("body").fadeIn(200);
	        },
			
			updateRecords: function() {
				/** 
				*	Updates the records section in the dashboard.
				*/
				
				var wDate = new Date(); 
				var bDate = new Date();
				
				try {
					wDate = ldstr.etc.date.fromString(ldstr.worst_day.date);
					bDate = ldstr.etc.date.fromString(ldstr.best_day.date);
				} catch (e) {
					// console.log((e)) // Debug // Debug
				};
							
				$('.records .highest .val').text((ldstr.worst_day.val/1000).toFixed(0));
				$('.records .highest .datelink a').attr(
					{ 'href': '#' + '/hour/' + wDate.format('yyyymmdd') + "/" + wDate.format('yyyymmdd') }
				).text(wDate.format('d mmmm yyyy'));
				
				$('.records .lowest .val').text((ldstr.best_day.val/1000).toFixed(0));
				$('.records .lowest .datelink a').attr(
					{ 'href': '#' + '/hour/' + bDate.format('yyyymmdd') +'/' + bDate.format('yyyymmdd') }
				).text(bDate.format('d mmmm yyyy'));
				
				$('.comparison .highest .val').text((((365 * ldstr.worst_day.val) * ldstr_no_households)/Math.pow(10, 12)).toFixed(1));
				$('.comparison .lowest .val').text((((365 * ldstr.best_day.val) * ldstr_no_households)/Math.pow(10, 12)).toFixed(1));
				var diff = $('.comparison .highest .val').text() - $('.comparison .lowest .val').text();
				var nuclear_plant = 22.3;
				$('.comparison .consequence .diff .val').text((diff/nuclear_plant).toFixed(1));
			},
			
			init_datepicker: function() {
				var options = {
					date: [ldstr.from, ldstr.to],
					flat: true,
					mode: "range",
					calendars: 2,
					starts: 1,
					onChange: function(formated, dates) {
						// console.log('These are the dates:'); // Debug
						// console.log(dates); // Debug
						$("#interval_from").val(dates[0].format('isoDate'));
						$("#interval_to").val(dates[1].format('isoDate'));
					},
					locale: {
						days: [
							gettext("Sunday"), 
							gettext("Monday"), 
							gettext("Tuesday"), 
							gettext("Wednesday"), 
							gettext("Thursday"), 
							gettext("Friday"), 
							gettext("Saturday"), 
							gettext("Sunday")],
						daysShort: [
							gettext("Sun"), 
							gettext("Mon"), 
							gettext("Tue"), 
							gettext("Wed"), 
							gettext("Thu"), 
							gettext("Fri"), 
							gettext("Sat"), 
							gettext("Sun")],
						daysMin: [
							gettext("Su"), 
							gettext("Mo"), 
							gettext("Tu"), 
							gettext("We"), 
							gettext("Th"), 
							gettext("Fr"), 
							gettext("Sa"), 
							gettext("Su")],
						months: [
							gettext("January"), 
							gettext("February"), 
							gettext("March"), 
							gettext("April"), 
							gettext("May"), 
							gettext("June"), 
							gettext("July"), 
							gettext("August"), 
							gettext("September"), 
							gettext("October"), 
							gettext("November"), 
							gettext("December")],
						monthsShort: [
							gettext("Jan"), 
							gettext("Feb"), 
							gettext("Mar"), 
							gettext("Apr"), 
							gettext("May"), 
							gettext("Jun"), 
							gettext("Jul"), 
							gettext("Aug"), 
							gettext("Sep"), 
							gettext("Oct"), 
							gettext("Nov"), 
							gettext("Dec")],
						weekMin: gettext('wk')
					}
				};
				$('#calendarPicker').DatePicker(options);
			},
			
			updateSpecifics: function(specs, isSelection) {
				// console.log(specs); // Debug
				// console.log("From is %s", specs.from.getTime()); // Debug
				// console.log("To is %s", ldstr.etc.date.normalize(specs.to).getTime());
				// var from_str = "", to_str = "";
				var from, to;
				
				
				// console.log(("%s", ldstr.series.selection.from_date)) // Debug
				// from_str = ldstr.series.selection.from_date.format('yyyymmdd');
				// to_str = ldstr.series.selection.to_date.format('yyyymmdd');
				// from = ldstr.series.selection.from_date;
				// to = ldstr.series.selection.to_date;

				console.log("%s", ldstr.from); // Debug
				// from_str = ldstr.from.format('yyyymmdd');	
				// to_str = ldstr.to.format('yyyymmdd');
				from = ldstr.from;
				to = ldstr.to;
				
				var same_date = (from.format('isoDate') == to.format('isoDate')) ? true : false;

				var dateinfo = {
					fdd: from.format('dd'),
					tdd: to.format('dd'),
					fmonth: from.format('mmmm'),
					tmonth: to.format('mmmm'),
					fyyyy: from.format('yyyy'),
					tyyyy: to.format('yyyy'),
					fhhmm: from.format('HH:MM'),
					thhmm: new Date(to.getTime()-1).format('HH:MM')
				};
				
				if (same_date) {
					ldstr.toggler.removeClass('long');
					ldstr.toggler.html(ldstr.etc.str.supplant(ldstr.templates.singleDate, dateinfo));
				} else {
					ldstr.toggler.addClass('long');
					ldstr.toggler.html(ldstr.etc.str.supplant(ldstr.templates.betweenDays, dateinfo));
				};

				if (isSelection) {
					console.log(ldstr.series.selection); // Debug
					from = new Date(ldstr.series.selection.closest_from[0]);
					to = new Date(ldstr.series.selection.closest_to[0]);
					same_date = (from.format('isoDate') == to.format('isoDate')) ? true : false;
					dateinfo.fdd = from.format('dd');
					dateinfo.tdd = to.format('dd');
					dateinfo.fmonth = gettext(from.format('mmmm', true));
					dateinfo.tmonth = gettext(to.format('mmmm', true));
					dateinfo.fyyyy = from.format('yyyy', true);
					dateinfo.tyyyy = to.format('yyyy', true);
					dateinfo.fhhmm = from.format('HH:MM', true);
					dateinfo.thhmm = to.format('HH:MM', true);
				};
				
				if (same_date) {
					console.log('Only one date.'); // Debug
					// console.log("%s", from); // Debug

					// insert into interval box:
					$('.measuredInterval .date').html(ldstr.etc.str.supplant(ldstr.templates.insideDayHours, dateinfo));
				} else {
					console.log('More than one date!');
					$('.measuredInterval .date').html(ldstr.etc.str.supplant(ldstr.templates.betweenHours, dateinfo));
				};
				
				
				$('.integral.box .val').text(Math.round(specs.total*10)/10000 == 0.0 ? Math.round(specs.total/1000) : (specs.total/1000).toFixed(2));
				$('#details_table .abbr').each(function() {
					$(this).attr(
						{'title': (ldstr.default_scope == "detail" ? gettext('Watts') : gettext('Watt hours'))}
					).text(ldstr.default_scope == "detail" ? "W" : "Wh");
				});
				$('#detail_high .val').text(specs.max);
				$('#detail_low .val').text(specs.min);
				$('#detail_diff .val').text(specs.diff.toFixed(1));
				$('#detail_avg .val').text(specs.avg);
				
				$('#comp_bulbs .val').text(parseInt((specs.total/LIGHTBULB_HOUR)/24, 10));
				
				$('#comp_price .val').text(parseInt(specs.total*(KWH_PRICE/1000), 10));
				
				$('#comp_carbon .val').text(((specs.total/1000)*CARBON_KWH).toFixed(2));
				
			},
			
			switchToDetails: function() {
				function showZoom() {
					$('#detail_zoom').addClass('open').slideDown(600);
				};
				// console.log('Switching GUI to detail mode.'); // Debug
				if ($('#viz_interval').hasClass('open')) {
					// console.log('picker is open, closing...'); // Debug
					$('#viz_interval').removeClass('open');
					ldstr.interval_selector.slideUp(600, showZoom);
				} else {
					showZoom();
				};
				
			},
			
			
			
			showSpinner: function() {
				// shows the ajax spinner in the graph area while waiting for response.
				$('#viz').append('<img id="ajax_spinner" alt="loading..." title="Loading..." src="'+STATIC_URL+'img/ajax-loader.gif">');
			}
		},
		ev: {
			/**
			*	Events fired by mouse/keyboard events etc.
			*/
			
			getSelectedData: function() {
	            /**
	            *
	            * A function to get a range of data out from the currently selected portion of the plot.
	            * Returns an object literal with the keys "data" (Array), from_date (Date) and to_date (Date).
				* @returns object
	            *
	            **/
	            var sel = {
	                data: [],
	                has_last_point: false,
	                closest_to_distance: 0,
	                closest_to: false,
	                closest_from: false,
					avgs: []
	            };
	            try {
	                var data = ldstr.the_plot.getData()[0].data;
	            }
	            catch(e) {
	                return false;
	                // console.log(e); // Debug
	            };
	            // console.log(data); // Debug

	            try {
	                var from = Math.round(ldstr.the_plot.getSelection().xaxis.from);
	                // console.log(plot.getSelection());
	                // console.log('from: ' + from); // Debug
	                var to = Math.round(ldstr.the_plot.getSelection().xaxis.to);
	                // console.log('to: ' + to); // Debug
	            }
	            catch(e) {
	                return false;
	                // console.log(e); // Debug
	            };
				// Should be correct, since the timestamps are already adjusted.
	            sel['from_date'] = new Date(from); 
	            sel['to_date'] = new Date(to);

	            // find closest points to start and finish:

	            function calcNearest(target, data) {
					var near_point = [];
					var min_dist = 999999999; // Default value, high so that comparisons fall under it in the loop without having to check for first step.
	                for (var i = 0, len = data.length; i<len; i++) { // loop over the data
						
						if ((Math.abs(data[i][0]) - target) < min_dist) {
							min_dist = Math.abs(data[i][0] - target);
							// console.log("New closest distance found: %s", min_dist); // Debug
							near_point = i;
						} else {
							break;
						};
	                };

	                if (data[near_point] == data[data.length-1]) {
	                    // console.log(('last point selected!')) // Debug // Debug
	                    // console.log('Distance is ' + min_dist); // Debug
	                    sel.has_last_point = true;
	                    sel.closest_to_distance = min_dist;
	                }
	                return data[near_point]; // return smalles values index, should correspond to index of closest data.
	            };

	            sel.closest_from = calcNearest(from, data);
	            sel.closest_to = calcNearest(to, data);
	            // console.log(sel['closest_from']); // Debug
	            // console.log(sel['closest_to']); // Debug

				
	            for (var i = 0, len = data.length; i<len; i++) {
	                // console.log('No'+i+': ' +data[i]+', type is ' + typeof(data[i][0]));
					
	                if (data[i][0] >= sel['closest_from'][0]) {
	                    if (data[i][0] <= sel['closest_to'][0]) {
	                        sel['data'].push(data[i]);
							try {
								sel['avgs'].push(ldstr.series.original.avg[i]);
							} catch(e) {
								// console.warn('No such avg index.');
							};
							
	                    };
	                };
	                // console.log(sel); // Debug
	            };
	            return sel;
	        },
	
			selectedHandler: function(event, ranges) {
                var selection = ldstr.ev.getSelectedData();
				if (selection.data.length < 2) {
					ldstr.the_plot.clearSelection();
					ldstr.ev.unselectedHandler(event, ranges);
				} else {
					var closest_from = selection.closest_from[0];
					ldstr.series.selection = selection;
					var cover_last = false;
	                if (selection['has_last_point'] && ldstr.scopes[ldstr.current_scope].opts.bars) {
						var closest_to = null;
	                    if (selection['closest_to_distance'] > (ldstr.scopes[ldstr.current_scope].opts.bars.barWidth/2)) {
							// console.log(('Selection is closer to last bar.')); // Debug
							cover_last = true;
	                        closest_to = selection.closest_to[0] + ldstr.scopes[ldstr.current_scope].opts.bars.barWidth;
	                    }
	                    else {
	                        closest_to = selection.closest_to[0];
	                    };
	                } else {
	                    closest_to = selection.closest_to[0];
	                };
	                ldstr.the_plot.setSelection({xaxis: {from: closest_from, to: closest_to}}, true);
					// console.log('Done setting selection.'); // Debug
					// console.log(ldstr.series);
					// console.log('Original series is %s positions long.', ldstr.series.original.data.length); // Debug
	                if (ldstr.current_scope !== "detail" && !cover_last) { // the visualization of bars is different from points: ditch last point.
	                    // console.log(('is barchart.')); // Debug
						// ldstr.series.selection.data.pop()
	                    // console.log((ldstr.series.selection.data.pop())); // Debug
	                };
					// console.log("%s", new Date(closest_to)); // Debug
					ldstr.specifics = ldstr.etc.data.getSeriesDetails(ldstr.series.selection.data, ldstr.series.selection.avgs);
					ldstr.gui.updateSpecifics(ldstr.specifics, true);


	                // day.events.showSelectionDetails(window.selection); TODO: refactor.
				};
                
            },

			unselectedHandler: function(event, ranges) {
				ldstr.specifics = ldstr.etc.data.getSeriesDetails(ldstr.series.original.data, ldstr.series.original.avg);
				ldstr.gui.updateSpecifics(ldstr.specifics, false);
			},
			
			barHoverHandler: function(event, pos, item) {
				return false;
			},
			
			barClickHandler: function(event, pos, item) {
				// console.log((pos)); // Debug
				var previousPoint;
                if(item) {
                    if (previousPoint != item.datapoint) {
                        $("#tooltip").remove();
                        // if(ldstr.t['tooltip']) {
                        //                             clearTimeout(ldstr.t['tooltip']);
                        //                         }
                        previousPoint = item.datapoint;
						console.log(item.datapoint[0]);
						var point_details = ldstr.etc.data.getPointDetails(item.datapoint);
						var data = {
							more_less: point_details.diff < 0 ? gettext("less"): gettext("more"),
							amount: item.datapoint[1],
							from: new Date(item.datapoint[0]),
							to: new Date(item.datapoint[0]),
							diff: (((Math.abs(point_details.diff)/item.datapoint[1])*10000)/100).toFixed(2),
							diff_w: point_details.diff
						};
                        // ldstr.t['tooltip'] = setTimeout(function() {
                        ldstr.gui.showTooltip(pos.pageX, pos.pageY, ldstr.current_scope, data);
                        // }, 0);
                     };

                }
                else {
                     $("#tooltip").remove();
                     if(ldstr.t['tooltip']) {
                         clearTimeout(ldstr.t['tooltip']);
                     }
                     previousPoint = null;          
                };
			},
			
			dateLinkHandler: function(fragment) {
				// console.log(fragment); // Debug
				var date_params = ldstr.etc.env.parseFragment(fragment);

				if (date_params) {
					if (date_params['scope'] !== "detail") {
						ldstr.from = new Date(date_params.from);
						ldstr.to = new Date(date_params.to);
						$('#detail_zoom').removeClass('open').slideUp(600);
					} else {
						// 	console.log('detail mode'); // Debug
						$('#intervalSelector').slideUp(600);
						ldstr.gui.switchToDetails();
						ldstr.plot.updateDetail();
						ldstr.current_scope = date_params.scope;
						return false;
					};
					ldstr.current_scope = date_params.scope;
				} else {
					// console.warn('Date params is %s!', date_params); // Debug
				};

				ldstr.plot.update(ldstr.from, ldstr.to, ldstr.current_scope);				
				return true;
			},
			
			updateGeneralStats: function(callback) {
				$.getJSON(ldstr.API_URL + 'general/det?id=' + ldstr_id + '&days=0&callback=?', function(result) {
					ldstr['worst_day'] = {
						'date': result.worst_day.date,
						'val': result.worst_day.val
					};
					
					ldstr['best_day'] = {
						'date': result.best_day.date,
						'val': result.best_day.val
					};
					if (typeof callback == "function") {
						callback();
					};
				});
			},
			
			getComparisonData: function(from, to, scope, user) {
				return false; // TODO!
			}
			
		},
		plot: {
			/**
			*	Functions for handling the plot drawing itself.
			*/
			
			update: function(from, to, scope) {
				ldstr.gui.showSpinner();
				// console.log('Running update()'); // Debug

				ldstr.series.original = {};
				// console.log(from, to, scope); // Debug
				// console.log(typeof(from)); // Debug
				if (scope !== "detail") {
					var date_str = from.format('yyyymmdd', false) +"/"+ to.format('yyyymmdd', false);
				} else {
					date_str = "";
				};
				
				var u = "data/" + scope + "/" + date_str + "?id=" + ldstr_id + "&callback=?";
				
				$.getJSON(ldstr.API_URL + u, function(response) {
					ldstr.opts.yaxis.tickFormatter = ldstr.etc.format[ldstr.scopes[scope].format];
					ldstr.opts.xaxis = ldstr.scopes[scope].opts.xaxis;
					ldstr.series = ldstr.etc.data.split(response[0]);
					ldstr.series.original = response[0];
					ldstr.specifics = ldstr.etc.data.getSeriesDetails(ldstr.series.original.data, ldstr.series.original.avgs);
					set = [
						{
							data: ldstr.series.original.data,
							bars: {
								show: true,
								barWidth: ldstr.scopes[scope].opts.bars.barWidth,
								fillColor: {
									colors: ['#fee901', '#fee901', '#fee901', '#d0e300']
								},
								lineWidth: 0
							},
							label: gettext('Below average'),
							selectable: true
						},
						{
							data: ldstr.series.over,
							bars: {
								show: true,
								barWidth: ldstr.scopes[scope].opts.bars.barWidth,
								fillColor: {
									colors: ['#c2272d', '#c2272d', '#c2272d', '#fee901']
								},
								lineWidth: 0
							},
							label: gettext('Above average'),
							selectable: true
						}
					];
					
					if (ldstr.series.original == {}) {
						// console.log('Sorry, no data received.');
						ldstr.t['updateTimer'] = setTimeout(function() {
							ldstr.plot.update(from, to, scope);
						}, 2000);
					} else {
						// console.log('Have data series, length is %s.', ldstr.series.original.data.length);
						clearTimeout(ldstr.t['updateTimer']);
						ldstr.plot.draw(set);
						if (ldstr.series.original.data.length > 0) {
							ldstr.gui.updateSpecifics(ldstr.specifics, null);
						};
					}
					// console.log(set); // Debug
					// console.log(ldstr.scopes[scope].opts.bars); // Debug
					
				});
				
				
			},
			
			updateDetail: function() {
				ldstr.gui.showSpinner();
				// console.log('Running updateDetail()'); // Debug
				var u = "data/detail/" + "?id=" + ldstr_id + "&callback=?";
				
				$.getJSON(ldstr.API_URL + u, function(response) {
					// console.log('Got response on details.'); // Debug
					// console.log(response[0].data[0][0]);
					
					// these should be corrected to account for GMT deviation.
					ldstr.from = new Date(response[0].data[0][0]-(3600*1000));
					ldstr.to = new Date(response[0].data[response[0].data.length-1][0]-(3600*1000));
					
					console.log('Showing details from %s to %s', ldstr.from, ldstr.to); // Debug
					ldstr.opts.yaxis.tickFormatter = ldstr.etc.format[ldstr.scopes.detail.format];
					ldstr.opts.xaxis = ldstr.scopes.detail.opts.xaxis;
					// console.log(ldstr.opts); // Debug
					// console.log(response[0]); // Debug
					ldstr.series.original = response[0];
					ldstr.specifics = ldstr.etc.data.getSeriesDetails(ldstr.series.original.data, ldstr.series.original.avg);
					ldstr.gui.updateSpecifics(ldstr.specifics, false);
					set = [
						{
							data: ldstr.series.original.data,
							lines: {
								show: true,
								lineWidth: 1,
								shadow: 0
							}
						}
					];
					
					ldstr.plot.draw(set);
					
					var zoom_opts = {
						series: {
						    lines: { show: true, lineWidth: 2 },
						    shadowSize: 0,
							color: '#cbd4db'
						},
						xaxis: { ticks: [], mode: "time" },
						yaxis: { ticks: [], min: 0, autoscaleMargin: 0.1 },
						selection: { mode: "x", color: "#fee901" },
						grid: {
							borderWidth: 1,
							borderColor: '#cbd4db',
							backgroundColor: '#f5f6f8'
						}
					};
					
					$.plot(ldstr.zoomer, set, zoom_opts);
					
				});
			},

			updateCommunity: function() {
				// console.log('Updating community data...'); // Debug
				$.getJSON(ldstr.API_URL + 'community/hour?callback=?', function(response) {
					ldstr.series.community = response[0];
					// ldstr.series.community.data.colors = ['#2f475a', '#5a6770', '#788692'];
					console.log(response[0]); // Debug
					var opts = {
						lines: {
							show: true
						},
						xaxis: {
							mode: "time"
						},
						grid: {
							show: false
						},
						yaxis: {
							min: 0
						},
						colors: ['#2f475a', '#5a6770', '#788692','#2f475a', '#5a6770', '#788692','#2f475a', '#5a6770', '#788692']
					};
					// console.log(ldstr.series.community.data);
					ldstr.the_plot = $.plot(ldstr.chart, ldstr.series.community.data, opts);
				});
			},
			
			draw: function(response) {
				// remove the spinner (if existing), since we know we're done.
				$('#ajax_spinner').remove();
				ldstr.the_plot = $.plot(ldstr.chart, set, ldstr.opts);
			}
			
			
		},
		init: function() {
			/**
			*	Setup of functionality, to be run at page load.
			*/
			if ($('body').hasClass('home')) {
				$.getJSON(ldstr.API_URL + 'data/current?id='+ ldstr_id +'&callback=?', function(response) {
					$('.your.stats .val').text(response.current);
				});
				
				$.getJSON(ldstr.API_URL + 'data/current?id='+ ldstr.community_id +'&callback=?', function(response) {
					$('.our.stats .val').text(response.current);
				});
				
				ldstr.plot.updateCommunity();
			};
			
			if($('body').hasClass('dashboard')) {
				// set up parameters
				ldstr.opts.grid.markings = ldstr.etc.markings.weekendAreas;
				
				// bind events:
				ldstr.chart.bind("plotclick", function(event, pos, item) {
					// TODO: Fix so that mouseup does not trigger handler when fired in the end of a selection.
					ldstr.ev.barClickHandler(event, pos, item);
	            });

				ldstr.chart.bind("plotselected", function(event, ranges) {
					ldstr.ev.selectedHandler(event, ranges);
				});

				ldstr.chart.bind("plotselecting", function(event, ranges) {

				});
				
				ldstr.chart.bind('plotunselected', function(event, ranges) {
					ldstr.ev.unselectedHandler(event, ranges);
				});
				
				ldstr.zoomer.bind('plotselected', function(event, ranges) {
					// console.log('Binding events to zoomer.'); // Debug
					var opts = ldstr.the_plot.getOptions();
					opts.xaxis = { min: ranges.xaxis.from, max: ranges.xaxis.to, mode: "time" };
					ldstr.the_plot.setupGrid();
					ldstr.the_plot.draw();
					
				});
				ldstr.zoomer.bind('plotunselected', function(event, ranges) {
					var opts = ldstr.the_plot.getOptions();
					opts.xaxis = { min: null, max: null, mode: "time" };
					ldstr.the_plot.setupGrid();
					ldstr.the_plot.draw();
				});
				
				// attach validation and handling to form
				$('button#submitForm').click(function() {
					fromstr = $('#interval_from').val();
					tostr = $('#interval_to').val();
					ldstr.from = ldstr.etc.date.fromString(fromstr);
					// console.log('From is %s', ldstr.from); // Debug
					ldstr.to = ldstr.etc.date.fromString(tostr);
					// console.log('To is %s', ldstr.to); // Debug
					if (ldstr.from > ldstr.to) {
						if (confirm(gettext('Oops! You seem to have typed a "From"-date that is later than the "To"-date. Would you like to switch them?'))) {
							var tempstr = fromstr;
							var tempdate = ldstr.from;
							fromstr = tostr;
							ldstr.from = ldstr.to;
							tostr = tempstr;
							ldstr.to = tempdate;
							$('#interval_from').val(fromstr);
							$('#interval_to').val(tostr);
						} else {
							return false;
						};
					};
					var scope = $('input[name="granularity"]:checked').eq(0).val();
					ldstr.current_scope = scope;
					ldstr.specifics = ldstr.etc.data.getSeriesDetails(ldstr.series.original.data);
					ldstr.gui.updateSpecifics(ldstr.specifics, null);
					ldstr.plot.update(ldstr.from, ldstr.to, scope);
					window.location.hash = "/"+scope+"/"+ fromstr.split("-").join("")+"/"+tostr.split("-").join("")+"/";
					return false;
				});

				$('a#img_upload_toggler').toggle(
					function() {
						$(this).parents('form').find('.file_control').show();
					},
					function() {
						$(this).parents('form').find('.file_control').hide();
					}
				);

				$('#tooltip p.close a').live('click', function() {
					$('#tooltip').remove();
					return false;
				});

				ldstr.toggler.bind('click', function() {
					if ($('#viz_interval').hasClass('open')) {
						//// console.log('picker is open, closing...'); // Debug
						$('#viz_interval').removeClass('open');
						ldstr.interval_selector.slideUp(600);
					} else {
						// console.log('picker is closed, opening...'); // Debug
						if ($('#detail_zoom').hasClass('open')) {
							
							$('#detail_zoom').removeClass('open').slideUp(600, function() {
								// Callback if detail mode is open.
								$('#viz_interval').addClass('open');
								ldstr.interval_selector.slideDown(600);
							});
						} else {
							$('#viz_interval').addClass('open');
							ldstr.interval_selector.slideDown(600);
						} 
					};
					return false;
				});

				$('.graph .nav li a').bind('click', function() {
					if ($('#viz_interval').hasClass('open')) {
						// console.log('picker is open, closing...'); // Debug
						$('#viz_interval').removeClass('open');
						$('#intervalSelector').slideUp(400);
					};
				});
				$('a[href^="#/hour"], a[href^="#/day"], a[href^="#/month"], a[href^="#/today"], a[href^="#/this-week"], a[href^="#/this-month"], a[href^="#/detail"]').live('click', function() {
					// console.log(($(this).attr('href'))) // Debug
					ldstr.ev.dateLinkHandler($(this).attr('href'));
				});
				
				
				/** 
				*	This block tries to adjust to date parameters and update the plot depending on them.
				**/
				
				var date_params = ldstr.etc.env.parseFragment(window.location.hash);
				// console.log(date_params); // Debug
				if (date_params) {
					// so we have date parameters, but do we have actual dates (these should be false in details mode)?
					if (date_params.from && date_params.to) {
						ldstr.from = new Date(date_params.from);
						ldstr.to = new Date(date_params.to);
						// console.log(('New dates from URL params: From is %s, To is %s', ldstr.from, ldstr.to)) // Debug
					};
					ldstr.current_scope = date_params.scope;
				} else {
					// No date parameters, so we assume that this is "Today", in hours. Set dates to correspond.
					ldstr.from = new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate(), 0, 0, 0, 0);
					ldstr.to = new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate(), 23, 59, 59, 999);
					// console.log(('No date params or invalid params (date_params is %s):\n From is %s,\nto is %s', date_params, ldstr.from, ldstr.to)) // Debug // Debug
				};
				if (ldstr.current_scope !== "detail") {
					// if we're not in details-mode, update plot with the given parameters and scope.
					// console.log(('Have dates, updating using them + scope.')) // Debug // Debug
					ldstr.plot.update(ldstr.from, ldstr.to, ldstr.current_scope);
				} else {
					// in details mode, update accordingly...
					ldstr.gui.switchToDetails();
					ldstr.plot.updateDetail();
				};
				ldstr.ev.updateGeneralStats(ldstr.gui.updateRecords);
				ldstr.gui.init_datepicker();
			};
			
		}
	};

	
	ldstr.init();
	
	// fireunit tests:
	if (typeof fireunit == "object") {
		// place fireunit tests here...
	}
		
});
