﻿/*
 *  Copyright Outpost Technology Frontier Corp.
 *
 */

/**
 * Calendar class. Dynamically creates a calendar at a specified location
 * @constructor
 * @requires {@link Date#Class} Extensions
 * @requires HtmlUtility Class
 */
Calendar = function () {
    //----------------------------------------
    //  Private Properties
    //----------------------------------------
    var dateObj = new Date();
    dateObj.clearTime();
    //----------------------------------------
    //  Parameters
    //----------------------------------------
    this.minDate = new Date(1900, 1, 1, 0, 0, 0);
    this.maxDate = new Date(2050, 12, 31, 0, 0, 0);
    //----------------------------------------
    //  HTML characters
    //----------------------------------------
    this.chrPrevMonth = "&lt;";
    this.chrNextMonth = "&gt;";
    //----------------------------------------
    //  HTML Objects
    //----------------------------------------
    this.parentDiv = null;
    this.mainTable = null;
    /**
     * The first row of the table. Contains the month & year display together with the previous & next month buttons
     */
    this.titleRow = null;
    this.daysRow = null;
    this.titleMonthYearDisplay = null;
    this.linkPrevMonth = null;
    this.prevMonthChar = null;
    this.linkNextMonth = null;
    this.nextMonthChar = null;
    this.dateRows = {};
    this.dateLinks = {};
    this.dateLinkSelected = null;
    //----------------------------------------
    //  CSS Styles
    //----------------------------------------
    /**
     * CSS Class for the title row
     */
    this.cssTitleRow = null;
    this.cssDaysRow = null;
    this.cssDatesRowCell = null;
    this.cssDatesRowCellAlt = null;
    this.cssDatesDisabled = null;
    this.cssCellSelected = null;
    this.cssCellUnselected = null;
    this.cssLinkSelected = null;
    this.cssLinkDisabled = null;
    //----------------------------------------
    //  Events
    //----------------------------------------
    /**
     * Event which fires when the calendar's date changes. Requires function(Date) function handler
     * @type function
     */
    this.onDateChanged = null;
    
    //----------------------------------------
    //  Getter / Setter (Privileged Methods)
    //----------------------------------------
    /**
     * Returns the current date object used by the calendar
     * @return {Date} date object used by the calendar
     */
    this.getDateObject = function () { return dateObj; };
    /**
     * Returns the current date of the month
     * @return {number} Date of the month
     */
    this.getDate = function () { return dateObj.getDate(); };
    /**
     * Sets a new date for the calendar. If a number is provided, then only the date value is set, if a date object is provided, then the current date object is replaced
     * @param date can either be a number or a date object
     */
    this.setDate = function (date) {
        if ((typeof date) == 'object') {
            //  remove time data
            date.clearTime();
            if (!dateObj.equalsToDateOnly(date)) {
                //  ensure that it does not go over or less the max & min dates
                daet = Date.ensureDateInRange(date, this.minDate, this.maxDate);

                //  if curDate has changed, reflect it to the calendar
                if (!dateObj.equalsToDateOnly(date)) {
                    dateObj = date;

                    if (this.isInitialized()) {
                        this.refresh();
                        this.selectCurrentDateLink();

                        if (this.onDateChanged)
                            this.onDateChanged(dateObj);
                    }
                }
            }
        }
        else {
            if (dateObj.getDate() != date) {
                //  try setting the year, specifying to disallow overflows
                var curDate = Date.setDate(dateObj, date, false);
                //  ensure that it does not go over or less the max & min dates
                curDate = Date.ensureDateInRange(curDate, this.minDate, this.maxDate);
                //  if curDate has changed, reflect it to the calendar
                if (!dateObj.equalsToDateOnly(curDate)) {
                    dateObj = curDate;

                    if (this.isInitialized()) {
                        this.refresh();
                        this.selectCurrentDateLink();
                        
                        if (this.onDateChanged)
                            this.onDateChanged(dateObj);
                    }
                }
            }
        }
    };
    this.getMonth = function () { return dateObj.getMonth(); };
    this.setMonth = function (month) {
        if (this.getMonth() != month) {
            //  try setting the month, specifying to disallow overflow
            var curDate = Date.setMonth(dateObj, month, false);
            //  ensure that it does not go over or less of the max & min dates
            curDate = Date.ensureDateInRange(curDate, this.minDate, this.maxDate);
            //  if curDate has changed, reflect it to the calendar
            if (!dateObj.equalsToDateOnly(curDate)) {
                dateObj = curDate;

                if (this.isInitialized()) {
                    this.refresh();
                    this.selectCurrentDateLink();
                    
                    if (this.onDateChanged)
                        this.onDateChanged(dateObj);
                }
            }
        }
    };

    this.getFullYear = function () { return dateObj.getFullYear(); };
    this.setYear = function (year) {
        if (!this.getFullYear() != year) {
            //  try setting the year, specifying to disallow overflows
            var curDate = Date.setYear(dateObj, year, false);
            //  ensure that it does not go over or less the max & min dates
            curDate = Date.ensureDateInRange(curDate, this.minDate, this.maxDate);
            //  if curDate has changed, reflect it to the calendar
            if (!dateObj.equalsToDateOnly(curDate)) {
                dateObj = curDate;
            
                if (this.isInitialized()) {
                    this.refresh();
                    this.selectCurrentDateLink();
                    
                    if (this.onDateChanged)
                        this.onDateChanged(dateObj);
                }
            }
        }
    };
    //----------------------------------------
    //  Functions
    //----------------------------------------
    /**
     * Prints the current date to string format
     * @return {string} date in string format
     */
    this.print = function (str) { return dateObj.print(str); };
    this.isInitialized = function () { return (this.parentDiv)? true : false; };
};



