gDaysInMonth = {31, 28, 31, 30,  31,  30,  31,  31,  30,  31,  30,  31};
gDaysToMonth = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};

gCalendarDisplayStartDayOfWeek = 0;
gCalendarDisplayStartDate = 0;
gCalendarDisplayEndDate = 0;

gCalendarActualDate = -1;
gCalendarActualDateIndex = -1;

gCalendarSelectedDate = -1;
gCalendarSelectedDateIndex = -1;

gCalendarPlayerList_cNumVisibleEntries = 5;
gCalendarPlayerList_cItemHeight = 15;

gCalendarMinutesPerDay = 1440;
gCalendarSecondsPerDay = gCalendarMinutesPerDay * 60;

gCalendar_InitializedDebug = false;
gCalendar_DebugFrame = nil;

function Calendar_InitDebugging()
	if gCalendar_InitializedDebug then
		return;
	end
	
	gCalendar_InitializedDebug = true;
	
	-- Find the debug frame if there is one
	
	for vChatIndex = 1, NUM_CHAT_WINDOWS do
		local	vFrameName = "ChatFrame"..vChatIndex;
		local	vChatFrame = getglobal(vFrameName);
		
		if vChatFrame
		and (vChatFrame:IsVisible() or vChatFrame.isDocked) then
			local	vTab = getglobal("ChatFrame"..vChatIndex.."Tab");
			local	vName = vTab:GetText();
			
			if vName == "Debug" then
				gCalendar_DebugFrame = vChatFrame;
				_ERRORMESSAGE = function(message) Calendar_DebugMessage(message); end;
			end
		end
	end
	
	if gCalendar_DebugFrame then
		Calendar_DebugMessage("Found debugging chat frame");
	end
end

function Calendar_DebugMessage(pMessage)
	if gCalendar_DebugFrame then
		gCalendar_DebugFrame:AddMessage(pMessage, 0.7, 0.3, 1.0);
		
		local vTabFlash = getglobal(gCalendar_DebugFrame:GetName().."TabFlash");
		
		vTabFlash:Show();
		UIFrameFlash(vTabFlash, 0.25, 0.25, 60, nil, 0.5, 0.5);
	else
		DEFAULT_CHAT_FRAME:AddMessage(pMessage, 0.7, 0.3, 1.0);
	end
end

function Calendar_NoteMessage(pMessage)
	DEFAULT_CHAT_FRAME:AddMessage(pMessage, 0.8, 0.8, 0.5);
end

function Calendar_ErrorMessage(pMessage)
	DEFAULT_CHAT_FRAME:AddMessage(pMessage, 0.8, 0.3, 0.5);
end

function Calendar_TestMessage(pMessage)
	if gCalendar_DebugFrame then
		gCalendar_DebugFrame:AddMessage(pMessage, 0.7, 0.3, 1.0);
	else
		DEFAULT_CHAT_FRAME:AddMessage(pMessage, 0.7, 0.3, 1.0);
	end
end

function Calendar_GetCurrentYearMonthDayHourMinute()
	local	vDate = date("*t");
	
	return vDate.year, vDate.month, vDate.day, vDate.hour, vDate.min, vDate.sec;
end

function Calendar_GetCurrentLocalDateTimeStamp()
	local	vDate, vTime60 = Calendar_GetCurrentLocalDateTime60();
	
	return vDate * 86400 + vTime60;
end

function Calendar_GetCurrentLocalDateTime60()
	local	vDate = date("*t");
	
	return Calendar_ConvertMDYToDate(vDate.month, vDate.day, vDate.year), Calendar_ConvertHMSToTime60(vDate.hour, vDate.min, vDate.sec);
end

function Calendar_GetCurrentLocalDateTime()
	local	vDate = date("*t");
	
	return Calendar_ConvertMDYToDate(vDate.month, vDate.day, vDate.year), Calendar_ConvertHMToTime(vDate.hour, vDate.min);
end

function Calendar_GetCurrentUTCDateTime()
	local	vDate = date("!*t");
	
	return Calendar_ConvertMDYToDate(vDate.month, vDate.day, vDate.year), Calendar_ConvertHMToTime(vDate.hour, vDate.min);
end

function Calendar_GetCurrentLocalDate()
	local	vDate = date("*t");
	
	return Calendar_ConvertMDYToDate(vDate.month, vDate.day, vDate.year);
end

function Calendar_GetCurrentLocalTime()
	local	vDate = date("*t");
	
	return Calendar_ConvertHMToTime(vDate.hour, vDate.min);
end

function Calendar_GetCurrentUTCDateTime()
	local	vDate = date("!*t");
	
	return Calendar_ConvertMDYToDate(vDate.month, vDate.day, vDate.year), Calendar_ConvertHMToTime(vDate.hour, vDate.min);
end

function Calendar_GetCurrentUTCTime()
	local	vDate = date("!*t");
	
	return Calendar_ConvertHMToTime(vDate.hour, vDate.min);
end

function Calendar_GetCurrentServerTime()
	return Calendar_ConvertHMToTime(GetGameTime());
end

function Calendar_GetCurrentServerDateTime()
	return Calendar_GetServerDateTimeFromLocalDateTime(Calendar_GetCurrentLocalDateTime());
end

function Calendar_GetCurrentDateTimeUT60()
	local	vDate = date("!*t");
	
	return Calendar_ConvertMDYToDate(vDate.month, vDate.day, vDate.year) * gCalendarSecondsPerDay
	     + Calendar_ConvertHMSToTime60(vDate.hour, vDate.min, vDate.sec);
end

function Calendar_SetCheckButtonEnable(pCheckButton, pEnabled)
	if pEnabled then
		pCheckButton:Enable();
		pCheckButton:SetAlpha(1.0);
		getglobal(pCheckButton:GetName().."Text"):SetAlpha(1.0);
	else
		pCheckButton:Disable();
		pCheckButton:SetAlpha(0.7);
		getglobal(pCheckButton:GetName().."Text"):SetAlpha(0.7);
	end
end

function Calendar_SetEditBoxEnable(pEditBox, pEnabled)
	local	vText = getglobal(pEditBox:GetName().."Text");
	
	if pEnabled then
		pEditBox:SetAlpha(1.0);
		if vText then
			vText:SetAlpha(1.0);
		end
		pEditBox:EnableKeyboard(true);
		pEditBox:EnableMouse(true);
		
		pEditBox.isDisabled = false;
	else
		pEditBox:SetAlpha(0.7);
		if vText then
			vText:SetAlpha(0.7);
		end
		
		pEditBox:ClearFocus();
		pEditBox:EnableKeyboard(false);
		pEditBox:EnableMouse(false);

		pEditBox.isDisabled = true;
	end
end

function Calendar_SetDropDownEnable(pDropDown, pEnabled)
	if pEnabled then
		pDropDown:SetAlpha(1.0);
		getglobal(pDropDown:GetName().."Text"):SetAlpha(1.0);
		getglobal(pDropDown:GetName().."Button"):EnableMouse(true);
	else
		pDropDown:SetAlpha(0.7);
		getglobal(pDropDown:GetName().."Text"):SetAlpha(0.7);
		getglobal(pDropDown:GetName().."Button"):EnableMouse(false);
	end
end

function Calendar_SetButtonEnable(pButton, pEnabled)
	if pEnabled then
		pButton:Enable();
		pButton:SetAlpha(1.0);
		pButton:EnableMouse(true);
		--getglobal(pButton:GetName().."Text"):SetAlpha(1.0);
	else
		pButton:Disable();
		pButton:SetAlpha(0.7);
		pButton:EnableMouse(false);
		--getglobal(pButton:GetName().."Text"):SetAlpha(0.7);
	end
end

function CalendarDropDown_SetSelectedValue2(pDropDown, pValue)
	pDropDown.selectedName = nil;
	pDropDown.selectedID = nil;
	pDropDown.selectedValue = pValue;
end

function CalendarDropDown_SetSelectedValue(pDropDown, pValue)
	-- Just return if the value isn't changing
	
	if pDropDown.selectedValue == pValue then
		return;
	end
	
	--
	
	UIDropDownMenu_SetText("", pDropDown); -- Set to empty in case the selected value isn't there

	UIDropDownMenu_Initialize(pDropDown, pDropDown.initialize);
	UIDropDownMenu_SetSelectedValue(pDropDown, pValue);
	
	-- All done if the item text got set successfully
	
	local	vItemText = UIDropDownMenu_GetText(pDropDown);
	
	if vItemText and vItemText ~= "" then
		return;
	end
	
	-- Scan for submenus
	
	local	vRootListFrameName = "DropDownList1";
	local	vRootListFrame = getglobal(vRootListFrameName);
	local	vRootNumItems = vRootListFrame.numButtons;
	
	for vRootItemIndex = 1, vRootNumItems do
		local	vItem = getglobal(vRootListFrameName.."Button"..vRootItemIndex);
		
		if vItem.hasArrow then
			local	vSubMenuFrame = getglobal("DropDownList2");
			
			UIDROPDOWNMENU_OPEN_MENU = pDropDown:GetName();
			UIDROPDOWNMENU_MENU_VALUE = vItem.value;
			UIDROPDOWNMENU_MENU_LEVEL = 2;
			
			UIDropDownMenu_Initialize(pDropDown, pDropDown.initialize, nil, 2);
			UIDropDownMenu_SetSelectedValue(pDropDown, pValue);
			
			-- All done if the item text got set successfully
			
			local	vItemText = UIDropDownMenu_GetText(pDropDown);
			
			if vItemText and vItemText ~= "" then
				return;
			end
			
			-- Switch back to the root menu
			
			UIDROPDOWNMENU_OPEN_MENU = nil;
			UIDropDownMenu_Initialize(pDropDown, pDropDown.initialize, nil, 1);
		end
	end
