//  Reworking of Matt Kruse's (www.mattkruse.com) CalendarPopup so it could:
//  (1) be embedded within a web page as opposed to displaying as a popup
//	(2) show the dates that are available instead of dates that are unavailable
//  (3) allow availability to be set by passing RecurringDates and ExcludedDates objects
//
//	NOTE:  You must include Matt Kruse's full PopupWindow.js file for this code to work properly.

// CONSTRUCTOR for the EmbeddedCalendar Object
function AvailabilityCalendar(recurringDates,excludedDates) {
	
	var c = new Object();

	//  Initialize Variables
	c.now = new Date();

	c.recurringDates 	= recurringDates;
	c.excludedDates 	= excludedDates;

	c.monthNames = new Array("January","February","March","April","May","June","July","August","September","October","November","December");
	c.monthAbbreviations = new Array("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec");
	c.dayHeaders = new Array("S","M","T","W","T","F","S");
	c.weekStartDay = 0;
	c.isShowYearNavigation = false;
	c.displayType = "date";
	c.yearSelectStartOffset = 2;
	c.currentDate = null;
	c.todayText="Today";
	c.isShowNavigationDropdowns=false;
	c.isShowYearNavigationInput=false;

	// Call to display & get the Calendar
	c.showCalendar 	= CP_showCalendar; 
	c.getCalendar	= CP_getCalendar;
	c.refreshCalendar = CP_refreshCalendar;
	
	//  Method that will be called to display the available times in a popup div
	c.showAvailableTimesPopupFunction = "CP_showAvailableTimesPopup";
	
	c.getRefreshCalendarLink = CP_getRefreshCalendarLink;
	c.getAvailableTimes = CP_getAvailableTimes;
	c.timeComparator = CP_timeComparator;
	
	// Set the day for the first column in the calendar grid. By default this
	// is Sunday (0) but it may be changed to fit the conventions of other
	// countries.
	//cal.setWeekStartDay(1); // week is Monday - Sunday
	c.setWeekStartDay 	= CP_setWeekStartDay;

	c.getMonthNavigationHeader 	= CP_getMonthNavigationHeader;
	c.getDaysHeader 			= CP_getDaysHeader;

	window.CP_dateFormat = "MM/dd/yyyy";	
	
	//  Place reference for this Calendar into the parent window so it can be obtained later.
	c.storeCalendarInWindow = CP_storeCalendarInWindow;
	c.storeCalendarInWindow();

	return c;
}

//  Populate the calendar and display it.
//  The parentElement argument must be passes as a String reference to the div or span that the calendar should be placed into.
//  Date is not a mandatory argument. If not supplied, the current date will be used.  If supplied, the date format
//	must be MM/dd/yyyy.
function CP_showCalendar(parentElement, date) {

	this.parentElement = parentElement;
	if (arguments.length>1) 
	{
		if (arguments[1]==null||arguments[1]=="")
			this.currentDate=new Date();
		else
			this.currentDate=new Date(parseDate(arguments[1]));
	}
		
	if ( $(this.parentElement) == null || $(this.parentElement) == 'undefined')
		alert("Either the parent element could not be found or the prototype.js file has not been loaded.");
	else
		$(this.parentElement).innerHTML = this.getCalendar();
}

