var Gibbon = {
	Version: '0.0.1',
	prototypeVersion: parseFloat(Prototype.Version.split(".")[0] + "." + Prototype.Version.split(".")[1])
}

if((typeof Prototype=='undefined') || Gibbon.prototypeVersion < 1.3)
      throw("Gibbon requires the Prototype JavaScript framework >= 1.3");

Gibbon.Measure = {
	getPageSize: function(){
	  	var xScroll, yScroll;
	  	var windowWidth, windowHeight;
	  	var pageHeight, pageWidth;
	
		// Scroll
	  	if (window.innerHeight && window.scrollMaxY) {	
	  		xScroll = document.body.scrollWidth;
	  		yScroll = window.innerHeight + window.scrollMaxY;
	  	} else if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac
	  		xScroll = document.body.scrollWidth;
	  		yScroll = document.body.scrollHeight;
	  	} else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
	  		xScroll = document.body.offsetWidth;
	  		yScroll = document.body.offsetHeight;
	  	}	
	
		// Window
	  	if (self.innerHeight) {	// all except Explorer
	  		windowWidth = self.innerWidth;
	  		windowHeight = self.innerHeight;
	  	} else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
	  		windowWidth = document.documentElement.clientWidth;
	  		windowHeight = document.documentElement.clientHeight;
	  	} else if (document.body) { // other Explorers
	  		windowWidth = document.body.clientWidth;
	  		windowHeight = document.body.clientHeight;
	  	}	
	
		// Page
	  	if(yScroll < windowHeight){
		  	// for small pages with total height less then height of the viewport
	  		pageHeight = windowHeight;
	  	} else { 
	  		pageHeight = yScroll;
	  	}	
	  	if(xScroll < windowWidth){	
		  	// for small pages with total width less then width of the viewport
	  		pageWidth = windowWidth;
	  	} else {
	  		pageWidth = xScroll;
	  	}
	
	  	return {pageWidth: pageWidth, pageHeight: pageHeight, windowWidth: windowWidth, windowHeight: windowHeight};
	},

	getWindowScroll: function() {
		var w = window;
		var T, L, W, H;
		with (w.document) {
			if (w.document.documentElement && documentElement.scrollTop) {
				T = documentElement.scrollTop;
				L = documentElement.scrollLeft;
			} else if (w.document.body) {
				T = body.scrollTop;
				L = body.scrollLeft;
			}
			if (w.innerWidth) {
				W = w.innerWidth;
				H = w.innerHeight;
			} else if (w.document.documentElement && documentElement.clientWidth) {
				W = documentElement.clientWidth;
				H = documentElement.clientHeight;
			} else {
				W = body.offsetWidth;
				H = body.offsetHeight
			}
		}
		
		return { top: T, left: L, width: W, height: H };
	},
	
	getObjectOffset: function(obj) {
		var x = 0;
		var y = 0;
		
		if (obj.offsetLeft != null) {
			x += obj.offsetLeft;
			var parent = obj;
			while (parent.offsetParent) {
				x += parent.offsetParent.offsetLeft;
				parent = parent.offsetParent;
			}
		}

		if (obj.offsetTop != null) {
			y += obj.offsetTop;
			parent = obj;
			while (parent.offsetParent) {
				y += parent.offsetParent.offsetTop;
				parent = parent.offsetParent;
			}
		}
		
		return { x: x, y: y };
	}
}

Gibbon.Form = {
	hideSelectBoxes: function() {
		$$('select').each(function(element) {element.style.visibility = "hidden"});
	},
	
	showSelectBoxes: function() {
		$$('select').each(function(element) {element.style.visibility = "visible"});
	},
	
	autoSelectText: function(elementId) {
		Event.observe($(elementId), 'focus', function() {		
			$(elementId).select();
		});
	},
	
	blockEnter: function(elementId) {
		$(elementId).onkeypress = function(evt) {
		    evt = (evt) ? evt : event;
		    var charCode = (evt.charCode) ? evt.charCode : ((evt.which) ? evt.which : evt.keyCode);
		    if (charCode == 13 || charCode == 3) {
		        return false;
		    } else {
		        return true;
		    }
		};
	},
	
	disableAutocomplete: function(elementId, status) {
		if (Gibbon.UserAgent.isIE) {
			$(elementId).autocomplete = "off";
		} else {
			$(elementId).setAttribute("autocomplete", "off");
      }
	},
	
	highlightSelectBoxValue: function(elementId, value) {
		var selectbox = $(elementId);
		for (var i = 0; i < selectbox.options.length; i++) {
			var option = selectbox.options[i];
			if (option.value == value) {
				option.selected = true;
			}
		}
	}
}

