/*
    AMLI.DateChooser
    Renders a calendar either inline or tied to a text input mouse events.
    USAGE:
        var myDateChooser = new AMLI.DateChooser(
                       {
                        inputid     : 'sometextinputid',                          //an id from a text input
                        from        : new Date(),                                 //the first choosable date
                        to          : new Date().addDays(120),                    //the last choosable date
                        selected    : new Date(),                                 //the selected date
                        blocked     : [new Date(2009,11,25),new Date(2009,0,1)],  //an array of invalid dates
                        calwidth    : 140,                                        //the width of the calendar
                        daystyle    : 'font-size:10px;',                          //style for the day boxes
                        position    : 'top-right',                                //where the calendar appears relative to the input
                        posoffset   : [4,-28]                                     //position offset left,top
                        }
        );            
    TODO:
        3) Write .NET control to simplify usage
        4) Refine, Refactor, and Tune for size, speed, usability, and aesthetics
*/

//keeps track of the DateChooser instances so it may support more than one running at a time.
var _adcs = AMLI.DateChoosers = [];

//constructor - merges the definition with the default settings and loads the object on window load.
AMLI.DateChooser = function(def){

    //default values
    this.inputid    = undefined;
    this.from       = new Date().getFDOM();
    this.to         = new Date().getLDOM();
    this.selected   = new Date().getFloor();
    this.blocked    = [];
    this.calwidth   = 160;
    this.alwayson   = false;
    this.onclick    = null;
    this.position   = 'bottom-left';
    this.posoffset  = [0,0];
    this.daystyle   = '';
    this.cssarrow   = 'DCArrow';
    this.cssmonth   = 'DCMonth';
    this.cssdayheader='DCHeader';
    this.csscontainer='DCCont';
    this.cssmain    = 'DCMain';
    this.cssday     = 'DCDay';
    this.cssdayS    = 'DCDayS';
    this.cssdayT    = 'DCDayT';
    this.cssdayB    = 'DCDayB';
    this.cssdayH    = 'DCDayH';

    //merge the definition with default values
    for(var p in def)this[p] = def[p];

    //keep track of the instance
    this.instance = _adcs.length;
    _adcs[this.instance] = this;

    //attach the window load event to start it up
    _amli_add_event(window,'load',_amli_make_function(this,this.load));
}