/**
 * Retrieves the parent div where this calendar object is attached to
 * @returns {object} HTML Div Element
 */
Calendar.getParentDiv = function(evt) {
	var f = HtmlUtility.is_ie ? window.event.srcElement : evt.currentTarget;
	while (!(/^div$/i.test(f.tagName)))
		f = f.parentNode;
	return f;
};


Calendar.createDateLinkBox = function (parentTr) {
    var td = HtmlUtility.createElement("td", parentTr);
    var a = HtmlUtility.createElement("a", td);

    a.href = "javascript:void(0);";
    HtmlUtility.addEvent( a, "click", Calendar.onDateClicked );
    
    return a;
};



/*
    CALENDAR METHODS
*/

Calendar.prototype.create = function (parent) {
    //  create main div parent
    this.parentDiv = HtmlUtility.createElement("div", parent);
    this.parentDiv.calendar = this;
    
    //  create calendar table
    this.mainTable = HtmlUtility.createElement("table", this.parentDiv);
    //  create table body
    this.tableBody = HtmlUtility.createElement("tbody", this.mainTable);
    //  create title row
    this.titleRow = HtmlUtility.createElement("tr", this.tableBody);
    
    //  title data
    this.titleMonthYearDisplay = HtmlUtility.createElement("td", this.titleRow);
    this.titleMonthYearDisplay.colSpan = 6;
    
    //  prev/next month's link attachment node
    var tdNextPrevMonth = HtmlUtility.createElement("td", this.titleRow);
    
    //  previous month link
    this.linkPrevMonth = HtmlUtility.createElement("a", tdNextPrevMonth);
    this.linkPrevMonth.innerHTML = this.chrPrevMonth;
    this.linkPrevMonth.href = "javascript:void(0);";
    
    //  next month link
    this.linkNextMonth = HtmlUtility.createElement("a", tdNextPrevMonth);
    this.linkNextMonth.innerHTML = this.chrNextMonth;
    this.linkNextMonth.href = "javascript:void(0);";
    
    //  hookup event for prev & next month
    HtmlUtility.addEvent(this.linkPrevMonth, "click", Calendar.onPrevMonthClicked);
    HtmlUtility.addEvent(this.linkNextMonth, "click", Calendar.onNextMonthClicked);
    
    //  add days row
    this.daysRow = HtmlUtility.createElement("tr", this.tableBody);
    
    var sun = HtmlUtility.createElement("td", this.daysRow);
    var mon = HtmlUtility.createElement("td", this.daysRow);
    var tue = HtmlUtility.createElement("td", this.daysRow);
    var wed = HtmlUtility.createElement("td", this.daysRow);
    var thu = HtmlUtility.createElement("td", this.daysRow);
    var fri = HtmlUtility.createElement("td", this.daysRow);
    var sat = HtmlUtility.createElement("td", this.daysRow);
    
    sun.innerHTML = "S";
    mon.innerHTML = "M";
    tue.innerHTML = "T";
    wed.innerHTML = "W";
    thu.innerHTML = "T";
    fri.innerHTML = "F";
    sat.innerHTML = "S";
    
    //  add dates row
    var r1 = HtmlUtility.createElement("tr", this.tableBody);
    var r2 = HtmlUtility.createElement("tr", this.tableBody);
    var r3 = HtmlUtility.createElement("tr", this.tableBody);
    var r4 = HtmlUtility.createElement("tr", this.tableBody);
    var r5 = HtmlUtility.createElement("tr", this.tableBody);
    var r6 = HtmlUtility.createElement("tr", this.tableBody);
    
    this.dateRows = new Array( r1, r2, r3, r4, r5, r6 );
    
    //  date boxes w/ links    
    this.dateLinks = new Array (
            Calendar.createDateLinkBox(r1),Calendar.createDateLinkBox(r1),Calendar.createDateLinkBox(r1),Calendar.createDateLinkBox(r1),Calendar.createDateLinkBox(r1),Calendar.createDateLinkBox(r1),Calendar.createDateLinkBox(r1),
            Calendar.createDateLinkBox(r2),Calendar.createDateLinkBox(r2),Calendar.createDateLinkBox(r2),Calendar.createDateLinkBox(r2),Calendar.createDateLinkBox(r2),Calendar.createDateLinkBox(r2),Calendar.createDateLinkBox(r2),
            Calendar.createDateLinkBox(r3),Calendar.createDateLinkBox(r3),Calendar.createDateLinkBox(r3),Calendar.createDateLinkBox(r3),Calendar.createDateLinkBox(r3),Calendar.createDateLinkBox(r3),Calendar.createDateLinkBox(r3),
            Calendar.createDateLinkBox(r4),Calendar.createDateLinkBox(r4),Calendar.createDateLinkBox(r4),Calendar.createDateLinkBox(r4),Calendar.createDateLinkBox(r4),Calendar.createDateLinkBox(r4),Calendar.createDateLinkBox(r4),
            Calendar.createDateLinkBox(r5),Calendar.createDateLinkBox(r5),Calendar.createDateLinkBox(r5),Calendar.createDateLinkBox(r5),Calendar.createDateLinkBox(r5),Calendar.createDateLinkBox(r5),Calendar.createDateLinkBox(r5),
            Calendar.createDateLinkBox(r6),Calendar.createDateLinkBox(r6),Calendar.createDateLinkBox(r6),Calendar.createDateLinkBox(r6),Calendar.createDateLinkBox(r6),Calendar.createDateLinkBox(r6),Calendar.createDateLinkBox(r6)
            );
       
    //  update data
    this.refresh();
    
    //  update styles
    this.updateStyles();
    
    //  select current date
    this.selectCurrentDateLink();
};

