// Event Calendar 1.0 (20060725) By Ethan Gekoski (http://www.texas-xmas.com)
// Feel free to copy this file as needed. Just give me the credit at the top!

function Coll() {
	this.Rows = new Object();
}

Coll.prototype.Add = function(key, item) {
	this.Rows[key]=item;
}

Coll.prototype.Remove = function(key) {
	delete this.Rows[key];
}

Coll.prototype.Item = function(key) {
	return this.Rows[key];
}

Coll.prototype.RemoveAll = function() {
	// need to delete & recreate array because just setting length to zero
	// will not remove pointers for existing entries if you want to push
	// them back in a different order
	this.Rows = null;
	this.Rows = new Object();
}

Coll.prototype.Items = function() {
	return this.Rows;
}

Coll.prototype.Exists = function(key) {
	if (this.Rows[key]) {
		return true;
	} else {
		return false;
	}
}

Coll.prototype.Count = function() {
	var x = 0;
	for (var key in this.Rows) {
		x++;
	}
	return x;
}

function EventLib() {
	this.Events = new Coll();
}

EventLib.prototype.RecurringDate = function(intMonth, intDay) {
	// returns the upcoming date for this year or next year if the date has already passed for this year
	var datNow = new Date();
	intMonth-=1;
	var datTry = new Date(datNow.getFullYear(), intMonth, intDay);
	
	if (datNow > datTry) {
		return new Date(datNow.getFullYear()+1, intMonth, intDay);
	} else {
		return datTry;
	}
}

EventLib.prototype.RecurringDayOfWeek = function(intMonth, intWeekOfMonth, intDayOfWeek) {
	var datNow = new Date();
	var datTry = YearMonthWeekDay(datNow.getFullYear(), intMonth, intWeekOfMonth, intDayOfWeek);
	
	if (datNow > datTry) {
		return YearMonthWeekDay(datNow.getFullYear()+1, intMonth, intWeekOfMonth, intDayOfWeek);
	} else {
		return datTry;
	}
}

EventLib.prototype.ToUTC = function(datEvent, intUTCOffset) {
	// convert to UTC based on the difference between the originating timezone offset
	// and the current PC's timezone offset
	datEvent = new Date(datEvent); // keep a "real" date object
	var intOffsetDifference = (intUTCOffset*60)-datEvent.getTimezoneOffset();
	
	return new Date(datEvent.getTime()+(intOffsetDifference*1000*60));
}


function YearMonthWeekDay (intYear, intMonth, intWeekOfMonth, intDayOfWeek) {
	var datDate = new Date(intYear, intMonth-1, 1);
	var Day = 1000 * 60 * 60 * 24;
	var Week = Day * 7;
	var intOffset = (((7+(intDayOfWeek-datDate.getDay()))%7) * Day) + ((intWeekOfMonth-1) * Week);
	datDate = new Date(datDate.getTime() + (intOffset));

	// if we crossed the return to standard time since the 1st of the month, go to hour zero and set the day forward by one
	if (datDate.getHours()>12) {
		datDate.setHours(0);
		datDate.setDate(datDate.getDate()+1);
	} else {

		// if we crossed going to daylight savings time since the 1st of the month, go to hour zero and set the day back by one
		if (datDate.getHours()>0) {
			datDate.setHours(0);
			datDate.setDate(datDate.getDate()-1);
		}
	}

	return datDate;
}

EventLib.prototype.NextEaster = function(datNow) {
	// returns the date of the upcoming Easter Sunday
	var datTry = Easter(datNow.getFullYear());
	if (datNow > datTry) {
		return Easter(datNow.getFullYear()+1);
	} else {
		return datTry;
	}
}

function Easter (y) {
	// calculate Easter Sunday for the given year using the Gregorian calendar algorithm
	var c = Math.floor(y / 100);
	var n = y - 19 * Math.floor( y / 19 );
	var k = Math.floor(( c - 17 ) / 25);
	var i = Math.floor(c - Math.floor(c / 4) - ( c - k ) / 3 + 19 * n + 15);
	i = i - 30 * Math.floor( i / 30 );
	i = i - Math.floor(( i / 28 ) * ( 1 - ( i / 28 ) * ( 29 / ( i + 1 ) ) * ( ( 21 - n ) / 11 ) ));
	var j = y + Math.floor(y / 4) + i + 2 - c + Math.floor(c / 4);
	j = j - 7 * Math.floor( j / 7 );
	var l = i - j;
	var m = Math.floor(3 + ( l + 40 ) / 44);
	var d = l + 28 - 31 * Math.floor( m / 4 );
	
	return new Date(y, m-1, d);
}

EventLib.prototype.AddEvent = function(DateName, DateStart, DateEnd, Message) {
	// adds event record to the dictionary and keeps it sorted
	
	if (DateStart <= DateEnd && !this.Events.Exists(DateName)) {
		var objEvent = new EventRecord(DateName, DateStart, DateEnd, Message, -5);
		this.Events.Add(DateName, objEvent);
	} 
}

EventLib.prototype.RemoveEvent = function(DateName) {
	// remove event specified by date name
	if (this.Events.Exists(DateName)) {
		this.Events.Remove(DateName);
	} 
}

