/**
 * Calendar Class
 */
function Calendar(){
    //default date format to use (based on PHP's date function)
    this.dateFormat = 'Y-m-d';

    //Build the div that contains the calendar, and attach it to the document body
    this.calendarContainer = document.createElement("div");
    this.calendarContainer.id = 'calendarContainer';
    this.calendarContainer.style.position = 'absolute';
    this.calendarContainer.style.visibility = 'hidden';
    this.calendarContainer.style.zIndex = '99';
    document.body.appendChild(this.calendarContainer);

    //The today var will be used to highlight "today"
    this.today = new Date();

    //Default the current month/year to todays month/year
    this.current = new Date();
};

/**
 * Used to display the calendar. in the correct position, and set the current
 * date info to whatever is already in the input
 *
 * @param HTMLInputObject target - reference to the input that gets filled
 *
 * @return void
 */
Calendar.prototype.showCal = function (target) {
    this.stopHideCal();
    this.target = target;
    var position = this.getElementPosition(this.target);
    var parentHeight = this.target.offsetHeight;

//    this.target.value = this.interpretDate(this.target.value);
//    this.selected = new Date(this.target.value);
    this.selected = new Date(this.interpretDate(this.target.value));

    if (this.selected.getMonth() >=0 && this.selected.getMonth() <= 11) {
        this.current.setMonth(this.selected.getMonth());
    }

    //Throw in a little logic to see that the year is within 100 years of today...
    //so that we don't have people start at 0001 or something crazy
    if (Math.abs(this.selected.getFullYear()-this.today.getFullYear()) <= 100) {
        this.current.setFullYear(this.selected.getFullYear());
    }

    var thisCal = this;
    this.blurfunc = this.addEvent(this.target, 'blur', function(e) {thisCal.hideCal();});

    this.calendarContainer.style.left = position.left+10+'px';
    this.calendarContainer.style.top = position.top+this.target.offsetHeight+'px';
    this.setSelectVisibility('hidden');
    this.calendarContainer.style.visibility = 'visible';
    this.buildCalendar();
};

/**
 * Used to change the visibility of all select elements in the document. This
 * only affects Internet Explorer, as it is the only browser that will not allow
 * anything to display over a select.
 *
 * @param String vis - what to set the visibility of the selects to
 *
 * @return void
 */
Calendar.prototype.setSelectVisibility = function (vis) {
/*@cc_on

    var selects = document.getElementsByTagName('select');
    for(i in selects) {
        if(typeof(selects[i]) == 'object'){
            selects[i].style.visibility = vis;
        }
    }

@*/
}


/**
 * Used to set the format of the date string inserted into the text input.  Uses
 * formatting from PHP's date function
 *
 * @param {String} dateFormat
 *
 * @return void
 */
Calendar.prototype.setDateFormat = function (dateFormat) {
    this.dateFormat = dateFormat;
}

/**
 * Used to build the actual calendar, and attach it to the calendarContainer div
 *
 * @return void
 */
