// --------------------------------------------------------------------------
// ...  cbLBP.js
//          Location Box Pair
// --------------------------------------------------------------------------

"use strict";
/*global window, document, alert, unescape, Loc, LocBoxPair, isDef, isStr,
  xEvent, xAddEventListener, xStopPropagation,
  xAddClass, xRemoveClass,
  xGetElementById, xGetElementsByClassName,
  xOffsetX, xOffsetY, xPageX, xPageY, xWidth, xHeight, xMoveTo,
  LocBoxPair */

// --------------------------------------------------------------------------
// ... lib   some general stuff out of lib.js

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

// ... end of lib.js
// --------------------------------------------------------------------------


// --------------------------------------------------------------------------
//
//  cbKS     -   input selection on key events
//
// --------------------------------------------------------------------------
//
//   Synopsis:
//      ...
//      var kk;
//      var locs = [
//          { tlc: 'FRA', loc_name: 'Frankfurt' },
//          { tlc: 'PMI', loc_name: 'Mallorca' },
//          ...
//      ];
//      // locs should be an array of objects mith properties loc_name and tlc
//      function is_onload( ) {
//
//          ... ( lots of very clever stuff )
//          kk = new KS( "myInputId", locs, true );
//      }
//
// --------------------------------------------------------------------------
//  rg  09. 05. 2008
// --------------------------------------------------------------------------

// --------------------------------------------------------------------------
//  ... the KS key selector
//      Params:
//      input id    id of field to be monitored
//      objs        objects to be selected - need the property -loc_name-
//                  and ( maybe later ) perhaps TLCs ???
//      callback    function to be invoked. Will be called after selection
//                  with array of matching objects
//      debug       provides debug alerts, if set to true
// --------------------------------------------------------------------------

function KS ( input_el, locsSet, panel, locBox, debug ) {
    var _KS    = this;

    input_el.orgValue = input_el.value;
    _KS.debug  = !!debug;
    _KS.alert  = function( txt ) { if ( _KS.debug ) { alert( txt ); } };
    _KS.f  = { el: input_el };

    // key control
    _KS.f.el.kc = {
        locsSet: locsSet,
        panel: panel,
        box: locBox
    };

    // add EventListeners to the original fields
    function _KU( ev ) {
        var e = new xEvent( ev );
        xStopPropagation( e );
        // xPreventDefault( e );
        // e.target.kc.box.partnerBox.panel.hide( );
        KS.ku_Add_c( e );
    }

    function _KD( ev ) {
        var e = new xEvent( ev );
        xStopPropagation( e );
        // xPreventDefault( e );
        // e.target.kc.box.partnerBox.panel.hide( );
        KS.kd_Add_c( e );
    }

    _KS.registerEvents =
        function( ) {
            LocBoxPair.set_handler_for( _KS.f.el, 'onkeyup',   _KU );
            LocBoxPair.set_handler_for( _KS.f.el, 'onkeydown', _KD );
        };

    // remove EventListeners - to be called on unload
    _KS.unregisterEvents =
        function( ) {
            _KS.f.el.kc = null;
            _KS.f.el.onkeyup   = null;
            _KS.f.el.onkeydown = null;
        };

    _KS.registerEvents( );

    this.unload = function( ) {
        _KS.unregisterEvents( );
    };

    return _KS;

}

KS.kd_Add_c = function(e) {
    var el = e.target,
        c = e.keyCode,
        locsSet, box, matchlength, tlc_match;

    if (c === KS.KEY.tab) {
        box = el.kc.box;
        locsSet = el.kc.locsSet;

        matchlength = locsSet.matches.length;
        if (matchlength === 1) {
            box.selectLoc(locsSet.matches[0]);
        }
        else {
            if (matchlength > 1) {
                if (el.value.length === 3) {
                    tlc_match = locsSet.testMatches(el.value);
                    if (tlc_match) {
                        box.selectLoc(tlc_match);
                    }
                }
            }
        }
        if (box.ap_tlc_node.value === "") {
            el.value = "";
        }
        el.kc.panel.hide();
    }

};

KS.ku_Add_c = function ( e ) {
    var el = e.target,
        c  = e.keyCode,
        l;

    if ( c === KS.KEY.tab ) {
        el.select( );                       // JS core function
    }
    else if ( c === KS.KEY.enter ) {

        if ( el.kc.panel.panelmode === "list" ) {
            l = el.kc.locsSet.getSelectedMatch( );
            el.kc.box.selectLoc( l );
        }
        if ( el.kc.panel.panelmode === "table" ) {
            l = el.kc.locsSet.getSelectedLoc( );
            el.kc.box.selectLoc( l );
        }
    }
    else if ( c === KS.KEY.left ) {

        if ( el.kc.panel.panelmode === "list" ) {
            el.kc.locsSet.setPreviousMatch( );
        }
        if ( el.kc.panel.panelmode === "table" ) {
            el.kc.locsSet.setPreviousLoc( );
        }
    }
    else if ( c === KS.KEY.up ) {

        if ( el.kc.panel.panelmode === "list" ) {
            el.kc.locsSet.setPreviousMatch( );
        }
        if ( el.kc.panel.panelmode === "table" ) {
            el.kc.locsSet.setPreviousVerticalLoc( );
        }
    }
    else if ( c === KS.KEY.right ) {

        if ( el.kc.panel.panelmode === "list" ) {
            el.kc.locsSet.setNextMatch( );
        }
        if ( el.kc.panel.panelmode === "table" ) {
            el.kc.locsSet.setNextLoc( );
        }
    }
    else if ( c === KS.KEY.down ) {

        if ( el.kc.panel.panelmode === "list" ) {
            el.kc.locsSet.setNextMatch( );
        }
        if ( el.kc.panel.panelmode === "table" ) {
            el.kc.locsSet.setNextVerticalLoc( );
        }
    }
    else if ( c === KS.KEY.del || c === KS.KEY.esc ) {

        el.kc.box.selectLoc( null );
    }
    else if ( c === KS.KEY.back && el.value === '' ) {

        el.kc.box.selectLoc( null );
    }
    else if (el.orgValue === el.value) {

        return;
    }
    else {

        KS.kk_Find_kc( el );
    }
    el.orgValue = el.value;
};

KS.kk_Find_kc = function ( id ) {
    var e = xGetElementById( id );

    if ( e.value === '' ) {
        e.kc.panel.showTable( );
        return;
    }

    e.kc.locsSet.buildMatches( e.value );

};

KS.ts = 500;        // total time in milli seeconds
KS.ss = 100;        // rescan time in milli seconds
KS.debug = 0;

KS.KEY = {
    back:   8,
    tab:    9,
    enter:  13,
    left:   37,
    up:     38,
    right:  39,
    down:   40,
    del:    46,
    esc:    27
};


// --------------------------------------------------------------------------
// ... end of KS
// --------------------------------------------------------------------------


// ----------------------------------------------------------------------------
//  LocsSet
//      set of Locs - actLocs, allLocs, ...
// ----------------------------------------------------------------------------

function LocsSet( parent, locs ) {
    this.box         = parent;          // parent box
    this.locs        = locs;            // table locs
    this.names       = [];              // loc names
    this.loc_names   = [];              // loc names + tlcs
    this.locsByName  = {};              // loc name => Loc obj
    this.locsByTLC   = {};              // loc tlc  => Loc obj
    this.matches     = [];              // list locs
    this.list_index  = 0;               // index into list locs
    this.table_index = 0;               // index into table locs
    this.anz         = { "AP": 0, "RG": 0 };

    if ( locs ) {
        this.setLocs( locs );
    }

    return this;
}

/* ----------------------------------------------------------------------------
 * ...  LocsSet methods for populating with locs and indexing
 * ------------------------------------------------------------------------- */

LocsSet.method( "setLocs", function( locs ) {
    var names      = [],
        loc_names  = [],
        locsByName = {},
        hasTLC     = {},
        sel        = this.box.get_selectedLoc( ),
        pre        = this.box.get_preselectedLoc( ),
        pre_index  = -1,
        sel_index  = -1,
        anz = { 'AP': 0, 'RG': 0 },
        i;

    if ( locs ) {
        for ( i = 0; i < locs.length; i += 1 ) {
            names.push( locs[ i ].name );
            loc_names.push( locs[ i ].loc_name );
            locsByName[ locs[ i ].name ] = locs[ i ];
            hasTLC[ locs[ i ].tlc ] = true;
            anz[ locs[ i ].type ] += 1;

            if ( sel === locs[ i ] ) { sel_index = i; }
            if ( pre === locs[ i ] ) { pre_index = i; }
        }
    }
    // this.table_index = pre_index >= 0 ? pre_index : sel_index;
    this.table_index = sel_index >= 0 ? sel_index : pre_index;
    if ( this.table_index < 0 ) {
        this.table_index = 0;
    }
    this.locs        = locs;
    this.names       = names;
    this.loc_names   = loc_names;
    this.anz.AP      = anz.AP;
    this.anz.RG      = anz.RG;
    this.locsByName  = locsByName;
    this.hasTLC      = hasTLC;

} );

LocsSet.method( "getFirstLoc", function( ) {
    return this.locs[ 0 ] || null;
} );