// Return a string containing all the calendar code to be displayed
function CP_getCalendar() 
{
	// Write the html version of Calendar  
	var result = '<TABLE CLASS="ajCalBorder" WIDTH=144 BORDER=0 BORDERWIDTH=1 CELLSPACING=0 CELLPADDING=1>\n';
	result += '<TR><TD ALIGN=CENTER>\n';
	result += '<CENTER>\n';

	//  First init date to display...
	if (this.currentDate == null) 
		this.currentDate = new Date();
	//  else date was probably setting by showCalendar()

	var month = this.currentDate.getMonth()+1;
	var year = this.currentDate.getFullYear();
	
	//  Init days in a month
	var daysinmonth= new Array(0,31,28,31,30,31,30,31,31,30,31,30,31);
	
	//  Adjust for leap years
	if ( ( (year%4 == 0)&&(year%100 != 0) ) || (year%400 == 0) ) 
		daysinmonth[2] = 29;

	var current_month 	= new Date(year,month-1,1);
	var display_year 	= year;
	var display_month 	= month;
	var display_date 	= 1;
	
	//  Determine how many days from the prior month must be displayed before the current month starts.
	var weekday	= current_month.getDay(); //  getDay returns day of week as Sunday = 0, Monday = 1, ...
	var offset  = (weekday >= this.weekStartDay) ? weekday-this.weekStartDay : 7-this.weekStartDay+weekday ;
	if (offset > 0) {
		display_month--;
		if (display_month < 1) { display_month = 12; display_year--; }
		display_date = daysinmonth[display_month]-offset+1;
	}

	//  Now store variables for display of the last & next months
	var next_month = month+1;
	var next_month_year = year;
	if (next_month > 12) { next_month=1; next_month_year++; }
	var last_month = month-1;
	var last_month_year = year;
	if (last_month < 1) { last_month=12; last_month_year--; }

	result += this.getMonthNavigationHeader(last_month, last_month_year, month, year, next_month, next_month_year);
	result += this.getDaysHeader();

	//  Now create table for each day of the month
	for (var row=1; row<=6; row++) //  Days of month row
	{
		result += '<TR>\n';
		for (var col=1; col<=7; col++)  //  Days of week column
		{  
			//  Determine CSS class for displaying the current day of month
			var dateClass = "";
			if ((display_month == this.now.getMonth()+1) && (display_date==this.now.getDate()) && (display_year==this.now.getFullYear())) {
				dateClass = "ajCalCurrentDate";
			} else if (display_month == month) {
				dateClass = "ajCalCurrentMonthDate";
			} else {
				dateClass = "ajCalOtherMonthDate"; //typically the greyed out dates before and after the current month
			}

			//  Determine if the current date is available
			var availableTimes = this.getAvailableTimes(display_month, display_date, display_year);			
			if (availableTimes == null) {
				result += '	<TD CLASS="'+dateClass+'">'+display_date+'</TD>\n';
			} else {
				var selected_date = display_date;
				var selected_month = display_month;
				var selected_year = display_year;
				result += '	<TD CLASS="'+dateClass+'"><A id="popupLinkId_'+selected_month+'_'+selected_date+'" HREF="#" onclick="'+this.showAvailableTimesPopupFunction+'(\''+availableTimes +'\','+selected_month+','+selected_date+');return false;" CLASS="'+dateClass+'Available">'+display_date+'</A></TD>\n';
			}																
			// Increment the display date
			display_date++;
			if (display_date > daysinmonth[display_month]) {
				display_date=1;
				display_month++;
			}
			if (display_month > 12) {
				display_month=1;
				display_year++;
			}
		}
		result += '</TR>';
	}

	//  Add button so user can readily return the Calendar to the current date's month
	result += '<TR>\n <TD COLSPAN=7 ALIGN=CENTER CLASS="ajCalTodayText">\n';
	result += '<A CLASS="ajCalTodayText" HREF="'+this.getRefreshCalendarLink(this.now.getMonth()+1, this.now.getFullYear())+'">'+this.todayText+'</A>\n<BR>\n';
	result += '</TD></TR></TABLE></CENTER></TD></TR></TABLE>\n';
	return result;
}

function CP_getMonthNavigationHeader(last_month, last_month_year, month, year, next_month, next_month_year)
{
	//  Create month/year navigation hearder
	var header = "<TABLE WIDTH=144 BORDER=0 BORDERWIDTH=0 CELLSPACING=0 CELLPADDING=0><TR>\n";
	
	//  Add html for navigating between months		
	header += '<TD CLASS="ajCalMonthNavigation" WIDTH="22"><A CLASS="ajCalMonthNavigation" HREF="'+this.getRefreshCalendarLink(last_month,last_month_year)+'">&lt;&lt;</A></TD>\n';
	header += '<TD CLASS="ajCalMonth" WIDTH="100"><SPAN CLASS="ajCalMonth">'+this.monthNames[month-1]+' '+year+'</SPAN></TD>\n';
	header += '<TD CLASS="ajCalMonthNavigation" WIDTH="22"><A CLASS="ajCalMonthNavigation" HREF="'+this.getRefreshCalendarLink(next_month,next_month_year)+'">&gt;&gt;</A></TD>\n';
	header += '</TR></TABLE>\n';
	header += '<TABLE WIDTH=120 BORDER=0 CELLSPACING=0 CELLPADDING=1 ALIGN=CENTER>\n';
	return header;
}		