end

function Calendar_GetShortTimeString(pTime)
	if pTime == nil then
		return nil;
	end
	
	if TwentyFourHourTime then
		local	vHour, vMinute = Calendar_ConvertTimeToHM(pTime);
		
		return format(TEXT(TIME_TWENTYFOURHOURS), vHour, vMinute);
	else
		local	vHour, vMinute, vAMPM = Calendar_ConvertTimeToHMAMPM(pTime);
		
		if vAMPM == 0 then
			return format(TEXT(TIME_TWELVEHOURAM), vHour, vMinute);
		else
			return format(TEXT(TIME_TWELVEHOURPM), vHour, vMinute);
		end
	end
end

function Calendar_ConvertTimeToHM(pTime)
	local	vMinute = math.mod(pTime, 60);
	local	vHour = (pTime - vMinute) / 60;
	
	return vHour, vMinute;
end

function Calendar_ConvertHMToTime(pHour, pMinute)
	return pHour * 60 + pMinute;
end

function Calendar_ConvertHMSToTime60(pHour, pMinute, pSecond)
	return pHour * 3600 + pMinute * 60 + pSecond;
end

function Calendar_ConvertTimeToHMAMPM(pTime)
	local	vHour, vMinute = Calendar_ConvertTimeToHM(pTime);
	local	vAMPM;
	
	if vHour < 12 then
		vAMPM = 0;
		
		if vHour == 0 then
			vHour = 12;
		end
	else
		vAMPM = 1;

		if vHour > 12 then
			vHour = vHour - 12;
		end
	end

	return vHour, vMinute, vAMPM;
end

function Calendar_ConvertHMAMPMToTime(pHour, pMinute, pAMPM)
	local		vHour;
	
	if pAMPM == 0 then
		vHour = pHour;
		if vHour == 12 then
			vHour = 0;
		end
	else
		vHour = pHour + 12;
		if vHour == 24 then
			vHour = 12;
		end
	end
	
	return Calendar_ConvertHMToTime(vHour, pMinute);
end

function Calendar_GetLongDateString(pDate, pIncludeDayOfWeek)
	local	vFormat;
	
	if pIncludeDayOfWeek then
		vFormat = GroupCalendar_cLongDateFormatWithDayOfWeek;
	else
		vFormat = GroupCalendar_cLongDateFormat;
	end
	
	return Calendar_GetFormattedDateString(pDate, vFormat);
end

function Calendar_GetShortDateString(pDate, pIncludeDayOfWeek)
	return Calendar_GetFormattedDateString(pDate, GroupCalendar_cShortDateFormat);
end

function Calendar_FormatNamed(pFormat, pFields)
	return string.gsub(
					pFormat,
					"%$(%w+)", 
					function (pField)
						return pFields[pField];
					end);
end

function Calendar_GetFormattedDateString(pDate, pFormat)
	local	vMonth, vDay, vYear = Calendar_ConvertDateToMDY(pDate);
	
	local	vDate =
			{
				dow = GroupCalendar_cDayOfWeekNames[Calendar_GetDayOfWeekFromDate(pDate) + 1],
				month = GroupCalendar_cMonthNames[vMonth],
				monthNum = vMonth,
				day = vDay,
				year = vYear,
			};
	
	return Calendar_FormatNamed(pFormat, vDate);
end

function Calendar_SetDisplayDate(pStartDate)
	local		vMonth, vDay, vYear = Calendar_ConvertDateToMDY(pStartDate);
	local		vDaysInMonth = Calendar_GetDaysInMonth(vMonth, vYear);
	
	Calendar_SetCalendarRange(
			Calendar_GetDayOfWeek(vMonth, 1, vYear),
			vDaysInMonth);
	
	local	vCalendarTitle = getglobal("GroupCalendarMonthYearText");
	
	vCalendarTitle:SetText(GroupCalendar_cMonthNames[vMonth].." "..vYear);

	gCalendarDisplayStartDate = pStartDate;
	gCalendarDisplayEndDate = pStartDate + vDaysInMonth;
	
	Calendar_HiliteActualDate();
	Calendar_HiliteSelectedDate();
	
	Calendar_UpdateEventIcons();
end

function Calendar_SetActualDate(pDate)
	gCalendarActualDate= pDate;
	Calendar_HiliteActualDate();
end

function Calendar_HiliteActualDate()
	local	vDayButton;
	
	if gCalendarActualDateIndex >= 0 then
		vDayButton = getglobal("GroupCalendarDay"..gCalendarActualDateIndex.."SlotIcon");
		vDayButton:SetTexture("Interface\\Buttons\\UI-EmptySlot-Disabled");
		gCalendarActualDateIndex = -1;
		
		GroupCalendarTodayHighlight:Hide();
	end
	
	if gCalendarActualDate < gCalendarDisplayStartDate
	or gCalendarActualDate >= gCalendarDisplayEndDate then
		return;
	end
	
	gCalendarActualDateIndex = gCalendarActualDate - gCalendarDisplayStartDate + gCalendarDisplayStartDayOfWeek;
	
	if gCalendarActualDateIndex >= 0 then
		local	vDayButtonName = "GroupCalendarDay"..gCalendarActualDateIndex;
		local	vDayButtonIconName = vDayButtonName.."SlotIcon";
		
		vDayButton = getglobal(vDayButtonIconName);
		vDayButton:SetTexture("Interface\\Buttons\\UI-EmptySlot");
		
		GroupCalendarTodayHighlight:SetPoint("CENTER", vDayButtonName, "CENTER", 0, -1);
		GroupCalendarTodayHighlight:Show();
	end
end

function Calendar_SetSelectedDate(pDate)
	gCalendarSelectedDate = pDate;
	
	Calendar_HiliteSelectedDate();
end

function Calendar_ClearSelectedDate()
	gCalendarSelectedDate = -1;
	Calendar_HiliteSelectedDate();
end

function Calendar_SetSelectedDateIndexWithToggle(pIndex)
	GroupCalendar_SelectDateWithToggle(
			gCalendarDisplayStartDate + pIndex - gCalendarDisplayStartDayOfWeek);
end

function Calendar_SetSelectedDateIndex(pIndex)
	GroupCalendar_SelectDate(
			gCalendarDisplayStartDate + pIndex - gCalendarDisplayStartDayOfWeek);
end

function Calendar_HiliteSelectedDate()
	local	vDayButton;
	
	if gCalendarSelectedDateIndex >= 0 then
		vDayButton = getglobal("GroupCalendarDay"..gCalendarSelectedDateIndex);
		vDayButton:SetChecked(nil);
		gCalendarSelectedDateIndex = -1;
	end
	
	if gCalendarSelectedDate < gCalendarDisplayStartDate
	or gCalendarSelectedDate >= gCalendarDisplayEndDate then
		return;
	end
	
	gCalendarSelectedDateIndex = gCalendarSelectedDate - gCalendarDisplayStartDate + gCalendarDisplayStartDayOfWeek;
	
	if gCalendarSelectedDateIndex >= 0 then
		vDayButton = getglobal("GroupCalendarDay"..gCalendarSelectedDateIndex);
		vDayButton:SetChecked(true);
	end
end

function Calendar_GetEventTypeIconPath(pEventType)
	local	vIconSuffix;
	
	if EventDatabase_IsResetEventType(pEventType) then
		local	vIsSystemIcon;
		
		vIconSuffix, vIsSystemIcon = EventDatabase_GetResetEventLargeIconPath(pEventType);
		
		if not vIconSuffix then
			return "";
		end
		
		if vIsSystemIcon then
			return vIconSuffix;
		end
	else
		if EventDatabase_GetEventNameByID(pEventType) then -- Don't attempt to show icons for event types we don't recognize
			vIconSuffix = pEventType;
		else
			vIconSuffix = "Unknown";
		end
	end
	
	return "Interface\\AddOns\\GroupCalendar\\Textures\\Icon-"..vIconSuffix;
end

