var dropdown_nav = 
{
    className : 'dropdown_nav',
    inactiveClass : 'dropdown_nav_inactive',
    activeClass   : 'dropdown_nav_active',
    
    init : function(o)
    {
        o.className = o.className.replace(dropdown_nav.className, dropdown_nav.inactiveClass);
        observeEvent(o, 'click', function(event) 
            {
                toggleClassNames(o, dropdown_nav.activeClass, dropdown_nav.inactiveClass);
                cancelBubble(event);
            });
        observeEvent(window, 'click', function() 
            {
                replaceClassName(o, dropdown_nav.activeClass, dropdown_nav.inactiveClass);
            });        
    }
};
install(dropdown_nav);

var expandable_tree = 
{
    className    : 'expandable_tree',
    tagName      : 'ul',
    
    currentClass : 'current',
    openClass    : 'open',
    closedClass  : 'closed',
    buttonClass  : 'button',
    spacerClass  : 'spacer',
    
    init : function(o)
    {
        var children = getChildrenByTagName(o, 'li');
        for (var child in children)
        {
            var button = document.createElement('span');
            button.innerHTML = '\u00A0';
            button.className = this.spacerClass;
            var grandchildren = getChildrenByTagName(children[child], 'ul');
            if (grandchildren.length)
            {
                observeEvent(button, 'click', function(e)
                {
                    toggleClassNames(e.target.parentNode, expandable_tree.openClass, expandable_tree.closedClass);
                });
                button.className = this.buttonClass;
                children[child].className = children[child].className || this.closedClass;  // default to closed for children
                this.init(grandchildren[0]);
            }
            children[child].insertBefore(button, children[child].firstChild);                
        }    
    }
};
install(expandable_tree);


var DOUBLE_ARROW_LEFT   = 171;
var DOUBLE_ARROW_RIGHT  = 187;

var VK_BACKSPACE    = 8;
var VK_TAB          = 9;
var VK_ENTER        = 13;
var VK_SPACE        = 32;
var VK_LEFT         = 37;
var VK_UP           = 38;
var VK_RIGHT        = 39;
var VK_DOWN         = 40;
var VK_COMMA_1      = 44;
var VK_COMMA_2      = 188;
var VK_SEMICOLON    = 59;
var VK_A            = 65;
var VK_Z            = 90;
var VK_a            = 97;
var VK_z            = 122;
var VK_HYPHEN       = 45;

var date_utils = 
{
    Months : ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],

    dateToMonthYear : function(d)
    {
        return date_utils.Months[d.getMonth()] + " " + d.getFullYear();
    },

    dateToString : function(date)
    {
        return date.getDate() + ' ' + date_utils.dateToMonthYear(date);
    },

    getDaysOfMonth : function(date)
    {
        var d = new Date(date.getFullYear(), date.getMonth() + 1, 0);
        return d.getDate();
    },

    stringToDate : function(s)
    {
        var reDateM = /^([1-9]|0[1-9]|[12][0-9]|3[01])[- \\\/\.]?((?:J|F|M|A|May|Ju|Jul|Au|S|O|N|D)[a-z]*)[- \\\/\.]?((?:19|20)?\d\d)?$/i;
        var reDateN = /^(0[1-9]|[12][0-9]|3[01]|[1-9])[- \\\/\.]?([1-9]|0[1-9]|1[012])?[- \\\/\.]?((?:19|20)?\d\d)?$/i;
        var now = new Date();
        var month = null;
        var m = reDateM.exec(s); 
        if (m != null) 
        {
            var smonth = m[2].toLowerCase();
            for(var n= 0; n != date_utils.Months.length; ++n)
            {
                if (date_utils.Months[n].substr(0, smonth.length).toLowerCase() == smonth)
                {
                    month = n;
                    break;
                }
            }
            if (month == null)
            {
                throw "Please enter a valid date<sub>2</sub>";
            }
        }
        else
        { 
            m = reDateN.exec(s);
            if (m != null)
            {
                month = (m[2] == undefined || m[2] == 0) ? now.getMonth() : m[2] - 1;
            }
            else if (s.toLowerCase() == 'today')
            {   // what about next thursday, and expressions like that?
                return now;
            }
            else 
            {
                throw "Please enter a valid date<sub>0</sub>";
            }
        }
        var year  = m[3];
        if (year == undefined || year == null || year == '')
        {   // year was missing, assume this year or next if month has already passed
            year = month < now.getMonth() ? now.getFullYear() + 1: now.getFullYear();
        }

        year = parseInt(year);
        year = year < 50 ? 2000 + year : year < 100 ? 1900 + year :year;
        var day   = parseInt(m[1]);
        
        if (isNaN(year) || isNaN(day) || isNaN(month))
        {
            throw "Please enter a valid date<sub>1</sub>";
        }
        // we haven't done any boundary checking or leap year checking, because JScript's Date object does that for us.  
        // For example 31 jun becomes 1 july.  Whether that is correct or not is debatable....
        return new Date(year, month, day);
    }    
};