function CP_getDaysHeader()
{
	var header = '<TR>\n';
	for (var j=0; j<7; j++) {
		header += '<td class="ajCalDayColumnHeader" width="14%"><span class="ajCalDayColumnHeader">'+this.dayHeaders[(this.weekStartDay+j)%7]+'</td>\n';
	}
	header += '</TR>\n';
	return header;
}

//  Return method String for refreshing Calendar to a different date
function CP_getRefreshCalendarLink(month, year)
{
	return 'javascript:CP_windowRefreshCalendar'+'('+month+','+year+','+this.windowIndex+')';
}

//  Return method String for refreshing Calendar to a different date
function CP_windowRefreshCalendar(month, year, calendarIndex)
{
	window.AJ_availabilityCalendar[calendarIndex].refreshCalendar(month, year);
}

//  Return method String for refreshing Calendar to a different date
function CP_refreshCalendar(month, year)
{
	this.currentDate 	= new Date(year,month-1,1);
	$(this.parentElement).innerHTML = this.getCalendar();
}

// Run through all the RecurringDates and Excluded dates to determine any times that would
// be available for the provided Date.
//
//@return null if this date is not available else return a string of the times that it is available.
function CP_getAvailableTimes(month, dayOfMonth, year) {
	var testDate = new Date(year,month-1,dayOfMonth);

	//  First test if this date is Excluded
	if ( this.excludedDates != null && typeof(this.excludedDates) != "undefined" && this.excludedDates.length > 0) {
		for (i = 0; i < this.excludedDates.length; i++) {
			if ( this.excludedDates[i].isExcluded(testDate))
				return null;
		}
	}
	
	//  Otherwise see if it matches one of the "recurringDates"
	var timeArr = [];
	var index = 0;
	for (i = 0; i < this.recurringDates.length; i++) {
		if ( this.recurringDates[i].occursOn(testDate)) {
			for (j = 0; j < this.recurringDates[i].getTimes().length; j++) {
				timeArr[index] = this.recurringDates[i].getTimes()[j];
				index++;
			}
		}
	}
	
	//  Sort the times for proper display
	var timeStr = null;
	timeArr.sort(this.timeComparator);
	if ( timeArr.length > 0) {
		timeStr = convertToColloquialTime(timeArr[0]);
		for (i = 1; i < timeArr.length; i++) {
			timeStr += "<br>" + convertToColloquialTime(timeArr[i]);
		}
	}
	
	return timeStr;
}

function convertToColloquialTime(time) {
	if ( time == "12:00am")
		return "midnight";
	else if ( time == "12:00pm")
		return "noon";
	else return time;
}

// Calling this method would result in display of a popup showing the available times
function CP_showAvailableTimesPopup(timesStr, month, date )
{
	var availabilityPopup = new PopupWindow("journeyTimesPopupDiv");
	availabilityPopup.offsetY = 5;
	availabilityPopup.offsetX = 15;
	availabilityPopup.autoHide();

	var linkId = "popupLinkId_" + month + "_" + date;
	
	availabilityPopup.populate("<div class='ajBoldText'>Tour Time(s):<div><div class='ajText'>" + timesStr + "</div>");
	availabilityPopup.showPopup(linkId);

}

// Set the day of the week (0-7) that the calendar display starts on
// This is for countries other than the US whose calendar displays start on Monday(1), for example
function CP_setWeekStartDay(day) { this.weekStartDay = day; }

//  Place reference for this Calendar into the parent window so it can be obtained later.
function CP_storeCalendarInWindow() {
	if ( window.AJ_availabilityCalendar == null)
		window.AJ_availabilityCalendar = new Array();
		
	var index = 0;
	while ( window.AJ_availabilityCalendar[index] != null)
		index++;
	
	window.AJ_availabilityCalendar[index] = this;
	this.windowIndex = index;
}


