﻿function Calendar(d, m, y)
{
    var _container, _monthBar, _yearBar, _monthBackCtrl, _monthForwardCtrl, _yearBackCtrl, _yearForwardCtrl;
    var _onclickHandler, _dayIsActiveChecker;
    this.bindContainer = function (c) { _container = c; _container && (_container.clearChildren = function () { while (0 != this.childNodes.length) { this.removeChild(this.firstChild); } }); }
    this.bindMonthBar = function (b) { _monthBar = b; }
    this.bindYearBar = function (b) { _yearBar = b; }
    this.bindMonthForward = function (mf) { _monthForwardCtrl = mf; }
    this.bindMonthBack = function (mb) { _monthBackCtrl = mb; }
    this.bindYearForward = function (yf) { _yearForwardCtrl = yf; }
    this.bindYearBack = function (yb) { _yearBackCtrl = yb; }
    this.bindHandler = function (h) { _onclickHandler = h; }
    this.bindChecker = function (c) { _dayIsActiveChecker = c; }
    this.setMonthNames = function (mn) {
        if ((mn instanceof Array || !!mn.length) && (12 == mn.length || 13 == mn.length)) {
            if (12 == mn.length) {
                mn.unshift('');
            }

            /* to prevent external changing */
            for (var i = 0; i < mn.length; ++i) {
                _monthNames[i] = mn[i].toString();
            }
        }
    }
    this.setStartDate = function (d, m, y) {
        /* not used yet */
        try {
            d = parseInt(d);
            m = parseInt(m);
            y = parseInt(y);

            if (d > 0 && d < 32 && m > 0 && m < 13 && y > 0) {
                _startDay = d;
                _startMonth = m;
                _startYear = y;
            }
        } catch (e) {}
    }
    this.setEndDate = function (d, m, y) {
        /* not used yet */
        try {
            d = parseInt(d);
            m = parseInt(m);
            y = parseInt(y);

            if (d > 0 && d < 32 && m > 0 && m < 13 && y > 0) {
                _endDay = d;
                _endMonth = m;
                _endYear = y;
            }
        } catch (e) {}
    }
    this.setStartFromToday = function (daysFromToday) {
        try {
            var d = parseInt(daysFromToday);
            _startDay = _todayDay + d;
            _startMonth = _todayMonth;
            _startYear = _todayYear;
            _refresh();
        } catch (e) {}
    }
    this.setActiveDaysCount = function (count) {
        try {
            _rangeLength = parseInt(count);
            _refresh();
        } catch (e) {}
    }

    /* engine */
    var _format = '';
    var _highlight = false;
    var _todayDay, _todayMonth, _todayYear, _day, _month, _year;
    var _startDay, _startMonth, _startYear, _endDay, _endMonth, _endYear, _rangeLength = 140;
    var _monthNames = [null, 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
    var _getMonthDayCount = function (m, y) {
        var md = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
        !!y || (y = _year);
        !!m || (m = _month);

        if (2 == m && (0 == y % 400 || 0 == y % 4 && 0 != y % 100)) {
            return 29;
        } else {
            return md[m - 1];
        }
    }
    var _getDifference = function(d, m, y, todayDay, todayMonth, todayYear) {
        if (!(d && m && y)) {
            return 0;
        }

        var today;
        var day = new Date(y, m - 1, d);

        if (todayDay && todayMonth && todayYear) {
            today = new Date(todayYear, todayMonth - 1, todayDay);
        } else {
            today = new Date(_todayYear, _todayMonth - 1, _todayDay);
        }

        return Math.floor((day.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
    }
    var _dayIsActive = function(d, m, y) {
        /* not used yet */
        var diff = _getDifference(d, m, y);
        var startDiff = false, endDiff = false;

        if (_startDay && _startMonth && _startYear) {
            startDiff = _getDifference(d, m, y, _startDay, _startMonth, _startYear);
        }

        if (_endDay && _endMonth && _endYear) {
            endDiff = _getDifference(d, m, y, _endDay, _endMonth, _endYear);
        }

        if (false !== startDiff && false !== endDiff) {
            return startDiff > 0 && endDiff < 0;
        } else {
            if (false !== startDiff) {
                return startDiff > 0;
            }

            if (false !== endDiff) {
                return endDiff < 0;
            }
        }

        return (diff > 0 && diff < 40 || diff > 190);
    }
    var _dayIsOutOfRange = function(d, m, y) {
        var diff = _getDifference(d, m, y, _startDay, _startMonth, _startYear);
        
        return diff < 0 || diff > _rangeLength;
        
    }

    this.init = function () {
        var date = new Date();
        _todayDay = _day = d || date.getDate();
        _todayMonth = _month = m || date.getMonth() + 1;
        _todayYear = _year = y || date.getFullYear();
        _startDay = _todayDay + 40;
        _startMonth = _todayMonth;
        _startYear = _todayYear;
        _monthBackCtrl && (_monthBackCtrl.onclick = _monthBack);
        _monthForwardCtrl && (_monthForwardCtrl.onclick = _monthForward);
        _yearBackCtrl && (_yearBackCtrl.onclick = _yearBack);
        _yearForwardCtrl && (_yearForwardCtrl.onclick = _yearForward);
        _refresh();
    }

    var _refresh = function () {
        if (null == _container) {
            return false;
        }

        _container.clearChildren();
        var domDays = _getDomDays();
        
        while (domDays.childNodes.length > 0) {
            _container.appendChild(domDays.firstChild);
        }

        if (_monthBar) {
            while (_monthBar.childNodes.length > 0) {
                _monthBar.removeChild(_monthBar.firstChild);
            }
            
            _monthBar.appendChild(document.createTextNode(_monthNames[_month]));
        }
        
        if (_yearBar) {
            while (_yearBar.childNodes.length > 0) {
                _yearBar.removeChild(_yearBar.firstChild);
            }
            
            _yearBar.appendChild(document.createTextNode(_year));
        }
    }
    
    var _getDomDays = function () {
        var calendarDiv = document.createElement('div');
        
        var date = new Date(_year, _month - 1, 1);
        var dayOfWeek = 0 == date.getDay() ? 7 : date.getDay();

        if (1 != date.getDay()) {
            var prevMonthDayCount = _getMonthDayCount(1 == _month ? 12 : _month - 1, 1 == _month ? _year - 1 : _year);

            for (var k = 1; k < dayOfWeek; ++k) {
                var p = document.createElement('p');
                _defaultOtherClass && (p.className = _defaultOtherClass);
                calendarDiv.appendChild(p);
            }
        }

        for (var day = 1; day <= _getMonthDayCount(_month, _year); ++day) {
            var p = document.createElement('p');
            var em;
            _defaultCurrentClass && (p.className = _defaultCurrentClass);
            (7 == dayOfWeek || 6 == dayOfWeek) && _weekendClass && (p.className = _weekendClass + ' ' + p.className);
            
            if (_todayDay == day && _todayMonth == _month && (new Date()).getFullYear() == _year) {
                p.className = 'selected';
                em = document.createElement('em');
                em.className = 'selected';
                p.appendChild(em)
            }
            
            if (_highlight) {
                var diff = _getDifference(day, _month, _year);

                if (diff < 0) {
                    p.className = 'fc_grey';
                    p.appendChild(document.createTextNode('' + day));
                } else if (0 == diff) {
                    p.className += ' fc_orange';
                    p.appendChild(document.createTextNode('' + day));
                } else if (_dayIsOutOfRange(day, _month, _year)) {
                    em = document.createElement('em');
                    em.appendChild(document.createTextNode('' + day));
                    p.appendChild(em);
                } else {
                    p.appendChild(document.createTextNode('' + day));
                    p.onclick = (function (d) { return function () { _onclickHandler && _onclickHandler(d, _month, _year); } })(day);
                }
            } else {
                _activeClass && _dayIsActiveChecker && _dayIsActiveChecker(day, _month, _year) && (p.className = _activeClass + ' ' + p.className);
                p.onclick = (function (d) { return function () { _onclickHandler && _onclickHandler(d, _month, _year); } })(day);
                p.appendChild(document.createTextNode('' + day));
            }

            calendarDiv.appendChild(p);

            dayOfWeek = 7 == dayOfWeek ? 1 : dayOfWeek + 1;
        }

        if (1 != dayOfWeek) {
            for (var k = dayOfWeek, m = 1; k <= 7; ++k, ++m) {
                var p = document.createElement('p');
                _defaultOtherClass && (p.className = _defaultOtherClass);
                calendarDiv.appendChild(p);
            }
        }

        return calendarDiv;
    }

    /* view */
    var _defaultCurrentClass, _defaultOtherClass, _currentDayClass, _weekendClass, _activeClass;
    this.setDefaultCurrentClass = function (c) { _defaultCurrentClass = c; }
    this.setDefaultOtherClass = function (c) { _defaultOtherClass = c; }
    this.setCurrentDayClass = function (c) { _currentDayClass = c; }
    this.setWeekendClass = function (c) { _weekendClass = c; }
    this.setActiveDayClass = function (c) { _activeClass = c; }
    
    this.highlight = function (h) { _highlight = !!h; }

    /* controlling */
    var _monthBack = function () { if (1 == _month) { _month = 12; --_year; } else { --_month }; _refresh(); }
    var _monthForward = function () { if (12 == _month) { _month = 1; ++_year; } else { ++_month; }; _refresh(); }
    var _yearBack = function () { --_year; _refresh(); }
    var _yearForward = function () { ++_year; _refresh(); }
}


function bindCalendarToElement(elementId, inputId, date, positionData, onclickHandler, highlight)
{
    var element = document.getElementById(elementId);
    var input = document.getElementById(inputId);

    if (!window.Calendar || !(date instanceof Date) || !element || !input) {
        return false;
    }

    var id = parseInt(Math.random() * 1000) + (new Date()).getMilliseconds();
    var calendar, nav, dates, yearBar, monthBar, yearPrevBtn, yearNextBtn, monthPrevBtn, monthNextBtn, ul_1, ul_2, clearDiv;
    calendar = document.createElement('div');
    calendar.className = 'calendar';
    nav = document.createElement('div');
    nav.className = 'nav';
    dates = document.createElement('div');
    ul_1 = document.createElement('ul');
    ul_2 = document.createElement('ul');
    yearPrevBtn = document.createElement('li');
    yearPrevBtn.className = 'prev';
    yearPrevBtn.setAttribute('title', 'Previous year');
    yearPrevBtn.appendChild(document.createComment('*'));
    yearNextBtn = document.createElement('li');
    yearNextBtn.className = 'next';
    yearNextBtn.setAttribute('title', 'Next year');
    yearNextBtn.appendChild(document.createComment('*'));
    yearBar = document.createElement('li');
    yearBar.appendChild(document.createComment('*'));
    ul_1.appendChild(yearPrevBtn);
    ul_1.appendChild(yearBar);
    ul_1.appendChild(yearNextBtn);
    monthPrevBtn = document.createElement('li');
    monthPrevBtn.className = 'prev';
    monthPrevBtn.setAttribute('title', 'Previous month');
    monthPrevBtn.appendChild(document.createComment('*'));
    monthNextBtn = document.createElement('li');
    monthNextBtn.className = 'next';
    monthNextBtn.setAttribute('title', 'Next month');
    monthNextBtn.appendChild(document.createComment('*'));
    monthBar = document.createElement('li');
    monthBar.appendChild(document.createComment('*'));
    ul_2.appendChild(monthPrevBtn);
    ul_2.appendChild(monthBar);
    ul_2.appendChild(monthNextBtn);
    clearDiv = document.createElement('div');
    clearDiv.className = 'clear';
    nav.appendChild(ul_1);
    nav.appendChild(clearDiv);
    nav.appendChild(ul_2);

    dates = document.createElement('div');
    dates.className = 'dates';
    calendar.appendChild(nav);
    calendar.appendChild(dates);

    document.body.appendChild(calendar);

    calendar.style.display = 'none';
    var elementOnclick = function (ev) {
        var event = ev || window.event;
        var target = event.target || window.event.srcElement;

        if (!calendar) {
            return false;
        }

        if ('' === calendar.style.display || 'block' === calendar.style.display) {
            calendar.style.display = 'none';
        } else {
            if (window.calendarCollection) {
                for (var i in window.calendarCollection) {
                    window.calendarCollection[i].style.display = 'none';
                }
            }

            if (!!window.getCoords) {
                var coords = getCoords(element);
                calendar.style.position = 'absolute';
                
                if (positionData && positionData.x && positionData.y) {
                    calendar.style.left = positionData.x.toString() + 'px';
                    calendar.style.top = positionData.y.toString() + 'px';
                } else {
                    calendar.style.left = (coords.x + 25) + 'px';
                    calendar.style.top = (coords.y  - 30) + 'px';
                }
            }

            calendar.style.display = 'block';
        }
    }

    var focusOnInput = (function (inp) {
        return function () {
            inp.focus();
        }
    })(input);
    
    if (input.addEventListener) {
        yearPrevBtn.addEventListener('click', focusOnInput, false);
        yearNextBtn.addEventListener('click', focusOnInput, false);
        monthPrevBtn.addEventListener('click', focusOnInput, false);
        monthNextBtn.addEventListener('click', focusOnInput, false);
    } else if (input.attachEvent) {
        yearPrevBtn.attachEvent('onclick', focusOnInput);
        yearNextBtn.attachEvent('onclick', focusOnInput);
        monthPrevBtn.attachEvent('onclick', focusOnInput);
        monthNextBtn.attachEvent('onclick', focusOnInput);
    }
    
    if (element.addEventListener) {
        element.addEventListener('click', elementOnclick, false);
    } else if (element.attachEvent) {
        element.attachEvent('onclick', elementOnclick);
    }
    
    element.closeCalendar = (function (clndr) {
        return function () {
            clndr.style.display = 'none';
        }
    })(calendar);

    var _onclickHandler = (function (inp) {
        return function (d, m, y) {
            if (!window.localDateFormat) {
                window.localDateFormat = '%m/%d/%Y';
            }

            if (d<10) d = '0' + d;
            if (m<10) m = '0' + m;
            calendar.style.display = 'none';
            input.value = window.localDateFormat.replace('%d', d).replace('%m', m).replace('%Y', y);
            onclickHandler && onclickHandler();
            inp.focus();
        }
    })(input);

    var c = new Calendar(date.getDate(), date.getMonth() + 1, date.getFullYear());
    c.bindContainer(dates);
    c.bindMonthBar(monthBar);
    c.bindYearBar(yearBar);
    c.bindMonthForward(monthNextBtn);
    c.bindMonthBack(monthPrevBtn);
    c.bindYearForward(yearNextBtn);
    c.bindYearBack(yearPrevBtn);
    c.bindHandler(_onclickHandler);
    c.highlight(highlight);
    c.init();

    if (!window.calendarCollection) {
        window.calendarCollection = new Array();
    }

    window.calendarCollection.push(calendar);
    return new (function (cObject, cView) {
        this.closeCalendar = function () {
            cView.style.display = 'none';
        }
        this.setStartFromToday = function (fromToday) {
            cObject.setStartFromToday(fromToday);
        }
        this.setActiveDaysCount = function(count) {
            cObject.setActiveDaysCount(count);
        }
    })(c, calendar);
}