function Calendar_UpdateCompiledEventIcon(pDate, pCompiledSchedule, pCutoffServerDateTime)
	if pDate < gCalendarDisplayStartDate or pDate >= gCalendarDisplayEndDate then
		return;
	end
	
	local		vIndex = pDate - gCalendarDisplayStartDate + gCalendarDisplayStartDayOfWeek;
	local		vDayIcon = getglobal("GroupCalendarDay"..vIndex.."Icon");
	local		vOverlayIcon = getglobal("GroupCalendarDay"..vIndex.."OverlayIcon");
	local		vCircledDate = getglobal("GroupCalendarDay"..vIndex.."CircledDate");
	local		vDogEarIcon = getglobal("GroupCalendarDay"..vIndex.."DogEarIcon");
	
	vOverlayIcon:Hide();
	
	if pCompiledSchedule then
		local		vDayIconType = nil;
		local		vUnqualifiedDayIconType = nil;
		local		vExpiredDayIconType = nil;
		local		vExpiredUnqualifiedDayIconType = nil;
		local		vResetEventType = nil;
		local		vExpiredResetEventType = nil;
		
		local		vShowBirthdayIcon = false;
		local		vHasAppointment = false;
		local		vAppointmentIsDimmed = false;
		local		vGotDogEarEvent = false;
		
		for vEventIndex, vCompiledEvent in pCompiledSchedule do
			local	vPlayerIsQualified = EventDatabase_PlayerIsQualifiedForEvent(vCompiledEvent.mEvent, gGroupCalendar_PlayerLevel);
			
			if vCompiledEvent.mEvent.mType then
				-- Determine if the event is expired
				
				local	vEventIsExpired = false;
				
				if pCutoffServerDateTime
				and vCompiledEvent.mEvent.mTime
				and vCompiledEvent.mEvent.mDuration then
					local	vEventStartDateTime = vCompiledEvent.mEvent.mDate * gCalendarMinutesPerDay + vCompiledEvent.mEvent.mTime;
					local	vEventEndDateTime = vEventStartDateTime + vCompiledEvent.mEvent.mDuration;
					
					vEventIsExpired = vEventEndDateTime <= pCutoffServerDateTime;
				end
				
				-- Check for birthday events
				
				if vCompiledEvent.mEvent.mType == "Birth" then
					vShowBirthdayIcon = true;
				
				-- Check for cooldown/reset events
				
				elseif EventDatabase_IsResetEventType(vCompiledEvent.mEvent.mType) then
				
					if vEventIsExpired then
						if not vExpiredResetEventType then
							vExpiredResetEventType = vCompiledEvent.mEvent.mType;
						end
					else
						if not vResetEventType then
							vResetEventType = vCompiledEvent.mEvent.mType;
						end
					end
					
				-- Check for ordinary events
					
				else
					local	vIconPath = Calendar_GetEventTypeIconPath(vCompiledEvent.mEvent.mType);
					
					if not vPlayerIsQualified then
						if vEventIsExpired then
							if not vExpiredUnqualifiedDayIconType then
								vExpiredUnqualifiedDayIconType = vIconPath;
							end
						else
							if not vUnqualifiedDayIconType then
								vUnqualifiedDayIconType = vIconPath;
							end
						end
					else
						if vEventIsExpired then
							if not vExpiredDayIconType then
								vExpiredDayIconType = vIconPath;
							end
						else
							if not vDayIconType then
								vDayIconType = vIconPath;
							end
						end
					end
				end -- else Birth
			end -- if mType
			
			if (not vHasAppointment
			or (vAppointmentIsDimmed and vPlayerIsQualified))
			and EventDatabase_PlayerIsAttendingEvent(vCompiledEvent.mOwner, vCompiledEvent.mEvent) then
				vHasAppointment = true;
				vAppointmentIsDimmed = not vPlayerIsQualified;
			end
		end -- for vEventIndex
		
		-- Update the day icon
		
		if vDayIconType then
			vDayIcon:SetTexture(vDayIconType);
			vDayIcon:SetAlpha(1.0);
			vDayIcon:Show();
		elseif vUnqualifiedDayIconType then
			vDayIcon:SetTexture(vUnqualifiedDayIconType);
			vDayIcon:SetAlpha(0.25);
			vDayIcon:Show();
		elseif vExpiredDayIconType then
			vDayIcon:SetTexture(vExpiredDayIconType);
			vDayIcon:SetAlpha(1.0);
			vDayIcon:Show();
		elseif vExpiredUnqualifiedDayIconType then
			vDayIcon:SetTexture(vExpiredUnqualifiedDayIconType);
			vDayIcon:SetAlpha(0.25);
			vDayIcon:Show();
		else
			vDayIcon:SetTexture(nil);
			vDayIcon:Hide();
		end
		
		-- Show or hide the birthday icon
		
		if vShowBirthdayIcon then
			vOverlayIcon:SetTexture(Calendar_GetEventTypeIconPath("Birth"));
			vOverlayIcon:Show();
		else
			vOverlayIcon:Hide();
		end
		
		-- Circle the date if necessary
		
		if vHasAppointment then
			if vAppointmentIsDimmed then
				-- Show dimmed
				
				vCircledDate:Show();
				vCircledDate:SetAlpha(0.4);
			else
				-- Show full brightness
				
				vCircledDate:Show();
				vCircledDate:SetAlpha(1.0);
			end
		else
			vCircledDate:Hide();
		end
		
		-- Show the dog ear
		
		if vResetEventType
		or vExpiredResetEventType then
			if not vResetEventType then
				vResetEventType = vExpiredResetEventType;
			end
			
			local	vIconCoords = EventDatabase_GetResetIconCoords(vResetEventType);
			
			if vIconCoords then
				vDogEarIcon:SetTexCoord(vIconCoords.left, vIconCoords.right, vIconCoords.top, vIconCoords.bottom);
				vDogEarIcon:Show();
			else
				vDogEarIcon:Hide();
			end
		else
			vDogEarIcon:Hide();
		end
		
	else -- if pCompiledSchedule
		vDayIcon:SetTexture(nil);
		vDayIcon:Hide();
	end
end

function Calendar_GetCurrentCutoffDateTime()
	local		vCurrentDate, vCurrentTime;
	local		vCurrentServerDateTime;
	
	if gGroupCalendar_Settings.ShowEventsInLocalTime then
		local	vServerDate, vServerTime;
		
		vCurrentDate, vCurrentTime = Calendar_GetCurrentLocalDateTime();
		vServerDate, vServerTime = Calendar_GetServerDateTimeFromLocalDateTime(vCurrentDate, vCurrentTime);
		vCurrentServerDateTime = vServerDate * gCalendarMinutesPerDay + vServerTime;
	else
		vCurrentDate, vCurrentTime = Calendar_GetCurrentServerDateTime();
		vCurrentServerDateTime = vCurrentDate * gCalendarMinutesPerDay + vCurrentTime;
	end
	
	return vCurrentDate, vCurrentTime, vCurrentServerDateTime;
end

function Calendar_UpdateEventIcons()
	local		vIndex = gCalendarDisplayStartDayOfWeek;
	local		vCurrentDate, vCurrentTime, vCurrentServerDateTime = Calendar_GetCurrentCutoffDateTime();
	
	for vDate = gCalendarDisplayStartDate, gCalendarDisplayEndDate - 1 do
		local	vCompiledSchedule = EventDatabase_GetCompiledSchedule(vDate);
		local	vCutoffDateTime = nil;
		
		if vDate == vCurrentDate then
			vCutoffDateTime = vCurrentServerDateTime;
		end
		
		Calendar_UpdateCompiledEventIcon(vDate, vCompiledSchedule, vCutoffDateTime);
		
		vIndex = vIndex + 1;
	end
end

function Calendar_ScheduleChanged(pDate, pSchedule)
	if not GroupCalendarFrame:IsVisible() then
		return;
	end
	
	local		vCompiledSchedule = EventDatabase_GetCompiledSchedule(pDate);
	local		vCurrentDate, vCurrentTime, vCurrentServerDateTime = Calendar_GetCurrentCutoffDateTime();
	local		vCutoffDateTime = nil;
	
	if pDate == vCurrentDate then
		vCutoffDateTime = vCurrentServerDateTime;
	end
	
	Calendar_UpdateCompiledEventIcon(pDate, vCompiledSchedule, vCutoffDateTime);
end

function Calendar_MajorDatabaseChange()
	if not GroupCalendarFrame:IsVisible() then
		return;
	end
	
	Calendar_UpdateEventIcons();
end

function Calendar_NextMonth()
	local	vMonth, vDay, vYear = Calendar_ConvertDateToMDY(gCalendarDisplayStartDate);
	
	vMonth = vMonth + 1;
	
	if vMonth == 13 then
		vMonth = 1;
		vYear = vYear + 1;
	end
	
	Calendar_SetDisplayDate(Calendar_ConvertMDYToDate(vMonth, 1, vYear));
end