LocsSet.method( "setIndices", function( loc ) {
    this.setTableIndex( loc );
    // this.setListIndex( loc );
} );

LocsSet.method( "includesTLC", function( tlc ) {
    return this.hasTLC[ tlc ] || false;
} );


/* ----------------------------------------------------------------------------
 * ...  LocsSet methods for List Selection with arrow keys
 * ------------------------------------------------------------------------- */

LocsSet.method( "setListIndex", function( loc ) {
    var i;
    this.list_index = 0;
    for ( i = 0; i < this.locs.length; i = i + 1 ) {
        if ( this.matches[ i ] === loc ) {
            this.list_index = i;
        }
    }
} );

LocsSet.method( "clearMatches", function( ) {
    this.matches = [];
} );

LocsSet.method( "buildMatches", function( str ) {

    var toIgnoreCase = function toIgnoreCase( s ) {
            var sS = '', c, i;
            for ( i = 0; i < s.length; i += 1 ) {
                c   = s.substr( i, 1 );
                sS += '[' + c.toUpperCase( ) + c.toLowerCase( ) + ']';
            }
            return sS;
        },
        ucFirst    = function ucFirst( s ) {
            var first = s.substr( 0, 1 ),
                rest  = s.substr( 1, s.length - 1 );
            return first.toUpperCase( ) + toIgnoreCase( rest );
        },
        val        = ucFirst( str ),
        re         = new RegExp( '\\b' + val ),
        matches    = [],
        sel        = this.box.get_selectedLoc( ),
        pre        = this.box.get_preselectedLoc( ),
        pre_index  = 0,
        sel_index  = 0,
        i,
        newLoc,
        Ergebnis;

    for ( i = 0; i < this.names.length; i += 1 ) {
        Ergebnis = this.loc_names[ i ].match( re );
        if ( Ergebnis ) {
            newLoc = this.locsByName[ this.names[ i ] ];

            if ( sel === newLoc ) { sel_index = matches.length; }
            if ( pre === newLoc ) { pre_index = matches.length; }

            matches.push( newLoc );
        }
    }
    this.matches    = matches;
    this.list_index = pre_index || sel_index;

    this.box.presentMatches( this.matches, this.list_index );

} );

LocsSet.method( "testMatches", function( str ) {
    var tlc = str.toUpperCase(),
        len = this.matches.length,
        i;

    for ( i = 0; i < len; i += 1 ) {
        if ( this.matches[ i ].tlc === tlc ) {
            return this.matches[ i ];
        }
    }

    return null;
} );

LocsSet.method( "setNextMatch", function( ) {
    this.list_index += 1;
    if ( this.list_index >= this.matches.length ) {
        this.list_index = 0;
    }
    this.box.preselectLoc( this.matches[ this.list_index ] );
} );

LocsSet.method( "setPreviousMatch", function( ) {
    this.list_index -= 1;
    if ( this.list_index < 0 ) {
        this.list_index = this.matches.length - 1;
    }
    this.box.preselectLoc( this.matches[ this.list_index ] );
} );

LocsSet.method( "getSelectedMatch", function( ) {
    return ( this.matches[ this.list_index ] );
} );


/* ----------------------------------------------------------------------------
 * ...  LocsSet methods for Table Selection with arrow keys
 * ------------------------------------------------------------------------- */

LocsSet.method( "setTableIndex", function( loc ) {
    var i, ti = 0;
    for ( i = 0; i < this.locs.length; i = i + 1 ) {
        if ( this.locs[ i ] === loc ) {
            ti = i;
        }
    }
    this.table_index = ti;
} );

LocsSet.method( "setNextLoc", function( ) {
    this.table_index += 1;
    if ( this.table_index >= this.locs.length ) {
        this.table_index = 0;
    }
    this.box.preselectLoc( this.locs[ this.table_index ] );
} );

LocsSet.method( "setPreviousLoc", function( ) {
    this.table_index -= 1;
    if ( this.table_index < 0 ) {
        this.table_index = this.locs.length - 1;
    }
    this.box.preselectLoc( this.locs[ this.table_index ] );
} );

LocsSet.method( "setNextVerticalLoc", function( ) {
    var COLS  = this.box.panelCols,
        n_aps = this.anz.AP,
        n_rgs = this.anz.RG,
        n_all = n_aps + n_rgs,
        empty_aps = n_all > n_aps ? ( COLS - n_aps % COLS ) % COLS : 0,
        ti = this.table_index;

    if ( n_aps -COLS <= ti && ti < n_aps ) {
        ti += COLS - empty_aps;
        while ( ti < n_aps ) {
            ti += COLS;
        }
    }
    else {
        ti += COLS;
    }

    if ( ti >= n_all ) {
        ti = ( ti + 1 + empty_aps ) % COLS;
    }

    this.table_index = ti;
    this.box.preselectLoc( this.locs[ this.table_index ] );
} );

LocsSet.method( "setPreviousVerticalLoc", function( ) {
    var COLS  = this.box.panelCols,
        n_aps = this.anz.AP,
        n_rgs = this.anz.RG,
        n_all = n_aps + n_rgs,
        empty_aps = ( COLS - n_aps % COLS ) % COLS,
        empty_rgs = ( COLS - n_rgs % COLS ) % COLS,
        ti = this.table_index;

    if ( 0 <= ti && ti < COLS ) {
        ti += n_all + empty_aps + empty_rgs - 1;
        while ( ti - empty_aps >= n_all  ) {
            ti -= COLS;
        }
        if ( ti >= n_aps + empty_aps ) {
            ti -= empty_aps;
        }
        else {
            while ( ti >= n_aps ) {
                ti -= COLS;
            }
        }
    }
    else if ( ti < n_aps ) {
        ti -= COLS;
    }
    else if ( n_aps <= ti && ti < n_aps + COLS ) {
        ti += empty_aps;
        while ( ti >= n_aps ) {
            ti -= COLS;
        }
    }
    else if ( n_aps + COLS <= ti ) {
        ti -= COLS;
    }

    this.table_index = ti;

    this.box.preselectLoc( this.locs[ this.table_index ] );
} );

LocsSet.method( "getSelectedLoc", function( ) {
    return ( this.locs[ this.table_index ] );
} );


/* ----------------------------------------------------------------------------
 * ...  LocsSet methods for general retrieving
 * ------------------------------------------------------------------------- */

LocsSet.method( "getLocs",       function( ) { return this.locs;  } );
LocsSet.method( "getNames",      function( ) { return this.names; } );
LocsSet.method( "getLocsByName", function( ) { return this.locsByName; } );


// ----------------------------------------------------------------------------
// ... LocsSet ... the End
// ----------------------------------------------------------------------------