//starts the calendar - should be called on window load and once only per instance
AMLI.DateChooser.prototype.load = function() {

    if(!this.input){
        this.input = document.getElementById(this.inputid);
        if(!this.input || this.input.tagName!='INPUT'){
            alert('DateChooser Error: Target input was not found.');
            return;
        }
    }

    var s = '<div style="width:'+this.calwidth+'px;position:relative;overflow:hidden;">';
    s+='<table id="adc.'+this.instance+'.table" style="position:relative;" cellpadding="0" cellspacing="0">';
    s+='<tr id="adc.'+this.instance+'.row"></tr>';
    s+='</table>';
    s+='</div>';

    if(this.alwayson){

        //create the container right next to the input element
        this.container = document.createElement('div');
        this.container.id = 'adc.'+this.instance+'.container';
        this.container.innerHTML = s;
        this.input.parentNode.insertBefore(this.container,this.input.nextSibling);
    }else{

        //create the container as an invisible div appended to the form
        this.container = document.createElement('div');
        this.container.id = 'adc.'+this.instance+'.container';
        this.container.className = this.csscontainer;
        this.container.style.position='absolute';
        this.container.style.display='none';
        this.container.style.zIndex=1000;
        this.container.innerHTML = s;
        document.forms[0].appendChild(this.container);

        //the container visibility events on the input and container
        var showF = _amli_make_function(this,this.show);
        var hideF = _amli_make_function(this,this.hide);

        _amli_add_event(this.input,'focus',showF);
        _amli_add_event(this.input,'click',showF);
        _amli_add_event(this.input,'blur',hideF);
        
        _amli_add_event(this.container,'mouseover',showF);
        _amli_add_event(this.container,'mouseout',hideF);

        _amli_add_event(this.input,'mouseover',_amli_make_function(this,this.showDelay));
        _amli_add_event(this.input,'mouseout',_amli_make_function(this,this.hideDelay));

        this.showeffect = new AMLI.Effects.Animation([{ element: this.container, type: 'opacity', duration: 350 }]);
    }

    //manual text entry events
    _amli_add_event(this.input,'change',_amli_make_function(this,this.textChange));
    _amli_add_event(this.input,'focus',_amli_make_function(this,this.textFocus));

    //disable selecting text in the calendar
    this.container.onselectstart=function(){return false};
    this.container.style.MozUserSelect="none";
    this.container.onmousedown=function(){return false};

    //keep some frequently used elements and draw the first month
    this.caltable = document.getElementById('adc.'+this.instance+'.table');
    this.calrow = document.getElementById('adc.'+this.instance+'.row');
    this.slider = new AMLI.Effects.Animation([{ element: this.caltable, type: 'left', duration: 350 }]);
    this.drawMonth(this.selected);

}
//generates the html table for the month of the date provided
AMLI.DateChooser.prototype.drawMonth = function(caldt){

    caldt = caldt.getCalendarDateAndInfo();
    var today = new Date().getFloor();

    var s= '<table class="'+this.cssmain+'" style="width:'+this.calwidth+'px;" cellpadding="0" cellspacing="0">';

    for(var i=0; i< caldt.monthdays; i++){

        var d = caldt.fdom.addDays(i);

        if(d.getDate()==1){

            s+= '<tr>'

            //previous month link
            if(caldt.addMonths(-1).getLDOM()>=this.from){
                s+= '<td style="cursor:pointer;" class="'+ this.cssarrow +'"';
                s+= ' onclick="_adcs['+this.instance+'].showMonth('+caldt.valueOf()+',-1)"';
                s+= '><</td>';
            }else{
                s+= '<td>&nbsp;</td>';
            }

            //month and year text
            s+='<td colspan="5" class="'+this.cssmonth+'">' + caldt.toString('MMMM yyyy') + '</td>'

            //next month link
            if(caldt.addMonths(1).getFDOM()<=this.to){
                s+= '<td style="cursor:pointer;" class="'+ this.cssarrow +'"';
                s+= ' onclick="_adcs['+this.instance+'].showMonth('+caldt.valueOf()+',1)"';
                s+= '>></td>';
            }else{
                s+= '<td>&nbsp;</td>';
            }

            s+= '</tr>'

            //days of the week header
            s+= '<tr>'
            for (var j=0; j<7;j++)
                s+= '<td class="'+this.cssdayheader+'">' + "SMTWTFS".substr(j, 1) + '</td>';

            s+='</tr><tr>';

            //ending days of the previous month
            for(var j=0;j<caldt.fdomd;j++)
                s+='<td class="dcdb">&nbsp;</td>';

        }else if(d.getDay()==0){
            //Sunday new table row
            s+='</tr><tr>';
        }

        var blocked = this.dateIsBlocked(d);

        //the different css classes for a day
        var css = this.cssday;
        if(blocked) css = this.cssdayB;
        else if(d.equals(this.selected)) css = this.cssdayS;

        if(d.equals(today)) css += ' ' + this.cssdayT;

        //the day cell with style and events
        s+='<td class="'+css+'" style="cursor: pointer;'+this.daystyle+'"';
        if(!blocked){
            s+=' onmouseover="this.className+=\' '+this.cssdayT+'\';"';
            s+=' onmouseout="this.className=this.className.replace(\' '+this.cssdayT+'\',\'\');"';
            s+=' onclick="_adcs['+this.instance+'].clickDate('+d.valueOf()+')"';
        }
        s+= '>'+d.getDate();
        s+='</td>';
    }

    //keep all months the same number of week rows even when the days do not span them all
    for(var wr=caldt.weekrows;wr<6;wr++)
        s+='</tr><tr><td class="'+this.cssdayB+'">&nbsp;</td>';

    s+= '</tr></table>';

    //find the month cell by id
    var mcell_id = 'adc.'+this.instance+'.mcell.' + caldt.getFullYear() + '.' + caldt.getMonth();
    var mcell = document.getElementById(mcell_id);

    if(mcell==null){

        //create it and find out where to place it
        mcell = document.createElement('td');
        mcell.id = mcell_id;

        var next_mcell_id = 'adc.'+this.instance+'.mcell.' + caldt.addMonths(1).getFullYear() + '.' + caldt.addMonths(1).getMonth();
        var next_mcell = document.getElementById(next_mcell_id);

        this.calrow.insertBefore(mcell,next_mcell);

        if(this.calrow.childNodes.length==1){
            this.caltable.style.left = '0px';
        }
        if(next_mcell!=null)
            this.caltable.style.left = (this.caltable.offsetLeft - this.calwidth) +'px';
    }

    mcell.innerHTML = s;
}
//returns true if the date is outside of the allowed range or it is in the blcked list
AMLI.DateChooser.prototype.dateIsBlocked = function(d){
    var blocked = (d<this.from || d>this.to);
    for(var b=0;!blocked && b<this.blocked.length;b++)
        blocked=d.equals(this.blocked[b]);
    return blocked;
}
//draws a month m months from the dv date
AMLI.DateChooser.prototype.showMonth = function(dv, m) {
    this.drawMonth(new Date(dv).addMonths(m));
    var fromleft = this.caltable.offsetLeft;
    var toleft = fromleft - (this.calwidth*m);
    this.slider.run([fromleft,toleft]);
    if(!this.alwayson)this.show();
}
//fires when the date is clicked
AMLI.DateChooser.prototype.clickDate = function(dv) {
    this.selected = new Date(dv);
    this.input.value = this.selected.toString('MM/dd/yyyy');
    this.drawMonth(this.selected);
    if(!this.alwayson)this.container.style.display = 'none';
    if (this.onclick) this.onclick(this.selected);
}
//selects all of the text in the input when it gets the focus
AMLI.DateChooser.prototype.textFocus = function(){
    this.input.select();
}
//validates any manually entered text in the input
AMLI.DateChooser.prototype.textChange = function() {

    var msg = null;
    var parts = this.input.value.split('/');

    if (parts.length == 2) parts[2] = new Date().getFullYear();

    if (parts.length == 3) {
        var m = parseInt(parts[0], 10);
        var d = parseInt(parts[1], 10);
        var y = parseInt(parts[2], 10);
        if (!isNaN(m + d + y)) {
            if (y < 100) y += 2000;
            var dt = new Date(y, m - 1, d);
            if (!this.dateIsBlocked(dt)) {
                this.selected = dt;
                while (this.calrow.childNodes.length >= 1)
                    this.calrow.removeChild(this.calrow.firstChild);
                this.drawMonth(this.selected);
            } else {
                msg = 'Date Not Allowed';
            }
        } else {
            msg = 'Invalid Entry';
        }
    } else {
        msg = 'Use mm/dd/yyyy format';
    }

    if (!msg) {
        this.input.value = this.selected.toString('MM/dd/yyyy');
    } else {
        this.input.disabled = true;
        this.input.style.color = 'red';
        this.input.value = msg;
        setTimeout("_get('" + this.inputid + "').value = '';", 200);
        setTimeout("_get('" + this.inputid + "').value = '" + msg + "';", 400);
        setTimeout("_get('" + this.inputid + "').value = '';", 600);
        setTimeout("_get('" + this.inputid + "').value = '" + msg + "';", 800);
        setTimeout("_get('" + this.inputid + "').value = '" + this.selected.toString('MM/dd/yyyy') + "';"
                  +"_get('" + this.inputid + "').disabled=false;"
                  +"_get('" + this.inputid + "').style.color='';"
                  +"_get('" + this.inputid + "').focus();", 1000);
    }
}
//makes the calendar visible - only when alwayson = false
AMLI.DateChooser.prototype.show = function(){
    if(this.hide_timeout){
        clearTimeout(this.hide_timeout);
        this.hide_timeout = null;
    }
    if(this.container.style.display!='block'){
        var pos = element_info(this.input);
        switch(this.position)
        {
            case 'top-right':
                this.container.style.left = (pos.right+this.posoffset[0]) + 'px';
                this.container.style.top = (pos.top+this.posoffset[1]) + 'px';
                break;

            case 'bottom-left':
            default:
                this.container.style.left = (pos.left+this.posoffset[0]) + 'px';
                this.container.style.top = (pos.bottom+this.posoffset[1]) + 'px';
        }
        this.container.style.visibility = 'hidden';
        this.container.style.display = 'block';
        this.showeffect.run([0,1]);
    }
}
//makes the calendar hidden - only when alwayson = false
AMLI.DateChooser.prototype.hide = function(){
    this.hide_timeout = setTimeout("_get('"+this.container.id+"').style.display='none';",750);
}
//makes the calendar visible with delay - only when alwayson = false
AMLI.DateChooser.prototype.showDelay = function(){
    this.show_timeout = setTimeout('_adcs['+this.instance+'].show();',200);
}
AMLI.DateChooser.prototype.hideDelay = function(){
    if(this.show_timeout){
        clearTimeout(this.show_timeout);
        this.show_timeout = null;
    }
    this.hide();
}

