/**
 * @author PeteAllison
 * Based heavily on the MooCalendar by Aeron Glemann, but designed for an
 * inline calendar within a webpage and allows a user to select multiple dates.
 */
// <![CDATA[

/**
 */
var ec_Calendar = new Class({
    Implements: [Events, Options],

    options: {
        classes: [],
        data: [],
        calendarID: 'eventCalendar',
        direction: 0, // -1 past, 0 past + future, 1 future
        navigation: 1, // 0 = no nav, 1 = month, 2 = month and year
        months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
        days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], // days of the week starting at sunday
        onSelect: Class.empty,
        fieldName: 'event_dates'
    },

    initialize: function(obj, options){
        if (!obj)
            return false;

        this.setOptions(options);

        /**
         * If the target object contains a hidden input field, then take data from here
         */
        if (data = obj.getElement('input[type=hidden]')) {
            if (data.get('value') != '')
                $extend(this.options.data, data.get('value').clean().split(' '));
        }

        this.activeDates = [];
        if (this.options.data.length > 0)
            this.options.data.each(function(item){
                // Convert the date back
                var date = this.unformat(item, 'j/n/Y');
                // Month index begins at zero for purpose of the date object
                date[1]++;
                this.activeDates.push(date[0] + (date[1] < 10 ? '0' : '') + date[1] + (date[2] < 10 ? '0' : '') + date[2]);
            }, this);


        /**
         * Allow the user to override the default classes
         */
        var keys = ['calendar', 'calendarinner', 'datelist', 'prev', 'next', 'month', 'year', 'today', 'invalid', 'valid', 'inactive', 'active', 'hover', 'hilite'];

        var values = keys.map(function(key, i){
            if (this.options.classes[i]) {
                if (this.options.classes[i].length) {
                    key = this.options.classes[i];
                }
            }
            return key;
        }, this);

        /**
         * Assign classes to the this.classes variable
         */
        // Ensure that we have a 'ec_calendar' class in addition to others
        if (values[0] != 'ec_calendar')
            values[0] += ' ' + 'ec_calendar';
        this.classes = values.associate(keys);

        this.calendar = obj;

        var d = new Date(); // today
        d.setDate(d.getDate() + this.options.direction.toInt()); // Directional offset
        this.calendar.addClass(this.classes.calendar);

        this.cal = {
            month: d.getMonth(),
            year: d.getFullYear(),
            els: [],
            active: this.activeDates
        }

        $extend(this.cal, this.bounds());
        $extend(this.cal, this.values());

        this.display();
    },


    /**
     * Outputs the month being currently viewed to the screen
     */
    display: function(){
        var cal = this.cal;

        this.calendar.empty();
        this.calendar.set('class', this.classes.calendar + ' ' + this.options.months[cal.month].toLowerCase());

        var div = new Element('div').addClass(this.classes.datelist).injectInside(this.calendar);
        this.dateList = new Element('textarea', {
            name: this.options.fieldName
        }).addEvent('focus', function(){
            this.blur();
        }).injectInside(div);

        var div = new Element('div').addClass(this.classes.calendarinner).injectInside(this.calendar);
        var table = new Element('table').injectInside(div).adopt(this.caption());

        var thead = new Element('thead').injectInside(table);
        var tr = new Element('tr').injectInside(thead);

        for (var i = 0; i <= 6; i++) {
            var th = this.options.days[(i) % 7];
            tr.adopt(new Element('th', {
                'title': th
            }).appendText(th.substr(0, 1)));
        }

        var tbody = new Element('tbody').injectInside(table);
        var tr = new Element('tr').injectInside(tbody);

        var d = new Date(cal.year, cal.month, 1);
        var offset = ((d.getDay() + 7)) % 7;
        var last = new Date(cal.year, cal.month + 1, 0).getDate(); // last day of this month
        var prev = new Date(cal.year, cal.month, 0).getDate(); // last day of previous month
        var d = new Date();
        var today = new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();

        var active = cal.active;
        var valid = cal.days;
        var inactive = [];
        var hilited = [];

        for (var i = 1; i < 43; i++) {
            if ((i - 1) % 7 == 0)
                tr = new Element('tr').injectInside(tbody);

            var td = new Element('td').injectInside(tr);

            var day = i - offset;
            var date = new Date(cal.year, cal.month, day);
            var dateSerial = this.format(date, 'Ymd');

            var cls = ''; // Holds classes to apply to day cell
            // Here goes the bit that defines what items are highlighted!
            if (active.contains(dateSerial)) {
                if (valid.contains(day)) {
                    cls = this.classes.active;
                }
            } else {
                if (inactive.contains(day)) {
                    cls = this.classes.inactive;
                } else if (valid.contains(day)) {
                    cls = this.classes.valid;
                } else if (day >= 1 && day <= last) {
                    cls = this.classes.invalid;
                }

            }
            if (date.getTime() == today) {
                cls += ' ' + this.classes.today;
            }
            if (hilited.contains(day)) {
                cls += ' ' + this.classes.hilite;
            }
            td.addClass(cls);

            if (valid.contains(day)) {
                td.setProperty('title', this.format(date, 'D M jS Y'));

                td.addEvents({
                    'click': function(td, day){
                        this.clicked(td, day);
                    }.pass([td, day], this),
                    'mouseover': function(td, cls){
                        td.addClass(cls);
                    }.pass([td, this.classes.hover]),
                    'mouseout': function(td, cls){
                        td.removeClass(cls);
                    }.pass([td, this.classes.hover])
                });
            }

            if (day < 1) {
                day = prev + day;
            } else {
                if (day > last) {
                    day = day - last;
                }
            }
            td.appendText(day);
        }
        // Update the date list
        this.write();
    },


    /**
     * Generates the caption that contains date navigation plus the month & year
     */
    caption: function(){
        var cal = this.cal;

        var navigation = {
            prev: {
                'month': true,
                'year': true
            },
            next: {
                'month': true,
                'year': true
            }
        };

        if (cal.year == cal.start.getFullYear()) {
            navigation.prev.year = false;
            if (cal.month == cal.start.getMonth() && this.options.navigation == 1) {
                navigation.prev.month = false;
            }
        }
        if (cal.year == cal.end.getFullYear()) {
            navigation.next.year = false;
            if (cal.month == cal.end.getMonth() && this.options.navigation == 1) {
                navigation.next.month = false;
            }
        }

        var caption = new Element('caption');

        var prev = new Element('a').addClass(this.classes.prev).appendText('\x3c');
        var next = new Element('a').addClass(this.classes.next).appendText('\x3e');
        if (this.options.navigation == 2) {
            var month = new Element('span').addClass(this.classes.month).injectInside(caption);

            if (navigation.prev.month) {
                prev.clone().addEvent('click', function(){
                    this.navigate('m', -1);
                }.bind(this)).injectInside(month);
            }

            month.adopt(new Element('span').appendText(this.options.months[cal.month]));

            if (navigation.next.month) {
                next.clone().addEvent('click', function(){
                    this.navigate('m', 1);
                }.bind(this)).injectInside(month);
            }

            var year = new Element('span').addClass(this.classes.year).injectInside(caption);

            if (navigation.prev.year) {
                prev.clone().addEvent('click', function(){
                    this.navigate('y', -1);
                }.bind(this)).injectInside(year);
            }

            year.adopt(new Element('span').appendText(cal.year));

            if (navigation.next.year) {
                next.clone().addEvent('click', function(){
                    this.navigate('y', 1);
                }.bind(this)).injectInside(year);
            }
        } else {
            if (navigation.prev.month && this.options.navigation) {
                prev.clone().addEvent('click', function(){
                    this.navigate('m', -1);
                }.bind(this)).injectInside(caption);
            }

            caption.adopt(new Element('span').addClass(this.classes.month).appendText(this.options.months[cal.month]));
            caption.adopt(new Element('span').addClass(this.classes.year).appendText(cal.year));

            if (navigation.next.month && this.options.navigation) {
                next.clone().addEvent('click', function(){
                    this.navigate('m', 1);
                }.bind(this)).injectInside(caption);
            }
        }

        return caption;
    },


    /**
     * Works out the earliest and latest dates allowed
     */
    bounds: function(){
        var cal = this.cal;
        var start = new Date(1000, 0, 0);
        var end = new Date(2999, 11, 31);
        var date = new Date().getDate() + this.options.direction.toInt();

        if (this.options.direction > 0) {
            start = new Date();
            start.setDate(date);
        }
        if (this.options.direction < 0) {
            end = new Date();
            end.setDate(date);
        }

        return {
            'start': start,
            'end': end
        };
    },

    /**
     * Works the state of each date in the current month. Can be blocked, selected
     * etc
     */
    values: function(){
        var cal = this.cal;
        var years, months, days;

        var first = 1;
        var last = new Date(cal.year, cal.month + 1, 0).getDate();

        if (cal.year == cal.start.getFullYear()) {
            if (months == null && this.options.navigation == 2) {
                months = [];
                for (var i = 0; i < 12; i++) {
                    months.push(i);
                }
            }

            if (cal.month == cal.start.getMonth()) {
                first = cal.start.getDate();
            }
        }
        if (cal.year == cal.end.getFullYear()) {
            if (months == null && this.options.navigation == 2) {
                months = [];

                for (var i = 0; i < 12; i++) {
                    if (i <= cal.end.getMonth()) {
                        months.push(i);
                    }
                }
            }
            if (cal.month == cal.end.getMonth()) {
                last = cal.end.getDate();
            }
        }

        var blocked = [];

        if ($type(days) == 'array') {
            days = days.filter(function(day){
                if (day >= first && day <= last && !blocked.contains(day)) {
                    return day;
                }
            });
        } else {
            days = [];
            for (var i = first; i <= last; i++) {
                if (!blocked.contains(i)) {
                    days.push(i);
                }
            }
        }

        days.sort(this.sort);
        return {
            'days': days,
            'months': months,
            'years': years
        };
    },


    clicked: function(td, day){
        var cal = this.cal;
        // Create string version of the date - YYYYMMDD
        var dateSerial = this.format(new Date(cal.year, cal.month, day), 'Ymd');

        if (cal.active.contains(dateSerial)) {
            // Currently selected
            cal.active.erase(dateSerial);
            td.removeClass(this.classes.active);
            td.addClass(this.classes.valid);
        } else {
            // Not selected
            cal.active.push(dateSerial);
            td.removeClass(this.classes.valid);
            td.addClass(this.classes.active);
        }

        // Write to the screen
        this.write();

        this.fireEvent('onSelect', cal.el)
    },

    /**
     * Outputs all active dates to a textbox
     */
    write: function(){
        var temp = [];
        this.cal.active.sort();
        // Convert all date serials to real dates
        this.cal.active.each(function(dateSerial){
            var date = new Date(dateSerial.substr(0, 4), dateSerial.substr(4, 2) - 1, dateSerial.substr(6, 2));
            temp.push(this.format(date, 'j/n/Y'));
        }, this);
        this.dateList.set('value', temp.join('\n'));
    },

    /**
     * Provides a mechanism for navigating through months and years
     * @param {String} type - 'm' for month or 'y' for year
     * @param {Integer} n - the number to traverse, negative to go back
     */
    navigate: function(type, n){
        var cal = this.cal;
        switch (type) {
            case 'm':
                if ($type(cal.months) == 'array') {
                    var i = cal.months.indexOf(cal.month) + n;
                    if (i < 0 || i == cal.months.length) {
                        if (this.options.navigation == 1) {
                            this.navigate('y', n);
                        }
                        i = (i < 0) ? cal.months.length - 1 : 0;
                    }
                    cal.month = cal.months[i];
                } else {
                    var i = cal.month + n;
                    if (i < 0 || i == 12) {
                        if (this.options.navigation == 1) {
                            this.navigate('y', n);
                        }
                        i = (i < 0) ? 11 : 0;
                    }
                    cal.month = i;
                }
                break;

            case 'y':
                if ($type(cal.years) == 'array') {
                    var i = cal.years.indexOf(cal.year) + n;
                    cal.year = cal.years[i];
                } else {
                    cal.year += n;
                }
                break;
        }

        $extend(cal, this.values(cal));

        if ($type(cal.months) == 'array') {
            var i = cal.months.indexOf(cal.month);
            if (i < 0) {
                cal.month = cal.months[0];
            }
        }

        this.display();
    },

    sort: function(a, b){
        return a - b;
    },

    /**
     * Formats a JavaScript date object using PHP numbering
     * @param {Object} date
     * @param {String} format
     */
    format: function(date, format){
        var str = '';
        if (date) {

            for (var i = 0, len = format.length; i < len; i++) {
                var j = date.getDate(); // Numeric day (1~31)
                var w = date.getDay(); // Day number (0~6)
                var l = this.options.days[w]; // Day name (Sunday ~ Saturday)
                var n = date.getMonth() + 1; // Month number (1~12)
                var f = this.options.months[n - 1]; // Month name (January ~ December)
                var y = date.getFullYear() + ''; // Full year (xxxx~yyyy)
                var cha = format.charAt(i);
                switch (cha) {
                    // Year
                    case 'y':
                        y = y.substr(2);
                    case 'Y':
                        str += y;
                        break;
                    // Month
                    case 'm':
                        if (n < 10) {
                            n = '0' + n;
                        }
                    case 'n':
                        str += n;
                        break;
                    case 'M':
                        f = f.substr(0, 3);
                    case 'F':
                        str += f;
                        break;
                    // Day
                    case 'd':
                        if (j < 10) {
                            j = '0' + j;
                        }
                    case 'j':
                        str += j;
                        break;
                    case 'D':
                        l = l.substr(0, 3);
                    case 'l':
                        str += l;
                        break;
                    case 'N':
                        w += 1;
                    case 'w':
                        str += w;
                        break;
                    case 'S':
                        if (j % 10 == 1 && j != '11') {
                            str += 'st';
                        } else {
                            if (j % 10 == 3 && j != '12') {
                                str += 'nd';
                            } else {
                                if (j % 10 == 3 && j != '13') {
                                    str += 'rd';
                                } else {
                                    str += 'th'
                                }
                            }
                        }
                        break;
                    default:
                        str += cha;
                }

            }
        }
        return str;
    },

    /**
     * Extracts the d, m and y elements from a passed string
     * @param {String} value
     * @param {String} format
     */
    unformat: function(value, format){
        format = format.escapeRegExp();

        var re = {
            d: '([0-9]{2})',
            j: '([0-9]{1,2})',
            D: '(' +
            this.options.days.map(function(day){
                return day.substr(0, 3);
            }).join('|') +
            ')',
            l: '(' + this.options.days.join('|') + ')',
            S: '(st|nd|rd|th)',
            F: '(' + this.options.months.join('|') + ')',
            m: '([0-9]{1,2})',
            M: '(' +
            this.options.months.map(function(month){
                return month.substr(0, 3);
            }).join('|') +
            ')',
            n: '([0-9]{1,2})',
            Y: '([0-9]{4})',
            y: '([0-9]{2})'
        }

        var arr = [];
        var g = '';

        for (var i = 0; i < format.length; i++) {
            var c = format.charAt(i);

            if (re[c]) {
                arr.push(c);
                g += re[c];
            } else {
                g += c;
            }
        }

        var matches = value.match('^' + g + '$');

        var dates = new Array(3);

        if (matches) {
            matches = matches.slice(1);
            arr.each(function(c, i){
                i = matches[i];

                switch (c) {
                    // Year
                    case 'y':
                        i = '19' + i;
                    case 'Y':
                        dates[0] = i.toInt();
                        break;
                    // Month
                    case 'F':
                        i = i.substr(0, 3);
                    case 'M':
                        i = this.options.months.map(function(month){
                            return month.substr(0, 3);
                        }).indexOf(i) +
                        1;
                    case 'm':
                    case 'n':
                        dates[1] = i.toInt() - 1;
                        break;
                    // Day
                    case 'd':
                    case 'j':
                        dates[2] = i.toInt();
                        break
                }
            }, this);
        }
        return dates;
    }
})
// ]]>