/*************  Now create a couple very useful Object types *************/
function ExcludedDate(startMonth, startDay, startYear,
					  endMonth, endDay, endYear,
					  // Both Java's Calendar and Javascript's Date use month ranges of 0-11 with January == 0;
					  excludedMonth,		
					  excludedDayOfMonth, 
					  // Note that Java's Calendar has a range is 1-7 with Sunday == 1 and Javascript's Date has a 
					  // range of 0-6 with Sunday == 0;.  
					  excludedDayOfWeek, 
					  isFirstWeekly, isSecondWeekly, isThirdWeekly, isFourthWeekly, isFifthWeekly) 
{
	//  Start and end dates for the Excluded period.
	this.startDate = this.endDate = null;
	if ( startMonth >= 0) {
		this.startDate = new Date(startYear, startMonth, startDay);
		if ( endMonth >= 0)
			this.endDate = new Date(endYear, endMonth, endDay);
	}
		

	//  Used to track yearly exclusions such as every December 25th that occur on a specific numbered day
	this.excludedMonth 	   = excludedMonth;
	this.excludedDayOfMonth  = excludedDayOfMonth;
	
	//  Note that Java's Calendar has a range is 1-7 with Sunday == 1 and Javascript's Date has a 
	//  range of 0-6 with Sunday == 0.  Sooo...we subtract 1 from the provided value to sync Java's Calendar 
	//  with Javascript's Date...!
	this.excludedDayOfWeek   = excludedDayOfWeek-1;

	//  Used to track yearly exclusions such as every fourth Thursday of November
	this.isFirstWeekly   = isFirstWeekly;
	this.isSecondWeekly  = isSecondWeekly;
	this.isThirdWeekly   = isThirdWeekly;
	this.isFourthWeekly  = isFourthWeekly;
	this.isFifthWeekly   = isFifthWeekly;
	
	this.isExcluded 					= ED_isExcluded;
	this.testSingleDayOccurrence 		= ED_testSingleDayOccurrence;
	this.testExcludedRange 				= ED_testExcludedRange;
	this.testDayOfWeekExclusion 		= ED_testDayOfWeekExclusion;
	this.testDayOfMonthExclusion		= ED_testDayOfMonthExclusion;
	this.matchesMonthlyType 			= ED_RD_matchesMonthlyType;
}

/**
 * @return true if this date occurs on one of the excluded dates 
 */
function ED_isExcluded(date)
{
	return  this.testSingleDayOccurrence(date)   	//  5.15.2010
			|| this.testExcludedRange(date) 		//  5.15.2010-6.2.2010
			|| this.testDayOfWeekExclusion(date)    //  Fourth Thursday in November
			|| this.testDayOfMonthExclusion(date);  //  Dec 25 in every year
}

/**
 * @return true if the provided date occurs on a single day set for exclusion
 */
function ED_testSingleDayOccurrence(date)
{
	var isMatch = false;
	if ( this.startDate != null && this.endDate == null) { //isSingleDayExclusion
		isMatch = (date.getDate() == this.startDate.getDate() && date.getMonth() == this.startDate.getMonth() && date.getYear() == this.startDate.getYear());
	}
	return isMatch;
}

/**
 * @return true if the provided date occurs on one of dates during the excluded range of dates
 */
function ED_testExcludedRange(date)
{
	var isMatch = false;
	if (this.startDate != null && this.endDate != null) { // isDateRangeExclusion
		if ( date.getYear() >= this.startDate.getYear() && date.getMonth() >= this.startDate.getMonth() && date.getDate() >= this.startDate.getDate() )
			return ( date.getYear() <= this.endDate.getYear() && date.getMonth() <= this.endDate.getMonth() && date.getDate() <= this.endDate.getDate() )
	}
	return isMatch;
}

/**
 * If this is an exclusion of type "First Monday of November", then test if the provided date 
 * is excluded.
 */
function ED_testDayOfWeekExclusion(date)
{
	var isMatch = false;
	
	//  All yearly exclusions have a recurring month so see if it's null. 
	if (this.excludedMonth != null)
	{
		var dD = date.getDay();
		var dM = date.getMonth();		
		var ini = 0;
		var mm = this.matchesMonthlyType(date);
		if ( date.getDate() == 16)
			ini = ini;
		//  Test day of week first (e.g. Monday, Tuesday, ...)
		if (date.getDay() == this.excludedDayOfWeek) // day of week matches
		{
			//  Now test "First ... of November" part
			isMatch = this.matchesMonthlyType(date) && this.excludedMonth == date.getMonth(); 
		}
	}
	return isMatch;
}