// ----------------------------------------------------------------------------
// ... LBPLoc --- prototype object for all LBP Loc objects
// ----------------------------------------------------------------------------
var LBPLoc = {
    init: function( ) {
        this.loc_name = this.type === 'AP' ? this.name + ' ' + this.tlc : this.name;

        this.boxLocNodes = {
            orig: { table: null, list: null },
            dest: { table: null, list: null }
        };

        this.node_attrs = {
            index:    this.seqno,
            tlc:      this.tlc,
            type:     this.type,
            loc_name: this.loc_name,

            selected: false,
            active:   false
        };
    },

    fitting_destinations: function( ) {
        alert( "Note to programmer: Please provide method in Loc --- fitting_destinations");
    },

    fitting_origins: function( ) {
        alert( "Note to programmer: Please provide method in Loc --- fitting_origins");
    },

    fitting_origins_to: function( ) {
        alert( "Note to programmer: Please provide method in Loc --- fitting_origins_to");
    },

    fitting_destinations_from: function( ) {
        alert( "Note to programmer: Please provide method in Loc --- fitting_destinations_from");
    },

    /* ----------------------------------------------------------------------------
     * ...  Loc representation methods
     * ------------------------------------------------------------------------- */

    toString: function( ) {
        return this.name + " [ " + this.tlc + " ] ( " + this.type + " )";
    },

    repr: function( ) {   // representation is language dependant!!!
        var lan    = LocBoxPair.language,
            msgs   = LocBoxPair.Messages[ lan ],
            m      = ( this.type === "AP" ) ? msgs.repr_AP : msgs.repr_RG,
            c_date = function ( d ) {       // turns 'YYYYMMDD' into "DD.MM.YYYY'
                        return ( d.substr( 6, 2 ) + "." +
                                 d.substr( 4, 2 ) + "." +
                                 d.substr( 0, 4 ) );
                     };
        return ( this.from && this.until ) ? m.pre + this.name + m.in1 +
                                             m.in2 + c_date( this.from ) +
                                             m.in3 + c_date( this.until ) + m.end
                :                            this.toString();
    },


    set_mouse_handlers: function( el ) {
        var that = this,
            factory = function( node ) {
                return {
                    _mouseover: function( ) {
                            that.node_attrs.active = true;
                            xAddClass( node, 'active' );
                        },
                    _mouseout:  function( ) {
                            that.node_attrs.active = false;
                            xRemoveClass( node, 'active' );
                        }
                };
            },
            hd = factory( el );
        el.onmouseover = hd._mouseover;
        el.onmouseout  = hd._mouseout;
    },


    set_click_handler: function( el, box ) {
        var factory = function( el, box ) {
                return {
                    _click: function( ) {
                            if ( box && box.selectLocByIndex ) {
                                var ind = el.loc.index;
                                box.selectLocByIndex( ind );
                            }
                        }
                };
            },
            hd = factory( el, box );
        LocBoxPair.set_handler_for( el, 'onclick', hd._click );
    },


    /* ----------------------------------------------------------------------------
     * ...  Loc DOM node creation
     * ------------------------------------------------------------------------- */

    create_box_node: function( box, panelname ) {
        var new_node = document.createElement( "P" ),
            tx       = document.createTextNode( this.name );

        xAddClass( new_node, "location" );
        new_node.appendChild( tx );
        new_node.loc = this.node_attrs;

        // add mouse event listener
        this.set_mouse_handlers( new_node );

        // add click event listener
        this.set_click_handler( new_node, box );

        this.boxLocNodes[ box.o_or_d ][ panelname ] = new_node;

        return new_node;
    },

    get_box_node: function( box, panelname ) {
        if ( this.boxLocNodes[ box.o_or_d ][ panelname ] ) {
            LBPLoc.clean_node( this.boxLocNodes[ box.o_or_d ][ panelname ] );
        }
        return this.create_box_node( box, panelname );
    },

    clean_node: function( el, props ) {
        if ( !el ) {
            return;
        }

        var events = [ "mouseover", "mouseout", "click" ],
            event, prop, i;

        for ( i = 0; i < events.length; i += 1 ) {
            event = "on" + events[ i ];
            if ( el[ event ] ) {
                el[ event ] = null;
            }
        }

        if ( props && props.length && el.hasOwnProperty ) {
            for ( i = 0; i < props.length; i += 1 ) {
                prop = props[ i ];
                if ( el.hasOwnProperty( prop ) ) {
                    delete el[ prop ];
                }
            }
        }
    },

    /* ----------------------------------------------------------------------------
     * ...  Loc visibility control
     * ------------------------------------------------------------------------- */

    scroll_into_view: function( box ) {
        this.boxLocNodes[ box ].table.scrollIntoView( false );
    },

    addClass: function( c, box ) {
        xAddClass( this.boxLocNodes[ box ].list, c );
        xAddClass( this.boxLocNodes[ box ].table, c );
    },

    hide: function( ) { this.addClass( 'hidden' );},

    removeClass: function( c, box ) {
        xRemoveClass( this.boxLocNodes[ box ].list, c );
        xRemoveClass( this.boxLocNodes[ box ].table, c );
    },

    show: function( ) { this.removeClass( 'hidden' );},

    /* ----------------------------------------------------------------------------
     * ...  Constants
     * ------------------------------------------------------------------------- */

    HB: {
        0: "0000", 1: "0001", 2: "0010", 3: "0011", 4: "0100",
        5: "0101", 6: "0110", 7: "0111", 8: "1000", 9: "1001",
        a: "1010", b: "1011", c: "1100", d: "1101", e: "1110", f: "1111",
        A: "1010", B: "1011", C: "1100", D: "1101", E: "1110", F: "1111"
    },

    DBG: false,


    /* ----------------------------------------------------------------------------
     * ...  unload
     * ------------------------------------------------------------------------- */

    unload: function( ) {
        var k1, k2, node, rs;
        for ( k1 in this.boxLocNodes ) {
            if ( this.boxLocNodes.hasOwnProperty( k1 ) ) {
                for ( k2 in this.boxLocNodes[ k1 ] ) {
                    if ( this.boxLocNodes[ k1 ].hasOwnProperty( k2 ) ) {
                        node = this.boxLocNodes[ k1 ][ k2 ];
                        if ( node ) {
                            LBPLoc.clean_node( node, [ "loc" ] );

                            if ( node && node.parentNode ) {
                                //// TEST
                                rs = node.parentNode.removeChild( node );
                                if ( rs !== node ) {
                                    alert( 'Problem removing node for loc ' + this.tlc );
                                }
                            }
                            node = null;
                        }
                    }
                }
            }
        }
    }
};




/* ----------------------------------------------------------------------------
 * ...  the end
 * ------------------------------------------------------------------------- */

function xRemoveChildNodes( node ) {
    var i, dNode;
    for ( i = node.childNodes.length; i > 0; i -= 1 ) {
        dNode = node.childNodes[ i - 1 ];
        node.removeChild( dNode );
    }
    ////TEST
    LBPLoc.clean_node( node );
}


/* ----------------------------------------------------------------------------
 * ...  LocPanel
 * ------------------------------------------------------------------------- */
function LocPanel( ap_name_node, parent, panel ) {
    var myLP = this,
        myBox = parent,
        lan   = LocBoxPair.language,
        headers = LocBoxPair.Messages[ lan ].loc_header,
        hdr_txt = parent.o_or_d === 'orig' ? headers.loc_table_orig :
                  parent.o_or_d === 'dest' ? headers.loc_table_dest : "",
        max_list_height,
        max_table_height,
        showTable,
        factory,
        hd,
        field_width, field_height;

    this.parent = parent;
    this.input_node  = ap_name_node;

    this.mobile_mode = parent.mobile_mode;      // MM LocPanel

    this.header_txt  = panel && panel.header_txt  ? panel.header_txt  : hdr_txt;
    this.close_txt   = panel && panel.close_txt   ? panel.close_txt   : headers.close_win;
    this.airport_txt = panel && panel.airport_txt ? panel.airport_txt : headers.airports;
    this.region_txt  = panel && panel.region_txt  ? panel.region_txt  : headers.regions;
    this.no_table_headers  = panel && panel.no_table_headers  ? panel.no_table_headers  : 0;

    this.container = this.mobile_mode ? xGetElementById( panel.list_div_id ) : null; // MM

    // geometry ...
    this.table_dist  = panel && isDef(panel.table_dist)  ? panel.table_dist  : 2;

    this.ofs_x       = panel && panel.offset_x ? panel.offset_x : 0;
    if ( typeof this.ofs_x === 'string' ) {
        field_width = xWidth( this.input_node );
        this.ofs_x =
            this.ofs_x === 'left'   ? -field_width                  :
            this.ofs_x === 'center' ? -Math.ceil( field_width / 2 ) :
            this.ofs_x === 'right'  ? 0                             : 0;
    }

    this.ofs_y       = panel && panel.offset_y ? panel.offset_y : 0;
    if ( typeof this.ofs_y === 'string' ) {
        field_height = xHeight(  this.input_node );
        this.ofs_y =
            this.ofs_y === 'top'    ? -field_height                  :
            this.ofs_y === 'middle' ? -Math.ceil( field_height / 2 ) :
            this.ofs_y === 'bottom' ? 0                              : 0;
    }


    max_list_height  = panel && panel.max_list_height ?
                       panel.max_list_height : LocPanel.listHeight;
    this.max_list_height  = max_list_height;

    max_table_height = panel && panel.max_table_height ?
                       panel.max_table_height : LocPanel.tableHeight;
    this.max_table_height = max_table_height;

    if ( panel  && panel.hide_ids ) {
        this.hide_ids   = panel.hide_ids;
        this.hide_ids.on_table_view = this.hide_ids.on_table_view || [];
        this.hide_ids.on_list_view  = this.hide_ids.on_list_view  || [];
    }
    else {
        this.hide_ids   = {};
        this.hide_ids.on_table_view =  [];
        this.hide_ids.on_list_view  =  [];
    }

    this.panelmode = null;

    this.hide = function hide( ) {
        if ( !myLP.mobile_mode ) {
            myLP.hideTable( );
        }
        myLP.hideList( );
    };

    showTable = function showTable( ) {
        if ( myLP.mobile_mode ) {
            return;
        }

        var max_body_height;

        myBox.parent.hideOthers();
        myBox.partnerBox.panel.hide( );
        myLP.hideList( );
        // xHideAllOf( myLP.hide_ids.on_table_view );
        myLP.hideCoveredNodesFor( 'table' );
        xRemoveClass( myLP.Table.main, 'hidden' );
        myLP.Table.main.style.zIndex = LocPanel.nextZ = LocPanel.nextZ + 1;

        max_body_height = myLP.max_table_height - xHeight( myLP.Table.head );

        if ( xHeight( myLP.Table.body ) > max_body_height ) {
            xHeight( myLP.Table.panel, max_body_height );
        }
        else {
            xHeight( myLP.Table.panel, xHeight( myLP.Table.body ) + 6 );
        }

        if ( !myLP.parent.container && myLP.parent.is_mobile ) {
            myLP.positionPanels( );
        }

        myLP.panelmode = 'table';

    };
    this.showTable = showTable;

    this.panelCols = parent.panelCols || 4;
    if ( !this.mobile_mode ) {
        this.Table = this.create_Panel( 'table', true );
    }
    this.List  = this.create_Panel( 'list', false );
    this.positionPanels();

    factory = function( el, box ) {
        var node = el;
        return {
            _click:  function( ) {
                    node.select( );
                    showTable( );
                },
            _focus: function( ) {
                    if( !box.selectedLoc  ) {
                        showTable( );
                    }
                },
            _unload: function( ) {
                    // bj_alert( "set_locMode_handlers ... unload" );
                    node.onclick = null;
                    node.onfocus = null;
                }
        };
    };

    hd = factory( this.input_node, parent );
    LocBoxPair.set_handler_for( this.input_node, 'onclick', hd._click );
    LocBoxPair.set_handler_for( this.input_node, 'onfocus', hd._focus );

    this.unload = function( ) {
        if ( this.input_node ) {
            this.input_node.onclick = null;
            this.input_node.onfocus = null;
            this.input_node.onkeyup = null;
        }

        if ( this.Table && this.Table.main ) {
            LocBoxPair.purge_node( this.Table.main );
        }
        if ( this.List && this.List.main ) {
            LocBoxPair.purge_node( this.List.main );
        }
    };

}