Gibbon.Popup = Class.create();
Gibbon.Popup.prototype = {
	initialize: function() {
		this.url = arguments[0];
		this.name = 'popup';
		this.width = 400;
		this.height = 400;

		if (arguments.length >= 2) {
			this.name = arguments[1];
			if (arguments.length >= 4) {
				this.width = arguments[2];
				this.height = arguments[3];
				// Future Options
				if (arguments.length > 4) {
				}
			}
		}

		this.window = window.open(this.url, this.name, "toolbar=no,location=0,directories=no,scrollbars=yes,resizable=yes,width=" + this.width + ",height=" + this.height);
		this.window.focus();
	},
	
	close: function() {
		this.window.close();
	}
}

Gibbon.PopupWarning = Class.create();
Gibbon.PopupWarning.prototype = {
	initialize: function() {
		this.url = arguments[0];
		this.name = 'popup';
		this.width = 650;
		this.height = 210;

		if (arguments.length >= 2) {
			this.name = arguments[1];
			if (arguments.length >= 4) {
				this.width = arguments[2];
				this.height = arguments[3];
				// Future Options
				if (arguments.length > 4) {
				}
			}
		}

		this.window = window.open(this.url, this.name, "toolbar=no,location=0,top=0,directories=no,scrollbars=yes,resizable=yes,width=" + this.width + ",height=" + this.height);
		if (this.window) {
			this.window.focus();
		}
	},
	
	close: function() {
		this.window.close();
	}
}

Gibbon.UserAgent = {
	isIE: navigator.userAgent.toLowerCase().indexOf("msie") >= 0
}

Gibbon.MessageBox = Class.create();
Gibbon.MessageBox.prototype = {
	initialize: function(id, glass) {
		this.element = $(id);
		this.glass = glass;
	},
	
	show: function() {
		if (this.glass) {
			this.glass.show();
			Element.setStyle(this.element, {
				zIndex:   this.glass.zIndex + 10
			});
		}

		Element.setStyle(this.element, {
			display:  'block',
			position: 'absolute'
		});

		if (!this.eventResize) {
			this.eventResize = this.center.bindAsEventListener(this);
		}
		
		Event.observe(window, 'resize', this.eventResize);
		Event.observe(window, 'scroll', this.eventResize);

		this.center();
	},
	
	hide: function() {
		if (this.element) {
			this.element.hide();
		}
		if (this.glass) {
			this.glass.hide();
		}

		Event.stopObserving(window, 'resize', this.eventResize);
		Event.stopObserving(window, 'scroll', this.eventResize);
	},
	
	center: function(event) {
		var page = Gibbon.Measure.getPageSize();
		var scroll = Gibbon.Measure.getWindowScroll();

		Element.setStyle(this.element, {
			left: (scroll.left + (page.windowWidth - this.element.offsetWidth) / 2) + 'px',
			top:  (scroll.top + (page.windowHeight - this.element.getHeight()) / 2) + 'px'
		});			
	}
}