/**
 * If this is an exclusion of type "December 25th", then test if the provided date 
 * is excluded.
 */
function ED_testDayOfMonthExclusion(date)
{
	var isMatch = false;
	//  First test day of month first (e.g. 24, 25, ...)
	if ( date.getDate() == this.excludedDayOfMonth)
		//  Then test month. E.g. November, December, .. 
		isMatch = (this.excludedMonth == date.getMonth()); 
	return isMatch;
}

// Both Java's Calendar and Javascript's Date use month ranges of 0-11 with January == 0;
// Note that Java's Calendar has a range is 1-7 with Sunday == 1 and Javascript's Date has a 
// range of 0-6 with Sunday == 0;.  
function RecurringDate(yearlyDateMonth, yearlyDateDay, yearlyDateYear,  // int values
					   times, // String[]
					   startMonth, // int value for month
					   endMonth,   // int value for month
					   isMonday, isTuesday,isWednesday,isThursday,isFriday,isSaturday,isSunday, // boolean
					   isFirstWeekly, isSecondWeekly, isThirdWeekly, isFourthWeekly, isFifthWeekly) // boolean
{
	//  Create the yearly date if provided
	if ( yearlyDateMonth > 0 && yearlyDateDay > 0 && yearlyDateYear >= 0) {
		this.yearlyDate = new Date(yearlyDateYear, yearlyDateMonth, yearlyDateDay); 
	}	
	
	this.times 		= times;
	
	this.startMonth = startMonth;
	this.endMonth   = endMonth;
	
	this.isMonday 		= isMonday;
	this.isTuesday		= isTuesday;
	this.isWednesday 	= isWednesday;
	this.isThursday  	= isThursday;
	this.isFriday 		= isFriday;
	this.isSaturday		= isSaturday;
	this.isSunday 		= isSunday;

	this.isFirstWeekly  = isFirstWeekly;
	this.isSecondWeekly = isSecondWeekly;
	this.isThirdWeekly  = isThirdWeekly;
	this.isFourthWeekly = isFourthWeekly;
	this.isFifthWeekly  = isFifthWeekly;
	
	this.occursOn 				= RD_occursOn;
	this.testWeeklyOccurrence  	= RD_testWeeklyOccurrence;
	this.testMonthlyOccurrence 	= RD_testMonthlyOccurrence;
	this.testSingleDayOccurrence 	= RD_testSingleDayOccurrence;
	this.isDayOfWeekMatch	   		= RD_isDayOfWeekMatch;

	this.isWithinRecurrenceRange	= RD_isWithinRecurrenceRange;
	this.isMonthlyRecurrence 		= RD_isMonthlyRecurrence;
	this.matchesMonthlyType 		= ED_RD_matchesMonthlyType;
	this.isYearlyRecurrence		= RD_isYearlyRecurrence;
	this.isWeeklyRecurrence		= RD_isWeeklyRecurrence;

	this.getTimes				= RD_getTimes;
	
}
	
/**
 * @return true if this given Date occurs on any of the recurring dates 
 */
function	RD_occursOn(date)
{
	return this.testWeeklyOccurrence(date) 
			|| this.testMonthlyOccurrence(date) 
			|| this.testSingleDayOccurrence(date);
}

/**
 * @return the time that this RecurringDate occurs on.
 */
function	RD_getTimes()
{
	return this.times;
}

/**
 * @return true if the provided date occurs on the day of week that this recurrence does
 */
function RD_testWeeklyOccurrence(date)
{
	if ( !this.isMonthlyRecurrence() && this.isDayOfWeekMatch(date))
		return this.isWithinRecurrenceRange(date);
	return false;
}

/**
 * @return true if the provided date occurs on one of the same dates that this recurrence does
 */
function RD_testMonthlyOccurrence(date)
{
	if (this.matchesMonthlyType(date) && this.isDayOfWeekMatch(date))
		return this.isWithinRecurrenceRange(date);
	return false;
}

/**
 * @return true if the provided date occurs on the day of week that this recurrence does
 */
function RD_testSingleDayOccurrence(date)
{
	if (this.isYearlyRecurrence()) {
		return date.getDate() == this.yearlyDate.getDate() && date.getMonth() == this.yearlyDate.getMonth() && date.getYear() == this.yearlyDate.getYear();
	}
}