Calendar.prototype.buildCalendar = function () {
    var thisCal = this;
    var start_month = this.current;
    start_month.setDate(1);
    var start_month_day = start_month.getDay();


    var count = 0;
    var day_class = "";
    var calendarTable = document.createElement("table");
    calendarTable.className = "cal";
    calendarTable.cellPadding = 0;
    calendarTable.cellSpacing = 0;

    var thead = document.createElement("thead");
    var tr = document.createElement("tr");

    //Change Year DOWN button
    var td = document.createElement("td");
    var btn = document.createElement("button");
    this.addEvent(btn, 'click', function() {thisCal.changeMonth(-12);});
    btn.appendChild(document.createTextNode('<<'));
    td.appendChild(btn);
    tr.appendChild(td);

    //Change Month DOWN button
    var td = document.createElement("td");
    var btn = document.createElement("button");
    this.addEvent(btn, 'click', function() {thisCal.changeMonth(-1);});
    btn.appendChild(document.createTextNode('<'));
    td.appendChild(btn);
    tr.appendChild(td);

    //Show month Year
    var td = document.createElement("td");
    td.colSpan = 3;
    td.appendChild(document.createTextNode(this.current.format('M Y')));
    tr.appendChild(td);

    //Change Month UP button
    var td = document.createElement("td");
    var btn = document.createElement("button");
    this.addEvent(btn, 'click', function() {thisCal.changeMonth(1);});
    btn.appendChild(document.createTextNode('>'));
    td.appendChild(btn);
    tr.appendChild(td);

    //Change Year UP button
    var td = document.createElement("td");
    var btn = document.createElement("button");
    this.addEvent(btn, 'click', function() {thisCal.changeMonth(12);});
    btn.appendChild(document.createTextNode('>>'));
    td.appendChild(btn);
    tr.appendChild(td);

    thead.appendChild(tr);

    //Display the days of the week
    var tr = document.createElement("tr");
    tr.className = 'dow';
    for (i=0; i<7; i++) {
        var td = document.createElement("td");
        td.appendChild(document.createTextNode(Date.prototype.dayNames[i].substr(0,2)));
        tr.appendChild(td);
    }
    thead.appendChild(tr);
    calendarTable.appendChild(thead);

    //Display the "Show Today" Link.
    var tfoot = document.createElement("tfoot");
    var tr = document.createElement("tr");
    var td = document.createElement("td");
    td.colSpan = 7;
    var btn = document.createElement("button");
    this.addEvent(btn, 'click', function() {thisCal.showToday();});
    btn.appendChild(document.createTextNode('Show Today'));
    td.appendChild(btn);
    tr.appendChild(td);
    tfoot.appendChild(tr);
    calendarTable.appendChild(tfoot);

    //Create the actual days
    var tbody = document.createElement("tbody");
    var tr = document.createElement("tr");
    //Add calls containing only a hard-space until the day we start on
    for (i=0;i<start_month_day;i++) {
        var td = document.createElement("td");
        td.appendChild(document.createTextNode("\xA0"));
        tr.appendChild(td);
        count++;
    }

    for (i=1;i<=this.current.getDaysInMonth();i++) {
        //adjust this.current
        this.current.setDate(i);
        if (count < 7) {
            day_class = "";
            if (this.current.format('Ym')+this.current.pad(i) == this.today.format('Ymd')) {
                day_class = "today";
            }
            //See if the day we are processing is the "selected" day (currently IN the
            //target input).
            if (this.current.format('Ym')+this.current.pad(i) == this.selected.format('Ymd')) {
                if(day_class != ''){day_class += ' ';}
                day_class += "selected";
            }

            var td = document.createElement("td");

            var btn = document.createElement("button");
            btn.className = day_class;
            //This is part of a closure to allow it to use the correct date...and not
            //the last date in the loop
            var clickFunc = thisCal.getFunction(i);
            this.addEvent(btn, 'click', clickFunc);
            btn.appendChild(document.createTextNode(i));
            td.appendChild(btn);
            count++;
            tr.appendChild(td);
        } else {
            tbody.appendChild(tr);
            var tr = document.createElement("tr");
            count = 0;
            i--;
        }
    }
    //Add calls containing only a hard-space until the end
    for(count=count;count<7;count++) {
        var td = document.createElement("td");
        td.appendChild(document.createTextNode("\xA0"));
        tr.appendChild(td);
    }
    tbody.appendChild(tr);
    calendarTable.appendChild(tbody);

    //remove all children from the calendar container, and then append the new table
    while (this.calendarContainer.childNodes[0]) {
        this.calendarContainer.removeChild(this.calendarContainer.childNodes[0]);
    }
    this.calendarContainer.appendChild(calendarTable);
};

/**
 * A closure used to create a function for inserting the date into the field.
 * Month and Year are already known to this instance of Calendar, so we simply
 * pass in the day of the month (1-31)
 *
 * @param {int} i
 *
 * @return function
 */
Calendar.prototype.getFunction = function(i) {
    var thisCal = this;
    function tempFunc() {
        thisCal.fillDate(i);
        //If there is an onkeyup action set, run it.  This will let you attach
        //validation functions to the onkeyup
        if (typeof thisCal.target.onkeyup === 'function') {
            thisCal.target.onkeyup();
        }
    };
    return tempFunc;
};

/**
 * Used to hide the calendar, with a 200ms delay.  This way the calendar is
 * still there to receive a click, and does not disappear and reappear when we
 * change month or year.
 *
 * @return void
 */
Calendar.prototype.hideCal = function() {
    var thisCal = this;
    this.timeout_id = setTimeout(function() {thisCal.hideCalendar();}, 200);
};

/**
 * Can stop a request to hide the calendar
 *
 * @return void
 */
Calendar.prototype.stopHideCal = function () {
    if (this.timeout_id) {
        clearTimeout(this.timeout_id);
    }
};

/**
 * Hides the calendar and removes the blur event we attached to the text input
 *
 * @return void
 */