LocPanel.method( "positionPanels", function( ) {
    if ( this.mobile_mode ) {
        return;
    }
    var x_off = xWidth(  this.input_node ) + this.table_dist,
        y_off = xHeight( this.input_node ) + this.table_dist,
        c     = this.parent.container,
        posx_fn = function (e) { return c ? xOffsetX(e,c) : xPageX(e); },
        posy_fn = function (e) { return c ? xOffsetY(e,c) : xPageY(e); };

    xMoveTo( this.Table.main, posx_fn( this.input_node ) + x_off + this.ofs_x,
                              posy_fn( this.input_node ) + y_off + this.ofs_y );

    xMoveTo( this.List.main, posx_fn( this.input_node ) + x_off + this.ofs_x,
                             posy_fn( this.input_node ) + y_off + this.ofs_y );
} );

LocPanel.method( "create_Panel", function( pref, fullTable ) {
    var c       = pref || 'panel',
        cv      = c + 'Canvas hidden',
        c_h     = c + 'Header',
        c_ht    = c + 'HeaderText',
        c_hc    = c + 'HeaderClose',
        c_p     = c + 'Panel',
        c_pb    = c + 'Body',
        c_pbh   = c + 'BodyHeader',
        c_pbd   = c + 'BodyData',
        c_cls   = c + 'Close',
        c_txt   = c + 'Text',
        that    = this,

        cr_el = function( tag, cls ) {
            var el = document.createElement( tag.toUpperCase( ) );
            if ( cls ) {
                xAddClass( el, cls );
            }
            return el;
        },

        cr_p = function( cls, txt ) {
            var p = cr_el( "P", cls );
            if ( txt ) {
                p.appendChild( document.createTextNode( txt ) );
            }
            return p;
        },

        cr_span = function( cls, txt ) {
            var p = cr_el( "SPAN", cls );
            if ( txt ) {
                p.appendChild( document.createTextNode( txt ) );
            }
            return p;
        },

        cr_ph = function( cls, txt ) {
            var p = cr_el( "DIV", cls );
            if ( txt ) {
                p.appendChild( document.createTextNode( txt ) );
            }
            return p;
        },


        factory = function( ) {
            return {
                _click: function( ) {
                        if ( that.panelmode === 'list'  ) { that.hideList( );  }
                        if ( that.panelmode === 'table' ) { that.hideTable( ); }
                    }
            };
        },

        // DOM elements
        C       = cr_el( 'div', cv ),       // canvas
        CH      = cr_el( 'div', c_h ),      // canvas header
        CHT     = cr_el( 'div', c_ht ),     // canvas header text
        CHC     = cr_el( 'div', c_hc ),     // canvas header close
        CP      = cr_el( 'div', c_p ),      // canvas panel
        CPB     = cr_el( 'div', c_pb ),     // canvas panel body
        CPB_rg  = cr_el( 'div', c_pbd ),    // canvas panel body regions
        CPB_ap  = cr_el( 'div', c_pbd ),    // canvas panel body airports
        pCP,
        Panel,
        container = this.parent.container || document.body;

    xAddClass( C, 'hidden' );
    LocBoxPair.set_handler_for( C, "onclick", function( ) {
        C.style.zIndex = LocPanel.nextZ = LocPanel.nextZ + 1; } );
    // xAddEventListener( C, 'click', function( ) {
    //    C.style.zIndex = LocPanel.nextZ += 1; }, false );

    C.appendChild( CH );
    C.appendChild( CP);
    CP.appendChild( CPB );


    if ( fullTable ) {

        CH.appendChild( CHT );
        CHT.appendChild( cr_span( c_txt, unescape( this.header_txt ) ) );

        CH.appendChild( CHC );
        pCP = cr_span( c_cls, this.close_txt );
        CHC.appendChild( pCP );
        CHC.onclick = factory( )._click;

        if ( !this.no_table_headers ) {
            CPB.appendChild( cr_ph( c_pbh, this.airport_txt ) );
        }

        CPB.appendChild( CPB_ap );

        if ( this.parent.has_regions ) {
            CPB.appendChild( cr_el( 'HR' ) );
            if ( !this.no_table_headers ) {
                CPB.appendChild( cr_ph( c_pbh, this.region_txt ) );
            }
            CPB.appendChild( CPB_rg );
        }
    }
    else {
        CH.appendChild( cr_p( c_txt, this.header_txt ) );
    }

    if ( this.mobile_mode ) {
        this.container.appendChild( C );
    }
    else {
        container.appendChild( C );
    }

    Panel = {
        main:  C,
        panel: CP,
        head:  CH,
        body:  CPB,
        rgs:   CPB_rg,
        aps:   CPB_ap
    };
    return Panel;

} );


LocPanel.method( "update_locs", function( loc_nodes ) {
    var i,
        loc_type,
        loc_name,
        rgs = [], sorted_rgs,
        aps = [], sorted_aps;

    this.allLocs = loc_nodes;

    this.rgsByName = {};        // regions representation hashmap
    this.apsByName = {};        // airport <p> hashmap
    this.rgNames   = [];        // region names array
    this.apNames   = [];        // airport names array

/* ----------------------------------------------------------------------------
 * ...  region names into rgNames, airport into apNames
 * ------------------------------------------------------------------------- */

    for ( i = 0; i < loc_nodes.length; i += 1 ) {
        loc_type = loc_nodes[i].loc.type;
        loc_name = loc_nodes[i].loc.loc_name;
        if ( loc_type.toUpperCase( ) === 'RG' ) {
            this.rgsByName[ loc_name ] = loc_nodes[ i ];
            this.rgNames.push( loc_name );
        }
        else {
            this.apsByName[ loc_name ] = loc_nodes[ i ];
            this.apNames.push( loc_name );
        }
    }


/* ----------------------------------------------------------------------------
 * ...  sorted regions go into rgs
 * ------------------------------------------------------------------------- */

    sorted_rgs = this.rgNames.sort();
    for(  i = 0; i < sorted_rgs.length; i += 1 ) {
        rgs.push( this.rgsByName[ sorted_rgs[ i ] ] );
    }
    this.rgs = rgs;


/* ----------------------------------------------------------------------------
 * ... sort  --- airports go into aps.
 *      Prepend with DOM objects
 *      1. empty pace keeper DOM object
 *      2. initial capital letter
 * ------------------------------------------------------------------------- */

    sorted_aps = this.apNames.sort();

    for ( i = 0; i < sorted_aps.length; i += 1 ) {
        loc_name = sorted_aps[ i ];
        aps.push( this.apsByName[ loc_name ] );
    }

    this.aps = aps;
} );


LocPanel.method( "paintList", function paintList( arr ) {
    var node = this.List.body,
        new_node,
        i;

    xRemoveChildNodes( node );

    for ( i = 0; i < arr.length; i += 1) {
        new_node = arr[ i ].get_box_node( this.parent, "list" );
        node.appendChild( new_node );
    }

} );


LocPanel.method( "paint_vertical", function( ) {    // not currently used
    function paintTable( arr_in, cols, node ) {
        var transpone_array = function transpone_array( arr, cols ) {

            var lines = Math.ceil( arr.length / cols ),
                new_arr = [],
                i, p,
                new_col,
                new_line;

            for ( i = 0; i < lines * cols; i += 1 ) {
                p = document.createElement( 'P' );
                // p.innerHTML = '';
                new_arr[ i ] = p;
            }

            for ( i = 0; i < arr.length; i += 1 ) {
                new_col  = Math.floor( i / lines );
                new_line = i % lines;
                new_arr[ new_line * cols  + new_col ] =  arr[ i ];
            }

            return new_arr;
        },
        i, j,
        tb, ty, tr, td, ps,
        arr = transpone_array( arr_in, cols );

        if ( node.childNodes ) {
            ps = xGetElementsByClassName( "location", node );
            for ( i = 0; i < ps.length; i += 1 ) {
                LBPLoc.clean_node( ps[ i ], [ "loc" ] );
            }
            xRemoveChildNodes( node );
        }

        tb = document.createElement( 'TABLE' );
        ty = document.createElement( 'TBODY' );

        i = 0;
        while ( i < arr.length ) {
            tr = document.createElement( 'TR' );
            for ( j = 0; j < cols && i < arr.length; j += 1 ) {
                td = document.createElement( 'TD' );
                td.appendChild( arr[ i  ] );
                tr.appendChild( td );
                i += 1;
            }
            ty.appendChild( tr );
        }
        tb.appendChild( ty );
        node.appendChild( tb );
    }
    paintTable( this.rgs, this.panelCols, this.Table.rgs );
    paintTable( this.aps, this.panelCols, this.Table.aps );

    if ( this.input_node.kc ) {
        this.paintList( this.input_node.kc.locsSet.locs );
    }

} );

