// extend the Date object to provide ordinals
Date.prototype.getOrd = function()
{
    var map = {1:'st',21:'st',31:'st',2:'nd',22:'nd',3: 'rd',23:'rd'};
    return map[this.getDate()] != null ? map[this.getDate()] : 'th';
};

// extend the Date object to provide month strings
Date.prototype.getMonthString = function()
{
    return ['January','February','March','April','May','June','July','August','September','October','November','December'][this.getMonth()];
};

// extend the Date object to provide day strings
Date.prototype.getDayString = function()
{
    return ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'][this.getDay()];
};

// CalendarBlock class
function CalendarBlock(id_string, selected_date, minimum_date, maximum_date, click_callback)
{
    // the base string that identifies this calendar and it's parts
    this.id_string = id_string;

    // check calendar element exists
    // TODO: take this out in production
    if (!document.getElementById(id_string)) {
    	alert("Calendar element: " + id_string + " doesn't exists!");
    	return false;
    }

	this.onclick_function_callback = click_callback;

	// set minimums and maxiumums
	this.minimum_date = minimum_date;
	this.maximum_date = maximum_date;

    // now
    this.now_date = new Date();

	// linked calendar placeholders
	this.linked_calendar = "";
	this.linked_calendar_type = "";

	// the selected calendar day
    this.selected_date = selected_date;

    // store the day, month and year
    this.day = this.selected_date.getDate();
    this.month = this.selected_date.getMonth();
    this.year = this.selected_date.getFullYear();

    // init date wrapper
    this.date_wrapper = document.getElementById(id_string + '_dates');
    if (this.date_wrapper) {
        this.date_wrapper.calendar = this;
        this.date_wrapper.onclick = function(e) { this.calendar.chooseDate(e); return false; };
    }

    // header
    this.header = document.getElementById(id_string + '_header');

    // next link
    this.a_next = document.getElementById(id_string + '_next');
    this.a_next.calendar = this;
    this.a_next.onclick = function() { this.calendar.next(); return false; };

    // prev link
    this.a_prev = document.getElementById(id_string + '_prev');
    this.a_prev.calendar = this;
    this.a_prev.onclick = function() { this.calendar.prev(); return false; };

    // open link
    this.a_open = document.getElementById(id_string + '_open');
    this.a_open.calendar = this;
    this.a_open.onclick = function() { this.calendar.show(); return false; };

    // close link
    this.a_close = document.getElementById(id_string + '_close');
    this.a_close.calendar = this;
    this.a_close.onclick = function() { this.calendar.hide(); return false; };

    // calendar container
    this.cal_container = document.getElementById(id_string);

    // days dropdown
    this.dropdown_days = document.getElementById(id_string + '_days');
    this.dropdown_days.calendar = this;
    this.dropdown_days.onchange = function() { this.calendar.changeDay(); calendar_click(this.calendar.getSelectedDate(), id_string); return false; };

    // months dropdown
    this.dropdown_months = document.getElementById(id_string + '_months');
    this.dropdown_months.calendar = this;
    this.dropdown_months.onchange = function() { this.calendar.changeMonth(); calendar_click(this.calendar.getSelectedDate(), id_string); return false; };
}