EventLib.prototype.SortByDate = function() {
	SortColl(this.Events);
}

EventLib.prototype.NextEvent = function() {
	var datNow = new Date();
	
	// returns event record for the next scheduled event
	
	for (var key in this.Events.Items()) {
		if (((datNow >= this.Events.Item(key).DateStart) && (datNow < this.Events.Item(key).DateEnd)) || (datNow < this.Events.Item(key).DateStart)) {
			return this.Events.Item(key);
		}		
	}
	
	// nothing left, so return an empty record
	return new EventRecord("None", "", "", "");
}

EventLib.prototype.CountdownText = function(strEvent) {
	var datNow = new Date();
	var objNextEvent = this.Events.Item(strEvent);
	var intDays = objNextEvent.DaysFrom(datNow);
	var intHours = 0;
	var intMinutes = 0;
	var intSeconds = 0;
	
	if (objNextEvent.TimeFrom(datNow) == 0) {
		// between start & end, so return the date's message
		return (objNextEvent.Message);
	} else {
		// counting down to next event
		if (intDays < 8) {
			// final seven days, so include hours too
			intHours = objNextEvent.HoursFrom(datNow) - (intDays * 24);
			if (intDays == 0) {
				// final day, so include minutes and ditch the days remaining
				intMinutes = objNextEvent.MinutesFrom(datNow) - (intHours * 60);
				if (intHours == 0) {
					// final hour, so include seconds and ditch the hours remaining
					intSeconds = objNextEvent.SecondsFrom(datNow) - (intMinutes * 60);
					if (intMinutes == 0) {
						// final minute so show only seconds
						return (intSeconds + " second" + Plural(intSeconds) + " until " + objNextEvent.DateName);
					} else {
						return (intMinutes + " minute" + Plural(intMinutes) + " and " + intSeconds + " second" + Plural(intSeconds) + " until " + objNextEvent.DateName);
					}
				} else {
					return (intHours + " hour" + Plural(intHours) + " and " + intMinutes + " minute" + Plural(intMinutes) + " until " + objNextEvent.DateName);
				}
			} else {
				return (intDays + " day" + Plural(intDays) + " and " + intHours + " hour" + Plural(intHours) + " until " + objNextEvent.DateName);
			}
		} else {
			return (intDays + " days until " + objNextEvent.DateName);
		}
	} 
}

EventLib.prototype.DumpEvents = function() {
	var strEvents = "<table border=1><tr style='font-weight:bold'><td>Date Name</td><td>Start Date</td><td>End Date</td><td>Message</td></tr>";
	for (var key in this.Events.Items()) {
		
		strEvents += "<tr><td>" + this.Events.Item(key).DateName + "</td><td>" +
			this.Events.Item(key).DateStart + "</td><td>" +
			this.Events.Item(key).DateEnd + "</td><td>" +
			this.Events.Item(key).Message + "</td></tr>"
	}			
	
	strEvents += "</table>";
	return strEvents;
}

function EventRecord(DateName, DateStart, DateEnd, Message, UTCOffset) {
	this.DateName = DateName;
	this.DateStart = new Date(DateStart);
	this.DateEnd = new Date(DateEnd);
	this.Message = Message;
	this.UTCOffset = UTCOffset;
}

EventRecord.prototype.TimeFrom = function(datNow) {
	var datRemaining = this.DateStart.getTime() - datNow.getTime();
	if (datRemaining > 0) {
		return (datRemaining);
	} else {
		return 0;
	}
}

EventRecord.prototype.DaysFrom = function(datNow) {
	return (Math.floor(this.TimeFrom(datNow) / 1000 / 60 / 60 / 24));
}

EventRecord.prototype.HoursFrom = function(datNow) {
	return (Math.floor(this.TimeFrom(datNow) / 1000 / 60 / 60));
}

EventRecord.prototype.MinutesFrom = function(datNow) {
	return (Math.floor(this.TimeFrom(datNow) / 1000 / 60));
}

EventRecord.prototype.SecondsFrom = function(datNow) {
	return (Math.floor(this.TimeFrom(datNow) / 1000));
}

function NumericSort(x, y) {
	return x - y;
}

function SortColl(objColl) {
	var objTempColl = new Coll();
	var objKey;
	var varKey;
	var x = 0;
	var y = 0;
	var z = 0;

	// get the dictionary count minus one since arrays start at zero
	z = (objColl.Count() - 1);
	
	// create array to hold all dictionary objects
	var arrColl = new Array(z);
	
	if (z > 0) {
		// populate the array and temporary dictionary
		for (var key in objColl.Items()) {	
			arrColl[x] = objColl.Item(key).DateStart;
			objTempColl.Add (arrColl[x], objColl.Item(key));
			x++;
		}

		arrColl.sort(NumericSort);

		// erase the contents of the dictionary object
		objColl.RemoveAll();
		
		//repopulate the dictionary with the sorted information
		for (x = 0; x <= z; x++) {
			objColl.Add (objTempColl.Item(arrColl[x]).DateName, objTempColl.Item(arrColl[x]));
		}
		
	}
}

function Plural(intNumber) {
	if (intNumber==0 || Math.abs(intNumber) > 1) {
		return "s";
	} else {
		return "";
	}
}