Gibbon.Glass = Class.create();
Gibbon.Glass.prototype = {	
	initialize: function(options) {
		this.setOptions(options);
		this.zIndex = 10;

		if ($(this.options.id) && this.element) {
			this.zIndex += 10;
			Element.setStyle(this.element, {zIndex: this.zIndex});
		}
		// create overlay div and hardcode some functional styles (aesthetic styles are in CSS file)
		else {
			var objBody = document.getElementsByTagName("body").item(0);
			var objOverlay = document.createElement("div");
			objOverlay.setAttribute('id', this.options.id);
			if (this.options.className) {
				objOverlay.className = this.options.className;
			}
			objOverlay.style.display = 'none';
			objOverlay.style.position = 'absolute';
			objOverlay.style.top = '0';
			objOverlay.style.left = '0';
			objOverlay.style.zIndex = this.zIndex;
		 	objOverlay.style.width = '100%';
		 	objOverlay.style.height = '100%';
		 	objOverlay.style.backgroundColor = this.options.color;

			objBody.insertBefore(objOverlay, objBody.firstChild);
	
			Element.setOpacity(objOverlay, this.options.opacity);

			if (!this.eventResize)
				this.eventResize = this.resize.bindAsEventListener(this);
			
			this.element = objOverlay; //$(element);
		}
	},
	
	setOptions: function(options) {
		this.options = {
			id:      'glass',
			color:   'black',
			opacity: 0.5
		};
		Object.extend(this.options, options || {});
	},

	show: function() {
		this.resize();
		if (Gibbon.UserAgent.isIE) {
			Gibbon.Form.hideSelectBoxes();
		}
		Element.setStyle(this.element, {display: 'block'});
		Event.observe(window, 'resize', this.eventResize);
		Event.observe(window, 'scroll', this.eventResize);
	},
	
	hide: function() {
		Event.stopObserving(window, 'resize', this.eventResize);
		Event.stopObserving(window, 'scroll', this.eventResize);

		Element.hide(this.element);

		if (Gibbon.UserAgent.isIE) {
			Gibbon.Form.showSelectBoxes();
		}
	},
	
	resize: function(event) {
		if (Gibbon.UserAgent.isIE) {
			//this.element.style.height = Gibbon.Measure.getPageSize().windowHeight + 'px';
			this.element.style.height = Gibbon.Measure.getPageSize().pageHeight + 'px';
		}
	}
}

