
// --------------------------------------------------------------------------
// ... lib   some general stuff
// --------------------------------------------------------------------------

Function.prototype.method = function( name, func ) {
    if ( ! this.prototype[ name ] ) {
        this.prototype[ name ] = func;
    }
};

Number.method( 'integer', function( ) {
    return Math[ this < 0 ? 'ceiling' : 'floor' ]( this );
} );

String.method( 'trim', function( ) {
    return this.replace( /^\s+|\s+$/g, '' );
} );

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

function purge( d ) {
    var i, a  = d.attributes;
    if ( a ) {
        for ( i = 0; i < a.length; i += 1 ) {
            var n = a[ i ].name;
            if ( typeof d[ n ] === 'function' ) {
                d[ n ] = null;
            }
        }
    }
    var c = d.childNodes;
    if ( c ) {
        for ( i = 0; i < c.length; i += 1 ) {
            purge( d.childNodes[ i ] );
        }
    }
}

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

}

function set_handler_for( el, evt, hd ) {
    var o = el[ evt ];
    if ( typeof o == 'function' ) {
        el[ evt ] = function( v ) { hd( v ); o( v ); };
    }
    else {
        el[ evt ] = hd;
    }
}

/*
(function() { // Use Object Detection to detect IE6
    try{
        if ( document.uniqueID && document.compatMode &&
                !window.XMLHttpRequest && document.execCommand ) {
            document.execCommand("BackgroundImageCache", false, true);
        }
    }
    catch( oh ){}
})();
*/


var ibe = window.ibe || {};
if ( !ibe.Constants ) { ibe.Constants = { }; }

ibe.Constants.cal = {

    // calendar dimensions
    num_cols:           3,
    num_cells:          42,
    num_quick_rows:     2,
    num_quick_cells:    7,

    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' ],
        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_txt1: [
        {
            de: "Bitte w&#228;hlen Sie mit dem ersten Klick Ihr Abflugdatum aus",
            en: "Please, select first the date of departure",
            es: "es Bitte w&#228;hlen Sie ... spanisch",
            fr: "fr Bitte w&#228;hlen Sie ... franz&#246;sisch",
            it: "it Bitte w&#228;hlen Sie ... italienisch",
            tr: "tr Bitte w&#228;hlen Sie ... t&#252;rkisch", 
            pl: "pl Bitte w&#228;hlen Sie ... polnisch", 
            pt: "pt Bitte w&#228;hlen Sie ... portugiesisc"
        },
        {
            de: "Bitte w&#228;hlen Sie Ihr R&#252;ckflugdatum aus",
            en: "Please, select the date of arrival",
            es: "es Bitte w&#228;hlen Sie Ihr ... spanisch",
            fr: "fr Bitte w&#228;hlen Sie Ihr ... franz&#246;sisch",
            it: "it Bitte w&#228;hlen Sie Ihr ... italienisch",
            tr: "tr Bitte w&#228;hlen Sie Ihr ... t&#252;rkisch", 
            pl: "pl Bitte w&#228;hlen Sie ... polnisch", 
            pt: "pt Bitte w&#228;hlen Sie ... portugiesisc"
        }
    ],

     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: ''

};

ibe.cal3Utils = {
    cr_el: function( tag, o ) {
        var el = document.createElement( tag.toUpperCase( ) );
        for ( var p in o ) {
            if ( o.hasOwnProperty( p ) ) {
                el[ p ] = o[ p ];
            }
        }
        return el;
    },
    cr_classed_el: function( tag, cl ) {
        var el = document.createElement( tag.toUpperCase( ) );
        if ( typeof cl == 'string' ) {
            el.className = cl;
        }
        return el;
    }
};

