TheCarbonAccount = window.TheCarbonAccount || {};
TheCarbonAccount.months = [
    'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August',
    'September', 'October', 'November', 'December'
];
TheCarbonAccount.enhanceDateInput = function(
        input_or_id, optional_title, prepopulate_from_id) {
        
    $D = YAHOO.util.Dom;
    $E = YAHOO.util.Event;
    var input = $D.get(input_or_id);
    if (!input || input.TheCarbonAccount_dateEnhanced) {
        return; // Only enhance it once
    }
    input.TheCarbonAccount_dateEnhanced = 1;
    
    var icon = document.createElement('img');
    icon.src = '/static/img/icon_calendar.gif';
    icon.alt = 'Calendar';
    icon.className = 'calendarIcon';
    // Add it after the input node
    if (input.nextSibling) {
        input.parentNode.insertBefore(icon, input.nextSibling);
        input.parentNode.insertBefore(document.createTextNode(' '), icon);
    } else {
        input.parentNode.appendChild(document.createTextNode(' '));
        input.parentNode.appendChild(icon);        
    }
    // Unique ID each time, to avoid possible collisions
    var calid = 'caldiv_' + input.id + '_' + parseInt(
        Math.random() * Math.pow(10, 20)
    );
    
    // Create it if it doesn't already exist
    var calDiv = document.createElement('div');
    calDiv.id = calid;
    calDiv.style.display = 'none';
    calDiv.style.position = 'absolute';
    calDiv.style.zIndex = 40;
    document.body.appendChild(calDiv);
    
    // Register it so we can make others invisible when one is displayed
    TheCarbonAccount.calDivs = TheCarbonAccount.calDivs || [];
    TheCarbonAccount.calDivs[TheCarbonAccount.calDivs.length] = calDiv;

    var calOptions = {'close': true};
    if (optional_title) {
        calOptions['title'] = optional_title;
    }

    var cal = new YAHOO.widget.Calendar('cal_' + calid, calid, calOptions);
    cal.selectEvent.subscribe(function(type, args, obj) {
        var dates = args[0]; 
        var date = dates[0];
        var year = date[0], month = date[1], day = date[2];
        input.value = day + ' ' + TheCarbonAccount.months[month - 1] + " " + year;

        // Close calDiv
        var anim = new YAHOO.util.Anim(calDiv, { opacity: {to: 0}}, 0.3);
        anim.onComplete.subscribe(function() {
            cal.hide();
        })
        anim.animate();
    });
    cal.render();
    
    $E.on(icon, 'click', function() {
        /* Make all others invisible */
        for (var i = 0, other; other = TheCarbonAccount.calDivs[i]; i++) {
            if (other != calDiv) {
                other.style.display = 'none';
            }
        }
        /* If prepopulate_from set, prepopulate from that field */
        if (prepopulate_from_id) {
            try {
                var d = TheCarbonAccount.dates.parseDate(
                    $D.get(prepopulate_from_id).value
                );
                cal.setMonth(d.getMonth());
                cal.setYear(d.getFullYear());
                cal.render();
            } catch (e) { /* Supress */ } 
        }
        calDiv.style.display = 'block';
        //YAHOO.util.Dom.setStyle(calDiv, 'opacity', 1.0);
        var pos = YAHOO.util.Dom.getXY(icon);
        YAHOO.util.Dom.setXY(calDiv, [pos[0] + 20, pos[1]]);
    });
    
    $E.on(input, 'blur', function() {
        TheCarbonAccount.dates.magicDate(input);
    });
    
    var HELP = "'today', '1st Jan' or similar";
    function onFocus() {
        if (this.value == HELP) {
            this.value = '';
        }
        this.style.color = '#000000';
    }
    $E.on(input, 'focus', onFocus);
    
    function onBlur() {
        if (this.value == '') {
            this.style.color = '#aaaaaa';
            this.value = HELP;
        }
    }
    $E.on(input, 'blur', onBlur);
    onFocus.call(input); onBlur.call(input); // Call once when page loads
    
    // Finally, hook up an onSubmit event to the parent form
    var parentForm = input;
    while (parentForm && parentForm != document.body) {
        parentForm = parentForm.parentNode;
        if (parentForm.nodeName.toLowerCase() == 'form') {
            break;
        }
    }
    if (parentForm.nodeName.toLowerCase() == 'form') {
        $E.on(parentForm, 'submit', function() {
            onFocus.call(input);
        });
    }
}
TheCarbonAccount.hideAllCalendars = function() {
    if (TheCarbonAccount.calDivs) {
        for (var i = 0, cal; cal = TheCarbonAccount.calDivs[i]; i++) {
            cal.style.display = 'none';
        }
    }
}
TheCarbonAccount.dates = (function() {
    /* 'Magic' date parsing, by Simon Willison (6th October 2003)
     http://simon.incutio.com/archive/2003/10/06/betterDateInput
     Adapted for 6newslawrence.com, 28th January 2004
    */

    /* Finds the index of the first occurence of item in the array, or -1 if not found */
    function arrayIndexOf(ar, item) {
        for (var i = 0; i < ar.length; i++) {
            if (ar[i] == item) {
                return i;
            }
        }
        return -1;
    }
    /* Returns an array of items judged 'true' by the passed in test function */
    function arrayFilter(ar, test) {
        var matches = [];
        for (var i = 0; i < ar.length; i++) {
            if (test(ar[i])) {
                matches[matches.length] = ar[i];
            }
        }
        return matches;
    };

    var monthNames = "January February March April May June July August September October November December".split(" ");
    var weekdayNames = "Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" ");

    /* Takes a string, returns the index of the month matching that string, throws
         an error if 0 or more than 1 matches
    */
    function parseMonth(month) {
        var matches = arrayFilter(monthNames, function(item) { 
            return new RegExp("^" + month, "i").test(item);
        });
        if (matches.length == 0) {
            throw new Error("Invalid month string");
        }
        if (matches.length > 1) {
            throw new Error("Ambiguous month");
        }
        return arrayIndexOf(monthNames, matches[0]);
    }
    /* Same as parseMonth but for days of the week */
    function parseWeekday(weekday) {
        var matches = arrayFilter(weekdayNames, function(item) {
            return new RegExp("^" + weekday, "i").test(item);
        });
        if (matches.length == 0) {
            throw new Error("Invalid day string");
        }
        if (matches.length > 1) {
            throw new Error("Ambiguous weekday");
        }
        return arrayIndexOf(weekdayNames, matches[0]);
    }

    /* Array of objects, each has 're', a regular expression and 'handler', a 
         function for creating a date from something that matches the regular 
         expression. Handlers may throw errors if string is unparseable. 
    */
    var dateParsePatterns = [
        // Today
        {     re: /^tod/i,
            handler: function() { 
                return new Date();
            } 
        },
        // Tomorrow
        {     re: /^tom/i,
            handler: function() {
                var d = new Date(); 
                d.setDate(d.getDate() + 1); 
                return d;
            }
        },
        // Yesterday
        {     re: /^yes/i,
            handler: function() {
                var d = new Date();
                d.setDate(d.getDate() - 1);
                return d;
            }
        },
        // 1 4 4 (= 1st April 2004)
        {     re: /^\s*(\d{1,2})\s+(\d{1,2})\s+(\d{1,4})\s*$/i,
            handler: function(bits) {
                var d = new Date();
                d.setDate(1);
                var year = parseInt(bits[3], 10);
                if (year < 2000) {
                    year += 2000;
                }
                d.setYear(year);
                d.setMonth(parseInt(bits[2], 10) - 1);
                d.setDate(parseInt(bits[1], 10));
                return d;
            }
        },
        // 4th
        {     re: /^(\d{1,2})(st|nd|rd|th)?$/i, 
            handler: function(bits) {
                var d = new Date();
                d.setDate(parseInt(bits[1], 10));
                return d;
            }
        },
        // 4th Jan
        {     re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+)$/i, 
            handler: function(bits) {
                var d = new Date();
                d.setDate(1);
                d.setMonth(parseMonth(bits[2]));
                d.setDate(parseInt(bits[1], 10));
                return d;
            }
        },
        // 4th Jan 2003
        {     re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+),? (\d{4})$/i,
            handler: function(bits) {
                var d = new Date();
                d.setDate(1);
                d.setYear(bits[3]);
                d.setMonth(parseMonth(bits[2]));
                d.setDate(parseInt(bits[1], 10));
                return d;
            }
        },
        // Jan 4th
        {     re: /^(\w+) (\d{1,2})(?:st|nd|rd|th)?$/i, 
            handler: function(bits) {
                var d = new Date();
                d.setDate(1);
                d.setMonth(parseMonth(bits[1]));
                d.setDate(parseInt(bits[2], 10));
                return d;
            }
        },
        // Jan 4th 2003
        {     re: /^(\w+) (\d{1,2})(?:st|nd|rd|th)?,? (\d{4})$/i,
            handler: function(bits) {
                var d = new Date();
                d.setDate(1);
                d.setYear(bits[3]);
                d.setMonth(parseMonth(bits[1]));
                d.setDate(parseInt(bits[2], 10));
                return d;
            }
        },
        // next Tuesday - this is suspect due to weird meaning of "next"
        {     re: /^next (\w+)$/i,
            handler: function(bits) {
                var d = new Date();
                var day = d.getDay();
                var newDay = parseWeekday(bits[1]);
                var addDays = newDay - day;
                if (newDay <= day) {
                    addDays += 7;
                }
                d.setDate(d.getDate() + addDays);
                return d;
            }
        },
        // last Tuesday
        {     re: /^last (\w+)$/i,
            handler: function(bits) {
                throw new Error("Not yet implemented");
            }
        },
        // mm/dd/yyyy (American style)
        {     re: /(\d{1,2})\/(\d{1,2})\/(\d{4})/,
            handler: function(bits) {
                var d = new Date();
                d.setDate(1);
                d.setYear(bits[3]);
                d.setMonth(parseInt(bits[1], 10) - 1); // Because months indexed from 0
                d.setDate(parseInt(bits[2], 10));
                return d;
            }
        },
        // yyyy-mm-dd (ISO style)
        {     re: /(\d{4})-(\d{1,2})-(\d{1,2})/,
            handler: function(bits) {
                var d = new Date();
                d.setDate(1);
                d.setYear(parseInt(bits[1]));
                d.setMonth(parseInt(bits[2], 10) - 1);
                d.setDate(parseInt(bits[3], 10));
                return d;
            }
        },
    ];

    function parseDateString(s) {
        for (var i = 0; i < dateParsePatterns.length; i++) {
            var re = dateParsePatterns[i].re;
            var handler = dateParsePatterns[i].handler;
            var bits = re.exec(s);
            if (bits) {
                return handler(bits);
            }
        }
        throw new Error("Invalid date string");
    }

    function fmt00(x) {
        // fmt00: Tags leading zero onto numbers 0 - 9.
        // Particularly useful for displaying results from Date methods.
        //
        if (Math.abs(parseInt(x)) < 10){
            x = "0"+ Math.abs(x);
        }
        return x;
    }

    function parseDateStringISO(s) {
        try {
            var d = parseDateString(s);
            return d.getFullYear() + '-' + (fmt00(d.getMonth() + 1)) + '-' + fmt00(d.getDate())
        }
        catch (e) { return s; }
    }
    function magicDateFunction(input) {
        var messagespan = input.id + 'Msg';
        try {
            var d = parseDateString(input.value);
            input.value = d.getDate() + ' ' + monthNames[d.getMonth()] + ' ' + d.getFullYear();
            input.className = '';
            // Human readable date
            if (document.getElementById(messagespan)) {
                document.getElementById(messagespan).firstChild.nodeValue = d.toDateString();
                document.getElementById(messagespan).className = 'normal';
            }
        }
        catch (e) {
            input.className = 'error';
            var message = e.message;
            // Fix for IE6 bug
            if (message.indexOf('is null or not an object') > -1) {
                message = 'Invalid date string';
            }
            if (document.getElementById(messagespan)) {
                document.getElementById(messagespan).firstChild.nodeValue = message;
                document.getElementById(messagespan).className = 'error';
            }
        }
    }
    return {
        'magicDate': magicDateFunction,
        'parseDate': parseDateString
    }
})();
