// ---------------------------------------------------------------------------
//      cbCal.js
// ---------------------------------------------------------------------------
//      provides a 1 - 4 months Calendar
//      needs cb.js, cbDate.js, cbcal.css
// ---------------------------------------------------------------------------
//  synopsis:
//
//    var dater1 = getDateMgr( { field_ym_id: "out1", field_d_id:  "out2" } );
//    var dater2 = getDateMgr( { field_ym_id: "in1",  field_d_id:  "in2"  } );
//
//    var dater1 = getDateMgr (
//          {
//             field_d_id: 'depDay',
//             field_ym_id: "depMonthYear",
//             read_fn: function ( ) {
//                var dt   = new Date(),
//                   el_d  = xGetElementById( 'depDay' ),
//                   el_ym = xGetElementById( 'depMonthYear' ),
//                   ym    = el_ym.value,
//                   a     = ym.split( '-' );
//
//                return ( a.length !== 2 ) ? undefined : dt.fromYMD(
//                                   { y: a[ 0 ], m: a[ 1 ], d: el_d.value } );
//             },
//             write_fn: function ( d ) {
//                var el_d = xGetElementById( 'depDay' ),
//                   el_ym = xGetElementById( 'depMonthYear' ),
//                   ymd   = d.toYMD_s422();
//
//                el_ym.value = "" + ymd.y + "-" + ymd.m;
//                el_d.value  = "" + ymd.d;
//                if ( cal1 ) {
//                   cal1.updateCalendar( d );
//                }
//             }
//          }
//       );
//
//    var cal1 = Object.create( cbCal );
//    cal1.init( {
//        display_permanent:  1,                // opt leaves cal visible after choice
//        canvas_id:          'calCanvasOut',   // integrated in DOM,
//                                                  no absolute positioning
//        calName:            "",               // opt for debugging and id naming
//        numMonths:          3,                // opt default 3
//        numQuickCellColumns: 4,               // opt default numMonths * 2 + 1
//        firstValidDate:     '20100408',       // opt first valid date,
//                                                  default: todays date
//        lastValidDate:      '20111008',       // opt last valid date
//                                      default: 13 months after firstValidDate
//        lan:                'de',  // opt language, one of: de, en, es, fr, it,..
//        labels: {                                         // all opt
//                nextMonth:      '++',
//                previousMonth:  '--',
//                headerOut:      "Hinflugdatum",           // opt
//                headerIn:       "Rückflugdatum",          // opt
//                monthNames:     [ "Januar", "Februar", ... ], // opt
//                monthNames3:    [ "Jan", "Feb", ... ],        // opt [ "Jan", "Feb", ... ]
//                weekdayNames2:  [ "Mo", "Di", ... ]           // opt [ "Mo", "Di", ... ]
//        },
//        originators: [
//            {
//                date_id:        'outwardDate',
//                communicator:   dater1,           // now mandatory!!!
//                toggle_id:      'outwardToggle',  // opt
//                ofs_x:          0,    // opt x Offset Calendar from date_id
//                ofs_y:          0,    // opt y Offset Calendar from date_id
//                sync_with: {          // if outwardDate is later than returnDate,
//                                      // set returnDate to outwardDate + 7
//                    id:             'returnDate',
//                    date_diff:      7
//                }
//            },
//            ...
//        ]
//    } );
//
//    var cal1 = Object.create( cbCal );
//    cal2.init( {
//        display_permanent:  1,                // opt leaves cal visible after choice
//        canvas_id:          'calCanvasIn,     // integrated in DOM,
//                                                  no absolute positioning
//        calName:            "",               // opt for debugging and id naming
//        numMonths:          1,                // opt default 3
//        numQuickCellColumns: 0,               // opt default numMonths * 2 + 1
//        firstValidDate:     '20100408',       // opt first valid date,
//                                                  default: todays date
//        lastValidDate:      '20111008',       // opt last valid date
//                                      default: 13 months after firstValidDate
//        lan:                'de',  // opt language, one of: de, en, es, fr, it,..
//        labels: {                             // all opt
//                nextMonth:      'next',
//                previousMonth:  'prev',
//                headerOut:      "Hinflugdatum",           // opt
//                headerIn:       "Rückflugdatum",          // opt
//                monthNames:     [ "Januar", "Februar", ... ], // opt
//                monthNames3:    [ "Jan", "Feb", ... ],        // opt [ "Jan", "Feb", ... ]
//                weekdayNames2:  [ "Mo", "Di", ... ]           // opt [ "Mo", "Di", ... ]
//        },
//        originators: [
//            {
//                date_id:        'inwardDate',
//                communicator:   dater2,           // now mandatory!!!
//                toggle_id:      'inwardToggle',  // opt
//                ofs_x:          0,    // opt x Offset Calendar from date_id
//                ofs_y:          0,    // opt y Offset Calendar from date_id
//                sync_with: {          // if outwardDate is later than returnDate,
//                                      // set returnDate to outwardDate + 7
//                    id:             'outwardDate',
//                    date_diff:      7
//                }
//            },
//            ...
//        ]
//    } );
//.
//
//        or maybe
//
//
//    ibe.loadCalendar = function() {
//
//       var dater1 = getDateMgr (
//          {
//             field_d_id: 'depDay',
//             field_ym_id: "depMonthYear",
//             read_fn: function ( ) {
//                var dt   = new Date(),
//                   el_d  = xGetElementById( 'depDay' ),
//                   el_ym = xGetElementById( 'depMonthYear' ),
//                   ym    = el_ym.value,
//                   a     = ym.split( '-' );
//
//                return ( a.length !== 2 ) ? undefined : dt.fromYMD(
//                                  { y: a[ 0 ], m: a[ 1 ], d: el_d.value } );
//             },
//             write_fn: function ( d ) {
//                var el_d = xGetElementById( 'depDay' ),
//                   el_ym = xGetElementById( 'depMonthYear' ),
//                   ymd   = d.toYMD_s422();
//
//                el_ym.value = "" + ymd.y + "-" + ymd.m;
//                el_d.value  = "" + ymd.d;
//                if ( ibe.calendar1 ) {
//                   ibe.calendar1.updateCalendar( d );
//                }
//             }
//          }
//       );
//
//       var dater2 = getDateMgr(
//          {
//            field_ymd_id: "arrDay",
//            field_ym_id: "arrMonthYear",
//            read_fn: function ( ) {
//                var dt   = new Date(),
//                   el_d  = xGetElementById( 'arrDay' ),
//                   el_ym = xGetElementById( 'arrMonthYear' ),
//                   ym    = el_ym.value,
//                   a     = ym.split( '-' );
//
//                return ( a.length !== 2 ) ? undefined : dt.fromYMD(
//                          { y: a[ 0 ], m: a[ 1 ], d: el_d.value } );
//             },
//             write_fn: function ( d ) {
//                var el_d = xGetElementById( 'arrDay' ),
//                   el_ym = xGetElementById( 'arrMonthYear' ),
//                   ymd   = d.toYMD_s422();
//
//                el_ym.value = "" + ymd.y + "-" + ymd.m;
//                el_d.value  = "" + ymd.d;
//                if ( ibe.calendar2 ) {
//                   ibe.calendar2.updateCalendar( d );
//                }
//             }
//          }
//       );
//
//        ibe.calendar1 = Object.create(cbCal);
//        ibe.calendar1.init( {
//            display_permanent: 1,
//            canvas_id: 'calCanvasOut',
//            // calName: "Cal1",
//            numMonths: 1,
//            numQuickCellColumns: 2,
//            firstValidDate: "20110603",
//            lastValidDate: "20120431",
//            labels: {
//                headerOut: "Abflugdatum:",
//                headerIn: "Rückflugdatum:",
//                monthNames: [
//                    "Januar", "Februar", "März",
//                    "April", "Mai", "Juni",
//                    "Juli", "August", "September",
//                    "Oktober", "November", "Dezember"
//                ],
//                monthNames3: [
//                    "Jan", "Feb", "Mär",
//                    "Apr", "Mai", "Jun",
//                    "Jul", "Aug", "Sep",
//                    "Okt", "Nov", "Dez"
//                ],
//                weekdayNames2: [
//                    "Mo", "Di", "Mi", "Do",
//                    "Fr", "Sa", "So"
//                ],
//                dummy: ""
//            },
//            originators: [
//                {
//                    date_id: "dep",
//                    communicator: dater1,
//                    ofs_x: 0,
//                    ofs_y: 30,
//                    toggle_id: 'iconDep',
//                    sync_with: {
//                        id: "arr",
//                        date_diff: 7
//                    }
//                }
//            ]
//        } );
//
//        ibe.calendar2 = Object.create(cbCal);
//        ibe.calendar2.init( {
//            display_permanent: 1,
//            canvas_id: 'calCanvasIn',
//            // calName: "Cal2",
//            numMonths: 1,
//            numQuickCellColumns: 2,
//            firstValidDate: "20110601",
//            lastValidDate: "20120431",
//            labels: {
//                headerOut: "Rückflugdatum:",
//                headerIn: "Rückflugdatum:",
//                monthNames: [
//                    "Januar", "Februar", "März",
//                    "April", "Mai", "Juni",
//                    "Juli", "August", "September",
//                    "Oktober", "November", "Dezember"
//                ],
//                monthNames3: [
//                    "Jan", "Feb", "Mär",
//                    "Apr", "Mai", "Jun",
//                    "Jul", "Aug", "Sep",
//                    "Okt", "Nov", "Dez"
//                ],
//                weekdayNames2: [
//                    "Mo", "Di", "Mi", "Do",
//                    "Fr", "Sa", "So"
//                ],
//                dummy: ""
//            },
//            originators: [
//                {
//                    date_id: "arr",
//                    communicator: dater2,
//                    ofs_x: 0,
//                    ofs_y: 30,
//                    toggle_id: 'iconArr',
//                    sync_with: {
//                     id: "dep",
//                     date_diff: -7
//                   }
//                }
//            ]
//
//        } );
//
//        xAddEventListener( "depDay", 'change', function() {
//             ibe.calendar1.showDate( "dep", dater1.read() );
//        }, false );
//        xAddEventListener( "depMonthYear", 'change', function() {
//             ibe.calendar1.showDate( "dep", dater1.read() );
//        }, false );
//
//        xAddEventListener( "arrDay", 'change', function() {
//             ibe.calendar2.showDate( "arr", dater2.read() );
//        }, false );
//        xAddEventListener( "arrMonthYear", 'change', function() {
//             ibe.calendar2.showDate( "arr", dater2.read() );
//        }, false );
//
//    };
//