var date_picker = 
{
    tagName : 'input',
    className : 'date_picker',
    activeClass : 'date_picker_focus',
    
    init : function(input)
    {
        input.onfocus        = date_picker.onDateFocus;
        input.onkeypress     = date_picker.onDateKeyPress;
        input.onkeyup        = date_picker.onDateKeyUp;
        input.onkeydown      = date_picker.onDateKeyDown;
        input.original_value = input.value;
        input.onclick        =  function(event)
        {
            cancelBubble(event);
        };
        observeEvent(document, 'click', function() 
        {
            date_picker.hideControl(input);
        });        
    },

    onDateKeyPress : function(event)
    {
        if (!event) event = window.event;
        switch (event.keyCode)
        {
            case VK_UP :        date_picker.addDate(this, -7, 0);   return false;
            case VK_LEFT :      date_picker.addDate(this, -1, 0);   return false;
            case VK_RIGHT :     date_picker.addDate(this, 1, 0);    return false;
            case VK_DOWN   :    date_picker.addDate(this, 7, 0);    return false;
            
            // opera/firefox catch the TAB here
            case VK_TAB :       date_picker.hideControl(this);      return true;
                
            default : 
                return true;
        }    
    },

    onDateKeyUp : function(event)
    {
        try
        {
            // we could use the getBestDate function here, but we want to interfere with the users typing as little as possible.
            // just interpret what the user types, ignore all other options
            var date = date_picker.getDate(this);
            date_picker.drawControl(this.date_picker, date);
        }
        catch (e)
        {
        }
    },
    
    onDateFocus : function()
    {
        if (typeof this.date_picker == 'undefined')
        {
            var div = document.createElement('div');
            this.parentNode.insertBefore(div, this);
            div.input_element = this;
            this.date_picker = div;
        }

        this.date_picker.className = "date_picker_focus";
        var date = date_picker.getBestDate(this);
        date_picker.drawControl(this.date_picker, date);
        hideOverlappingSelects(this);
    },

    onDateKeyDown: function(event)
    {
        if (!event) event = window.event;
        switch (event.keyCode)
        {   // IE catches the TAB here
            case VK_TAB :   date_picker.hideControl(this);       return true;
            case VK_ENTER : date_picker.hideControl(this);       return false;
            default: 
                return true;
        }
    },
    
    hideControl : function(input)
    {
        if (!input.date_picker) return;
        input.date_picker.className = "date_picker";
        disable_show_overlapping_selects = false;
        showOverlappingSelects();
        try
        {
            var date = date_picker.getDate(input);
            date_picker.putDate(input, date);  // if we recognised the date, make it obvious by normalising the format
            input.date_picker.innerHTML = '';
        }
        catch (e)
        {
            input.date_picker.innerHTML = e;
            input.date_picker.className = 'date_picker_error';
        }    
    },
    
    drawControl : function(div, date)
    {
        var s = '';
        var prev_month         =    new Date(date.getFullYear(), date.getMonth() - 1, 1);
        var this_month         =    new Date(date.getFullYear(), date.getMonth(), 1);
        var next_month         =    new Date(date.getFullYear(), date.getMonth() + 1, 1);
        s += "<div><table>\n"
        s += "  <thead>\n";
        s += "    <tr class='buttons'>\n";
        s += "      <td colspan='2'>" + String.fromCharCode(DOUBLE_ARROW_LEFT) + " " + date_utils.dateToMonthYear(prev_month) + "</td>\n";
        s += "      <th colspan='3'>" + date_utils.dateToMonthYear(this_month) + "</th>\n";
        s += "      <td colspan='2'>" + date_utils.dateToMonthYear(next_month) + " " + String.fromCharCode(DOUBLE_ARROW_RIGHT) + "</td>\n";
        s += "    </tr>\n";
        s += "    <tr><th>M</th><th>T</th><th>W</th><th>T</th><th>F</th><th>S</th><th>S</th></tr>\n";
        s += "  </thead>\n";
        s += "  <tbody>\n";
        
        var days_in_previous_month 	= 	date_utils.getDaysOfMonth(prev_month);
        var day = -(this_month.getDay() + 6) % 7 + 1;
        var days_in_month			=	date_utils.getDaysOfMonth(this_month);
        var now = new Date();
        while (day < days_in_month)
        {
            s += "    <tr>";
            for (var weekday = 0; weekday < 7; ++weekday)
            {
                var classname = '';
                var display_day = day;
                if (day <= 0 || day > days_in_month)
                {
                    classname = " class='nmd'";
                    display_day = day <= 0 ? (day + days_in_previous_month) : day - days_in_month;
                }
                else if (day == date.getDate())
                {
                    classname = " class='current'";
                }
                else if (day == now.getDate() && date.getMonth() == now.getMonth() && date.getFullYear() == now.getFullYear())
                {
                    classname = " class='today'";
                }
                else if (weekday == 5 || weekday == 6)
                {
                    classname = " class='we'";
                }
                s += "<td" + classname + ">" + display_day + "</td>";
                ++day;
            }
            s += "</tr>\n";
        }
        s += "  </tbody>\n";
        s += "  <tfoot>\n";
        s += "    <tr><th>M</th><th>T</th><th>W</th><th>T</th><th>F</th><th>S</th><th>S</th></tr>\n";
        s += "  </tfoot>\n";
        s += "</table></div>\n";
        purge(div);
        div.innerHTML = s;
        var tds = getElementsByTagNameI(div, 'td');
        for (var tdn = 0; tdn != tds.length; ++tdn)
        {
            tds[tdn].onclick = date_picker.onDateClick;
            if (window.event && ! window.opera)
            {
                tds[tdn].onmouseover = function() { this.className += ' sfhover'; };
                tds[tdn].onmouseout  = function() { replaceClassName(this, 'sfhover', ''); };
            }
        }
        disable_show_overlapping_selects = true;
    },

    onDateClick : function(event)
    {
        if (this.tagName == 'TD' || this.tagName == 'td')
        {
            var innerdiv = this;
            while (innerdiv && innerdiv.tagName != 'div' && innerdiv.tagName != 'DIV')
            {
                innerdiv = innerdiv.parentNode;
            }
            var div = innerdiv.parentNode;
            if (this.firstChild.nodeValue.charCodeAt(0) == DOUBLE_ARROW_LEFT)
            {
                date_picker.addDate(div.input_element, 0, -1);
            }
            else if (this.firstChild.nodeValue.charCodeAt(this.firstChild.nodeValue.length - 1) == DOUBLE_ARROW_RIGHT)
            {
                date_picker.addDate(div.input_element, 0, 1);
            }
            else if (this.className.indexOf('nmd') == -1)
            {
                var date = date_picker.getBestDate(div.input_element);
                var n = parseInt(this.firstChild.nodeValue);
                if (!isNaN(n))
                {
                    date.setDate(n);
                    date_picker.putDate(div.input_element, date);
                    date_picker.hideControl(div.input_element);
                }
            }
            else
            {   // treat everything else as a cancel button
                date_picker.hideControl(div.input_element);
            }
        }
        return false;
    },

    putDate : function(input, date)
    {
        input.value =   date_utils.dateToString(date);    
    },

    addDate : function(input, days, months)
    {   
        var date = date_picker.getBestDate(input);
        date = new Date(date.getFullYear(), date.getMonth() + months, date.getDate() + days);
        date_picker.putDate(input, date);
        date_picker.drawControl(input.date_picker, date);
    },
    
    // convert the inputs value into a Date object
    // throws if the date cannot be converted.
    getDate : function(input)
    {
        return date_utils.stringToDate(input.value);
    },

    // like getDate, only will not throw if the input is not valid, it will try the following values in order to get a suitable date : 
    //   the currently displaying date
    //   the original value of the control, 
    //   todays date
    getBestDate : function(input)
    {
        try 
        {
            try
            {
                try
                {
                    return date_picker.getDate(input);
                }
                catch (e)
                {
                    // now try to recover the previous valid date from the th in the buttons row.
                    var ths = getElementsByTagNameI(input.date_picker, 'th');
                    if (ths.length == 0)
                    {
                        throw "Couldn't find display date.";
                    }
                    var month_year = ths[0].firstChild.nodeValue;
                    
                    // that gives us a month and year.  now find a td with the class current.
                    var tds = getElementsByTagNameI(div, 'td');
                    var current = 1;
                    for (var n = 0; n < tds.length; ++n)
                    {
                        if (tds[n].className == 'current')
                        {
                            current = tds[n].firstChild.nodeValue;
                        }
                    }
                    return new Date(Date.parse(current + " " + month_year));
                }
            }
            catch (e)
            {
                // well, lets if the original value of the field was any better : 
                return date_utils.stringToDate(input.original_value);
            }
        }
        catch (e)
        {
            // I guess today will just have to do...
            return new Date();
        }
    }
};

install(date_picker);