Calendar.prototype.clearLink = function (link) {
    link.innerHTML = "";
    link.style.display = "";
    
    var parent = link.parentNode;
    var element;
    if (parent) {
        for (var i = parent.childNodes.length; --i >= 0;) {
            element = parent.childNodes[i];
            if (element.tagName.toLowerCase() == "span")
                parent.removeChild(element);
        }
    }
}

Calendar.prototype.selectDateLink = function (link) {
    //  make sure a link was given
    if (!link || link.tagName.toLowerCase() != "a")
        return;
    
    //  nothing to select since its already selected
    if (link == this.dateLinkSelected)
        return;

    var parentCell;
    //  make sure that there is a selected date link
    if (this.dateLinkSelected) {
        //  unselect previous link
        if (this.cssLinkSelected)
            HtmlUtility.removeClass(this.dateLinkSelected, this.cssLinkSelected);
        //  if there is an unselected css class, set it
        if (this.cssLinkUnselected)
            HtmlUtility.addClass(this.dateLinkSelected, this.cssLinkUnselected);

        parentCell = this.dateLinkSelected.parentNode;
        //  retrieve previous link parent cell
        if (parentCell) {
            //  unselect previous link cell
            if (this.cssCellSelected)
                HtmlUtility.removeClass(parentCell, this.cssCellSelected);
            //  if there is an unselected css class, set it
            if (this.cssCellUnselected)
                HtmlUtility.addClass(this.dateLinkSelected, this.cssCellUnselected);
        }
    }

    //  remove unselected class, we don't want them mixing together with selection class
    if (this.cssLinkUnselected)
        HtmlUtility.removeClass(link, this.cssLinkUnselected);
    //  set selection class if present
    if (this.cssLinkSelected)
        HtmlUtility.addClass(link, this.cssLinkSelected);
    
    //  retrieve parent cell
    var parentCell = link.parentNode;
    if (parentCell) {
        //  remove unselected class, we don't want them mixing together
        if (this.cssCellUnselected)
            HtmlUtility.removeClass(parentCell, this.cssCellUnselected);
        //  select cell
        if (this.cssCellSelected)
            HtmlUtility.addClass(parentCell, this.cssCellSelected);
    }
    
    this.dateLinkSelected = link;
}