/*
    Date helper methods
*/
//returns the same date without the hours, minutes, seconds, and millis
Date.prototype.getFloor = function(){
    return new Date(this.getFullYear(), this.getMonth(), this.getDate());
}
//returns the first date of the date's month
Date.prototype.getFDOM = function(){
    return new Date(this.getFullYear(), this.getMonth(),1);
}
//returns the last date of the date's month
Date.prototype.getLDOM = function(){
    return new Date(this.getFullYear(),this.getMonth()+1,0);
}
//returns the date plus the number of days.
Date.prototype.addDays = function(days){
    if(days == undefined) days=1;
    return new Date(this.getFullYear(),this.getMonth(),this.getDate()+days);
}
//returns the date plus the number of months.
Date.prototype.addMonths = function(months){
    if(months == undefined) months=1;
    return new Date(this.getFullYear(),this.getMonth()+months,1);
}
//returns true if the date has the same value as the date provided.
Date.prototype.equals = function(date){
    return  date.valueOf()==this.valueOf();
}
//returns the difference in days from the date provided.
Date.prototype.getCalendarDateAndInfo = function(){
        var d = this.getFloor();
        d.fdom = d.getFDOM();
        d.fdomd = d.fdom.getDay();
        d.ldom = d.getLDOM();
        d.ldomd = d.ldom.getDay();
        d.monthdays = d.ldom.getDate() - d.fdom.getDate() + 1;
        d.weekrows = (d.monthdays + d.fdomd + 6 - d.ldomd) / 7;

        return d;
}
//returns a string based on the .NET DateTime format conventions
Date.prototype.toStringDefault = Date.prototype.toString;
Date.prototype.toString = function(format){

    switch(format){

        case 'MM/dd/yyyy':
            return (this.getMonth() + 1) + "/" + this.getDate() + "/" + this.getFullYear();
            break;

        case 'MMMM yyyy':
            return ['January', 'February', 'March', 'April', 'May', 'June',
                  'July', 'August', 'September', 'October', 'November', 'December'][this.getMonth()] + ' ' + this.getFullYear();
            break;

        default:
            return this.toStringDefault();
    }
}