function Calendar_PreviousMonth()
	local	vMonth, vDay, vYear = Calendar_ConvertDateToMDY(gCalendarDisplayStartDate);
	
	vMonth = vMonth - 1;
	
	if vMonth == 0 then
		vMonth = 12;
		vYear = vYear - 1;
	end
	
	Calendar_SetDisplayDate(Calendar_ConvertMDYToDate(vMonth, 1, vYear));
end

function Calendar_Today()
	local	vMonth, vDay, vYear = Calendar_ConvertDateToMDY(gCalendarActualDate);
	
	Calendar_SetDisplayDate(Calendar_ConvertMDYToDate(vMonth, 1, vYear));
	GroupCalendar_SelectDateWithToggle(gCalendarActualDate);
end

function Calendar_SetCalendarRange(pStartDay, pNumDays)
	-- Hide days before the start day
	
	gCalendarDisplayStartDayOfWeek = pStartDay;
	
	for vIndex = 0, pStartDay - 1 do
		local	vDayButton = getglobal("GroupCalendarDay"..vIndex);
		vDayButton:Hide();
	end
	
	-- Set the text of the days
	
	for vIndex = pStartDay, pStartDay + pNumDays - 1 do
		local	vDayNumber = vIndex - pStartDay + 1;
		
		local	vDayButton = getglobal("GroupCalendarDay"..vIndex);
		local	vDayIcon = getglobal("GroupCalendarDay"..vIndex.."Icon");
		local	vDayText = getglobal("GroupCalendarDay"..vIndex.."Name");
		
		vDayButton:Show();
		vDayText:SetText(vDayNumber);
	end
	
	-- Hide the days after the end day
	
	for vIndex = pStartDay + pNumDays, 36 do
		local	vDayButton = getglobal("GroupCalendarDay"..vIndex);
		vDayButton:Hide();
	end
end

function Calendar_GetDaysInMonth(pMonth, pYear)
	if pMonth == 2 and Calendar_IsLeapYear(pYear) then
		return gDaysInMonth[pMonth] + 1;
	else
		return gDaysInMonth[pMonth];
	end
end

function Calendar_GetDaysToMonth(pMonth, pYear)
	if pMonth > 2 and Calendar_IsLeapYear(pYear) then
		return gDaysToMonth[pMonth] + 1;
	elseif pMonth == 2 then
		return gDaysToMonth[pMonth];
	else
		return 0;
	end
end

function Calendar_GetDaysInYear(pYear)
	if Calendar_IsLeapYear(pYear) then
		return 366;
	else
		return 365;
	end
end

function Calendar_IsLeapYear(pYear)
	return (math.mod(pYear, 400) == 0)
	   or ((math.mod(pYear, 4) == 0) and (math.mod(pYear, 100) ~= 0));
end

function Calendar_GetDaysToDate(pMonth, pDay, pYear)
	local	vDays;
	
	vDays = gDaysToMonth[pMonth] + pDay - 1;
	
	if Calendar_IsLeapYear(pYear) and pMonth > 2 then
		vDays = vDays + 1;
	end
	
	return vDays;
end

function Calendar_ConvertMDYToDate(pMonth, pDay, pYear)
	local		vDays = 0;
	
	for vYear = 2000, pYear - 1 do
		vDays = vDays + Calendar_GetDaysInYear(vYear);
	end
	
	return vDays + Calendar_GetDaysToDate(pMonth, pDay, pYear);
end

function Calendar_ConvertDateToMDY(pDate)
	local		vDays = pDate;
	local		vYear = 2000;
	local		vDaysInYear = Calendar_GetDaysInYear(vYear);
	
	while vDays >= vDaysInYear do
		vDays = vDays - vDaysInYear;

		vYear = vYear + 1;
		vDaysInYear = Calendar_GetDaysInYear(vYear);
	end
	
	local		vIsLeapYear = Calendar_IsLeapYear(vYear);
	
	for vMonth = 1, 12 do
		local vDaysInMonth = gDaysInMonth[vMonth];
		
		if vMonth == 2 and vIsLeapYear then
			vDaysInMonth = vDaysInMonth + 1;
		end
		
		if vDays < vDaysInMonth then
			return vMonth, vDays + 1, vYear;
		end
		
		vDays = vDays - vDaysInMonth;
	end
	
	-- error
	
	Calendar_DebugMessage("Calendar_ConvertDateToMDY: Failed: "..vDays.." unaccounted for in year "..vYear);

	return 0, 0, 0;
end

function Calendar_GetDayOfWeek(pMonth, pDay, pYear)
	local	vDayOfWeek = 6; -- January 1, 2000 is a Saturday
	
	for vYear = 2000, pYear - 1 do
		if Calendar_IsLeapYear(vYear) then
			vDayOfWeek = vDayOfWeek + 2;
		else
			vDayOfWeek = vDayOfWeek + 1;
		end
	end
	
	vDayOfWeek = vDayOfWeek + Calendar_GetDaysToDate(pMonth, pDay, pYear);
	
	return math.mod(vDayOfWeek, 7);
end

function Calendar_GetDayOfWeekFromDate(pDate)
	return math.mod(pDate + 6, 7);  -- + 6 because January 1, 2000 is a Saturday
end

function CalendarHourDropDown_OnLoad()
	UIDropDownMenu_Initialize(this, CalendarHourDropDown_Initialize);
	UIDropDownMenu_SetWidth(42);
	UIDropDownMenu_Refresh(this);
end

function CalendarMinuteDropDown_OnLoad()
	UIDropDownMenu_Initialize(this, CalendarMinuteDropDown_Initialize);
	UIDropDownMenu_SetWidth(43);
	UIDropDownMenu_Refresh(this);
end

function CalendarAMPMDropDown_OnLoad()
	if TwentyFourHourTime then
		this:Hide();
	else
		UIDropDownMenu_Initialize(this, CalendarAMPMDropDown_Initialize);
		UIDropDownMenu_SetWidth(48);
		UIDropDownMenu_Refresh(this);
	end
end

function CalendarDurationDropDown_OnLoad()
	UIDropDownMenu_Initialize(this, CalendarDurationDropDown_Initialize);
	UIDropDownMenu_SetWidth(120);
	UIDropDownMenu_Refresh(this);
end

function CalendarEventTypeDropDown_OnLoad()
	UIDropDownMenu_Initialize(this, CalendarEventTypeDropDown_Initialize);
	UIDropDownMenu_SetWidth(150);
	UIDropDownMenu_Refresh(this);
end

function CalendarGuildRank_OnLoad()
	UIDropDownMenu_Initialize(this, CalendarGuildRankDropDown_Initialize);
	UIDropDownMenu_SetWidth(100);
	UIDropDownMenu_Refresh(this);
end

function CalendarTrustGroup_OnLoad()
	UIDropDownMenu_Initialize(this, CalendarTrustGroupDropDown_Initialize);
	UIDropDownMenu_SetWidth(220);
	UIDropDownMenu_Refresh(this);
end

function CalendarPartySizeDropDown_OnLoad()
	UIDropDownMenu_Initialize(this, CalendarPartySizeDropDown_Initialize);
	UIDropDownMenu_SetWidth(130);
	UIDropDownMenu_Refresh(this);
end

function CalendarPriorityDropDown_OnLoad()
	UIDropDownMenu_Initialize(this, CalendarPriorityDropDown_Initialize);
	UIDropDownMenu_SetWidth(130);
	UIDropDownMenu_Refresh(this);
end

function CalendarCharactersDropDown_OnLoad()
	UIDropDownMenu_Initialize(this, CalendarCharactersDropDown_Initialize);
	UIDropDownMenu_SetWidth(110);
	UIDropDownMenu_Refresh(this);
end

function CalendarAttendanceDropDown_OnLoad()
	UIDropDownMenu_SetAnchor(-1, 4, this, "TOPLEFT", this:GetName(), "TOPRIGHT");
	UIDropDownMenu_Initialize(this, CalendarAttendanceDropDown_Initialize);
	--UIDropDownMenu_Refresh(this); -- Don't refresh on menus which don't have a text portion
end

function CalendarClassDropDown_OnLoad()
	UIDropDownMenu_Initialize(this, CalendarClassDropDown_Initialize);
	UIDropDownMenu_SetWidth(110);
	UIDropDownMenu_Refresh(this);
end

function CalendarRaceDropDown_OnLoad()
	UIDropDownMenu_Initialize(this, CalendarRaceDropDown_Initialize);
	UIDropDownMenu_SetWidth(110);
	UIDropDownMenu_Refresh(this);
end

function CalendarStatusDropDown_OnLoad()
	UIDropDownMenu_Initialize(this, CalendarStatusDropDown_Initialize);
	UIDropDownMenu_SetWidth(110);
	UIDropDownMenu_Refresh(this);
end

function GroupCalendarViewMenu_OnLoad()
	UIDropDownMenu_Initialize(this, GroupCalendarViewMenu_Initialize);
	UIDropDownMenu_SetWidth(100);
	UIDropDownMenu_Refresh(this);