/**
 * @return true if the provided date matches one of this recurrence's days of week (Monday, Tuesday, ...)
 */
function RD_isDayOfWeekMatch(date)
{
	if (this.isSunday    	  && date.getDay() == 0) return true; // Don't ask why they had to be inconsistent 
	else if (this.isMonday    && date.getDay() == 1) return true; // BUT sunday == 0, Monday  = 1, ...
	else if (this.isTuesday   && date.getDay() == 2) return true; // in JavaScript...even though Sunday == 1
	else if (this.isWednesday && date.getDay() == 3) return true; // in Java...!  So we adjust for Javascript
	else if (this.isThursday  && date.getDay() == 4) return true; // and test appropriately...!
	else if (this.isFriday    && date.getDay() == 5) return true;
	else if (this.isSaturday  && date.getDay() == 6) return true;
	else return false;
}


function ED_RD_matchesMonthlyType(date)
{
	if (this.isFirstWeekly && date.getDate() >= 1 && date.getDate() <=7 )
		return true;
	else if (this.isSecondWeekly && date.getDate() >= 8 && date.getDate() <=14 )
		return true;
	else if (this.isThirdWeekly && date.getDate() >= 15 && date.getDate() <=21 )
		return true;
	else if (this.isFourthWeekly && date.getDate() >= 22 && date.getDate() <=28 )
		return true;
	else if (this.isFifthWeekly && date.getDate() >= 29 && date.getDate() <=31 )
		return true;
	return false;
}

function RD_isWithinRecurrenceRange(date)
{
	//  test whether we occur within the monthly range
	if ( this.startMonth < 0 && this.endMonth  < 0) {
		return true; //  no range specified then everything is in range
	} else {
		//  Test whether we have something like a single month July-July (start == end)
		//	or January-March (start<end) or Oct-January (start>end) 
		var testMonth       = date.getMonth();
		
		if (this.startMonth == this.endMonth )
			return testMonth == this.startMonth;
		else if (this.startMonth < this.endMonth )
			return testMonth >= this.startMonth && testMonth <= this.endMonth;
		else if (this.startMonth > this.endMonth )
			return testMonth >= this.startMonth || testMonth <= this.endMonth;
	} 
}

function RD_isWeeklyRecurrence() {
	return !this.isMonthlyRecurrence() && (this.isMonday || this.isTuesday || this.isWednesday || this.isThursday || this.isFriday  || this.isSaturday || this.isSunday);
}

function RD_isMonthlyRecurrence() {
	return this.isFirstWeekly || this.isSecondWeekly || this.isThirdWeekly || this.isFourthWeekly || this.isFifthWeekly;
}

function RD_isYearlyRecurrence() {
	return this.yearlyDate != null && typeof(this.yearlyDate) != "undefined";
}
 
function CP_timeComparator(timeStr0, timeStr1)
{
	//  First convert from common language times to numeric times (if present)
	if ( timeStr0 == "noon") timeStr0 = "12:00pm";
	if ( timeStr1 == "noon") timeStr1 = "12:00pm";
	if ( timeStr0 == "midnight") timeStr0 = "12:00am";
	if ( timeStr1 == "midnight") timeStr1 = "12:00am";
	
	if ( timeStr0.endsWith("am") && timeStr1.endsWith("pm")) return -1;
	else if ( timeStr0.endsWith("pm") && timeStr1.endsWith("am")) return 1;
	else {
		var index0 = timeStr0.indexOf(":");
		var index1 = timeStr1.indexOf(":");
		var hour0 = parseInt(timeStr0.substring(0,index0));
		if ( hour0 == 12) hour0 = 0; //  Or else 12 from 12:00pm will be greater than 1 from 1:00pm
		var hour1 = parseInt(timeStr1.substring(0,index1));
		if ( hour1 == 12) hour1 = 0; //  Or else 12 from 12:00pm will be greater than 1 from 1:00pm
		if ( hour0 != hour1)
			return hour0 - hour1;
		else {
			hour0 = parseInt(timeStr0.substring(index0+1,timeStr0.length-2));
			hour1 = parseInt(timeStr1.substring(index1+1,timeStr1.length-2)); 
			return hour0 - hour1;
		}
	}
}