LocPanel.method( "paint", function( ) {
    function paintTable( arr, cols, node ) {
        var i, j, td, tr, tr_count,
            ps,
            tb = document.createElement( 'TABLE' ),
            ty = document.createElement( 'TBODY' );

        if ( node && node.childNodes ) {
            ps = xGetElementsByClassName( "location", node );
            for ( i = 0; i < ps.length; i += 1) {
                LBPLoc.clean_node( ps[ i ], [ "loc" ] );
            }
            xRemoveChildNodes( node );
        }

        node.appendChild( tb );
        tb.appendChild( ty );

        i = 0;
        tr_count = 0;
        while ( i < arr.length ) {
            tr = document.createElement( 'TR' );
            ty.appendChild( tr );
            tr_count += 1;
            xAddClass( tr, tr_count % 2 ? "lbp_odd" : "lbp_even" );
            for ( j = 0; j < cols && i < arr.length; j += 1 ) {
                td = document.createElement( 'TD' );
                tr.appendChild( td );
                td.appendChild( arr[ i ] );

                i += 1;
            }
        }
    }

    if ( !this.mobile_mode ) {
        paintTable( this.rgs, this.panelCols, this.Table.rgs );
        paintTable( this.aps, this.panelCols, this.Table.aps );
    }

    if ( this.input_node.kc ) {
        this.paintList( this.input_node.kc.locsSet.locs );
    }

} );


LocPanel.method( "hideTable", function( ) {
    this.showCoveredNodesFor( 'table' );
    xAddClass( this.Table.main, 'hidden' );
    if ( this.panelmode === 'table' ) { this.panelmode = null; }
} );


LocPanel.method( "hideList", function( ) {
    this.showCoveredNodesFor( 'list' );
    xAddClass( this.List.main, 'hidden' );
    if ( this.panelmode === 'list' ) { this.panelmode = null; }
} );

LocPanel.method( "showList", function( ) {
    var max_body_height;

    this.parent.partnerBox.panel.hide( );
    if ( !this.mobile_mode ) {
        this.hideTable( );
    }

    //xHideAllOf( this.hide_ids.on_list_view );
    this.hideCoveredNodesFor( 'list' );
    xRemoveClass( this.List.main, 'hidden' );
    this.List.main.style.zIndex = LocPanel.nextZ = LocPanel.nextZ + 1;

    if ( !this.mobile_mode ) {
        max_body_height = this.max_list_height - xHeight( this.List.head );
        if ( xHeight( this.List.body ) > max_body_height ) {
            xHeight( this.List.panel, max_body_height );
        }
        else {
            xHeight( this.List.panel, xHeight( this.List.body ) );
        }
    }

    this.panelmode = 'list';

} );

LocPanel.method( "hideCoveredNodesFor", function( list_or_table ) {
    var i, nodes;

    if ( window.addEventListener ) {
        return;
    }
    nodes = list_or_table === 'table' ?
                this.hide_ids.on_table_view : this.hide_ids.on_list_view;
    if ( !nodes || !nodes.length ) {
        return;
    }
    for ( i = 0; i < nodes.length; i += 1 ) {
        xAddClass( nodes[ i ], LocPanel.hiddenClass );
    }

} );

LocPanel.method( "showCoveredNodesFor", function( list_or_table ) {
    var i, nodes;

    if ( window.addEventListener ) {
        return;
    }
    nodes = list_or_table === 'table' ?
                this.hide_ids.on_table_view : this.hide_ids.on_list_view;
    if ( !nodes || !nodes.length ) {
        return;
    }
    for ( i = 0; i < nodes.length; i += 1 ) {
        xRemoveClass( nodes[ i ], LocPanel.hiddenClass );
    }
} );

LocPanel.nextZ = 99;
LocPanel.tableHeight = 380;
LocPanel.listHeight  = 300;
LocPanel.hiddenClass = 'hidden';


// --------------------------------------------------------------------------
// ... LocPanel     ... the End
// --------------------------------------------------------------------------


// ----------------------------------------------------------------------------
//
//       LocBox         Location Box
//
//          parent      LocBoxPair object
//          o_or_d      "orig" od "dest"
//          cfg_obj     "configuration object
//
//      Synopsis:
//
//          cfg = {                         // Origin
//              tlc:    'FRA',              // default tlc ( optional )
//              input:  {                   // DOM input node ids
//                  container: 'cont',      // opt. id of relative pos container
//                  title: 'Abflughafen',   // Default String
//                  ap:  'apOrigName',      // id of loc_name field
//                  tlc: 'apOrigTlc'        // id of tlc field
//                  is_mobile: true         // will be moved by resize ops
//                                          // table positions will be recalculated
//              },
//              delimit_locs_to: [ 'FRA', 'BRE', 'HAM', 'DUS', 'MUC' ],
//              airports_only: 1,           // don't show regions
//              panel: {
//                  cols: 3,                            // default: 4
//                  max_list_height: 200,               // opt
//                  max_table_height: 300,              // opt
//                  no_table_headers: 1;                // do not create DOM nodes
//                                                      // for 'Flughäfen'
//                  header_txt: '$table_header_orig',   // opt
//                  airport_txt: 'Flughäfen',           // opt
//                  region_txt: 'Länder, Bistümer',     // opt
//                  close_txt: 'Mach das Fenster zu!',  // opt
//                  offset_x: -100,         // left, center, right oder
//                                          // numerische Anzahl der pixel
//                  offset_y: 0,            // top, middle, bottom oder
//                                          // numerische Anzahl der pixel
//                  table_dist: 0,          // default: 2px
//                  hide_ids: {
//                      on_table_view: [],
//                      on_list_view: null
//                  }
//              }
//          };
//
//      origBox = new LocBox( this, "orig", cfg );
// ----------------------------------------------------------------------------

function LocBox( parent, o_or_d, cfg ) {

    var myBox    = this,
        i,
        locs;


    this.parent    = parent;
    this.o_or_d    = o_or_d;            // 'orig' or 'dest'

    this.tlc       = cfg.tlc    || '';

    this.mobile_mode = parent.mobile_mode;  // MM LocBox

    this.ap_name_node = xGetElementById( cfg.input.ap );
    this.ap_tlc_node  = xGetElementById( cfg.input.tlc );
    this.container    = xGetElementById( cfg.input.container );
    if ( this.container ) {
        this.container.style.position = 'relative';
    }
    this.is_mobile    = cfg.input.is_mobile;
    this.ap_only      = cfg.airports_only || 0;
    this.ap_title     = cfg.input.title || this.ap_name_node.title || '';

    this.check_input_content = function() {
            if ( myBox.ap_name_node.value === '' ) {
                myBox.ap_name_node.value = myBox.ap_title;
            }
        };

    xAddEventListener( this.ap_name_node, 'blur', this.check_input_content, false );

    // Try to set off autocompletion
    this.ap_name_node.setAttribute( "autocomplete", "off" );

    this.inp = '';                  // temporarily save ap

    // will be filled by parent using set_partnerBox
    this.partnerBox  = "";
    this.set_partnerBox =
        function ( partner ) { this.partnerBox = partner; };

    this.selectedLoc    = null;
    this.preselectedLoc = null;


    this.locs = this.get_all_parent_locs( cfg );

    // ourLocs  all parent locs which can serve as origin if o_or_d === 'orig'
    //                                  or as destination if o_or_d === 'dest'
    // actLocs  set of locs fitting to mode (OW/RT) and partner location
    this.ourLocs = [ ];
    this.actLocs = new LocsSet( this );

    this.fill_LocsSets( );  // LocBox --- fills this.ourLocs

    locs = this.ourLocs;
    for ( i = 0; i < locs.length; i += 1 ) {
        locs[ i ].get_box_node( this, 'table' );
        locs[ i ].get_box_node( this, 'list'  );
    }

    this.has_regions = this.actLocs.anz.RG > 0;

    try {
        this.panelCols = cfg.panel.cols || 4;
        this.panel     = new LocPanel( this.ap_name_node, this, cfg.panel );
    }
    catch( e ) {
        alert( "Could not create panel for LocBox " + o_or_d + " :" + e );
        this.panel = null;
    }

    if ( this.tlc ) {
        this.set_selectedLoc( this.parent.loc( this.tlc ) );
    }

    this.build_for = ( this.o_or_d === "orig" ) ?
        this.activate_origins_to : this.activate_destinations_from;

    this.ks = new KS( this.ap_name_node, this.actLocs, this.panel, this );

    this.hide = function () {
        this.panel.hide();
    };

    this.unload = function( ) {
        if ( this.ks && this.ks.unload ) {
            this.ks.unload( );
        }
        if ( myBox.panel ) {
            myBox.panel.unload( );
        }
    };

    return this;
}


/* ----------------------------------------------------------------------------
 * ...  LocBox  ... the methods
 * ------------------------------------------------------------------------- */

// ----------------------------------------------------------------------------
// ...  LocBox method   get_all_parent_locs
//          inspect parent's configuration object and put all the locations
//          into an array.
//          return he sorted array
// ----------------------------------------------------------------------------