CalendarBlock.prototype = {

   show: function() {

		// hide the other calendar
		// this is just a backup for when a user tabs from one text box to another
		// it seems that the onblur event handler doesn't support relatedEvent so this
		// seems to be the best solution for now.
		if (this.linked_calendar !== "") {
			this.linked_calendar.hide();
			if (this.id_string == "calendar_departing") {
				hideElement(this.linked_calendar.id_string + "_open");
			}
		}

        // add click off handler
        document.body.calendar = this;
        document.body.onmousedown = function(e) { this.calendar.checkClick(e); };

		// redraw the calendar
        this.refresh();

        // show the calendar block
        showElement(this.id_string);

        // fix for ie6 - WCH will only exist in < ie6.5
        // stops select drop downs from showing through divs
        if (typeof(WCH) != "undefined") {
            WCH.Apply(this.id_string, this.id_string + "_pos");
        }

    },

    hide: function() {

		// remove the click off handler
		// this will conflict with any existing onmousedown handlers and should be integrated
		// with a proper js event handler
        document.body.onmousedown = "";

		if (this.id_string == "calendar_departing") {
			showElement(this.linked_calendar.id_string + "_open");
		}

		// hide the calendar block
        hideElement(this.id_string);

        // fix for ie6 - WCH will only exist in < ie6.5
        // stops select drop downs from showing through divs
        if (typeof(WCH) != "undefined") {
            WCH.Discard(this.id_string, this.id_string + "_pos");
        }
    },

	// called when the calendar is visible and someone clicks anywhere
    checkClick: function(e) {

        // get event and target
        var e = e || window.event;
        var t = e.target || e.srcElement;

        // loop up and see if we're still in the calendar
        while (t.tagName != "BODY" && t.id != this.id_string) {
            t = t.parentNode;
        }

        if (t.id != this.id_string) {
            this.hide();
        }
    },

    // called when a date is clicked on the calendar
    chooseDate: function(e)
    {
        // get event and target
        var e = e || window.event;
        var t = e.target || e.srcElement;

		// return if a link wasn't clicked
        if (t.tagName != "A") {
            return false;
        }

        // loop up and see if we're still in the calendar
		var p = t;
        while (p.tagName != "BODY" && p.id != this.id_string) {
            p = p.parentNode;
        }

		// only choose a date if we are inside the calendar
        if (p.id != this.id_string) {
            return false;
        }

		// get the day of the month
        var selected_day = parseInt(t.innerHTML);

        // check if another month has been clicked
		var new_month = this.getMonth();
        if (t.className.match(/ntm/)) {
            // adjust the month
            if (selected_day < 16) {
                new_month++;
            } else {
                new_month--;
            }
        }

		// create modified date
        var new_date = new Date(this.getYear(), new_month, selected_day);

		// update the calendar date
		this.setCalendarDate(new_date);

		// update selected date
		this.setSelectedDate(new_date);

        // fill drop downs
        this.selectMonth();

        // fill days
        this.fillSelectDays()

		// check with any synched calendars
		this.synchWithLinkedCalendar();

		// call the onlick callback (if it's been set) and pass selected date
		if (this.onclick_function_callback != null) {
			this.onclick_function_callback(this.getSelectedDate(), this.id_string); 
		}

        // hide the calendar
        this.hide();
    },

	// checks for any synched calendars
	// used to make sure a user can't input an end date before a start date and vice versa
	synchWithLinkedCalendar: function() {
		// check linked calendar
		if (this.linked_calendar !== "") {
			if (this.linked_calendar_type == "greater_than") {
				// if this calendar is greater than linked calendar synch the linked cal
				if (this.getSelectedDate() > this.linked_calendar.getSelectedDate()) {
					this.linked_calendar.setSelectedDate(this.getSelectedDate());
					this.linked_calendar.setCalendarDate(this.getSelectedDate());
					this.linked_calendar.selectMonth();
					this.linked_calendar.fillSelectDays();
				}
			} else if (this.linked_calendar_type == "greater_than_plus_1") {
				// if this calendar is greater than linked calendar synch the linked cal
				if (this.getSelectedDate() >= this.linked_calendar.getSelectedDate()) {
					// Increment the day by one
					targetDate = new Date();
					numberOfDays = 1;
					targetDate.setTime( this.getSelectedDate().getTime() + (numberOfDays*86400000) );
					
					this.linked_calendar.setSelectedDate(targetDate);
					this.linked_calendar.setCalendarDate(targetDate);
					this.linked_calendar.selectMonth();
					this.linked_calendar.fillSelectDays();
				}
			} else if (this.linked_calendar_type == "less_than") {
				// if this calendar is less than linked calendar synch the linked cal
				if (this.getSelectedDate() < this.linked_calendar.getSelectedDate()) {
					this.linked_calendar.setSelectedDate(this.getSelectedDate());
					this.linked_calendar.setCalendarDate(this.getSelectedDate());
					this.linked_calendar.selectMonth();
					this.linked_calendar.fillSelectDays();
				}
			} else if (this.linked_calendar_type == "less_than_minus_1") {
				// if this calendar is less than linked calendar synch the linked cal
				if (this.getSelectedDate() <= this.linked_calendar.getSelectedDate()) {
					// Decrement the day by one
					targetDate = new Date();
					numberOfDays = 1;
					targetDate.setTime( this.getSelectedDate().getTime() - (numberOfDays*86400000) );
					
					this.linked_calendar.setSelectedDate(targetDate);
					this.linked_calendar.setCalendarDate(targetDate);
					this.linked_calendar.selectMonth();
					this.linked_calendar.fillSelectDays();
				}
			}
		}
	},

    // called when day drop down is changed
    changeDay: function()
    {
        if (this.dropdown_days) {
            // this relies on date options values being in mmyyyy format
            var day = this.dropdown_days[this.dropdown_days.selectedIndex].value;
			this.setSelectedDate(new Date(this.getSelectedDate().getFullYear(),this.getSelectedDate().getMonth(),day));
        }
    },

    // called when month drop down is changed
    changeMonth: function(selected_day)
    {
        if (this.dropdown_months) {

            // this relies on date options values being in mmyyyy format
            var month = this.dropdown_months[this.dropdown_months.selectedIndex].value.substr(0,2);
            var year = this.dropdown_months[this.dropdown_months.selectedIndex].value.substr(2,4);
			var prev_day = this.getSelectedDate().getDate();

            // generate date
          	var new_date = new Date(year,(month-1),prev_day);
            if (!selected_day && new_date.getMonth() != (month-1)) {
                while (new_date.getMonth() != (month-1)){
                    prev_day--;
                    new_date = new Date(year,(month-1),prev_day);
                }
            }

            // if date is less than minium set it to min
            if (new_date < this.minimum_date) {
				new_date = this.minimum_date;
            } else if (new_date > this.maximum_date) {
            	new_date = this.maximum_date;
            }


			// set the dates
            this.setCalendarDate(new_date);
            this.setSelectedDate(new_date);

            // fill the day drop down
            this.fillSelectDays();
        }
    },

    selectMonth: function()
    {
        if (this.dropdown_months) {
            var monthyear = (this.getSelectedDate().getMonth()+1).toString() + this.getSelectedDate().getFullYear().toString();
            for (var i = 0;i < this.dropdown_months.options.length;i++) {
                if (monthyear == (this.dropdown_months.options[i].value*1).toString()) {
                    this.dropdown_months.options[i].selected = true;
                }
            }
        }
    },

    fillSelectDays: function()
    {

        if (this.dropdown_days) {

			var year = this.getSelectedDate().getFullYear();
			var month = this.getSelectedDate().getMonth();
			var selected_day = this.getSelectedDate().getDate();

            var month_start = new Date(year, month, 1);
            var day_pointer = new Date(year, month, 1);

            // reset options
            this.dropdown_days.length = 0;

            // fill the dropdown
            var fill_dropdown = 1;
            while (fill_dropdown) {

                // only add the days we can choose
                if (day_pointer < this.minimum_date || day_pointer > this.maximum_date) {
                    // do nothing
                } else {
                    // add new date option
                    this.dropdown_days.options[this.dropdown_days.options.length] = new Option(day_pointer.getDate() + " " + day_pointer.getDayString(), day_pointer.getDate());
                    if (selected_day != undefined && selected_day == day_pointer.getDate()) {
                        this.dropdown_days.options[this.dropdown_days.options.length-1].selected = true;
                    }
                }

                // increment date
                day_pointer.setDate(day_pointer.getDate() + 1);

                // stop filling drop down at end of month
                if (day_pointer.getMonth() != month_start.getMonth()) {
                    fill_dropdown = 0;
                }
            }
        }
    },

	// fills the months dropdown from minimum to maximum
	fillMonths: function()
	{
		var selected_date = this.getSelectedDate();

		if (this.dropdown_months) {
			var d_pointer = this.minimum_date;
			var month;
			while (d_pointer < this.maximum_date) {

				// prefix 0 on dates less than 10
				month = d_pointer.getMonth()+1;
				if (month < 10) {
					month = "0" + month;
				}

	            // add new date option
	            this.dropdown_months.options[this.dropdown_months.options.length] = new Option(d_pointer.getMonthString() + "  " + d_pointer.getFullYear(), month + "" + d_pointer.getFullYear());

                // select current month
                if (selected_date.getMonth() == d_pointer.getMonth() && selected_date.getFullYear() == d_pointer.getFullYear()) {
                    this.dropdown_months.options[this.dropdown_months.options.length-1].selected = true;
                }
                // increment date
	            d_pointer = new Date(d_pointer.getFullYear(), d_pointer.getMonth()+1, 1);
			}
		}
	},

	// generates the calendar month
    calGenerateMonth: function (month, year) {

        // update stored month and year
        this.month = month;
        this.year = year;

        // the calendar month, the previous and the next month
        var cal_month_start = new Date(year, this.month, 1);
        var cal_month_prev = new Date(year, this.month-1, 1);
        var cal_month_next = new Date(year, this.month+1, 1);

        // update the header
		this.header.innerHTML = cal_month_start.getMonthString() + " " + cal_month_start.getFullYear();

        // update previous link
        if (cal_month_prev.getMonth() < this.minimum_date.getMonth() && cal_month_prev.getFullYear() <= this.minimum_date.getFullYear()) {
        	this.a_prev.style.display = 'none';
        } else {
			this.a_prev.style.display = '';
			this.a_prev.innerHTML = cal_month_prev.getMonthString();
        }

		// update next link
        if (cal_month_next >= this.maximum_date) {
        	this.a_next.style.display = 'none';
        } else {
			this.a_next.style.display = '';
			this.a_next.innerHTML = cal_month_next.getMonthString();
        }

        // how many days to move to get to monday
        var move_days;
        if (cal_month_start.getDay() === 0) {
            move_days = 6;
        } else {
            move_days = cal_month_start.getDay() - 1;
        }

        // the date where we start to draw the calendar from
        var cal_start = new Date(year, this.month, 1-move_days);
        var cal_pointer = cal_start;

		// the days html
        var cal_html = "";

        var a_class;
        var day_count = 1;
        var drawing_calendar = true;

        while (drawing_calendar) {

            // reset the a_class array
            a_class = new Array();

            // add at the end of each row
            if (day_count != 0 && day_count % 7 == 0) {
                a_class.push("last");
            }

            // this month or not this month
            if (cal_pointer.getMonth() == this.getCalendarDate().getMonth()) {
                // this month (can be removed if no extra styles are being attached)
                a_class.push("tm");
            } else {
            	// not this month
				a_class.push("ntm");
            }

			// selected date
            if (cal_pointer.getTime() == this.getSelectedDate().getTime()) {
                a_class.push("today");
            }

            // bottom row
            if (day_count > 35) {
                a_class.push("bottom");
            }

			// generate the a classes
            if (a_class.length > 0) {
                a_class = "class='" + a_class.join(" ") + "'";
            }

            // check if the date is valid
            if (cal_pointer > this.maximum_date || cal_pointer < this.minimum_date) {
                cal_html += "<span " + a_class + ">" + cal_pointer.getDate() + "</span>";
            } else {
                // create links
                cal_html += "<a href='#'" + a_class + ">" + cal_pointer.getDate() + "</a>";
            }

            // stop after 6 rows
            if (day_count == 42) {
                drawing_calendar = false;
            } else {
            	// increment the date
                cal_pointer.setDate(cal_pointer.getDate() + 1);
                day_count++;
            }

        }

		// redraw the date wrapper with the new days
        this.date_wrapper.innerHTML = cal_html;

    },

    // display the previous month
    prev: function()
    {
        var current_date = new Date(this.year, this.month-1, 1);
        this.calGenerateMonth(current_date.getMonth(), current_date.getFullYear());
    },

	// display the next month
    next: function()
    {
        var current_date = new Date(this.year, this.month+1, 1);
        this.calGenerateMonth(current_date.getMonth(), current_date.getFullYear());
    },

    // refresh the calendar based on current month and year
    refresh: function()
    {
        this.calGenerateMonth(this.month, this.year);
    },

	// sets the date that the calendar is drawing
	setCalendarDate: function(date_obj)
	{
		this.day = date_obj.getDate();
		this.month = date_obj.getMonth();
		this.year = date_obj.getFullYear();
	},

	// sets the selected day
	setSelectedDate: function(date_obj)
	{
		this.selected_date = date_obj;

		// synch with other calendar
		this.synchWithLinkedCalendar();
	},

	getSelectedDate: function()
	{
		return this.selected_date;
	},

    getCalendarDate: function()
    {
        return new Date(this.year, this.month, 1);
    },

	getYear: function()
	{
		return this.year;
	},

	getMonth: function()
	{
		return this.month;
	},

    // link a calendar to this one
    linkCalendar: function(calendar_obj, link_type)
    {
		this.linked_calendar = calendar_obj;
		this.linked_calendar_type = link_type;
    }
}