// ---------------------------------------------------------------------------
//      rg  2011-06-06
// ---------------------------------------------------------------------------

// for compatibility reasons. calls to object() should be replaced by
//  calls to Object.create()

if ( typeof object !== 'function' ) {
    var object = function (o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

if (!Object.hasOwnProperty('create')) {
    Object.create = function (object, properties) {
        var result;
        function F() {}
        F.prototype = object;
        result = new F();
        if (properties !== undefined) {
            if ( Object.defineOwnProperties ) {
                Object.defineOwnProperties(result, properties);
            }
        }
        return result;
    };
}


var cbCalLayout = function ( cfg ) {

    this.canvas_el      = cfg.canvas_id && xGetElementById( cfg.canvas_id );
    this.DOM_anchor     = this.canvas_el || window.document.body;
    this.layoutName     = '';
    this.num_months     =  cbCal.Constants.num_months;
    this.num_cells      =  cbCal.Constants.num_cells;
    this.num_quick_cols = 0;
    this.labels         = [
        cbCal.Constants.header_txt[ 0 ].de,
        cbCal.Constants.header_txt[ 1 ].de
    ];
    this.weekdayNames2 = cbCal.Constants.weekdayNames2.de;

    this.canvas         = null;
    this.header         = null;
    this.headertxt      = null;
    this.prev           = null;
    this.next           = null;
    this.quick_cells    = [];
    this.cal_cell_tds   = [ ];
    this.weekday_tds    = [ ];
    this.titles         = [ ];
    this.footer         = null;
};


cbCalLayout.prototype = {

    set_name: function( name ) {
        this.layoutName = name;
    },

    set_language: function( lan, oLabel ) {
        this.labels = ( oLabel && oLabel.headerIn && oLabel.headerOut ) ?
            [ oLabel.headerOut, oLabel.headerIn ]
            :   [ cbCal.Constants.header_txt[ 0 ][ lan ],
                  cbCal.Constants.header_txt[ 1 ][ lan ] ];

        this.weekdayNames2          = ( oLabel && oLabel.weekdayNames2 ) ?
            oLabel.weekdayNames2    :   cbCal.Constants.weekdayNames2[ lan ];

        Date.prototype.monthNames   = ( oLabel && oLabel.monthNames ) ?
            oLabel.monthNames       :   cbCal.Constants.monthNames[ lan ];

        Date.prototype.monthNames3  = ( oLabel && oLabel.monthNames3 ) ?
            oLabel.monthNames3      :   cbCal.Constants.monthNames3[ lan ];
    },

    setNextLabel: function( nextLabel ) {
        this.nextLabel = nextLabel;
    },

    setPreviousLabel: function( previousLabel ) {
        this.previousLabel = previousLabel;
    },

    set_defaults: function( cfg ) {
        var p;
        if ( typeof cfg === 'object' ) {
            for ( p in cfg ) {
                if ( cfg.hasOwnProperty( p ) ) {
                    this[ p ] = cfg[ p ];
                }
            }
        }
    },

    repaint_weekdays: function( ) {
        var i, j;
        for ( i = 0; i < this.num_months; i++ ) {
            for ( j = 0; j < 7; j += 1 ) {
                this.weekday_tds[ i ][ j ].innerHTML = this.weekdayNames2[ j ];
            }
        }
    },

    repaint: function( ) {
        this.repaint_weekdays( );
    },

    paint: function( ) {
        var i, mm, cont,
            sep_td,
            tcals,
            tbcal,
            act_td1,
            act_cal,
            act_table;

        this.canvas = cbCal.Utils.cr_classed_el( 'div', 'calCanvas' );

        if ( this.canvas_el ) {
            xSwitchClasses(this.canvas, 'calCanvas', 'calCanvasPermanent' );
        }
        xHide( this.canvas );

        mm = this.num_months;
        var cl_HDiv = 'cal' + mm + 'HeadDiv';
        var cl_CDiv = 'cal' + mm + 'CalsDiv';
        var cl_QDiv = 'cal' + mm + 'QuickDiv';

        // header
        this.header = cbCal.Utils.cr_classed_el( 'div', cl_HDiv );
        this.canvas.appendChild( this.header );
        this.headertxt = cbCal.Utils.cr_classed_el( 'div', 'calHeadDivTxt' );
        this.headertxt.innerHTML = this.labels[ 0 ];
        this.header.appendChild( this.headertxt );

        // calendars
        cont  = cbCal.Utils.cr_classed_el( 'div', cl_CDiv );

        tcals = cbCal.Utils.cr_classed_el( 'table', 'calMonthsTable' );
        cont.appendChild( tcals );
        tbcal = cbCal.Utils.cr_el( 'tbody' );
        tcals.appendChild( tbcal );

        var trow = cbCal.Utils.cr_el( 'tr' );
        tbcal.appendChild( trow );

        for ( i = 0; i < this.num_months; i++ ) {
            if ( i > 0 ) {
                sep_td = cbCal.Utils.cr_classed_el( 'td', 'calMonthSep' );
                trow.appendChild( sep_td );
            }
            act_td1  = cbCal.Utils.cr_classed_el( 'td', 'calMonthTd' );
            trow.appendChild( act_td1 );
            act_cal = cbCal.Utils.cr_classed_el( 'div', 'calMonthDiv' );
            act_table = this.paint_month( i );
            act_cal.appendChild( act_table );
            act_td1.appendChild( act_cal );
        }

        this.canvas.appendChild( cont );

        // quick months selection
        if ( this.num_quick_rows ) {
            this.footer = cbCal.Utils.cr_classed_el( 'div', cl_QDiv );
            this.footer.appendChild( this.paint_quick( ) );
            this.canvas.appendChild( this.footer );
        }

        this.DOM_anchor.appendChild( this.canvas );
    },

    paint_quick: function( ) {
        var i, j, tr, td,
            t  = cbCal.Utils.cr_classed_el( 'table', 'calQuick' ),
            tb = cbCal.Utils.cr_el( 'tbody' );
        t.appendChild( tb );
        for ( i = 0; i < this.num_quick_rows; i++ ) {
            tr = cbCal.Utils.cr_classed_el( 'tr',  'calQuickRow' );
            for ( j = 0; j < this.num_quick_cols; j++ ) {
                td = cbCal.Utils.cr_classed_el( 'td', 'calQuickCell' );
                this.quick_cells.push( td );
                tr.appendChild( td );
            }
            tb.appendChild( tr );
        }
        return t;
    },

    paint_month: function( ind ) {
        var i, j, cl, txt,
            td_els = [],
            wd_els = [],
            centerspan = 5,
            t      = cbCal.Utils.cr_classed_el( 'table', 'calMonth' ),
            th     = cbCal.Utils.cr_classed_el( 'thead', 'calMonthHead' ),
            tr1    = cbCal.Utils.cr_classed_el( 'tr',    'calMonthHeadRow' ),
            td     = cbCal.Utils.cr_el( 'td' );


        if ( this.previousLabel ) { centerspan -= 1; }
        if ( this.nextLabel     ) { centerspan -= 1; }

        if ( this.previousLabel ) {
            td.colSpan = 2;
        }

        if ( ind === 0 ) {
            if ( this.previousLabel ) {
                cl  = 'calMonthHeadPrevNextText';
                txt = this.previousLabel;
            }
            else {
                cl  = 'calMonthHeadPrevNext';
                txt = '&lt;';
            }
            td.className = cl;
            td.innerHTML = txt;
            this.prev = td;
        }
        tr1.appendChild( td );

        td = cbCal.Utils.cr_el( 'td',
            { className: 'calMonthTitle',
              id: this.layoutName + '_Title_' + ind.toString(),
              colSpan: centerspan } );
        this.titles[ ind ] = td;
        tr1.appendChild( td );

        td = cbCal.Utils.cr_el( 'td' );
        if ( this.nextLabel ) {
            td.colSpan = 2;
        }
        if ( ind === this.num_months - 1 ) {
            if ( this.nextLabel ) {
                cl  = 'calMonthHeadPrevNextText';
                txt = this.nextLabel;
            }
            else {
                cl  = 'calMonthHeadPrevNext';
                txt = '&gt;';
            }
            td.className = cl;
            td.innerHTML = txt;
            this.next = td;
        }
        tr1.appendChild( td );

        var tr2 = cbCal.Utils.cr_classed_el( 'tr', 'calMonthHead' );
        for ( j = 0; j < 7; j += 1 ) {
            td = cbCal.Utils.cr_classed_el( 'td', 'weekdays' );
            td.innerHTML = this.weekdayNames2[ j ];
            tr2.appendChild( td );
            wd_els.push( td );
        }
        this.weekday_tds[ ind ] = wd_els;

        th.appendChild( tr1 );
        th.appendChild( tr2 );
        t.appendChild( th );

        var tb = cbCal.Utils.cr_classed_el( 'tbody', 'calMonthBody' );
        for ( i = 0; i < 6; i += 1 ) {
            var tr = cbCal.Utils.cr_classed_el( 'tr', 'calMonthRow' );
            for ( j = 0; j < 7; j += 1 ) {
                var cl = j < 5 ? 'week' : 'weekend';
                var n = i * 7 + j;
                td = cbCal.Utils.cr_classed_el( 'td', 'calCell ' + cl );
                td.innerHTML = n.toString();
                td.id = this.layoutName + '_' + ind.toString() + 'cell_' + n.toString();
                td_els.push( td );
                tr.appendChild( td );
            }
            tb.appendChild( tr );
        }
        t.appendChild( tb );
        this.cal_cell_tds[ ind ] = td_els;

        return t;
    }
};

// ---------------------------------------------------------------------------
//  Calendar Object
// ---------------------------------------------------------------------------

var cbCal = {

    page_interface: {

        if_getCalPosition:  function( elem ) {
            // returns object with computed new calendar coords
            var el = elem;
            if ( this.fields && this.fields.date && this.fields.date.el ) {
                el = this.fields.date.el;
            }

            var x  = xPageX( el ),
                w  = xWidth( el ),
                y  = xPageY( el ),
                h  = xHeight( el ),
                ox = this.ofs && this.ofs.x ? this.ofs.x : 0,
                oy = this.ofs && this.ofs.y ? this.ofs.y : 1;

            return {
                x: x + Math.round( w / 2 ) + ox,
                y: y + h                   + oy
            };
        },

        if_putDateIntoPage: function ( theDate ) {
            // put selected Date into page
            var p,      // parent
                pi,     // partner interface
                pi_d,   // partner interface date
                diff,   // configured sync date difference
                dt;     // synced date

            this.putDateIntoPage( theDate );

            if ( this.sync_with ) {
                p    = this.parent;
                pi   = p.page_interfaces[ this.sync_with.id ];
                pi_d = pi.if_getDateFromPage( );
                diff = this.sync_with.date_diff || 0;
                dt   = cbCal.Utils.syncedDate( theDate, pi_d, diff );

                if ( dt ) {
                    if ( dt.is_earlier_than( this.firstValidDate ) ) {
                        dt = new Date( this.firstValidDate );
                    }
                    if ( this.lastValidDate.is_earlier_than( dt ) ) {
                        dt = new Date( this.lastValidDate );
                    }
                    pi.putDateIntoPage( dt );
                }
            }
        },

        putDateIntoPageAsPresentation: function( theDate ) {
            this.fields.date.el.innerHTML   = theDate.toIBEString( );
        },

        putCommunicatedDate: function( theDate ) {
        },

        putDateIntoPage: function ( theDate ) {
            // put selected Date into page
            this.putDateIntoPageAsPresentation(theDate);
            this.putCommunicatedDate(theDate);
            this.parent.selectDate(theDate);
        },

        getCommunicatedDate: function( ) {
            return undefined;
        },

        if_getDateFromPage: function( ) {
            // read Date from input fields
            try {
                return this.getCommunicatedDate( );
            }
            catch( e ) {
                return null;
            }
        },

        convertDottedDDMMYYToDate: function ( DD_MM_YY ) {
            // convertDottedDDMMYYToDate() takes 'DD.MM.YY' and produces Date

            var ss = DD_MM_YY.split( "." ),
                y, m, d, tDate;
            if ( ss.length === 3 ) {
                y = +DD_MM_YY.substr( 6, 2 );
                m = +DD_MM_YY.substr( 3, 2 );
                d = +DD_MM_YY.substr( 0, 2 );
                tDate = new Date().normalize( );
                tDate.setYear(tDate.getCentury( ) * 100 + y);
                tDate.setDate(1);
                tDate.setMonth(m - 1);
                tDate.setDate(d);
                return (tDate);
            }
            else {
                return null;
            }
        },

        convertIDateToDate: function ( my, d ) {
            // convertIDateToDate(): extract date from base widgets
            var m  = my % 12;
            var y  = (my - m) / 12 + 1900;

            var tDate = new Date( ).normalize( );
            tDate.setFullYear( y );
            // error in javascript implementation of setMonth!!!
            // if the day value exceeds the days in the requested month, javascript
            // sets the month to one later than requestes !!!
            // so we first step into safe and quiet water
            tDate.setDate( 1 );
            tDate.setMonth( m );
            tDate.setDate( Math.min( d, tDate.getDays( ) ) );
            return tDate;
        },

        findIntersections: function( el ) {
            // returns an array of all <select> DOM elements intersecting
            // with our Calendar Canvas. Uses dynamic programming
            // and builds the array only at the first call. Then it reuses it.
            // Note: this is a special service for the one and only most
            // respected and beloved browser out there.
            var i;

            if ( !this.toHide ) {
                this.toHide = [ ];
                var i_els = xGetElementsByTagName( 'select' );
                for ( i = 0; i < i_els.length; i++ ) {
                    if ( xIntersects( el, i_els[ i ] ) ) {
                        this.toHide.push( i_els[ i ] );
                    }
                }
            }
            return this.toHide;
        },

        dummy:       null
    },

    //  end declaration of page_interface

    display_permanent: 0,
    pi_keys:         [ ],

    lan:             'de',
    num_months:      null,
    num_cells:       null,

    actDate:         null,
    startDate:       null,
    firstValidDate:  null,
    lastValidDate:   null,
    lastUpdateDate:  null,

    layouter:        null,
    els:             null,
    ids:             null,
    page_interfaces: { },
    originator_id:   null,

    // mark selected date
    //  maybe it should be encapsulated into an own object
    mapDateTd:       {},
    selectedTd:      undefined,
    selectedDate:    undefined,
    selectedTdClass: 'selected',

    unselectTd:      function( ) {
        if ( this.selectedTd ) {
            xRemoveClass( this.selectedTd, this.selectedTdClass );
            this.selectedTd   = undefined;
            this.selectedDate = undefined;
        }
    },
    selectTd:        function( td, dt ) {
        this.unselectTd();
        this.selectedTd   = td;
        this.selectedDate = dt;
        xAddClass( this.selectedTd, this.selectedTdClass );
    },
    selectDate:      function( dt ) {
        var td = this.mapDateTd[ this.convertDateToYYYYMMDD( dt ) ];
        this.selectTd( td, dt );
    },

    isSelectedDate:  function( d ) {
        return this.selectedDate.valueOf() === d.valueOf();
    },

    init_quickcells: function( ) {
        var that = this,
            qcs = this.layouter.quick_cells,
            lastYYYYMM = this.lastValidDate.getYYYYMM(),
            qd  = new Date( this.startDate ),
            i;

        for ( i = 0; i < qcs.length; i++ ) {
            // Do not compare the dates but the months of the dates
            if ( qd.getYYYYMM() <= lastYYYYMM ) {

                qcs[ i ].innerHTML =
                    qd.getMonthName3( ) + ' ' + qd.getFullYear( );

                xAddEventListener( qcs[ i ], 'mouseover', ( function( cell ) {
                    return function() { cell.className = 'calQuickCell underline'; };
                }( qcs[ i ] ) ), false );

                xAddEventListener( qcs[ i ], 'mouseout', ( function( cell ) {
                    return function() {
                        cell.className = 'calQuickCell';
                    };
                }( qcs[ i ] ) ), false );

                xAddEventListener( qcs[ i ], 'click', ( function( d, obj ) {
                    var o = obj;
                    return function( ) {
                        var ooo = o; // debugging
                        o.startDate = d;
                        o.updateCalendar( d );
                    };
                }( new Date(qd), that ) ), false );

            }
            else {
                xHide( qcs[ i ] );
            }
            qd.addMonths( 1 );
        }
    },

    repaint_quickcells: function( ) {
        var qcs = this.layouter.quick_cells,
            qd  = new Date( this.firstValidDate ),
            i;
        for ( i = 0; i < qcs.length; i++ ) {
            if ( qd.is_earlier_than( this.lastValidDate ) ) {
                qcs[ i ].innerHTML =
                    qd.getMonthName3( ) + ' ' + qd.getFullYear( );
            }
            qd.addMonths( 1 );
        }
    },

    repaint_titles: function(  ) {
        var d = new Date( this.startDate ),
            i;
        for ( i = 0; i < this.num_months; i++ ) {
            this.els.titles[ i ].innerHTML = d.getMonthName( ) + ' ' + d.getYY( );
            d.addMonths( 1 );
        }
    },

    repaint_header: function( ) {
        var txt;
        if ( this.originator_id ) {
            txt = this.page_interfaces[ this.originator_id ].headertxt;
            this.layouter.headertxt.innerHTML = txt;
        }

    },

    init_page_interfaces: function( originators ) {
        var that = this,
            i;
        for ( i = 0; i < originators.length; i++ ) {

            ( function( ci ) {              // cfg.originators[ i ] is passed
                var key   = ci.date_id,
                    dt_el = xGetElementById( ci.date_id   ),
                    tg_el = xGetElementById( ci.toggle_id ),
                    pi = Object.create( that.page_interface );

                pi.parent = that;

                if ( dt_el ) {

                    dt_el.setAttribute( "autocomplete", "off" );

                    dt_el.onclick    =
                        function( e ) { that.toggleCalendar( key ); };
                    dt_el.onkeypress =
                    dt_el.onkeydown  =
                    dt_el.onkeyup    =
                        function( e ) {
                            var ev = new xEvent( e );
                            if ( ev.keyCode === 9 ) { // special treatment: tab
                                that.toggleCalendar( key );
                            }
                            else {
                                xPreventDefault( e );
                            }
                        };

                    pi.fields = { date: { id: ci.date_id, el: dt_el } };

                    if ( !ci.communicator ||
                         typeof ci.communicator !== "object" ||
                         !ci.communicator.read ||
                         !ci.communicator.write ) {
                        alert( "Cal: need a date communicator passed in!" );
                    }

                    pi.communicator = ci.communicator;
                    pi.getCommunicatedDate = pi.communicator.read;
                    pi.putCommunicatedDate = pi.communicator.write;

                    if ( tg_el ) {
                        pi.fields.toggle = { id: ci.toggle_id, el: tg_el };
                        tg_el.onclick = function( e ) { that.toggleCalendar( key ); };
                    }

                    // if values for monthyear and day provided, put date into page

                    pi.act = pi.if_getDateFromPage( );
                    if ( pi.act ) {
                        pi.if_putDateIntoPage( pi.act );
                    }

                    pi.ofs = { x: ci.ofs_x || 0,
                               y: ci.ofs_y || 0 };

                    pi.headertxt = ci.txt || pi.parent.layouter.labels[ i ] ;

                    pi.firstValidDate = ci.firstDate ?
                        that.convertYYYYMMDDToDate( ci.firstDate ) : that.firstValidDate;
                    if ( pi.firstValidDate.is_earlier_than( that.actDate ) ) {
                        pi.firstValidDate = new Date( that.actDate );
                    }

                    pi.lastValidDate = ci.lastDate ?
                        that.convertYYYYMMDDToDate( ci.lastDate )  : that.lastValidDate;

                    if ( ci.sync_with ) {
                        pi.sync_with = ci.sync_with;
                    }
                }

                that.page_interfaces[ key ] = pi;
                that.pi_keys.push( key );

            } ( originators[ i ] ) );

        }
    },

    init: function( cfg ) {

        var i,
            that = this,
            instanceNr = cbCal.instances.length;

        // first valid, last valid and actual Date
        this.startDate      = new Date( ).normalize( );
        this.actDate        = new Date( ).normalize( );
        if ( cfg.firstValidDate ) {
            this.firstValidDate = this.convertYYYYMMDDToDate( cfg.firstValidDate.replace(/-/g, "") );
        }
        if ( !this.firstValidDate || this.firstValidDate.is_earlier_than( this.actDate ) ) {
            this.firstValidDate = new Date( this.actDate );
        }

        if ( cfg.lastValidDate ) {
            this.lastValidDate = this.convertYYYYMMDDToDate( cfg.lastValidDate.replace(/-/g, "") );
        }
        else {
            this.lastValidDate = new Date( this.firstValidDate );
            this.lastValidDate.addMonths( 13 );
        }

        if ( cfg.display_permanent ) {
            this.display_permanent = cfg.display_permanent;
        }

        this.cName  = cfg.calName || 'Cal' + instanceNr.toString(); // for debugging
        this.lan    = cfg.lan     || 'de';
        this.labels = cfg.labels  || null;

        // Calendar layout
        this.layouter = new cbCalLayout( { canvas_id: cfg.canvas_id } );
        this.layouter.set_name( "Layout" + this.cName );
        this.layouter.set_language( this.lan, this.labels );
        if ( cfg && cfg.labels && cfg.labels.nextMonth ) {
            this.layouter.setNextLabel( cfg.labels.nextMonth );
        }
        if ( cfg && cfg.labels && cfg.labels.previousMonth ) {
            this.layouter.setPreviousLabel( cfg.labels.previousMonth );
        }

        var numMonths = cfg.numMonths ? +cfg.numMonths : cbCal.Constants.num_months;
        if ( numMonths < 1 ) {
            numMonths = 1;
        }
        if ( numMonths > 4 ) {
            numMonths = 4;
        }
        var qColMax = numMonths * 3 - 1,
            qCells  = 0,
            qCols   = 0,
            qRows   = 0;

        if ( !( isDef( cfg.numQuickCellColumns ) && cfg.numQuickCellColumns === 0 ) ) {
            qCols = cfg.numQuickCellColumns  ? +cfg.numQuickCellColumns : numMonths * 2 + 1;
            if ( qCols > qColMax ) {
                qCols = qColMax;
            }
            qCells = this.firstValidDate.getSpannedMonths( this.lastValidDate );
            qRows  = Math.ceil( qCells / qCols );
        }

        this.layouter.set_defaults( {
            num_months: numMonths,
            num_quick_rows: qRows,
            num_quick_cols: qCols
        } );

        this.num_months = this.layouter.num_months;
        this.num_cells = this.layouter.num_cells;
        this.layouter.paint( );
        this.els = {
            name:   this.cName,
            canvas: this.layouter.canvas,
            header: this.layouter.header,
            headertxt: this.layouter.headertxt,
            cal_cell_tds:    this.layouter.cal_cell_tds,
            titles: this.layouter.titles,
            prev:   this.layouter.prev,
            next:   this.layouter.next
        };

        xAddEventListener( this.els.header, 'click', function( e ) {
            that.hideCalendar( );
        }, false );

        xAddEventListener( this.els.prev, 'click', function( e ) {
            var sd = that.lastUpdateDate || that.startDate;
            sd.addMonths( -1 );
            that.updateCalendar( sd );
        }, false );

        xAddEventListener( this.els.next, 'click', function( e ) {
            var sd = that.lastUpdateDate || that.startDate;
            sd.addMonths( 1 );
            that.updateCalendar( sd );
        }, false );

        this.init_quickcells( );
        this.init_page_interfaces( cfg.originators );

        this.updateCalendar( this.firstValidDate );

        if ( cfg.canvas_id )  {
            for ( i = 0; i < cfg.originators.length; i += 1 ) {
                this.showCalendar( cfg.originators[ i ].date_id );
            }
        }

        cbCal.instances.push( this );
    },

    isValidLanguage: function( lan ) {
        var cc = cbCal.Constants;     // Calendar Constants
        return  cc.monthNames[ lan ]    &&
                cc.monthNames3[ lan ]   &&
                cc.weekdayNames2[ lan ] ;
    },

    setLanguage: function( lan, repaint ) {
        var i = 0,
            key;
        if ( lan === 'us' || lan === 'eu' ) { lan = 'en'; }
        if ( !this.isValidLanguage( lan ) ) { return;     }

        this.lan = lan;
        this.layouter.set_language( lan );

        for ( key in this.page_interfaces) {
            if ( this.page_interfaces.hasOwnProperty(key) ) {
                this.page_interfaces[ key ].headertxt = this.layouter.labels[ i ];
                i += 1;
            }
        }

        if ( repaint ) { this.repaintCalendar( ); }
    },

    repaintCalendar: function( ) {
        this.layouter.repaint( );
        this.repaint_titles( );
        this.repaint_quickcells( );
        this.repaint_header( );
    },

    updateCalendar: function( pDate ) {
        var i,
            map = {},
            d = new Date( pDate && pDate.addMonths ? pDate : this.firstValidDate );
        this.lastUpdateDate = new Date( d );
        for ( i = 0; i < this.num_months; i++ ) {
            this.updateCalendarTable( i, d, map );
            d.addMonths( 1 );
        }
        this.mapDateTd = map;
    },

    updateCalendarTable: function( ind, date, map ) {
        // set title
        var year = date[ this.display_permanent ? "getFullYear" : "getYY" ]();
        this.els.titles[ ind ].innerHTML = date.getMonthName( ) + ' ' + year;
        // tablecells
        var that  = this,
            cells = this.els.cal_cell_tds[ ind ],
            d     = new Date( date ),
            m     = d.getMonth( ),
            that  = this,
            i,
            is_oot,
            is_weekend,
            is_empty,
            ci,
            cl;

        d.setDate( 1 );                         // first day of month

        while ( d.getDay( ) !== 1 ) {            // back to previous monday
            d.addDays( -1 );
        }

        for ( i = 0; i < this.num_cells; i++ ) {
            is_oot     = this.is_oot( d );
            is_weekend = this.is_weekend( i );
            is_empty   = d.getMonth() !== m;
            ci         = cells[ i ];
            cl         = 'calCell ' +
                            ( is_weekend ? 'weekend' : 'week' ) +
                            ( is_oot     ? 'oot'     : '' ) +
                            ( is_empty   ? 'empty'   : '' );

            ci.className = cl;
            if ( ci === this.selectedTd && this.isSelectedDate( d ) ) {
                xAddClass( ci, this.selectedTdClass );
            }
            else {
                xRemoveClass( ci, this.selectedTdClass );
            }
            ci.innerHTML = is_empty ? '' : d.getDate( );

            // I had preferred to attach the handlers through xAddEventListener,
            // had I found a way to get rid of them again

            ci.onclick     = is_oot || is_empty ? null :
                ( function( dt, theCal ) {
                    return function( ) {
                        theCal.getCalendarDate( dt );
                    };
                }( new Date( d ), that ) );

            ci.onmouseover = is_oot || is_empty ? null :
                ( function( cell, theCal ) {
                    return function() {
                        if ( !xHasClass( cell, theCal.selectedTdClass ) ) {
                            cell.className = 'calCell active';
                        }
                    };
                }( ci, that ) );

            ci.onmouseout  = is_oot || is_empty ? null :
                ( function( cell, cl, theCal ) {
                    return function() {
                        if ( !xHasClass( cell, theCal.selectedTdClass ) ) {
                            cell.className = cl;
                        }
                    };
                }( ci, cl, that ) );

            if ( !is_oot && !is_empty ) {
                map[ this.convertDateToYYYYMMDD( d ) ] = ci;
            }

            d.addDays( 1 );
        }
    },

    is_oot: function( d ) {
        return( d.is_earlier_than( this.firstValidDate ) ||
                this.lastValidDate.is_earlier_than( d ) );
    },

    is_weekend: function ( i ) {
        return i % 7 >= 5;
    },

    hide: function ( ) {
        xHide( this.layouter.canvas );
    },

    show: function ( ) {
       xShow( this.layouter.canvas );
    },

    hideCalendar: function ( ) {

        if ( !this.display_permanent ) {
            /*@cc_on
            var page_interface = this.page_interfaces[ this.originator_id ];
            if ( document.uniqueID && document.compatMode &&
                    !window.XMLHttpRequest ) {
                xShowAllOf( page_interface.findIntersections( this.layouter.canvas ) );
            }
            @*/

            this.hide( this.layouter.canvas );
            this.originator_id = null;
        }
    },

    getCalendarDate: function ( d ) {
        this.showDate( this.originator_id, d );
        this.hideCalendar( );
    },

    showCalendar: function( id ) {
        var page_interface = this.page_interfaces[ id ];
        var po = page_interface.if_getCalPosition( );
        var pd = page_interface.if_getDateFromPage( ) || this.firstValidDate;

        this.startDate = new Date( pd );

        var lud = this.lastUpdateDate;
        if ( pd && lud && pd.getYYYYMM( ) !== lud.getYYYYMM( ) ) {
            this.updateCalendar( pd );
        }
        xMoveTo( this.layouter.canvas, po.x, po.y );

        /*@cc_on
        if ( document.uniqueID && document.compatMode &&
                !window.XMLHttpRequest ) {
            xHideAllOf( page_interface.findIntersections( this.layouter.canvas ) );
        }
        @*/

        this.originator_id = id;
        this.layouter.headertxt.innerHTML = page_interface.headertxt;
        xShow( this.layouter.canvas );
    },

    toggleCalendar: function( id ) {
        if ( this.originator_id && this.originator_id === id ) {
            this.hideCalendar( );
        }
        else {
            if ( this.originator_id ) {
                this.hideCalendar( );
            }
            this.showCalendar( id );
        }
    },

    showDate: function( originator_id, theDate ) {
        var page_interface = this.page_interfaces[ originator_id ];
        page_interface.if_putDateIntoPage( theDate );
    },


    convertYYYYMMDDToDate: function (s) {
        // convertYYYMMDDToDate() takes 'YYYYMMDD' and produces Date
        var y = +s.substr( 0, 4 );
        var m = +s.substr( 4, 2 );
        var d = +s.substr( 6, 2 );
        var tDate = new Date().normalize( );
        tDate.setFullYear(y);
        tDate.setDate(1);
        tDate.setMonth(m - 1);
        tDate.setDate(d);
        return (tDate);
    },

    convertDateToYYYYMMDD: function (d) {
        // convertDateToYYYYMMDD() takes Date and produces 'YYYYMMDD'

        var yyyy = String(d.getFullYear());
        while (yyyy.length < 4) { yyyy = "0" + yyyy; }
        var mm = String(d.getMonth() + 1);
        while (mm.length < 2)   {   mm = "0" + mm; }
        var dd = String(d.getDate());
        while (dd.length < 2)   {   dd = "0" + dd; }
        return yyyy + mm + dd;
    },

    purge_node: function( d ) {
        var i,
            c = d.childNodes;
        if ( c ) {
            for ( i = 0; i < c.length; i += 1 ) {
                this.purge_node( d.childNodes[ i ] );
            }
        }
        if ( d.onmouseover ) { d.onmouseover = null; }
        if ( d.onmouseout  ) { d.onmouseout  = null; }
        if ( d.onclick     ) { d.onclick     = null; }

    },

    unload: function( ) {
        /*@cc_on
        this.purge_node( this.layouter.canvas );
        @*/
    }

};

cbCal.instances = [];

cbCal.Constants = {

    // calendar dimensions
    num_months:          3,
    num_cells:          42,

    monthNames: {
        de: [ "Januar", "Februar", "M&#228;rz", "April",
              "Mai", "Juni", "Juli", "August",
              "September", "Oktober", "November", "Dezember" ],
        en: [ 'January', 'February', 'March', 'April',
              'May', 'June', 'July', 'August',
              'September', 'October', 'November', 'December' ],
        es: [ 'Enero', 'Febrero', 'Marzo', 'Abril',
              'Mayo', 'Junio', 'Julio', 'Agosto',
              'Septiembre', 'Octubre', 'Noviembre', 'Diciembre' ],
        fr: [ "janvier", "f&#233;vrier", "mars", "avril",
              "mai", "juin", "juillet", "ao&#251;t",
              "septembre", "octobre", "novembre", "d&#233;cembre" ],
        it: [ "Gennaio ", "Febbraio ", "Marzo ", "Aprile ",
              "Maggio ", "Giugno ", "Luglio ", "Agosto ",
              "Settembre ", "Ottobre ", "Novembre ", "Dicembre " ],
        tr: [ "Ocak", "&#350;ubat", "Mart", "Nisan",
              "May&#305;s", "Haziran", "Temmuz", "A&#287;ustos",
              "Eyl&#252;l", "Ekim", "Kas&#305;m", "Aral&#305;k" ],
        pl: [ "Styczen", "Luty", "Marzec", "Kwiecien",
              "Maj", "Czerwiec", "Lipiec", "Sierpien",
              "Wrzesien", "Pazdziernik", "Listopad", "Grudzien" ],
        pt: [ "Janeiro", "Fevereiro", "Mar&#231o", "Abril",
              "Maio", "Junho", "Julho", "Agosto",
              "Setembro", "Outubro", "Novembro", "Dezembro" ]
    },
    monthNames3: {
        de: [ 'Jan', 'Feb', 'M&#228;r', 'Apr', 'Mai', 'Jun',
              'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez' ],
        en: [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
              'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ],
        es: [ 'Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun',
              'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic' ],
        fr: [ "jan", "f&#233;v", "mar", "avr", "mai", "juin",
              "juil", "ao&#251;t", "sep", "oct", "nov", "d&#233;c" ],
        it: [ "Gen", "Feb", "Mar", "Apr", "Mag", "Giu",
              "Lug", "Ago", "Set", "Ott", "Nov", "Dic" ],
        tr: [ "Oca", "&#350;ub", "Mar", "Nis", "May", "Haz",
              "Tem", "A&#287;u", "Eyl", "Eki", "Kas", "Ara" ],
        pl: [ "Sty", "Lut", "Mar", "Kwi", "Maj", "Cze",
              "Lip", "Sie", "Wrz", "Paz", "Lis", "Gru" ],
        pt: [ "Jan", "Fev", "Mar", "Abr", "Mai", "Jun",
              "Jul", "Ago", "Set", "Out", "Nov", "Dez" ]
    },

    weekdayNames2: {
        de: [ 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So' ],
        en: [ 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su' ],
        es: [ "Lu", "Ma", "Mi", "Ju", "Vi", "S&#225;", "Do" ],
        // fr: [ 'lun', 'mar', 'mer', 'jeu', 'ven', 'sam', 'dim' ],
        fr: [ 'l', 'm', 'm', 'j', 'v', 's', 'd' ],
        it: [ 'Lu', 'Ma', 'Me', 'Gi', 'Ve', 'Sa', 'Do' ],
        tr: [ 'Pts', 'Sa', '&#199;ar', 'Per', 'Cu', 'Pts', 'Paz' ],
        pl: [ "Pn", "Wt", "Sr", "Cz", "Pt", "So", "Nd" ],
        pt: [ "2a", "3a", "4a", "5a", "6a", "S&#225", "Do" ]
    },

     header_txt: [
        {
            de: "Bitte w&#228;hlen Sie Ihr Hinflugdatum aus.",
            en: "Please select the date of your outbound flight.",
            es: "Por favor elija la fecha de ida.",
            it: "Volete scegliere la vostra data del volo di partenza.",
            fr: "Veuillez choisir la date de d&#233;part de votre vol.",
            tr: "L&#252;tfen gidi&#351; tarihini se&#231;in.",
            pl: "Prosze wybrac date wylotu",
            pt: "Por favor escolha a sua data de partida"
        },
        {
            de: "Bitte w&#228;hlen Sie Ihr R&#252;ckflugdatum aus.",
            en: "Please select the date of your return flight.",
            es: "Por favor elija la fecha de vuelta.",
            it: "Volete scegliere la vostra data del volo di ritorno.",
            fr: "Veuillez choisir la date de retour de votre vol.",
            tr: "L&#252;tfen d&#246;n&#252;&#351; tarihini se&#231;in.",
            pl: "Prosze wybrac date lotu powrotnego.",
            pt: "Por favor escolha a sua data de regresso"
        }
    ],
    dummy: ''

};

cbCal.Utils = {
    cr_el: function( tag, o ) {
        var p,
            el = window.document.createElement( tag.toUpperCase( ) );
        for ( p in o ) {
            if ( o.hasOwnProperty( p ) ) {
                el[ p ] = o[ p ];
            }
        }
        return el;
    },
    cr_classed_el: function( tag, cl ) {
        var el = window.document.createElement( tag.toUpperCase( ) );
        if ( typeof cl === 'string' ) {
            el.className = cl;
        }
        return el;
    },
    syncedDate: function( myDate, yourDate, diff ) {
        if ( !myDate ) { return null; }

        var result;
        if ( !yourDate ||
             ( diff < 0 && myDate.is_earlier_than( yourDate ) ) ||
             ( diff > 0 && yourDate.is_earlier_than( myDate ) ) ) {
            result = new Date( myDate );
            result.addDays( diff || 0 );
        }
        else {
            result = new Date( yourDate );
        }
        return result;
    }
};


// ---------------------------------------------------------------------------
//  the End of Calendar Object
// ---------------------------------------------------------------------------