Calendar.prototype.hideCalendar = function() {
    this.remEvent(this.target, 'blur', this.blurfunc);
    this.setSelectVisibility('');
    this.calendarContainer.style.visibility = 'hidden';
}

/**
 * Put the correct date into the target text input formatted according to
 * this.dateFormat.  Then hide the calendar.
 *
 * @param {int} i - day of the month (1-31)
 *
 * @return void
 */
Calendar.prototype.fillDate = function(i) {
    this.current.setDate(i);
    this.target.value = this.current.format(this.dateFormat);
    this.hideCalendar();
};

/**
 * Changes the month of the calendar.  The only parameter is an integer to
 * adjust the month by (Usually -1 to go back one month, 1 to go forward one
 * month, -12 to go back one year, or 12 to go forward one year.  However, it
 * should be able to handle any integer value)
 * @param {int} adj
 */
Calendar.prototype.changeMonth = function (adj) {
    this.stopHideCal();
    this.current.setDate(1);
    this.current.setMonth(this.current.getMonth()+adj);
    this.buildCalendar();
};

Calendar.prototype.showToday = function() {
    this.stopHideCal();
    this.current = new Date();
    this.buildCalendar();
};

Calendar.prototype.getElementPosition = function(elemID) {
    var offsetTrail = elemID;
    var offsetLeft = 0;
    var offsetTop = 0;
    while (offsetTrail) {
        offsetLeft += offsetTrail.offsetLeft;
        offsetTop += offsetTrail.offsetTop;
        offsetTrail = offsetTrail.offsetParent;
    }
    if (navigator.userAgent.indexOf("Mac") != -1 &&
        typeof document.body.leftMargin != "undefined") {
        offsetLeft += document.body.leftMargin;
        offsetTop += document.body.topMargin;
    }
    return {left:offsetLeft, top:offsetTop};
};

Calendar.prototype.pad_zero = function(num) {
    return ((num <= 9) ? ("0" + num) : num);
};

Calendar.prototype.interpretDate = function(date) {
    date=date.replace(/[\\\.\-\s]/g,"/");
    date=date.replace(/^(\d\d)(\d\d)(\d\d)$/,"$1/$2/$3");
    date=date.replace(/^(\d\d)(\d\d)(\d\d\d\d)$/,"$1/$2/$3");
    date=date.replace(/^(\d\d)\/(\d\d)\/(\d\d)$/,"$1/$2/20$3");
    return date;
};

Calendar.prototype.addEvent = function (el, ev, f) {
  if (el.addEventListener) {
    el.addEventListener(ev, f, false);
    } else if(el.attachEvent) {
    el.attachEvent("on" + ev, f);
    } else {
    el['on' + ev] = f;
    }
    return f;
};

//make this available statically
Calendar.addEvent = Calendar.prototype.addEvent;

function calInit() {
    cal = new Calendar();
}

//Start the calendar onload
Calendar.addEvent(window, 'load', calInit);

Calendar.prototype.remEvent = function (el, ev, f) {
  if (el.removeEventListener) {
    el.removeEventListener(ev, f, false);
    } else if(el.detachEvent) {
    el.detachEvent("on" + ev, f);
    } else {
    el['on' + ev] = null;
    }
};



String.leftPad = function (s, len, ch) {
    var str = new String(s);
    if (ch == null) {
        ch = " ";
    }
    while (str.length < len) {
        str = ch + result;
    }
    return str;
}

/**
 * Date formatting Stuff
 */
Date.prototype.dayNames = new Array(
    "Sunday",
    "Monday",
    "Tuesday",
    "Wednesday",
    "Thursday",
    "Friday",
    "Saturday"
);

Date.prototype.monthNames = new Array(
    "January",
    "February",
    "March",
    "April",
    "May",
    "June",
    "July",
    "August",
    "September",
    "October",
    "November",
    "December"
);

Date.prototype.daysInMonths = new Array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);

Date.prototype.isLeapYear = function () {
    return (this.getFullYear() % 4 == 0) && ((this.getFullYear() % 100 != 0) || (this.getFullYear() % 400 == 0));
}

Date.prototype.pad = function (num) {
    if (Math.abs(num) <= 9) {
        if (num < 0) {
            return '-0'+Math.abs(num);
        } else {
            return '0'+num;
        }
    } else {
        return String(num);
    }
    return ((Math.abs(num) <= 9) ? ("0" + num) : num);
}