Gibbon.Date = Class.create();
Gibbon.Date.YEAR = 1;
Gibbon.Date.MONTH = 2;
Gibbon.Date.DAY = 3;
Gibbon.Date.DAY_NAMES = new Array("Måndag", "Tisdag", "Onsdag", "Torsdag", "Fredag", "Lördag", "Söndag");
Gibbon.Date.MONTH_NAMES = new Array("Januari", "Februari", "Mars", "April", "Maj", "Juni", "Juli", "Augusti", "September", "Oktober", "November", "December", "");
Gibbon.Date.MONTH_LENGTHS = new Array(31,28,31,30,31,30,31,31,30,31,30,31);
Gibbon.Date.prototype = {	
	// Arguments:	
	// > nicedate
	// > year, month, day
	initialize: function() {
		this.now = new Date();
		var valid = false;

		if (arguments.length > 0) {
			var year = 0;
			var month = 0;
			var day = 0;
			if (arguments.length == 1) {
				var dateFields = arguments[0].split('-');
				if (dateFields.length == 3) {
					year = parseInt(dateFields[0], 10);
					month = parseInt(dateFields[1], 10);
					day = parseFloat(dateFields[2], 10);
					valid = true;
				}
			} else if (arguments.length == 3) {
				year = arguments[0];
				month = arguments[1];
				day = arguments[2];
				valid = true;
			}
		}
		
		if (valid) {
			this.setDate(year, month, day);
		} else {
			this.date = this.now;
		}
	},

	setDate : function(year, month, day) {
		this.date = new Date(year, month-1, day);		
	},

	set : function(type, value) {
		switch (type) {
			case Gibbon.Date.YEAR:
				this.date.setYear(value);
				break;
			case Gibbon.Date.MONTH:
				this.date.setMonth(value - 1);
				break;
			case Gibbon.Date.DAY:
				this.date.setDate(value);
				break;
		} 
	},
	
	add : function(type, value) {
		switch (type) {
			case Gibbon.Date.YEAR:
				this.date.setYear(this.date.getYear() + value);
				break;
			case Gibbon.Date.MONTH:
				this.date.setMonth(this.date.getMonth() + value);
				break;
			case Gibbon.Date.DAY:
				this.date.setDate(this.date.getDate + value);
				break;
		} 
	},

	getYear : function() {
		return this.date.getFullYear();
	},

	getMonth : function(zeroPad) {
		return zeroPad ? this.zeroPad(this.date.getMonth() + 1) : this.date.getMonth() + 1;
	},

	getDay : function(zeroPad) {
		return zeroPad ? this.zeroPad(this.date.getDate()) : this.date.getDate();
	},

	getEpoch : function() {
		return this.date.valueOf();
	}, 
		
	getWeek : function() {
		return this.calculateWeekNumber(this.getYear(), this.getMonth(), this.getDay());
	},
	
	getFirstWeekInMonth : function() {
		return this.calculateWeekNumber(this.getYear(), this.getMonth(), 0);
	},
	
	calculateWeekNumber : function(year, month, day) {
		now = Date.UTC(year,month-1,day+1,0,0,0);
		var firstday = new Date();
		firstday.setYear(year);
		firstday.setMonth(0);
		firstday.setDate(1);
		then = Date.UTC(year,0,1,0,0,0);
		var compensation = firstday.getDay();
		if (compensation > 3) compensation -= 4;
		else compensation += 3;
		nr = Math.round((((now-then)/86400000)+compensation)/7);
		return nr;
	},
	
	getMonthName : function() {
		return Gibbon.Date.MONTH_NAMES[this.date.getMonth()];
	},

	getFirstWeekdayInMonth : function() {
		return new Date(this.getYear(), this.date.getMonth(), 0).getDay();
	},

	getNofDaysInMonth : function() {
		var nr = Gibbon.Date.MONTH_LENGTHS[this.date.getMonth()];
		if (this.date.getMonth() == 1) {
			nr += this.isLeapYear() ? 1 : 0;
		}
		return nr;
	}, 
	
	isLeapYear: function() { 
		if (this.getYear() < 0) return (this.getYear() +1) % 4 == 0;
		if (this.getYear() < 1582) return this.getYear() % 4 == 0;
		if (this.getYear() % 4 != 0) return false;
		if (this.getYear() % 100 != 0) return true;
		if (this.getYear() % 400 != 0) return false;
		if (this.getYear() % 3200 != 0) return true;
		return false; 
	},
	
	after : function(date) {
		if (this.getYear() > date.getYear()) {
			return true;
		} else if (this.getYear() >= date.getYear() &&
			this.getMonth() > date.getMonth()) {
			return true;
		} else if (this.getYear() >= date.getYear() &&
			this.getMonth() >= date.getMonth() &&
			this.getDay() > date.getDay()) {
			return true;
		} else {
			return false;
		}
	},
	
	before : function(date) {
		return date.after(this);
	},

	inRange : function(range) {
		return (this.after(range.start) && this.before(range.stop));
	},
	
	zeroPad : function(number) {
		return (number < 10 ? '0' + number : number);
	},
	
	clone : function() {
		return new Gibbon.Date(this.getYear(), this.getMonth(), this.getDay());
	},
		
	copy : function(date) {
		this.setDate(date.getYear(), date.getMonth(), date.getDay());
	},
		
	equals : function(date) {
		return (date.getYear() == this.getYear() && date.getMonth() == this.getMonth() && date.getDay() == this.getDay());
	},
	
	equalsCurrentYear : function() {		
		return this.date.getFullYear() == this.now.getFullYear();
	},
	
	toString : function() {
		return this.getYear() + "-" + this.zeroPad(this.getMonth()) + "-" + this.zeroPad(this.getDay());
	}
}