Calendar.prototype.enableLink = function (link) {
    if (link.style.display == "")
        return;
    
    var parent = link.parentNode;
    var span = parent.childNodes[i];
    if (parent) {
        var childLen = parent.childNodes.length;
        for (var i = childLen; --i >= 0;) {
            if (parent.childNodes[i].tagName.toLowerCase() != "span")
                continue;
                       
            if (parent.childNodes[i].innerHTML.toLowerCase() == link.innerHTML.toLowerCase()) {
                parent.removeChild(parent.childNodes[i]);
                break;
            }
        }
    }
    
    link.style.display = "";
}

Calendar.prototype.disableLink = function (link) {
    if (link.style.display == "none" || !link.innerHTML || link.innerHTML == "")
        return;
        
    var parent = link.parentNode;
    if (parent) {
        var span = HtmlUtility.createElementBefore( "span", link, parent );
        span.innerHTML = link.innerHTML;
        
        if (this.cssLinkDisabled)
            span.className = this.cssLinkDisabled;
    }
    
    link.style.display = "none";
}

Calendar.prototype.refresh = function () {
    //  we can't do anything if the current object is not yet initialized
    if (!this.isInitialized())
        return;
    
    //  update header (month year)
    this.titleMonthYearDisplay.innerHTML = this.print("%MMMM %yyyy");

    //  enable / disable nextMonth if this is the maximum month+year
    if (this.getFullYear() >= this.maxDate.getFullYear() && this.getMonth() >= this.maxDate.getMonth())
        this.disableLink(this.linkNextMonth);
    else
        this.enableLink(this.linkNextMonth);
    
    //  enable / disable prevMonth if this is the minimum month+year
    if (this.getFullYear() <= this.minDate.getFullYear() && this.getMonth() <= this.minDate.getMonth())
        this.disableLink(this.linkPrevMonth);
    else
        this.enableLink(this.linkPrevMonth);

    //  update date link box contents
    var boxLen = this.dateLinks.length;
    var maxDate = this.getDateObject().getMonthMaxDate();
    var firstDateOfMonth = new Date(this.getFullYear(), this.getMonth(), 1, 0, 0, 0);
    var firstDayOfMonth = firstDateOfMonth.getDay();
    var lastBoxIndex = maxDate + firstDayOfMonth;
    var isMinMonth = (this.getMonth() == this.minDate.getMonth() && this.getFullYear() == this.minDate.getFullYear());
    var isMaxMonth = (this.getMonth() == this.maxDate.getMonth() && this.getFullYear() == this.maxDate.getFullYear());
    var date;
    for (var i = 0; i < boxLen; i++) {
        this.clearLink(this.dateLinks[i]);
        if (firstDayOfMonth <= i && i < lastBoxIndex) {
            date = (i + 1 - firstDayOfMonth);
            this.dateLinks[i].innerHTML = date;
            if (isMinMonth && date < this.minDate.getDate())
                this.disableLink(this.dateLinks[i]);
            else if (isMaxMonth && date > this.maxDate.getDate())
                this.disableLink(this.dateLinks[i]);
            else
                this.enableLink(this.dateLinks[i]);
        }
    }
};