LocBox.method( "get_all_parent_locs", function ( cfg ) {
    var is_delim = cfg.delimit_locs_to && cfg.delimit_locs_to.length > 0,
        arr      = is_delim ? cfg.delimit_locs_to : this.parent.locations,
        unsorted = [],
        sorted   = [],
        i,
        newLoc,
        runs     = 0;

    for ( i = 0; i < arr.length; i += 1 ) {
        newLoc = is_delim ? this.parent.locByTLC( arr[ i ] ) : arr[ i ];
        if ( newLoc ) {
            unsorted.push( newLoc );
        }
    }

    ////TEST
    // sorted enough for IE6
    /*@cc_on
        return unsorted;
    @*/


    function sortLocs( unsorted_arr ) {
        function byName( a, b ) {
            runs += 1;
            return  a.type  <  b.type ? -1 :
                    a.type  >  b.type ?  1 :
                    a.name  <  b.name ? -1 :
                    a.name  >  b.name ?  1 : 0;
        }

        return unsorted_arr.sort( byName );
    }

    sorted = unsorted.length > 0 ? sortLocs( unsorted ) : sortLocs( arr );

    return sorted;

} );


// ----------------------------------------------------------------------------
// ...  LocBox method   fill_LocsSets
//          take all our locations ( this.locs ),
//          store the possibly fitting ones in this.ourLocs and
//          and fill the LocsSet for this.actLocs
// ----------------------------------------------------------------------------

LocBox.method( "fill_LocsSets", function ( ) {
    var type = this.ap_only ? 'AP' : undefined,
        fits = this.o_or_d === 'dest' ? Loc.fitting_destinations( this.locs, type )
                    :                   Loc.fitting_origins( this.locs, type );

    this.ourLocs = fits;
    this.actLocs.setLocs( fits );        // LocsSet object

    if ( ! this.actLocs.includesTLC( this.get_selectedTLC( ) ) ) {
        this.clear_selectedLoc();
    }

} );

LocBox.method( "reinitLocs", function( ) {

    // this.fill_LocsSets( );

    var tlc =  this.partnerBox.get_selectedTLC( );
    if ( tlc ) {
        this.build_for( this.parent.loc( tlc ) );
    }

    // TODO
    if ( this.panel && this.panel.panelmode === 'list' ) {
        this.panel.hideList( );
    }
    this.paint();
    // TODO
} );

LocBox.method( "build_partner_box", function ( ) {
    this.partnerBox.build_for( this.get_selectedLoc( ) );
} );


// ----------------------------------------------------------------------------
// ...  LocBox method   activate_destinations_from
//                      creates destination list for an origin location
// ----------------------------------------------------------------------------

LocBox.method( "activate_destinations_from", function( loc ) {
    var fits = Loc.fitting_destinations_from( loc, this.ourLocs, this.parent.mode );

    this.actLocs.setLocs( fits );

    if ( ! this.actLocs.includesTLC( this.get_selectedTLC( ) ) ) {
        this.clear_selectedLoc();
    }
} );


// ----------------------------------------------------------------------------
// ...  LocBox method   activate_origins_to
//                      create origins for a possibly given destination loc
// ----------------------------------------------------------------------------

LocBox.method( "activate_origins_to", function( loc ) {
    var fits = Loc.fitting_origins_to( loc, this.ourLocs, this.parent.mode );

    this.actLocs.setLocs( fits );

    if ( ! this.actLocs.includesTLC( this.get_selectedTLC( ) ) ) {
        this.clear_selectedLoc();
    }
} );


// ----------------------------------------------------------------------------
// ... LocBox method   set and get selectedLoc
// ----------------------------------------------------------------------------

LocBox.method( "selectLoc", function( loc ) {
    this.set_selectedLoc( loc );
    this.preselectLoc( loc );

    this.build_partner_box( );
    this.partnerBox.paint( );

    if ( this.actLocs ) {
        this.actLocs.setIndices( this.preselectedLoc );
        this.actLocs.clearMatches();
    }

    if ( this.panel ) {
        this.panel.hide( );
        if ( !loc ) {
            this.panel.showTable( );
        }
    }

    if ( loc ) {
        this.ap_name_node.select( );
    }
} );

LocBox.method( "selectLocByIndex", function( loc_index ) {
    var loc = this.parent.locations[ loc_index ];
    this.selectLoc( loc );
} );

LocBox.method( "get_selectedLoc", function( ) {
    return this.selectedLoc;
} );

LocBox.method( "get_selectedTLC", function ( ) {
    return this.selectedLoc ? this.selectedLoc.tlc : '';
} );

LocBox.method( "clear_selectedLoc", function( ) {
    this.ap_name_node.value = '';
    this.ap_tlc_node.value  = '';

    if ( this.selectedLoc ) {
        this.selectedLoc.removeClass( 'selected', this.o_or_d );
    }
    this.selectedLoc = null;
} );

LocBox.method( "set_selectedLoc", function ( loc ) {
    if ( loc ) {
        this.ap_name_node.value = loc.name;
        this.ap_tlc_node.value  = loc.tlc;
        if (this.ks && this.ks.f && this.ks.f.el ) {
            this.ks.f.el.orgValue = loc.name;
        }
        if ( this.selectedLoc ) {
            this.selectedLoc.removeClass( 'selected', this.o_or_d );
        }
        loc.addClass('selected', this.o_or_d);
        this.selectedLoc = loc;
    }
    else {
        this.clear_selectedLoc( );
    }
} );


// ----------------------------------------------------------------------------
// ... LocBox method   set and get preselectedLoc
// ----------------------------------------------------------------------------


LocBox.method( "preselectLoc", function( loc, no_scroll ) {
    if ( this.preselectedLoc ) {
        this.preselectedLoc.removeClass( 'preselected', this.o_or_d );
    }

    var myLoc = loc || this.actLocs.getFirstLoc( );

    if ( myLoc ) {
        this.preselectedLoc = myLoc;
        if ( !no_scroll ) {
            this.preselectedLoc.scroll_into_view( this.o_or_d );
        }
        this.preselectedLoc.addClass( 'preselected', this.o_or_d );
    }
} );

LocBox.method( "preselectFirstLoc", function( ) {
    this.preselectLoc( );
} );

LocBox.method( "preselectLocByIndex", function( loc_index ) {
    var loc = this.parent.locations[ loc_index ];
    this.preselectLoc( loc );
} );

LocBox.method( "get_preselectedLoc", function( ) {
    return this.preselectedLoc;
} );


// ----------------------------------------------------------------------------
// ... LocBox method   paint
// ----------------------------------------------------------------------------

LocBox.method( "paint", function( ) {

    var loc_nodes = [],                             // DOM nodes array
        paintLocs = this.actLocs.getLocs( ),        // Locs array
        startLoc,
        i,
        p;

    for ( i = 0; i < paintLocs.length; i += 1 ) {
        p = paintLocs[ i ].get_box_node( this, "table" );
        loc_nodes.push( p );
        if ( this.get_selectedTLC( ) === paintLocs[ i ].tlc ) {
            this.set_selectedLoc( paintLocs[ i ] );
        }
    }

    if ( this.panel ) {
        this.panel.update_locs( loc_nodes );
        this.panel.paint();
    }

    startLoc = this.get_selectedLoc( );
    this.preselectLoc( startLoc, true );
    this.actLocs.setTableIndex( startLoc );

    this.check_input_content();

} );


LocBox.method( "presentMatches", function( matches ) {

    if ( matches.length > 0 ) {
        if ( this.get_selectedLoc( ) ) {
            this.save_ap( );
            this.selectLoc( null );
            this.restore_ap( );
        }

        this.panel.paintList( matches );
        this.preselectLoc( matches[ 0 ] );
        this.panel.showList( );
    }
    else {
        if ( this.mobile_mode ) {
            this.panel.hideList();
        }
        this.panel.showTable( );
    }

} );


LocBox.method( "save_ap", function( )    { this.inp = this.ap_name_node.value; } );
LocBox.method( "restore_ap", function( ) { this.ap_name_node.value = this.inp; } );
LocBox.method( "get_tlc_node", function( ) { return this.ap_tlc_node; } );

/* ----------------------------------------------------------------------------
 * ...	LocBox  --- the end
 * ------------------------------------------------------------------------- */