end

function CalendarHourDropDown_Initialize()
	local	vFrame = getglobal(UIDROPDOWNMENU_INIT_MENU);
	local	vStartHour, vEndHour;
	
	if TwentyFourHourTime then
		vStartHour = 0;
		vEndHour = 23;
	else
		vStartHour = 1;
		vEndHour = 12;
	end
	
	for vIndex = vStartHour, vEndHour do
		vItem = {};
		vItem.text = ""..vIndex;
		vItem.value = vIndex;
		vItem.owner = vFrame;
		vItem.func = CalendarDropDown_OnClick;
		UIDropDownMenu_AddButton(vItem);
	end
end

function CalendarMinuteDropDown_Initialize()
	local	vFrame = getglobal(UIDROPDOWNMENU_INIT_MENU);
	
	for vIndex = 0, 59, 5 do
		vItem = {};
		if vIndex < 10 then
			vItem.text = "0"..vIndex;
		else
			vItem.text = ""..vIndex;
		end
		
		vItem.value = vIndex;
		vItem.owner = vFrame;
		vItem.func = CalendarDropDown_OnClick;
		
		UIDropDownMenu_AddButton(vItem);
	end
end

function CalendarAMPMDropDown_Initialize()
	local	vFrame = getglobal(UIDROPDOWNMENU_INIT_MENU);
	
	--
	
	local	vItem = {};
	vItem.text = "AM";
	vItem.value = 0;
	vItem.owner = vFrame;
	vItem.func = CalendarDropDown_OnClick;

	UIDropDownMenu_AddButton(vItem);
	
	--
	
	local	vItem = {};
	vItem.text = "PM";
	vItem.value = 1;
	vItem.owner = vFrame;
	vItem.func = CalendarDropDown_OnClick;

	UIDropDownMenu_AddButton(vItem);
end

function CalendarDurationDropDown_Initialize()
	local	vFrame = getglobal(UIDROPDOWNMENU_INIT_MENU);
	
	--
	
	local	vDurations = {15, 30, 60, 90, 120, 150, 180, 240, 300, 360};

	for _, vDuration in vDurations do
		local	vItem = {};

		local	vMinutes = math.mod(vDuration, 60);
		local	vHours = (vDuration - vMinutes) / 60;

		if vHours == 0 then
			vItem.text = format(GroupCalendar_cPluralMinutesFormat, vMinutes);
		else
			if vMinutes ~= 0 then
				if vHours == 1 then
					vItem.text = format(GroupCalendar_cSingularHourPluralMinutes, vHours, vMinutes);
				else
					vItem.text = format(GroupCalendar_cPluralHourPluralMinutes, vHours, vMinutes);
				end
			else
				if vHours == 1 then
					vItem.text = format(GroupCalendar_cSingularHourFormat, vHours);
				elseif vHours > 0 then
					vItem.text = format(GroupCalendar_cPluralHourFormat, vHours);
				end
			end
		end
		
		vItem.value = vDuration;
		vItem.owner = vFrame;
		vItem.func = CalendarDropDown_OnClick;

		UIDropDownMenu_AddButton(vItem);
	end
end

function Calendar_AddDividerMenuItem()
	UIDropDownMenu_AddButton({text = " ", notCheckable = true, notClickable = true});
end

function Calendar_AddCategoryMenuItem(pName)
	UIDropDownMenu_AddButton({text = pName, notCheckable = true, notClickable = true});
end

function Calendar_AddMenuItem(pFrame, pName, pValue, pChecked, pLevel)
	UIDropDownMenu_AddButton({text = pName, value = pValue, owner = pFrame, checked = pChecked, func = CalendarDropDown_OnClick, textR = NORMAL_FONT_COLOR.r, textG = NORMAL_FONT_COLOR.g, textB = NORMAL_FONT_COLOR.b}, pLevel);
end

function Calendar_AddMenuItem2(pFrame, pName, pValue, pChecked, pLevel)
	UIDropDownMenu_AddButton({text = pName, value = pValue, owner = pFrame, checked = pChecked, func = CalendarDropDown_OnClick2, textR = NORMAL_FONT_COLOR.r, textG = NORMAL_FONT_COLOR.g, textB = NORMAL_FONT_COLOR.b}, pLevel);
end

function CalendarEventTypeDropDown_AddEventGroupSubMenu(pFrame, pEventGroupName)
	local	vEventTypes = gGroupCalendar_EventTypes[pEventGroupName];
	
	local	vItem = {};

	vItem.text = vEventTypes.Title;
	vItem.owner = pFrame;
	vItem.hasArrow = 1;
	--vItem.notCheckable = 1;
	vItem.value = pEventGroupName;
	vItem.func = nil;

	UIDropDownMenu_AddButton(vItem);
end

function CalendarEventTypeDropDown_AddEventTypes(pFrame, pEventGroupName, pMenuLevel)
	local	vEventTypes = gGroupCalendar_EventTypes[pEventGroupName];
	
	for vIndex, vEventItem in vEventTypes.Events do
		local	vItem = {};

		vItem.text = vEventItem.name;
		vItem.value = vEventItem.id;
		vItem.owner = pFrame;
		vItem.func = CalendarDropDown_OnClick;

		UIDropDownMenu_AddButton(vItem, pMenuLevel);
	end
end

function CalendarEventTypeDropDown_Initialize()
	local	vFrame = getglobal(UIDROPDOWNMENU_INIT_MENU);
	
	if UIDROPDOWNMENU_MENU_LEVEL == 2 then
		CalendarEventTypeDropDown_AddEventTypes(vFrame, UIDROPDOWNMENU_MENU_VALUE, UIDROPDOWNMENU_MENU_LEVEL);
	else
		-- Populate the root menu items
		
		CalendarEventTypeDropDown_AddEventTypes(vFrame, "General");
		Calendar_AddDividerMenuItem(vFrame);
		CalendarEventTypeDropDown_AddEventGroupSubMenu(vFrame, "Dungeon");
		CalendarEventTypeDropDown_AddEventGroupSubMenu(vFrame, "Battleground");
	end
end

function CalendarGuildRankDropDown_Initialize()
	local	vFrame = getglobal(UIDROPDOWNMENU_INIT_MENU);
	local	vNumRanks = GuildControlGetNumRanks();
	
	for vIndex = 1, vNumRanks do
		local	vRankName = GuildControlGetRankName(vIndex);
		local	vItem = {};

		vItem.text = vRankName;
		vItem.value = vIndex - 1;
		vItem.owner = vFrame;
		vItem.func = CalendarDropDown_OnClick;

		UIDropDownMenu_AddButton(vItem);
	end
end

function CalendarTrustGroupDropDown_Initialize()
	local	vFrame = getglobal(UIDROPDOWNMENU_INIT_MENU);
	
	for vIndex, vText in GroupCalendar_cTrustGroups do
		if vIndex ~= 2
		or IsInGuild() then
			local	vItem = {};
			
			vItem.text = vText;
			vItem.value = vIndex;
			vItem.owner = vFrame;
			vItem.func = CalendarDropDown_OnClick;

			UIDropDownMenu_AddButton(vItem);
		end
	end
end

function CalendarPartySizeDropDown_Initialize()
	local	vFrame = getglobal(UIDROPDOWNMENU_INIT_MENU);
	local	vSizes = {0, 5, 10, 15, 20, 40};
	
	for vIndex, vSize in vSizes do
		local	vText = vSize;
		
		if vText == 0 then
			vText = GroupCalendar_cNoMaximum;
		else
			vText = format(GroupCalendar_cPartySizeFormat, vSize);
		end
		
		UIDropDownMenu_AddButton({text = vText, value = vSize, owner = vFrame, func = CalendarDropDown_OnClick});
	end
end

function CalendarPriorityDropDown_Initialize()
	local	vFrame = getglobal(UIDROPDOWNMENU_INIT_MENU);
	
	Calendar_AddMenuItem(vFrame, GroupCalendar_cPriorityDate, "Date")
	Calendar_AddMenuItem(vFrame, GroupCalendar_cPriorityRank, "Rank")
end

function CalendarCharactersDropDown_Initialize()
	local	vFrame = getglobal(UIDROPDOWNMENU_INIT_MENU);
	local	vOwnedDatabases = EventDatabase_GetOwnedDatabases();
	
	for vIndex, vDatabase in vOwnedDatabases do
		local	vItem = {};

		vItem.text = vDatabase.UserName;
		vItem.value = vDatabase.UserName;
		vItem.owner = vFrame;
		vItem.func = CalendarDropDown_OnClick;

		UIDropDownMenu_AddButton(vItem);
	end
end