Calendar.prototype.updateStyles = function () {
    //  set title row style, if present
    if (this.cssTitleRow)
        this.titleRow.className = this.cssTitleRow;
    //  set days label row style, if present
    if (this.cssDaysRow)
        this.daysRow.className = this.cssDaysRow;
                
    //  set dates style, if present
    if (this.cssDatesRowCell || this.cssDatesRowCellAlt) {
        var rowIndex;
        var parentNode;
        for (var i = this.dateLinks.length; --i >= 0;) {
            rowIndex = ((i - (i % 7)) / 7);
            
            if ((rowIndex % 2) == 0 && this.cssDatesRowCell)
                HtmlUtility.addClass(this.dateLinks[i].parentNode, this.cssDatesRowCell);
            else if ((rowIndex % 2) == 1 && this.cssDatesRowCellAlt)
                HtmlUtility.addClass(this.dateLinks[i].parentNode, this.cssDatesRowCellAlt);
        }
    }
};

Calendar.prototype.setPrevMonth = function () {
    //  move date 1 month back
    this.setMonth( this.getMonth() - 1);
};

Calendar.prototype.setNextMonth = function () {
    //  move date 1 month ahead
    this.setMonth( this.getMonth() + 1);
};

Calendar.prototype.selectCurrentDateLink = function () {
    //  make sure this object has already been initialized
    if (!this.isInitialized())
        return;
    
    var date = this.getDate();
    for (var i = this.dateLinks.length; --i >= 0;) {
        if (this.dateLinks[i].innerHTML == date) {
            this.selectDateLink(this.dateLinks[i]);
        }
    }
};



/*
    EVENT HANDLERS
*/

Calendar.onDateClicked = function (evt) {
    
    var parentDiv = Calendar.getParentDiv(evt);
    if (!parentDiv)
        return;
        
    var link = HtmlUtility.getEventSource(evt);
    
    var calendar = parentDiv.calendar;
    if (!calendar)
        return;
    
    //  get selected date
    if (!link.innerHTML || link.innerHTML == "")
        return;
    
    calendar.selectDateLink(link);
    
    var clickedDate = link.innerHTML;
    calendar.setDate(clickedDate);
};

Calendar.onPrevMonthClicked = function (evt) {
    var parentDiv = Calendar.getParentDiv(evt);
    if (!parentDiv)
        return;
    
    var calendar = parentDiv.calendar;
    if (!calendar)
        return;
    
    calendar.setPrevMonth();
};

Calendar.onNextMonthClicked = function (evt) {
    var parentDiv = Calendar.getParentDiv(evt);
    if (!parentDiv)
        return;
    
    var calendar = parentDiv.calendar;
    if (!calendar)
        return;
    
    calendar.setNextMonth();
};