Date.prototype.format = function(format) {
    format = String(format).split("");
    var dateStr = '';

    for (var i = 0; i < format.length; i++) {
        switch (format[i]) {
            case 'd':
                dateStr += this.pad(this.getDate());
                break;
            case 'D':
                dateStr += this.dayNames[this.getDay()].substr(0,3);
                break;
            case 'j':
                dateStr += this.getDate();
                break;
            case 'l':
                dateStr += this.dayNames[this.getDay()];
                break;
            case 'N':
                dateStr += (this.getDay() == 0)? '7':this.getDay();
                break;
            case 'S':
                dateStr += this.getSuffix();
                break;
            case 'w':
                dateStr += this.getDay();
                break;
            case 'z':
                dateStr += this.getDayOfYear();
                break;
/*
            case 'W':
                dateStr += '*W*';
                break;
*/
            case 'F':
                dateStr += this.monthNames[this.getMonth()];
                break;
            case 'm':
                dateStr += this.pad(this.getMonth()+1);
                break;
            case 'M':
                dateStr += this.monthNames[this.getMonth()].substr(0,3);
                break;
            case 'n':
                dateStr += this.getMonth()+1;
                break;
            case 't':
                dateStr += this.getDaysInMonth();
                break;
            case 'L':
                dateStr += (this.isLeapYear())? 1:0;
                break;
/*
            case 'o':
                dateStr += '*o*';
                break;
*/
            case 'Y':
                dateStr += this.getFullYear();
                break;
            case 'y':
                dateStr += String(this.getFullYear()).substr(-2);
                break;
            case 'a':
                dateStr += this.getAmPm().toLowerCase();
                break;
            case 'A':
                dateStr += this.getAmPm().toUpperCase();
                break;
            case 'B':
                dateStr += this.getSwatchTime();
                break;
            case 'g':
                dateStr += (this.getHours()<= 12)? this.getHours():this.getHours()-12;
                break;
            case 'G':
                dateStr += this.getHours();
                break;
            case 'h':
                var hour = (this.getHours()<= 12)? this.getHours():this.getHours()-12;
                dateStr += this.pad(hour);
                break;
            case 'H':
                dateStr += this.pad(this.getHours());
                break;
            case 'i':
                dateStr += this.pad(this.getMinutes());
                break;
            case 's':
                dateStr += this.pad(this.getSeconds());
                break;
/*
            case 'e':
                dateStr += '*e*';
                break;
*/
            case 'I':
                dateStr += this.getDST();
                break;
            case 'O':
                dateStr += (this.pad(this.getTimezoneOffset()/60*-1)+'00');
                break;
            case 'P':
                dateStr += (this.pad(this.getTimezoneOffset()/60*-1)+':00');
                break;
/*
            case 'T':
                dateStr += '*T*';
                break;
*/
            case 'Z':
                dateStr += this.getTimezoneOffset()*60*-1;
                break;
            case 'c':
                dateStr += this.format('Y-m-d\\TH:i:sP');
                break;
            case 'r':
                dateStr += this.format('D, d M Y H:i:s O');
                break;
            case 'U':
                dateStr += Math.floor(this.getTime()/1000);
                break;
            case '\\':
                i++;
            default:
                dateStr += format[i];
        }
    }
    return dateStr;
}
Date.prototype.getDaysInMonth = function () {
    if (this.getMonth() == 1) {
        this.daysInMonths[1] = (this.isLeapYear())? 29:28;
    }
    return this.daysInMonths[this.getMonth()];
}
Date.prototype.getDayOfYear = function () {
    var start = Date.UTC(this.getFullYear(), 0, 0);
    var end = Date.UTC(this.getFullYear(), this.getMonth(), this.getDate());
    return Math.floor((end - start) / (1000 * 60 * 60 * 24));
}
Date.prototype.getSuffix = function () {
    d = this.getDate();
    if (d >=11 && d <= 13) {
        return 'th';
    } else {
        switch(String(d).substr(-1)) {
            case '1':
                return 'st';
            case '2':
                return 'nd';
            case '3':
                return 'rd';
            default:
                return 'th';
        }
    }
}

Date.prototype.getSwatchTime = function () {
    return String.leftPad(Math.floor((((this.getTime()/1000/60/60/24)%1)+(1/24))*1000), 3, '0');
}

Date.prototype.getDST = function () {
    var start = new Date(this.getFullYear(), 0, 1);
    return (this.getTimezoneOffset() != start.getTimezoneOffset()) ? '1':'0';
}

Date.prototype.getAmPm = function () {
    return (this.getHours() < 12)? 'am':'pm';
}