function CalendarAttendanceDropDown_Initialize()
	local	vFrame = getglobal(UIDROPDOWNMENU_INIT_MENU);
	local	vAttendanceItem = vFrame:GetParent();
	local	vItem = vAttendanceItem.Item;
	
	if vItem then
		if vItem.mType == "Whisper" then
			Calendar_AddCategoryMenuItem(vItem.mName);
			Calendar_AddMenuItem2(vFrame, GroupCalendar_cAddPlayerEllipses, "ADD");
			Calendar_AddMenuItem2(vFrame, GroupCalendar_cRemove, "DELETE");
		else
			Calendar_AddCategoryMenuItem(vItem.mName);
			Calendar_AddMenuItem2(vFrame, GroupCalendar_cEditPlayer, "EDIT");
			Calendar_AddMenuItem2(vFrame, GroupCalendar_cRemove, "DELETE");
			Calendar_AddCategoryMenuItem(GroupCalendar_cStatus);
			Calendar_AddMenuItem2(vFrame, GroupCalendar_cConfirmed, "Y");
			Calendar_AddMenuItem2(vFrame, GroupCalendar_cStandby, "S");
			Calendar_AddMenuItem2(vFrame, GroupCalendar_cDeclined, "N");
		end
	end
	
	vFrame:SetHeight(20);
end

function CalendarClassDropDown_Initialize()
	local	vFrame = getglobal(UIDROPDOWNMENU_INIT_MENU);
	
	local	vItem = {};

	vItem.text = GroupCalendar_cUnknown;
	vItem.value = "?";
	vItem.owner = vFrame;
	vItem.func = CalendarDropDown_OnClick;

	UIDropDownMenu_AddButton(vItem);
	
	for vClassCode, vClassInfo in gGroupCalendar_ClassInfoByClassCode do
		if not vClassInfo.faction
		or vClassInfo.faction == gGroupCalendar_PlayerFactionGroup then
			local	vItem = {};

			vItem.text = vClassInfo.name;
			vItem.value = vClassCode;
			vItem.owner = vFrame;
			vItem.func = CalendarDropDown_OnClick;

			UIDropDownMenu_AddButton(vItem);
		end
	end
end

function CalendarRaceDropDown_Initialize()
	local	vFrame = getglobal(UIDROPDOWNMENU_INIT_MENU);
	
	local	vItem = {};

	vItem.text = GroupCalendar_cUnknown;
	vItem.value = "?";
	vItem.owner = vFrame;
	vItem.func = CalendarDropDown_OnClick;

	UIDropDownMenu_AddButton(vItem);
	
	for vRaceCode, vRaceInfo in gGroupCalendar_RaceNamesByRaceCode do
		if vRaceInfo.faction == gGroupCalendar_PlayerFactionGroup then
			local	vItem = {};

			vItem.text = vRaceInfo.name;
			vItem.value = vRaceCode;
			vItem.owner = vFrame;
			vItem.func = CalendarDropDown_OnClick;

			UIDropDownMenu_AddButton(vItem);
		end
	end
end

function CalendarStatusDropDown_Initialize()
	local	vFrame = getglobal(UIDROPDOWNMENU_INIT_MENU);
	
	vItem = {text = GroupCalendar_cConfirmed, value = "Y", owner = vFrame, func = CalendarDropDown_OnClick};
	UIDropDownMenu_AddButton(vItem);
	
	vItem = {text = GroupCalendar_cStandby, value = "S", owner = vFrame, func = CalendarDropDown_OnClick};
	UIDropDownMenu_AddButton(vItem);
end

function GroupCalendarViewMenu_Initialize()
	local	vFrame = getglobal(UIDROPDOWNMENU_INIT_MENU);
	
	UIDropDownMenu_AddButton({text = GroupCalendar_cViewByDate, value = "Date", owner = vFrame, func = CalendarDropDown_OnClick});
	UIDropDownMenu_AddButton({text = GroupCalendar_cViewByRank, value = "Rank", owner = vFrame, func = CalendarDropDown_OnClick});
	UIDropDownMenu_AddButton({text = GroupCalendar_cViewByName, value = "Name", owner = vFrame, func = CalendarDropDown_OnClick});
end

function CalendarDropDown_OnClick()
	UIDropDownMenu_SetSelectedValue(this.owner, this.value);
	CalendarDropDown_OnClick2();
end

function CalendarDropDown_OnClick2()
	if this.owner.ChangedValueFunc then
		this.owner.ChangedValueFunc(this.owner, this.value);
	end
	
	CloseDropDownMenus();
end

function Calendar_SetEditBoxAutoCompleteText(pEditBox, pText)
	local	vEditBoxText = strupper(pEditBox:GetText());
	local	vEditBoxTextLength = strlen(vEditBoxText);
	
	pEditBox:SetText(pText);
	pEditBox:HighlightText(vEditBoxTextLength, -1);
end

function Calendar_AutoCompletePlayerName(pEditBox)
	if Calendar_AutoCompleteFriend(pEditBox) then
		return true;
	end
	
	return Calendar_AutoCompleteGuildMember(pEditBox);
end

function Calendar_AutoCompleteFriend(pEditBox)
	local	vNumFriends = GetNumFriends();
	
	if vNumFriends == 0 then
		return false;
	end
	
	local	vEditBoxText = strupper(pEditBox:GetText());
	local	vEditBoxTextLength = strlen(vEditBoxText);
	
	for vIndex = 1, vNumFriends do
		local	vName = GetFriendInfo(vIndex);
		
		if strfind(strupper(vName), "^"..vEditBoxText) then
			Calendar_SetEditBoxAutoCompleteText(pEditBox, vName);
			return true;
		end
	end
	
	return false;
end

function Calendar_AutoCompleteGuildMember(pEditBox)
	local	vNumMembers = GetNumGuildMembers(true);
	
	if vNumMembers == 0 then
		return false;
	end
	
	local	vEditBoxText = strupper(pEditBox:GetText());
	local	vEditBoxTextLength = strlen(vEditBoxText);
	
	for vIndex = 1, vNumMembers do
		local	vName = GetGuildRosterInfo(vIndex);
		
		if strfind(strupper(vName), "^"..vEditBoxText) then
			Calendar_SetEditBoxAutoCompleteText(pEditBox, vName);
			return true;
		end
	end
	
	return false;
end

function Calendar_InputFrameSizeChanged(pInputFrame)
	local	vName = this:GetName();
	local	vWidth = pInputFrame:GetWidth();
	local	vHeight = pInputFrame:GetHeight();
	
	local	vTopLeft = getglobal(vName.."TopLeft");
	local	vTop = getglobal(vName.."Top");
	local	vTopRight = getglobal(vName.."TopRight");

	local	vLeft = getglobal(vName.."Left");
	local	vCenter = getglobal(vName.."Center");
	local	vRight = getglobal(vName.."Right");

	local	vBottomLeft = getglobal(vName.."BottomLeft");
	local	vBottom = getglobal(vName.."Bottom");
	local	vBottomRight = getglobal(vName.."BottomRight");
	
	local	vInnerWidth = vWidth - 12;
	local	vInnerHeight = vHeight - 12;
	
	vTopLeft:SetWidth(6); vTopLeft:SetHeight(6);
	vTop:SetWidth(vInnerWidth); vTop:SetHeight(6);
	vTopRight:SetWidth(6); vTopRight:SetHeight(6);
	
	vLeft:SetWidth(6); vLeft:SetHeight(vInnerHeight);
	vCenter:SetWidth(vInnerWidth); vCenter:SetHeight(vInnerHeight);
	vRight:SetWidth(6); vRight:SetHeight(vInnerHeight);
		
	vBottomLeft:SetWidth(6); vBottomLeft:SetHeight(6);
	vBottom:SetWidth(vInnerWidth); vBottom:SetHeight(6);
	vBottomRight:SetWidth(6); vBottomRight:SetHeight(6);
end

function CalendarPlayerList_OnLoad()
	this.ItemFunction = CalendarPlayerList_NullItemFunction;
	this.SelectedIndex = 0;
end

function CalendarPlayerList_OnShow()
	CalendarPlayerList_Update(this);
end

function CalendarPlayerList_SetItemFunction(pPlayerListControl, pItemFunction)
	pPlayerListControl.ItemFunction = pItemFunction;
end

function CalendarPlayerList_SetSelectionChangedFunction(pPlayerListControl, pSelectionChangedFunction)
	pPlayerListControl.SelectionChangedFunction = pSelectionChangedFunction;
end

function CalendarPlayerList_SetColor(pPlayerListControl, pRed, pGreen, pBlue)
	local	vControlName = pPlayerListControl:GetName();
	local	vHighlightFrameTextureName = vControlName.."HighlightFrameTexture";
	local	vHighlightFrameTexture = getglobal(vHighlightFrameTextureName);
	
	pPlayerListControl.r = pRed; pPlayerListControl.g = pGreen; pPlayerListControl.b = pBlue;
	
	for vIndex = 0, 4 do
		CalendarPlayerList_SetIndexedItemFrameColor(pPlayerListControl, vIndex, pRed, pGreen, pBlue);
	end
	
	vHighlightFrameTexture:SetVertexColor(pRed, pGreen, pBlue);