// ---------------------------------------------------------------------------
//
//  LocBoxPair        Location Box Pair
//
// ---------------------------------------------------------------------------
//  Synopsis:
//
//      var minimal_cfg = {                 // ... these are neede
//          locations: Locations,           // Array of Loc Objects
//          mode: {
//              rt_id: 'mode_rt',           // roundtrip radio id
//              ow_id: 'mode_ow',           // oneway radio id
//              mode_name: 'mode'           // common html loc_name
//          },
//          orig: {                         // Origin
//              input:  {                   // DOM input node ids
//                  ap:  'apOrigName',      //  id of loc_name field
//                  tlc: 'apOrigTlc'        //  id of tlc field
//              },
//          dest: {                         // Destination
//              input:  {                   // DOM input node ids
//                  ap:  'apDestName',      // id of input DOM node
//                  tlc: 'apDestTlc'        // id of tlc field
//              }
//          }
//      };
//
//
//      var cfg = {
//          language: 'es'                  // Default is 'de'
//          locations: Locations,           // Array of Loc Objects
//          loc_data: '',                   // Array of Loc configuration objects
//                                          // to be passed to Loc.buildLocations
//          hideOthers: function( ) { ... },// Function to hide other dynymic objects,
//          mobile_mode: true,              // changes general settings for mobile mode display
//          mode: {
//              rt_id: 'mode_rt',           // roundtrip radio id
//              ow_id: 'mode_ow',           // oneway radio id
//              mode_name: 'mode'           // common html loc_name
//          },
//          orig: {                         // Origin
//              tlc:    'FRA',              // default tlc ( optional )
//              input:  {                   // DOM input node ids
//                  container: 'cont',      // opt. id of relative pos container
//                  title: 'Abflughafen',   //  Default String
//                  ap:  'apOrigName',      //  id of loc_name field
//                  tlc: 'apOrigTlc'        //  id of tlc field
//                  is_mobile: true         // will be moved by resize ops
//              },
//              delimit_locs_to: [ 'FRA', 'BRE', 'HAM', 'DUS', 'MUC' ],
//              airports_only: 1,           // don't show regions
//              panel: {
//                  list_div_id: 'origin_list',
//                  cols: 4,                                // default: 4
//                  max_list_height: 200,
//                  max_table_height: 300,
//                  no_table_headers: 1;                    // do not create DOM nodes for 'Flughäfen'
//                  header_txt: '$table_header_orig',       // opt
//                  airport_txt: 'Flughäfen',               // opt
//                  region_txt: 'Länder, Bistümer, ...',    // opt
//                  close_txt: 'Mach das Fenster zu!',      // opt
//                  offset_x: -100,                         // left, center, right oder numerische Anzahl der pixel
//                  offset_y: 0,                            // top, middle, bottom oder numerische Anzahl der pixel
//                  table_dist: 0,                          // default: 2px
//                  hide_ids: {
//                      on_table_view: [],
//                      on_list_view: null
//                  }
//              }
//          },
//          dest: {                         // Destination
//              tlc:    'PMI',              // default TLC ( optional )
//              input:  {                   // DOM input node ids
//                  container: 'cont',      // opt. id of relative pos container
//                  title: 'Zielflughafen', // Default String
//                  ap:  'apDestName',      // id of input DOM node
//                  tlc: 'apDestTlc',       // id of tlc field
//                  is_mobile: false        // will be moved by resize ops
//              },
//              delimit_locs_to: [ 'PMI', 'IBZ', 'ANC', 'LAS', 'MRU', 'AYT' ],
//              airports_only: 0,           // show regions, this is default behaviour
//              panel: {
//                  list_div_id: 'destionation_list',
//                  cols: 3,
//                  max_list_height: 200,
//                  max_table_height: 300,
//                  header_txt: '$table_header_dest',
//                  airport_txt: 'Flughäfen',               // opt
//                  region_txt: 'Länder, Bistümer, ...',    // opt
//                  close_txt: 'Mach das Fenster zu!',      // opt
//                  offset_x: 0,
//                  offset_y: 0,
//                  hide_ids: {
//                      on_table_view: [],
//                      on_list_view: []
//                  }
//              }
//          }
//      };
//
//
//      var lbp = new LocBoxPair( cfg );
//
// ---------------------------------------------------------------------------
//      rg
// ---------------------------------------------------------------------------

var LocBoxPair = function LocBoxPair ( cfg ) {

    var mode,
        i,
        ow_el;

    LocBoxPair.language = cfg.language || 'de';

    //  1. if cfg.static_mode defined, take cfg.static_mode.value
    //  2. else, if mode defined, inspect the dom
    //  3. else take 'RT' as default

    if ( cfg.static_mode && cfg.static_mode.value) {
        this.mode = this.static_mode = cfg.static_mode.value;
    }
    else {
        if ( cfg.mode ) {
            mode = 'RT';                            // default: return flight
            if ( cfg.mode.ow_id ) {
                ow_el = xGetElementById( cfg.mode.ow_id );
                if ( ow_el && ow_el.checked ) {
                    mode = 'OW';
                }
            }
            this.mode = mode;
            this.static_mode = null;
        }
        else {
            this.mode = this.static_mode = 'RT';
        }
    }

    // array of Loc objects ( --> locations )
    //       or location data ( --> loc_data )

    this.locations  = cfg.locations || Loc.buildLocations( cfg.loc_data );
    this.hideOthers = cfg.hideOthers || function() { };

    this.mobile_mode = cfg.mobile_mode || false;    // MM LocBoxPair

    // Build loc index for reverse lookup
    this.locIndex   = {};
    for ( i = 0; i < this.locations.length; i += 1 ) {
        this.locIndex[ this.locations[ i ].tlc ] = this.locations[ i ].seqno;
    }

    // Initialize Boxes ( Origin and Destination )
    try {
        this.origBox = new LocBox( this, "orig", cfg.orig );
        this.destBox = new LocBox( this, "dest", cfg.dest );
        this.origBox.set_partnerBox( this.destBox );
        this.destBox.set_partnerBox( this.origBox );

        this.init_boxes( cfg.orig.tlc, cfg.dest.tlc );

        this.origBox.check_input_content();
        this.destBox.check_input_content();
    }
    catch( ex1 ) {
        alert( "LBP - Could not create Location Boxes " + ex1 );
        return null;
    }

    // Setup Loc Mode Switch
    if ( !this.static_mode ) {
        try {
            this.node_mode_ow = xGetElementById( cfg.mode.ow_id );
            this.node_mode_rt = xGetElementById( cfg.mode.rt_id );

            //// TEST only roundtrips
            this.set_locMode_handlers( this.node_mode_ow, 'OW' );
            this.set_locMode_handlers( this.node_mode_rt, 'RT' );
        }
        catch( ex2 ) {
            alert( "LBP - Could not create Loc Mode Selection " + ex2 );
            return null;
        }
    }

    return this;
};

LocBoxPair.method( "unload", function ( ) {
    // Throw everything away. This is a special service for the IE,
    // which has an own understanding of garbage collection.

/*@cc_on

    var i;

    try {
        this.origBox.unload( );
    }
    catch( e ) {
        alert( "LBP --- error unloading origBox object " + e );
    }

    try {
        this.destBox.unload( );
    }
    catch( e ) {
        alert( "LBP --- error unloading destBox object " + e );
    }

    try {
        for ( i = 0; i < this.locations.length; i += 1 ) {
            this.locations[ i ].unload( );
        }
    }
    catch( e ) {
        alert( "LBP --- error unloading location objects " + e );
    }
@*/

} );

//--------------------------------------------------------------------------
//... LocBoxPair   hide
//--------------------------------------------------------------------------


LocBoxPair.method( "hide", function( ) {
    this.origBox.hide();
    this.destBox.hide();
} );

LocBoxPair.method( "loc", function ( tlc ) {
    return  this.locByTLC( tlc );
} );

LocBoxPair.method( "locByTLC", function ( tlc ) {
    return  tlc && isDef( this.locIndex[ tlc ] ) ?
                this.locations[ this.locIndex[ tlc ] ] : null;
} );

LocBoxPair.method( "locByIndex", function ( idx ) {
    return idx && isDef( this.locations[ idx ] ) ?
                this.locations[ idx ] : null;
} );

LocBoxPair.method( "getOrigNode", function ( ) {
    return this.origBox.get_tlc_node( );
} );

LocBoxPair.method( "getDestNode", function ( ) {
    return this.destBox.get_tlc_node( );
} );

LocBoxPair.method( "getLocMode", function ( ) {
    return this.mode;
} );

/* ----------------------------------------------------------------------------
 * ...  Check for valid location tlc
 * ------------------------------------------------------------------------- */

LocBoxPair.method( "check_tlc", function ( tlc ) {
    return( typeof( this.locIndex[ tlc  ] ) !== 'undefined' ? true : false );
} );


/* ----------------------------------------------------------------------------
 * ...  Initialize boxes to passed values of destination and/or origin TLC
 *      ...  mainly delegated to the boxes themselves
 * ------------------------------------------------------------------------- */

LocBoxPair.method( "init_boxes", function( otlc, dtlc ) {

    if ( this.check_tlc( otlc ) ) {
        this.destBox.activate_destinations_from( this.locByTLC( otlc ) );
    }
    this.destBox.paint();
    // this.destBox.preselectFirstLoc( );

    if ( this.check_tlc( dtlc ) ) {
        this.origBox.activate_origins_to( this.locByTLC( dtlc ) );
    }
    this.origBox.paint();
    // if ( !this.origBox.get_selectedLoc( ) ) {
    //     this.origBox.preselectFirstLoc( );
    // }

} );


/* ----------------------------------------------------------------------------
 * ...  Reinit box locations on locmode switch ( 'OW' / 'RT' )
 *      ...  mainly delegated to the boxes themselves
 * ------------------------------------------------------------------------- */

LocBoxPair.method( "setLocmode", function( m ) {
    this.mode = m && isStr( m ) && m.toUpperCase() === 'OW' ? 'OW' : 'RT';

    this.origBox.reinitLocs();
    this.destBox.reinitLocs();
} );