Gibbon.Calendar = Class.create();
Gibbon.Calendar.prototype = {	
	initialize: function(year, month, options) {
		this.date = new Gibbon.Date(year, month, 1);
		this.currentDateIndex = -1;
		this.refresh = true;
		this.setOptions(options);

		var objBody = document.getElementsByTagName("body").item(0);
		var objCalendar = document.createElement("div");
		objCalendar.setAttribute('id', this.options.id);
		if (this.options.className) {
			objCalendar.className = this.options.className;
		}
		objCalendar.style.display = 'none';
		objCalendar.style.position = 'absolute';

		objBody.insertBefore(objCalendar, objBody.firstChild);
			
		this.element = objCalendar;
	},
	
	setOptions: function(options) {
		this.options = {
			id:		'calendar',
			hideOnChange: true,
			datesInOrder: true,
			range:	{start: new Gibbon.Date(2006, 1, 1), stop: new Gibbon.Date(2016, 1, 1)}
		};
		Object.extend(this.options, options || {});
	},

	// Arguments:	
	// > nicedate, triggerId, offsetX, offsetY
	// > year, month, day, triggerId, offsetX, offsetY
	addDate : function() {
		var aStep = 0;
		var newDate;
		
		if (arguments.length == 4) {
			newDate = new Gibbon.Date(arguments[0]);
		} else if (arguments.length == 6) {
			newDate = new Gibbon.Date(arguments[0], arguments[1], arguments[2]);			
			aStep = 2;
		}
		
		var triggerId = arguments[1+aStep];
		var offsetX = arguments[2+aStep];
		var offsetY = arguments[3+aStep];
		
		if (!this.dates) {
			this.dates = new Array();
		}

		var index = this.dates.length;		
		
		// Date module
		this.dates[index] = {
			index: index,
			date: newDate,
			trigger: $(triggerId),
			offset: {x:offsetX,y:offsetY},
			click: function(e) {
				if (index == this.currentDateIndex) {
					this.toggle();
				} else {
					this.open(index, true);
				}
			}
		}
		
		// Connect onclick-event for trigger
		this.dates[index].trigger.onclick = this.dates[index].click.bindAsEventListener(this);
	},
		
	setDate : function(year, month, day, switchToMonth, hideOnChange, fireEvent) {
		// Hide
		var hideCalendar = false;
		if (typeof hideOnChange == 'undefined') {
			hideCalendar = this.options.hideOnChange;
		} else {
			hideCalendar = hideOnChange;
		}
		if (hideCalendar) {
			this.close();
		}

		// Change
		var date = this.getCurrentDate();
		var newDate = new Gibbon.Date(year, month, day);
		if (!date.equals(newDate)) {
			date.copy(newDate);
			this.refresh = true;
		}
				
		// Fire Event
		if (typeof fireEvent == 'undefined' || fireEvent == true) {
			this.dateChangedEvent(this.dates[this.currentDateIndex]);
		}

		// Check Order
		if (this.options.datesInOrder) {
			for (var i = 0; i < this.dates.length-1; i++) {
				if (this.dates[i].date.after(this.dates[i+1].date)) {
					this.dates[i+1].date.copy(this.dates[i].date);
					this.dateChangedEvent(this.dates[i+1]);
				}
			}
		}
		
		// Update shown Year and Month
		if (typeof switchToMonth == 'undefined' || switchToMonth == true) {
			this.date.set(Gibbon.Date.YEAR, date.getYear());			
			this.date.set(Gibbon.Date.MONTH, date.getMonth());
		}
		
		// Re-render
		this.render();
	},

	setRange : function(start, stop) {
		this.options.range = {start: new Gibbon.Date(start), stop: new Gibbon.Date(stop)};
	},

	getCurrentDate : function() {
		return this.dates[this.currentDateIndex].date;
	},
		
	nextMonth : function() {
		this.date.add(Gibbon.Date.MONTH, 1);
		this.render();
	},

	previousMonth : function() {
		this.date.add(Gibbon.Date.MONTH, -1);
		this.render();
	},
	
	showCurrentMonth : function() {		
		this.date.set(Gibbon.Date.MONTH, getCurrentDate().getMonth());
		this.render();
	},
	
	toggle : function() {
		this.element.toggle();
	},
	
	open : function(index, switchToMonth) {
		if (index != this.currentDateIndex) {
			this.currentDateIndex = index;
			this.refresh = true;
		}

		if (typeof switchToMonth != 'undefined' && switchToMonth == true) {
			var year = this.getCurrentDate().getYear();
			var month = this.getCurrentDate().getMonth();

			if (!isNaN(year) && year > 0 && !isNaN(month) && month > 0) {
				this.date.set(Gibbon.Date.YEAR, year);
				this.date.set(Gibbon.Date.MONTH, month);
			} else {
				var now = new Gibbon.Date();
				this.date.set(Gibbon.Date.MONTH, now.getMonth());
			}
		}

		if (this.refresh) {
			this.render();
		}

		var offset = Gibbon.Measure.getObjectOffset(this.dates[index].trigger);
		var x = offset.x + this.dates[index].offset.x;
		var y = offset.y + this.dates[index].offset.y;
		Element.setStyle(this.element, {
			left: x + 'px',
			top: y + 'px'
		});

		this.element.show();
	},
	
	close : function() {
		this.element.hide();
	},
	
	render : function() {
		var date = this.date;
		var firstWeekdayInMonth = date.getFirstWeekdayInMonth();	
		var nofDaysInMonth = date.getNofDaysInMonth();
		var renderedWeeks = 0;

		var html = '<table class="calendar" cellpadding="0" cellspacing="0" border="0">';

		// Caption
		html += '<caption>';
		html += '<a href="#" onclick="' + this.options.id + '.previousMonth();" class="leftArrow">&laquo;</a>';
		html += '<div>' + date.getMonthName() + " " + date.getYear() + '</div>';
		html += '<a href="#" onclick="' + this.options.id + '.nextMonth();" class="rightArrow">&raquo;</a>';
		html += '</caption>';

		// Weekday Names
	    html += '<thead><tr><th>&nbsp;</th>';
		for (var i=0; i < Gibbon.Date.DAY_NAMES.length; i++) {
			html += '<th>' + Gibbon.Date.DAY_NAMES[i].substring(0,1) + '</th>';
		}
		html += '</tr></thead>';

		// Month Body
		html += '<tbody class="date' + this.currentDateIndex + '"><tr>';

		// First WeekNumber
		html += '<th>v' + date.getFirstWeekInMonth() + '</th>';
		// Blanks till first weekday in month
	    for (var i = 0; i < firstWeekdayInMonth; i++) {
			html += '<td>&nbsp;</td>';
		}
	
		// Day cells
		for (var day=1; day <= nofDaysInMonth; day++) {
			var loopDate = new Gibbon.Date(date.getYear(), date.getMonth(), day);			
			var selectedIndex = -1;
			var multiSelectedIndex = false;
					
			// Render Links if in valid date-range	
			if (loopDate.inRange(this.options.range)) {
				// Dertermine if current looped day equals any of the dates for highlight
				for (var dateIndex = 0; dateIndex < this.dates.length; dateIndex++) {
					if (this.dates[dateIndex].date.equals(loopDate)) {
						if (selectedIndex != -1) {
							multiSelectedIndex = true;
							break;
						}
						selectedIndex = dateIndex;					
					}
				}
				// Handle different selection hightlights
				if (multiSelectedIndex) {
					html += '<td class="multiSelected">';
				} else if (selectedIndex != -1) {
					html += '<td class="selected' + parseInt(selectedIndex, 10) + '">';
				} else {
					html += '<td class="day">';
				}
				// The Link
				html += '<a href="#" onclick="' + this.options.id + '.setDate(' + loopDate.getYear() + ', ' + loopDate.getMonth() + ', ' + day + ');">' + day + '</a></td>';
			} else {
				html += '<td>' + day + '</td>';
			}
			
			// New Row/Week Check
			if ((day+firstWeekdayInMonth)%7==0 && (day < nofDaysInMonth)) {
				html += '</tr><tr>'
				html += '<th class=calWeekNo>v' + loopDate.getWeek() + '</th>';
				renderedWeeks++;
			}
		}
		
		// Blank cells at end
		var totCells = firstWeekdayInMonth + nofDaysInMonth;	
	    for (var i=0; i<(totCells>28?(totCells>35?42:35):28)-totCells; i++) {
			html += '<td>&nbsp;</td>';
		}
		
		// Blank Row/Week if only 5 weeks
		if (renderedWeeks == 4) {
			html += '</tr><tr><th>&nbsp;</th><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>';
		}
		
		html += '</tr></tbody></table>\n';
		
		// Render HTML
		Element.update(this.element, html);
		this.refresh = false;
	}
}