end

function CalendarPlayerList_SetIndexedItemFrameColor(pPlayerListControl, pIndex, pRed, pGreen, pBlue)
	local	vControlName = pPlayerListControl:GetName();
	local	vItemFrameName = vControlName.."Item"..pIndex;
	local	vItemFrame = getglobal(vItemFrameName);
	local	vTextItem = getglobal(vItemFrameName.."Text");
	
	vItemFrame.r = pRed;
	vItemFrame.g = pGreen;
	vItemFrame.b = pBlue;
	
	vTextItem:SetTextColor(pRed, pGreen, pBlue);
end

gCalendarPlayerList_ActiveList = nil;

function CalendarPlayerList_ScrollUpdate()
	CalendarPlayerList_Update(gCalendarPlayerList_ActiveList);
end

function CalendarPlayerList_Update(pPlayerListControl)
	local	vControlName = pPlayerListControl:GetName();
	local	vScrollFrame = getglobal(vControlName.."ScrollFrame");
	local	vHighlightFrame = getglobal(vControlName.."HighlightFrame");
	
	local	vNumItems = pPlayerListControl.ItemFunction(0);
	
	for vIndex = 0, 4 do
		local	vItemName = vControlName.."Item"..vIndex;
		local	vItemFrame = getglobal(vItemName);
		
		if vIndex < vNumItems then
			vItemFrame:Show();
		else
			vItemFrame:Hide();
		end
	end

	FauxScrollFrame_Update(
			vScrollFrame,
			vNumItems,
			gCalendarPlayerList_cNumVisibleEntries,
			gCalendarPlayerList_cItemHeight,
			nil,
			nil,
			nil,
			vHighlightFrame,
			130,
			130);
	
	CalendarPlayerList_UpdateItems(pPlayerListControl);
end

function CalendarPlayerList_UpdateItems(pPlayerListControl)
	local	vControlName = pPlayerListControl:GetName();
	local	vScrollFrame = getglobal(vControlName.."ScrollFrame");
	local	vFirstItemOffset = FauxScrollFrame_GetOffset(vScrollFrame);
	
	for vIndex = 0, 4 do
		local	vItem = pPlayerListControl.ItemFunction(vIndex + vFirstItemOffset + 1);
		local	vItemName = vControlName.."Item"..vIndex;
		local	vTextItem = getglobal(vItemName.."Text");
		
		vTextItem:SetText(vItem.Text);
	end
end

function CalendarPlayerList_NullItemFunction(pIndex)
	if pIndex == 0 then
		return 10;
	end
	
	return
	{
		Text = "Null item "..pIndex,
	};
end

function CalendarPlayerListItem_OnClick(pButton)
	local	vPlayerListControl = this:GetParent();
	
	local	vControlName = vPlayerListControl:GetName();
	local	vScrollFrame = getglobal(vControlName.."ScrollFrame");
	local	vFirstItemOffset = FauxScrollFrame_GetOffset(vScrollFrame);
	
	local	vPlayerIndex = this:GetID() + vFirstItemOffset + 1;
	
	CalendarPlayerList_SelectIndexedPlayer(vPlayerListControl, vPlayerIndex);
end

function CalendarPlayerList_SelectIndexedPlayer(pPlayerListControl, pIndex)
	local	vControlName = pPlayerListControl:GetName();
	local	vScrollFrame = getglobal(vControlName.."ScrollFrame");
	local	vHighlightFrame = getglobal(vControlName.."HighlightFrame");
	
	-- Remove the old highlighting
	
	if pPlayerListControl.SelectedIndex then
		local	vItemFrameIndex = pPlayerListControl.SelectedIndex - FauxScrollFrame_GetOffset(vScrollFrame) - 1;
		
		CalendarPlayerList_SetIndexedItemFrameColor(pPlayerListControl, vItemFrameIndex, pPlayerListControl.r, pPlayerListControl.g, pPlayerListControl.b);
	end
	
	-- Show the new highlighting
	
	if pIndex > 0 then
		local	vItemFrameIndex = pIndex - FauxScrollFrame_GetOffset(vScrollFrame) - 1;
		local	vItemFrameName = vControlName.."Item"..vItemFrameIndex;
		
		vHighlightFrame:SetPoint("TOPLEFT", vItemFrameName, "TOPLEFT", 5, 0);
		vHighlightFrame:Show();
		
		CalendarPlayerList_SetIndexedItemFrameColor(pPlayerListControl, vItemFrameIndex, 1.0, 1.0, 1.0);
		pPlayerListControl.SelectedIndex = pIndex;
	else
		vHighlightFrame:Hide();
		pPlayerListControl.SelectedIndex = nil;
	end
	
	if pPlayerListControl.SelectionChangedFunction then
		pPlayerListControl.SelectionChangedFunction(pIndex);
	end
end

function Calendar_GetChangedFieldList(pOldTable, pNewTable)
	local	vChangedFields = {};
	
	for vIndex, vNewValue in pNewTable do
		local	vOldValue = pOldTable[vIndex];
		
		if vOldValue == nil then -- New field
			vChangedFields[vIndex] = "NEW";
		elseif vOldValue ~= vNewValue then -- Changed field
			vChangedFields[vIndex] = "UPD";
		end
	end

	for vIndex, vOldValue in pOldTable do
		local	vNewValue = pNewTable[vIndex];
		
		if vNewValue == nil then -- Deleted field
			vChangedFields[vIndex] = "DEL";
		end
	end
	
	return vChangedFields;
end

function Calendar_DumpArray(pPrefixString, pArray)
	if not pArray then
		Calendar_DebugMessage(pPrefixString.." is nil");
		return;
	end
	
	local	vFoundElement = false;
	
	for vIndex, vElement in pArray do
		vFoundElement = true;
		
		local	vType = type(vElement);
		local	vPrefix;
		
		if type(vIndex) == "number" then
			vPrefix = pPrefixString.."["..vIndex.."]";
		else
			vPrefix = pPrefixString.."."..vIndex;
		end
		
		if vType == "number" then
			Calendar_DebugMessage(vPrefix.." = "..vElement);
		elseif vType == "string" then
			Calendar_DebugMessage(vPrefix.." = \""..vElement.."\"");
		elseif vType == "boolean" then
			if vElement then
				Calendar_DebugMessage(vPrefix.." = true");
			else
				Calendar_DebugMessage(vPrefix.." = false");
			end
		elseif vType == "table" then
			Calendar_DumpArray(vPrefix, vElement);
		else
			Calendar_DebugMessage(vPrefix.." "..vType);
		end
	end
	
	if not vFoundElement then
		Calendar_DebugMessage(pPrefixString.." is empty");
	end
end

-- NOTE: If any of LUA's "magic" characters ever need to be escaped they will
--       need to be coded properly with a % preceeding them in the cSpecialChars
--       string.  Currently the "magic" characters are: ^$()%.[]*+-?

gGroupCalendar_cSpecialChars = ",/:;&|\n";

gGroupCalendar_cSpecialCharMap =
{
	c = ",",
	s = "/",
	cn = ":",
	sc = ";",
	a = "&",
	b = "|",
	n = "\n",
};

function Calendar_EscapeString(pString)
	return string.gsub(
					pString,
					"(["..gGroupCalendar_cSpecialChars.."])",
					function (pField)
						for vName, vChar in gGroupCalendar_cSpecialCharMap do
							if vChar == pField then
								return "&"..vName..";";
							end
						end
						
						return "";
					end);
end

function Calendar_UnescapeString(pString)
	return string.gsub(
					pString,
					"&(%w+);", 
					function (pField)
						local	vChar = gGroupCalendar_cSpecialCharMap[pField];
						
						if vChar ~= nil then
							return vChar;
						else
							return pField;
						end
					end);
end

-- NOTE: If any of LUA's "magic" characters ever need to be escaped they will
--       need to be coded properly with a % preceeding them in the cSpecialChars
--       string.  Currently the "magic" characters are: ^$()%.[]*+-?

gGroupCalendar_cSpecialChatChars = "`sS";

gGroupCalendar_cSpecialChatCharMap =
{
	a = "`",
	l = "s",
	u = "S"
};

function Calendar_EscapeChatString(pString)
	return string.gsub(
					pString,
					"(["..gGroupCalendar_cSpecialChatChars.."])",
					function (pField)
						for vName, vChar in gGroupCalendar_cSpecialChatCharMap do
							if vChar == pField then
								return "`"..vName;
							end
						end
						
						return "";
					end);
end

function Calendar_UnescapeChatString(pString)
	return string.gsub(
					pString,
					"`(.)", 
					function (pField)
						local	vChar = gGroupCalendar_cSpecialChatCharMap[pField];
						
						if vChar ~= nil then
							return vChar;
						else
							return pField;
						end
					end);
end