LocBoxPair.method( 'set_locMode_handlers', function( el, p ) {
    var that = this,
        factory = function( node, para ) {
            return {
                _click:  function( ) {
                        // bj_alert( "set_locMode_handlers ... click" );
                        that.setLocmode( para );
                    },
                _unload: function( ) {
                        // bj_alert( "set_locMode_handlers ... unload" );
                        LocBoxPair.purge_node( node );
                    }
            };
        },
        hd = factory( el, p );

    LocBoxPair.set_handler_for( el, "onclick",  hd._click  );
    // LocBoxPair.set_handler_for( el, "onunload", hd._unload );

} );

// --------------------------------------------------------------------------
// ... LocBoxPair   ... the End
// --------------------------------------------------------------------------

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

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

LocBoxPair.Messages = {
    de: {
        ap: {
            all_origs: "Alle Abflughäfen",
            all_dests: "Alle Zielflughäfen"
        },
        repr_AP: {
            pre: "Die Fluege von/nach ",
            in1: " sind zur Zeit buchbar ",
            in2: " vom ",
            in3: " bis zum ",
            end: "."
        },
        repr_RG: {
            pre: "Die Fluege aus der / in die Region ",
            in1: " sind zur Zeit buchbar ",
            in2: " vom ",
            in3: " bis zum ",
            end: "."
        },
        loc_header: {
            airports: "Flughäfen",
            regions: "Länder/Regionen",
            close_win: "Fenster schließen",
            loc_table_orig: "Bitte wählen Sie ein Land, eine Region oder einen Ort für Ihren Abflughafen aus.",
            loc_table_dest: "Bitte wählen Sie ein Land, eine Region oder einen Ort für Ihren Zielflughafen aus.",
            loc_list_orig: "Bitte wählen Sie ein Land, eine Region oder einen Ort für Ihren Abflughafen aus.",
            loc_list_dest: "Bitte wählen Sie ein Land, eine Region oder einen Ort für Ihren Zielflughafen aus."
        }
    },

    en: {
        ap: {
            all_origs: "All Departure Airports",
            all_dests: "All Departure Airports"
        },
        repr_AP: {
            pre: "The airport ",
            in1: " is operating ",
            in2: " from ",
            in3: " until ",
            end: "."
        },
        repr_RG: {
            pre: "The airports of region ",
            in1: " are operating ",
            in2: " from ",
            in3: " until ",
            end: "."
        },
        loc_header: {
            airports: "airports",
            regions: "countries/regions",
            close_win: "close window",
            loc_table_orig: "Please choose a country, a region or a place for your departure airport.",
            loc_table_dest: "Please choose a country, a region or a place for your airport of destination.",
            loc_list_orig: "Please choose a country, a region or a place for your departure airport.",
            loc_list_dest: "Please choose a country, a region or a place for your airport of destination."
        }
    },

    es: {
        ap: {
            all_origs: "Aerop. de Salida",
            all_dests: "Aerop. de Destino"
        },
        repr_AP: {
            pre: "Aeropuerto ",
            in1: " opera ",
            in2: " del ",
            in3: " al ",
            end: "."
        },
        repr_RG: {
            pre: "Los aeropuertos de la región ",
            in1: " opera ",
            in2: " del ",
            in3: " al ",
            end: "."
        },
        loc_header: {
            airports: "aeropuertos",
            regions: "países / regiones",
            close_win: "Cerrar ventana",
            loc_table_orig: "Por favor elija un país, una región o una ciudad para su aeropuerto de salida.",
            loc_table_dest: "Por favor elija un país, una región o una ciudad para su aeropuerto de destino. ",
            loc_list_orig: "Por favor elija un país, una región o una ciudad para su aeropuerto de salida.",
            loc_list_dest: "Por favor elija un país, una región o una ciudad para su aeropuerto de destino. "
        }
    },

    fr: {
        ap: {
            all_origs: "Tous les aéroports de départ",
            all_dests: "Tous les aéroports d'arrivée"
        },
        repr_AP: {
            pre: "Les vols au départ de/à destination de",
            in1: "sont  actuellement disponibles à la réservation",
            in2: "du",
            in3: " jusqu'au",
            end: "."
        },
        repr_RG: {
            pre: "Les vols au départ de/à destination de cette région",
            in1: " sont actuellement disponibles à la réservation",
            in2: "du",
            in3: "jusqu'au",
            end: "."
        },
        loc_header: {
            airports: "aéroports",
            regions: "Pays/région",
            close_win: "Fermer la fenêtre",
            loc_table_orig: "Veuillez choisir le pays, la région ou le lieu de votre  aéroport de départ",
            loc_table_dest: "Veuillez choisir le pays, la région ou le lieun de votre aéroport d'arrivé",
            loc_list_orig: "Veuillez choisir le pays, la région ou le lieu de votre  aéroport de départ",
            loc_list_dest: "Veuillez choisir le pays, la région ou le lieun de votre aéroport d'arrivé"
        }
    },

    it: {
        ap: {
            all_origs: "Tutti gli aeroporti di partenza ",
            all_dests: "Tutti gli aeroporti di arrivo "
        },
        repr_AP: {
            pre: "I voli da/a ",
            in1: " sono al momento prenotabili ",
            in2: " dal ",
            in3: " fino al ",
            end: "."
        },
        repr_RG: {
            pre: "I voli dalla/per la regione ",
            in1: " sono al momento prenotabili ",
            in2: " dal ",
            in3: " fino al ",
            end: "."
        },
        loc_header: {
            airports: "Aeroporti",
            regions: "Paesi/Regioni",
            close_win: "Chiudere",
            loc_table_orig: "Si prega di scegliere un paese, una regione o un luogo per l'aeroporto di partenza.",
            loc_table_dest: "Si prega di scegliere un paese, una regione o un luogo per l'aeroporto di arrivo.",
            loc_list_orig: "Si prega di scegliere un paese, una regione o un luogo per l'aeroporto di partenza.",
            loc_list_dest: "Si prega di scegliere un paese, una regione o un luogo per l'aeroporto di arrivo."
        }
    },

    pl: {
        ap: {
            all_origs: "Alle Abflughäfen",
            all_dests: "Alle Zielflughäfen"
        },
        repr_AP: {
            pre: "Loty z / do ",
            in1: " mozna aktualnie bukowac",
            in2: " od ",
            in3: " do ",
            end: "."
        },
        repr_RG: {
            pre: "Loty z / do regionu ",
            in1: "mozna aktualnie bukowac",
            in2: " od ",
            in3: " do ",
            end: "."
        },
        loc_header: {
            airports: "Porty lotnicze",
            regions: "Kraje/regiony",
            close_win: "Zamkniecie okna",
            loc_table_orig: "Prosze wybrac kraj, region lub miejscowosc portu wylotowego.",
            loc_table_dest: "Prosze wybrac kraj, region lub miejscowosc portu docelowego.",
            loc_list_orig: "Prosze wybrac kraj, region lub miejscowosc portu wylotowego.",
            loc_list_dest: "Prosze wybrac kraj, region lub miejscowosc portu docelowego."
        }
    },

     pt: {
        ap: {
            all_origs: "Alle Abflughäfen",
            all_dests: "Alle Zielflughäfen"
        },
        repr_AP: {
            pre: "De momento, os voos de/para ",
            in1: " poderăo ser marcados para as datas situadas ",
            in2: " entre ",
            in3: " e ",
            end: "."
        },
        repr_RG: {
            pre: "De momento, os voos a partir da/para a regiăo ",
            in1: "poderăo ser marcados para as datas situadas ",
            in2: " entre ",
            in3: " e ",
            end: "."
        },
        loc_header: {
            airports: "Aeroportos",
            regions: "Países/Regiőes",
            close_win: "Fechar janela",
            loc_table_orig: "Escolha um país, uma regiăo ou um local para o seu aeroporto de partida.",
            loc_table_dest: "Escolha um país, uma regiăo ou um local para o seu aeroporto de destino.",
            loc_list_orig: "Escolha um país, uma regiăo ou um local para o seu aeroporto de partida.",
            loc_list_dest: "Escolha um país, uma regiăo ou um local para o seu aeroporto de destino."
        }
    },

    tr: {
        ap: {
            all_origs: "Bütün kalkış havaalanları",
            all_dests: "Bütün varış havaalanları"
        },
        repr_AP: {
            pre: "",
            in1: " havalimanına ya da havalimanından yapılacak uçuşlar ",
            in2: "",
            in3: "  den ",
            end: " tarihine kadar satın alınabilmektedir."
        },
        repr_RG: {
            pre: "",
            in1: " bölgesine ya da bölgesinden yapılacak uçuşlar ",
            in2: "",
            in3: " tarihinden ",
            end: " tarihine kadar satın alınabilmektedir."
        },
        loc_header: {
            airports: "bölgesine",
            regions: "Ülkeler/Bölgeler",
            close_win: "Pencereyi kapat",
            loc_table_orig: "Lütfen kalkış havalimanınız için bir ülke, bölge veya şehir seçin.",
            loc_table_dest: "Lütfen varış havalimanınız için bir ülke, bölge veya şehir seçin.",
            loc_list_orig: "Lütfen kalkış havalimanınız için bir ülke, bölge veya şehir seçin.",
            loc_list_dest: "Lütfen varış havalimanınız için bir ülke, bölge veya şehir seçin."
        }
    }
};