ibe.cal3Layout = {

    num_cols:        ibe.Constants.cal.num_cols,
    num_cells:       ibe.Constants.cal.num_cells,
    num_quick_rows:  ibe.Constants.cal.num_quick_rows,
    num_quick_cells: ibe.Constants.cal.num_quick_cells,
    labels:         [
        ibe.Constants.cal.header_txt[ 0 ].de,
        ibe.Constants.cal.header_txt[ 1 ].de
    ],
    weekdayNames2:   ibe.Constants.cal.weekdayNames2.de,

    canvas:         null,
    header:         null,
    headertxt:      null,
    prev:           null,
    next:           null,
    quick_cells:    [ ],
    cal_cell_tds:   [ ],
    weekday_tds:    [ ],
    titles:         [ ],
    group:          [ ],
    footer:         null,

    set_language: function( lan ) {
        this.labels = [
            ibe.Constants.cal.header_txt[ 0 ][ lan ],
            ibe.Constants.cal.header_txt[ 1 ][ lan ]
        ];
        this.weekdayNames2         = ibe.Constants.cal.weekdayNames2[ lan ];
        Date.prototype.monthNames  = ibe.Constants.cal.monthNames[ lan ];
        Date.prototype.monthNames3 = ibe.Constants.cal.monthNames3[ lan ];
    },

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

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

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

    paint: function( ) {
        this.canvas = ibe.cal3Utils.cr_classed_el( 'div', 'cal3Canvas' );
        xHide( this.canvas );

        // header
        this.header = ibe.cal3Utils.cr_classed_el( 'div', 'cal3HeadDiv' );
        this.canvas.appendChild( this.header );
        this.headertxt = ibe.cal3Utils.cr_classed_el( 'div', 'cal3HeadDivTxt' );
        this.headertxt.innerHTML = this.labels[ 0 ];
        this.header.appendChild( this.headertxt );


        // calendars
        var cont  = ibe.cal3Utils.cr_classed_el( 'div', 'cal3CalsDiv' );

        var tcals = ibe.cal3Utils.cr_classed_el( 'table', 'cal3CalsTable' );
        cont.appendChild( tcals );
        var tbcal = ibe.cal3Utils.cr_el( 'tbody' );
        tcals.appendChild( tbcal );

        var trow = ibe.cal3Utils.cr_el( 'tr' );
        tbcal.appendChild( trow );

        for ( var i = 0; i < this.num_cols; i++ ) {
            var act_td1  = ibe.cal3Utils.cr_el( 'td' );
            trow.appendChild( act_td1 );
            var act_cal = ibe.cal3Utils.cr_classed_el( 'div', 'cal3MonthDiv' );
            var act_table = this.paint_month( i );
            act_cal.appendChild( act_table );
            act_td1.appendChild( act_cal );
        }

        this.canvas.appendChild( cont );

        // quick months selection
        this.footer = ibe.cal3Utils.cr_classed_el( 'div', 'cal3QuickDiv' );
        this.footer.appendChild( this.paint_quick( ) );
        this.canvas.appendChild( this.footer );

        document.body.appendChild( this.canvas );
    },

    paint_quick: function( ) {
        var i, j, tr, td;
        var t  = ibe.cal3Utils.cr_classed_el( 'table', 'cal3Quick' );
        var tb = ibe.cal3Utils.cr_el( 'tbody' );
        t.appendChild( tb );
        for ( i = 0; i < this.num_quick_rows; i++ ) {
            tr = ibe.cal3Utils.cr_classed_el( 'tr',  'cal3QuickRow' );
            for ( j = 0; j < this.num_quick_cells; j++ ) {
                td = ibe.cal3Utils.cr_classed_el( 'td', 'cal3QuickCell' );
                this.quick_cells.push( td );
                tr.appendChild( td );
            }
            tb.appendChild( tr );
        }
        return t;
    },

    paint_month: function( ind ) {
        var i, j,
            td_els = [],
            wd_els = [],
            t      = ibe.cal3Utils.cr_classed_el( 'table', 'cal3Month' ),
            th     = ibe.cal3Utils.cr_classed_el( 'thead', 'cal3MonthHead' ),
            tr1    = ibe.cal3Utils.cr_classed_el( 'tr',    'cal3MonthHeadRow' ),
            td     = ibe.cal3Utils.cr_el( 'td' );

        if ( ind === 0 ) {
            td.innerHTML = '&lt;';
            td.className = 'cal3MonthHeadPrevNext';
            this.prev = td;
        }
        tr1.appendChild( td );

        td = ibe.cal3Utils.cr_el( 'td',
            { className: 'cal3MonthTitle', id: 'Cal3Title_' + ind, colSpan: 5 } );
        this.titles[ ind ] = td;
        tr1.appendChild( td );

        td = ibe.cal3Utils.cr_el( 'td' );
        if ( ind == this.num_cols - 1 ) {
            td.innerHTML = '&gt;';
            td.className = 'cal3MonthHeadPrevNext';
            this.next = td;
        }
        tr1.appendChild( td );

        var tr2 = ibe.cal3Utils.cr_classed_el( 'tr', 'cal3MonthHead' );
        for ( j = 0; j < 7; j += 1 ) {
            td = ibe.cal3Utils.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 = ibe.cal3Utils.cr_classed_el( 'tbody', 'cal3MonthBody' );
        for ( i = 0; i < 6; i += 1 ) {
            var tr = ibe.cal3Utils.cr_classed_el( 'tr', 'cal3MonthRow' );
            for ( j = 0; j < 7; j += 1 ) {
                var cl = j < 5 ? 'week' : 'weekend';
                var n = i * 7 + j;
                td = ibe.cal3Utils.cr_classed_el( 'td', 'cal3Cell ' + cl );
                td.innerHTML = n;
                td.id = 'cal_' + ind + 'cell_' + n;
                td_els.push( td );
                tr.appendChild( td );
            }
            tb.appendChild( tr );
        }
        t.appendChild( tb );
        this.cal_cell_tds[ ind ] = td_els;

        return t;
    },

    dummy: null
};


//============================================================================
// Add new properties and methods to the Date object.
//============================================================================

Date.prototype.monthNames = [
    "Januar", "Februar", "Maerz", "April", "Mai", "Juni",
    "Juli", "August", "September", "Oktober", "November", "Dezember" ];

Date.prototype.monthNames3 = [
    'Jan', 'Feb', 'Mae', 'Apr', 'Mai', 'Jun',
    'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez' ];

Date.prototype.getMonthName = function () {
    // Returns the name of the date's month.

    return this.monthNames[ this.getMonth( ) ];
};

Date.prototype.getMonthName3 = function () {
    // Returns the short of the date's month.

    return this.monthNames3[ this.getMonth( ) ];
};

Date.prototype.is_earlier_than = function ( d ) {
    // Returns the name of the date's month.

    return Number( this ) < Number( d );
};

Date.prototype.getDays = function () {
    // Returns the number of days in the date's month.

    // var tDate = new Date( Date.parse( this ) );
    var tDate = new Date( this );
    var m     = tDate.getMonth( );
    var last  = 28;

    do {
        tDate.setDate(++last);
    } while( tDate.getMonth() == m );

    return last - 1;
};

Date.prototype.getYY = function ( ) {
    // returns Year of Century --- YY

    var YY = String( this.getFullYear( ) % 100 );
    while ( YY.length < 2 ) {
        YY = "0" + YY;
    }
    return YY;
};

Date.prototype.getYYYYMM = function ( ) {
    // returns Year of Century --- YY

    var YYYY = String( this.getFullYear() );
    while ( YYYY.length < 4 ) {
        YYYY = "0" + YYYY;
    }
    var MM = String( this.getMonth() );
    while ( MM.length < 2 ) {
        MM = "0" + MM;
    }
    return YYYY + MM;
};

Date.prototype.getCentury = function ( ) {
    // get Year of Century --- CC

    return String( this.getFullYear( ) ).substr( 0, 2 );
};

Date.prototype.normalize = function () {
    // normalizes Dates to 12:00:00:000 for numerical compares of Dates

    this.setHours( 12 );
    this.setMinutes( 0 );
    this.setSeconds( 0 );
    this.setMilliseconds( 0 );
    return this;
};

Date.prototype.addDays = function (n) {
    // Adds the specified number of days to the date.

    this.setDate( this.getDate() + n );
    // this.savedDate = this.getDate();
};

Date.prototype.addMonths = function (n) {
    // Adds the specified number of months to the date, adjusting
    // the day of the month if necessary.

    var sd = this.getDate( );
    this.setDate(1);
    var newm = this.getMonth() + n;
    while ( newm < 0 ) {
        newm += 12;
        this.setFullYear( this.getFullYear() - 1 );
    }
    this.setMonth( newm );

    this.setDate( Math.min( sd, this.getDays() ) );
};

Date.prototype.addYears     = function (n) {
    // Adds the specified number of years to the date, adjusting the
    // day of the month for leap years.

    var sd = this.getDate( );
    this.setDate( 1 );
    this.setFullYear( this.getFullYear( ) + n );
    this.setDate( Math.min( sd, this.getDays( ) ) );
};

Date.prototype.toIBEString  = function( ) {
    // string representation of date: DD. mmm YYYY
    var dd = String(this.getDate());
    while (dd.length < 2) { dd = "0" + dd; }

    return dd + '. ' + this.getMonthName3( ) + ' ' + this.getFullYear( );
};

Date.prototype.toDottedDDMMYY = function (  ) {
    // string representation of Date: 'DD.MM.YY'

    var mm = String( this.getMonth() + 1 );
    while (mm.length < 2) { mm = "0" + mm; }
    var dd = String( this.getDate() );
    while (dd.length < 2) { dd = "0" + dd; }
    return dd + '.' + mm + '.' + this.getYY( );

};

Date.prototype.toIDate =  function (  ) {
    // object with ibe monthyear and day
    return {
        my: 12 * (this.getFullYear() - 1900) + this.getMonth(),
        d: this.getDate()
    };
};

Date.prototype.getSpannedMonths =  function ( d ) {
    if ( !d || !d.is_earlier_than ) { return 1; }
    var months  = 0,
        earlier = this.is_earlier_than( d ),
        first   = earlier ? new Date( this ) : new Date( d ),
        last    = earlier ? d : this,
        first_m = first.getYYYYMM( ),
        last_m  = last.getYYYYMM( );

/*
    do {
        months += 1;
        first.addMonths( 1 );
        first_m = first.getYYYYMM( );
    }while( first_m <= last_m );
*/

    while( first_m <= last_m ) {
        months += 1;
        first.addMonths( 1 );
        first_m = first.getYYYYMM( );
    }

    return months;
};

// ---------------------------------------------------------------------------
//  Calendar object
// ---------------------------------------------------------------------------

ibe.cal3 = {

    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

            this.putDateIntoPage( theDate );

            if ( this.sync_with ) {
                var p    = this.parent;
                var pi   = p.page_interfaces[ this.sync_with.id ];
                var pi_d = pi.if_getDateFromPage( );
                var diff = this.sync_with.date_diff || 0;
                var dt   = ibe.cal3.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 );
                }
            }
        },

        putDateIntoPage: function ( theDate ) {
            // put selected Date into page

            this.fields.date.el.innerHTML = theDate.toIBEString( );
            var ibe_dt                    = theDate.toIDate( );
            this.fields.day.el.value         = ibe_dt.d;
            this.fields.month_year.el.value  = ibe_dt.my;
        },

        if_getDateFromPage: function( ) {
            // read Date from input fields

            // var dottedDate = this.fields.date.el.value;
            // var date = this.convertDottedDDMMYYToDate( dottedDate );

            try {
                var page_d    = this.fields.day.el.value;
                var page_my   = this.fields.month_year.el.value;

                if ( page_d && page_my ) {
                    return this.convertIDateToDate( page_my, page_d );
                }
                else {
                    return null;
                }
            }
            catch( e ) {
                return null;
            }

        },

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

            var ss = DD_MM_YY.split( "." );
            if ( ss.length === 3 ) {
                var y = DD_MM_YY.substr( 6, 2 ) - 0;
                var m = DD_MM_YY.substr( 3, 2 ) - 0;
                var d = DD_MM_YY.substr( 0, 2 ) - 0;
                var 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.

            if ( !this.toHide ) {
                this.toHide = [ ];
                var i_els = xGetElementsByTagName( 'select' );
                for ( var 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

    pi_keys:         [ ],

    lan:             'de',
    num_cols:        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,

    init_quickcells: function( ) {
        var qcs = this.layouter.quick_cells;
        var qd  = new Date( this.startDate );
        var lm  = this.lastValidDate.getYYYYMM( );
        for ( var i = 0; i < qcs.length; i++ ) {
            // if ( qd.is_earlier_than( this.lastValidDate ) ) {
            if ( qd.getYYYYMM( ) <= lm ) {
                qcs[ i ].innerHTML =
                    qd.getMonthName3( ) + ' ' + qd.getFullYear( );
                qcs[ i ].cal3 = {
                    cal: this,
                    d: new Date( qd )
                };
                qcs[ i ].onmouseover = function( e ) {
                    var el = new xEvent( e ).target;
                    el.className = 'cal3QuickCell underline';
                };
                qcs[ i ].onmouseout = function( e ) {
                    var el = new xEvent( e ).target;
                    el.className = 'cal3QuickCell';
                };
                qcs[ i ].onclick = function( e ) {
                    var el = new xEvent( e ).target;
                    el.cal3.cal.startDate = new Date( el.cal3.d );
                    el.cal3.cal.updateCalendar( el.cal3.cal.startDate );
                };
            }
            else {
                xHide( qcs[ i ] );
            }
            qd.addMonths( 1 );
        }
    },

    repaint_quickcells: function( ) {
        var qcs = this.layouter.quick_cells;
        var qd  = new Date( this.firstValidDate );
        for ( var 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 );
        for ( var i = 0; i < this.num_cols; i++ ) {
            this.els.titles[ i ].innerHTML = d.getMonthName( ) + ' ' + d.getYY( );
            d.addMonths( 1 );
        }
    },

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

    },

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

            ( function( ci ) {              // cfg.originators[ i ] is passed
                var key   = ci.date_id,
                    dt_el = xGetElementById( ci.date_id       ),
                    dy_el = xGetElementById( ci.day_id        ),
                    my_el = xGetElementById( ci.month_year_id ),
                    tg_el = xGetElementById( ci.toggle_id     );

                // prototypal inheritance from page_interface
                var pi = object( 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 ( dy_el ) {
                        pi.fields.day = { id: ci.day_id,  el: dy_el };
                    }
                    if ( my_el ) {
                        pi.fields.month_year = { id: ci.month_year_id, el: my_el };
                    }
                    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
                    if ( dy_el && dy_el.value && my_el && my_el.value ) {
                        var ddd = pi.if_getDateFromPage( );
                        pi.if_putDateIntoPage( ddd );
                    }

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

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

                    pi.act = pi.if_getDateFromPage( );

                    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 that = this;

        // 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 );
        }
        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 );
        }
        else {
            this.lastValidDate = new Date( this.firstValidDate );
            this.lastValidDate.addMonths( 14 );
        }

        this.lan = cfg.lan || 'de';

        // Calendar layout
        this.layouter  = object( ibe.cal3Layout );
        this.layouter.set_language( this.lan );
        var qrows = this.firstValidDate.getSpannedMonths( this.lastValidDate );
        qrows = Math.ceil( qrows / 7 );
        this.layouter.set_defaults( { num_cols: 3, num_quick_rows: qrows } );
        this.num_cols  = this.layouter.num_cols;
        this.num_cells = this.layouter.num_cells;
        this.layouter.paint( );
        this.els = {
            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 el = new xEvent( e ).target;
            var sd = that.startDate;
            sd.addMonths( -1 );
            that.updateCalendar( sd );
        }, false );

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

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

        this.updateCalendar( this.firstValidDate );

    },

    isValidLanguage: function( lan ) {
        var cc = ibe.Constants.cal;     // Calendar Constants
        return  lan in cc.monthNames    &&
                lan in cc.monthNames3   &&
                lan in cc.weekdayNames2 ;
    },

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

        this.lan = lan;
        this.layouter.set_language( lan );
        var i = 0;
        for ( var key in this.page_interfaces) {
            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 d = new Date( pDate && pDate.addMonths ? pDate : this.firstValidDate );
        this.lastUpdateDate = new Date( d );
        for ( var i = 0; i < this.num_cols; i++ ) {
            this.updateCalendarTable( i, d );
            d.addMonths( 1 );
        }
    },

    updateCalendarTable: function( ind, date ) {
        // set title
        this.els.titles[ ind ].innerHTML = date.getMonthName( ) + ' ' + date.getYY( );
        // tablecells
        var cells = this.els.cal_cell_tds[ ind ],
            d     = new Date( date ),
            m     = d.getMonth( );
        d.setDate( 1 );                         // first day of month

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

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

            ci.className = cl;
            ci.innerHTML = is_empty ? '' : d.getDate( );
            // ci.title     = d.getDate() + '. ' + d.getMonthName() + ' ' + d.getYY();
            ci.cal3      = { d: new Date( d ), cl: cl };

            if ( is_oot || is_empty ) {
                ci.onclick     = null;
                ci.onmouseover = null;
                ci.onmouseout  = null;
            }
            else {
                var that = this;
                ci.onclick = function( e ) {
                    var el = new xEvent( e ).target;
                    that.getCalendarDate( el.cal3.d );
                };
                ci.onmouseover = function( e ) {
                    var el = new xEvent( e ).target;
                    el.className = 'cal3Cell active';
                };
                ci.onmouseout = function( e ) {
                    var el = new xEvent( e ).target;
                    el.className = el.cal3.cl;
                };
            }

            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 ( ) {
        var page_interface = this.page_interfaces[ this.originator_id ];

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

        xHide( 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;
        var lud = this.lastUpdateDate;
        if ( pd && this.lastUpdateDate && 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 ) - 0;
        var m = s.substr( 4, 2 ) - 0;
        var d = s.substr( 6, 2 ) - 0;
        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;
    },

    convertYMDToDate:    function (y,m,d) {
        // convertYMDToDate(): convert fullyear, month [ 0-11 ], day to date

        var tDate = new Date().normalize( );
        tDate.setFullYear(y);

        tDate.setDate(1);
        tDate.setMonth(m);

        tDate.setDate(d);
        return tDate;
    },

    purge_node: function( d ) {
        var c = d.childNodes;
        if ( c ) {
            for ( var i = 0; i < c.length; i += 1 ) {
                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 );
        @*/
    },

    dummy: false
};

ibe.cal3.syncedDate = function( myDate, yourDate, diff ) {
    if ( !myDate || !yourDate ) { return null; }

    var result;
    if ( ( 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
// ---------------------------------------------------------------------------