function Calendar_GetLocalTimeFromServerTime(pServerTime)
	if not pServerTime then
		return nil;
	end
	
	local	vLocalTime = pServerTime + gGroupCalendar_ServerTimeZoneOffset;

	if vLocalTime < 0 then
		vLocalTime = vLocalTime + gCalendarMinutesPerDay;
	elseif vLocalTime >= gCalendarMinutesPerDay then
		vLocalTime = vLocalTime - gCalendarMinutesPerDay;
	end
	
	return vLocalTime;
end

function Calendar_GetServerTimeFromLocalTime(pLocalTime)
	local	vServerTime = pLocalTime - gGroupCalendar_ServerTimeZoneOffset;

	if vServerTime < 0 then
		vServerTime = vServerTime + gCalendarMinutesPerDay;
	elseif vServerTime >= gCalendarMinutesPerDay then
		vServerTime = vServerTime - gCalendarMinutesPerDay;
	end
	
	return vServerTime;
end

function Calendar_GetLocalDateTimeFromServerDateTime(pServerDate, pServerTime)
	if not pServerTime then
		return pServerDate, nil;
	end
	
	local	vLocalTime = pServerTime + gGroupCalendar_ServerTimeZoneOffset;
	local	vLocalDate = pServerDate;
	
	if vLocalTime < 0 then
		vLocalTime = vLocalTime + gCalendarMinutesPerDay;
		vLocalDate = vLocalDate - 1;
	elseif vLocalTime >= gCalendarMinutesPerDay then
		vLocalTime = vLocalTime - gCalendarMinutesPerDay;
		vLocalDate = vLocalDate + 1;
	end
	
	return vLocalDate, vLocalTime;
end

function Calendar_GetServerDateTimeFromLocalDateTime(pLocalDate, pLocalTime)
	if not pLocalTime then
		return pLocalDate, nil;
	end
	
	local	vServerTime = pLocalTime - gGroupCalendar_ServerTimeZoneOffset;
	local	vServerDate = pLocalDate;
	
	if vServerTime < 0 then
		vServerTime = vServerTime + gCalendarMinutesPerDay;
		vServerDate = vServerDate - 1;
	elseif vServerTime >= gCalendarMinutesPerDay then
		vServerTime = vServerTime - gCalendarMinutesPerDay;
		vServerDate = vServerDate + 1;
	end
	
	return vServerDate, vServerTime;
end

function Calendar_AddOffsetToDateTime(pDate, pTime, pOffset)
	local	vDateTime = pDate * gCalendarMinutesPerDay + pTime + pOffset;
	
	return math.floor(vDateTime / gCalendarMinutesPerDay), math.mod(vDateTime, gCalendarMinutesPerDay);
end

function Calendar_AddOffsetToDateTime60(pDate, pTime60, pOffset60)
	local	vDateTime60 = pDate * gCalendarSecondsPerDay + pTime60 + pOffset60;
	
	return math.floor(vDateTime60 / gCalendarSecondsPerDay), math.mod(vDateTime60, gCalendarSecondsPerDay);
end

function Calendar_GetServerDateTimeFromSecondsOffset(pSeconds)
	-- Calculate the local date and time of the reset (this is done in
	-- local date/time since it has a higher resolution)

	local	vLocalDate, vLocalTime60 = Calendar_GetCurrentLocalDateTime60();
	
	vLocalDate, vLocalTime60 = Calendar_AddOffsetToDateTime60(vLocalDate, vLocalTime60, pSeconds);
	
	local	vLocalTime = math.floor(vLocalTime60 / 60);

	-- Convert to server date/time

	return Calendar_GetServerDateTimeFromLocalDateTime(vLocalDate, vLocalTime);
end

function Calendar_ArrayIsEmpty(pArray)
	if not pArray then
		return true;
	end
	
	for vIndex, vValue in pArray do
		return false;
	end
	
	return true;
end


local	gGroupCalendar_PrimaryTradeskills =
{
	Herbalism =
	{
		name = GroupCalendar_cHerbalismSkillName
	},
	
	Alchemy =
	{
		name = GroupCalendar_cAlchemySkillName,
		
		cooldownItems =
		{
			GroupCalendar_cTransmuteMithrilToTruesilver,
			GroupCalendar_cTransmuteIronToGold,
			GroupCalendar_cTransmuteLifeToEarth,
			GroupCalendar_cTransmuteWaterToUndeath,
			GroupCalendar_cTransmuteWaterToAir,
			GroupCalendar_cTransmuteUndeathToWater,
			GroupCalendar_cTransmuteFireToEarth,
			GroupCalendar_cTransmuteEarthToLife,
			GroupCalendar_cTransmuteEarthToWater,
			GroupCalendar_cTransmuteAirToFire,
			GroupCalendar_cTransmuteArcanite,
		}
	},
	Enchanting =
	{
		name = GroupCalendar_cEnchantingSkillName
	},
	Engineering =
	{
		name = GroupCalendar_cEngineeringSkillName,
	},
	Leatherworking =
	{
		name = GroupCalendar_cLeatherworkingSkillName,
	},
	Blacksmithing =
	{
		name = GroupCalendar_cBlacksmithingSkillName,
	},
	Tailoring =
	{
		name = GroupCalendar_cTailoringSkillName,
		cooldownItems =
		{
			GroupCalendar_cMooncloth,
		}
	},
	Mining =
	{
		name = GroupCalendar_cMiningSkillName
	},
	Skinning =
	{
		name = GroupCalendar_cSkinningSkillName
	},
};

function Calendar_LookupTradeskillIDByName(pName)
	for vTradeskillID, vTradeskillInfo in gGroupCalendar_PrimaryTradeskills do
		if vTradeskillInfo.name == pName then
			return vTradeskillID;
		end
	end
	
	return nil;
end

function Calendar_GetPrimaryTradeskills()
	local	vNumSkillLines = GetNumSkillLines();
	local	vFoundSkillHeader = false;
	local	vTradeskillIDs = {};
	
	for vSkillLineIndex = 1, vNumSkillLines do
		local	vSkillName, vHeader, vIsExpanded, vSkillRank, vNumTempPoints, vSkillModifier,
				vSkillMaxRank, vIsAbandonable, vStepCost, vRankCost, vMinLevel, vSkillCostType,
				vSkillDescription = GetSkillLineInfo(vSkillLineIndex);
		
		if vHeader then
			if vFoundSkillHeader then
				return vTradeskillIDs;
			end
			
			if vSkillName == TRADE_SKILLS then
				vFoundSkillHeader = true;
			end
		elseif vFoundSkillHeader then
			local	vTradeskillID = Calendar_LookupTradeskillIDByName(vSkillName);
			
			if vTradeskillID then
				table.insert(vTradeskillIDs, vTradeskillID);
			else
				Calendar_DebugMessage("GroupCalendar: Unknown primary profession: "..vSkillName);
			end
		end
	end
	
	return vTradeskillIDs;
end

function Calendar_IsCooldownItem(pCooldownItems, pItemName)
	for vIndex, vItemName in pCooldownItems do
		if vItemName == pItemName then
			return true;
		end
	end
	
	return false;
end

function Calendar_GetTradeskillCooldown(pTradeskillID)
	local	vNumSkills = GetNumTradeSkills();
	local	vCooldownItems = gGroupCalendar_PrimaryTradeskills[pTradeskillID].cooldownItems;
	
	if not vCooldownItems then
		return nil;
	end
	
	for vSkillIndex = 1, vNumSkills do
		local	vSkillName, vSkillType, vNumAvailable, vIsExpanded = GetTradeSkillInfo(vSkillIndex);
		
		if Calendar_IsCooldownItem(vCooldownItems, vSkillName) then
			local	vCooldown = GetTradeSkillCooldown(vSkillIndex);
			
			return vCooldown;
		end
	end
	
	return nil;
end

function CalendarInputBox_OnLoad(pChildDepth)
	if not pChildDepth then
		pChildDepth = 0;
	end
	
	local	vParent = this:GetParent();
	
	for vDepthIndex = 1, pChildDepth do
		vParent = vParent:GetParent();
	end
	
	if vParent.lastEditBox then
		this.prevEditBox = vParent.lastEditBox;
		this.nextEditBox = vParent.lastEditBox.nextEditBox;
		
		this.prevEditBox.nextEditBox = this;
		this.nextEditBox.prevEditBox = this;
	else
		this.prevEditBox = this;
		this.nextEditBox = this;
	end

	vParent.lastEditBox = this;
end

function CalendarInputBox_TabPressed()
	local		vReverse = IsShiftKeyDown();
	local		vEditBox = this;
	
	for vIndex = 1, 50 do
		local	vNextEditBox;
			
		if vReverse then
			vNextEditBox = vEditBox.prevEditBox;
		else
			vNextEditBox = vEditBox.nextEditBox;
		end
		
		if vNextEditBox:IsVisible()
		and not vNextEditBox.isDisabled then
			vNextEditBox:SetFocus();
			return;
		end
		
		vEditBox = vNextEditBox;
	end
end
