/* Main functions */
var cntStopOver = 0;        // counter for stopovers


/* Code for Google Maps interaction */
var map = null;             // the map
var gdir = null;            // the directions object
var geocoder = null;        // the geocoder
var clickedPixel = null;    // pixel clicked in a map (used for displaying context menu)
var markers = new Array();  // map overlays for starting, destination point and stop overs
var action = null;          // the type of element (start, stop, stopover) currently modified
var markerLabels = new Array();   // marker labels
var icons = new Array();    // the icons used in the map (keys: start, stopover, destination, cargo, tour)
var mapInitializationCallbacks = new Array(); // callbacks after map initialization

// some JavaScript internal constants
var MARKER_IMPORTANCE_SEARCH = 10;
var MARKER_IMPORTANCE_TOUR_POINT = 100;




function getElement(elementId) 
{
    return (document.getElementById) ? document.getElementById(elementId) : document.all.elementId;
}

function showMsg(msg)
{
    showLightbox("messagePane");
    var text = getElement("messagePaneText");
    text.innerHTML = msg;
    messagePaneFadeOut();
}

function messagePaneFadeOut() 
{
    window.setTimeout(function() {
        hideLightbox("messagePane");
    }, 8000);
}

function backToList(target, msg)
{
    if(target === undefined || target === '') {
        target = 'AllVehicles';
    }
    if(msg === undefined) {
        msg = '';
    }
    if(msg !== '') {
        msg = '?_messages=' + encodeURIComponent(msg);
    }
    window.open("/cpc/"+target+".ftl" + msg, "_self");
}

function getScrollPos() 
{
    return { scrollTop: (window.pageYOffset || document.documentElement.scrollTop || 0),  
        scrollLeft: (window.pageXOffset || document.documentElement.scrollLeft || 0)};
}


/** 
 * Dumps the given data, like print_r in PHP
 */
function print_r(arr, level) {
    var dumped_text = "";
    if(!level) { level = 0; }
    
    //The padding given at the beginning of the line.
    var level_padding = "";
    for(var j=0;j<level+1;j=j+1) {level_padding += "    "; }
    
    if(typeof(arr) == 'object') { //Array/Hashes/Objects 
        for(var item in arr) {
            var value = arr[item];
            
            if(typeof(value) == 'object') { //If it is an array,
                dumped_text += level_padding + "'" + item + "' ...\n";
                dumped_text += print_r(value,level+1);
            } else {
                dumped_text += level_padding + "'" + item + "' => \"" + value + "\"\n";
            }
        }
    } else { //Stings/Chars/Numbers etc.
        dumped_text = "===>"+arr+"<===("+typeof(arr)+")";
    }
    return dumped_text;
}

/** 
 * Searches for the given key (case-sensitive) in a nested associated array.
 * Returns the corresponding value or null if it is not found.
 */
function searchNestedArray(arr, key, level) {
    if (!level) {level = 0;}
    
    if (typeof(arr) == 'object') {  
        for (var item in arr) {
            var value = arr[item];
            if (item == key) {
                return value;
            }
            
            if (typeof(value) == 'object') { 
                var result = searchNestedArray(value, key, level+1);
                if (result) {return result;}
            }
        }
    }
    return null;
}

/**
 * These callbacks will be called when the Google Map is initialized
 */
function registerMapInitializationCallback(callback) {
    if (!callback)  return;
    mapInitializationCallbacks.push(callback);
}

/**
 * Display the next stopover element
 */
function addStopOver(tagClass) {    
    if (cntStopOver >= config.maxStopovers) {
        return;
    }
    cntStopOver = cntStopOver + 1;
    
    // has this stopover been used before?
    var stopOverText, newDiv;
    if (getElement("stopover" + cntStopOver + "_div")) {
        getElement("stopover" + cntStopOver + "_div").style.display="block";
    } else {
        stopOverText = getElement("templateStopover").innerHTML;
        stopOverText = stopOverText.replace(/\(_number_\)/g, "" + cntStopOver);
        newDiv = document.createElement("div");
        newDiv.id = "stopover" + cntStopOver + "_div";
        newDiv.className = tagClass;
        newDiv.innerHTML = stopOverText;  
        getElement("marker_stopover").appendChild(newDiv);
    }
    
    if (cntStopOver == config.maxStopovers) {
        getElement("stopover_adder").style.display="none";
        getElement("mapContextMenuStopover").style.display="none";
    }
}

/**
 * Remove the selected stopover 
 * and copy the content from the next fields. 
 */
function removeStopOver(number, callback) {
    if (cntStopOver === 0) {
        return;
    }
    if (cntStopOver == config.maxStopovers) {
        getElement("stopover_adder").style.display="";     
        getElement("mapContextMenuStopover").style.display="";     
    }
    
    removeMarker("stopover" + number);
    getElement("stopover" + cntStopOver + "_div").style.display = "none";
    for (var i=number+1 ; i<=cntStopOver ; i=i+1) {
        getElement("stopover" + (i-1) + "_street").value = getElement("stopover" + i + "_street").value; 
        getElement("stopover" + (i-1) + "_postalcode").value = getElement("stopover" + i + "_postalcode").value; 
        getElement("stopover" + (i-1) + "_city").value = getElement("stopover" + i + "_city").value; 
        getElement("stopover" + (i-1) + "_country").value = getElement("stopover" + i + "_country").value; 
        getElement("stopover" + (i-1) + "_radius").value = getElement("stopover" + i + "_radius").value;        
        getElement("stopover" + (i-1) + "_longitudeValue").value = getElement("stopover" + i + "_longitudeValue").value;        
        getElement("stopover" + (i-1) + "_latitudeValue").value = getElement("stopover" + i + "_latitudeValue").value;
        markers["stopover" + (i-1)] = markers["stopover" + i];         
    }
    getElement("stopover" + cntStopOver + "_street").value = "";
    getElement("stopover" + cntStopOver + "_postalcode").value = "";
    getElement("stopover" + cntStopOver + "_city").value = "";
    getElement("stopover" + cntStopOver + "_country").value = "";
    getElement("stopover" + cntStopOver + "_radius").value = "";
    getElement("stopover" + cntStopOver + "_longitudeValue").value = "";
    getElement("stopover" + cntStopOver + "_latitudeValue").value = "";
    markers["stopover" + cntStopOver] = null;
    cntStopOver = cntStopOver - 1;
    setDirections();
    
    if (callback) {
        callback();
    }
}

/**
 * Remove all stopovers 
 */
function removeAllStopOvers() {
    while (cntStopOver > 0) {
        removeStopOver(cntStopOver);
    }
}

/**
 * Shows the popup window for the given element
 */
function showPopup(element) {
    _togglePopup(element, "visible");
}
/**
 * Hides the popup window for the given element
 */
function hidePopup(element) {
    _togglePopup(element, "hidden");
}
/**
 * Toggles the visibility for the given popup element name
 */
function _togglePopup(element, state) {
    var infobox = getElement(element);  
    var iframe = getElement(element + "_iframe");
    infobox.style.visibility = state;
    if (iframe) {
        iframe.style.visibility = state;
    }
}

/**
 * Modifies the vehicle size values according to the current selection in the list
 */
 function updateVehicleSizes() {
    getElement("length").value = lengths[getElement("transport_class").selectedIndex];
    getElement("height").value = heights[getElement("transport_class").selectedIndex];
    getElement("width").value  = widths[getElement("transport_class").selectedIndex];
    getElement("weight").value = weights[getElement("transport_class").selectedIndex];
}

/**
 * Constructs an address string from the input fields in the order:
 *  [street], [zip] [city], [country]
 * The fields with the given name prefix are used
 * Null is returned if all fields are empty
 */
function getGoogleAddressString(type) {
    var street = getElement(type + "_street").value;
    var zip = getElement(type + "_postalcode").value;
    var city = getElement(type + "_city").value;
    var country = getElement(type + "_country").value;

    if ((!street || street==="") && 
        (!zip || zip==="") && (!city || city==="")) {
        return null;
    }  

    return street + ", " + zip + " " + city + ", " + country;
}

/**
 * Returns either the address data returned by getGoogleAddressString
 * or lat/lng values, if available for the given type.
 */
function getGoogleMapsData(type) {
    if (getElement(type + "_latitudeValue").value && getElement(type + "_longitudeValue").value) {
        return getElement(type + "_latitudeValue").value + " " + getElement(type + "_longitudeValue").value;
    }
    return getGoogleAddressString(type);
}

/**
 * Initialize Google-Map. Set center of map and add user controls
 */
function initializeMaps() {
    // not required before loading the maps
    markerLabels["start"] = msg["start"];
    markerLabels["destination"] = msg["destination"];
    for (var i=1 ; i<=config.maxStopovers ; i=i+1) {
        markerLabels["stopover"+i] = msg["stopover"] + " " + i;
    }

    if (GBrowserIsCompatible()) {
        var mapDiv = getElement("map");
        if (mapDiv) {
            map = new GMap2(mapDiv);
            centerMap();
            map.addControl(new GLargeMapControl3D());
            map.addControl(new GScaleControl());
            map.addControl(new GMapTypeControl());
            map.enableScrollWheelZoom();
            var contextMenu = getElement("mapContextMenu");
            map.getContainer().appendChild(contextMenu);    
            gdir = new GDirections(map, null);
            
            // adding listeners
            GEvent.addListener(gdir, "addoverlay", handleAddOverlay);       // overlay handling            
            GEvent.addListener(map, "singlerightclick", showContextMenu);   // context menu
            GEvent.addListener(map, "click", hideContextMenu);              // hides the context menu
            
            geocoder = new GClientGeocoder();
            defineIcons();
            
            for (var i=0 ; i<mapInitializationCallbacks.length ; i++) {
                mapInitializationCallbacks[i]();
            }
        }
    }
}

/**
 * Centers the Google map at a default or downloaded location 
 */ 
function centerMap() {
    if (typeof(ip) != "undefined") {
        map.setCenter(new GLatLng(ip.latitude, ip.longitude), ip.zoom);
    } else {
        map.setCenter(new GLatLng(51.059, 10.41), 6);
    }
}             

/**
 * Defines the icons used for the markers in the Google Map 
 */
function defineIcons() {
    var icon = new GIcon();
        icon.image = "/cpc/images/default/stopover.png";
        icon.iconSize = new GSize(24, 38);
        icon.shadow = "/cpc/images/default/shadow.png";
        icon.shadowSize = new GSize(37, 34);
        icon.iconAnchor = new GPoint(10, 34);
        icon.infoWindowAnchor = new GPoint(11, 11);
        icon.transparent = "/cpc/images/default/markerTransparent.png";
        icon.printImage = "/cpc/images/default/stopover.png";
        icon.mozPrintImage = "/cpc/images/default/stopover.png";
        icon.printShadow = "/cpc/images/default/shadow.png";
        icon.imageMap=[7,2,2,7,2,14,8,21,9,24,10,29,10,34,11,34,11,29,12,24,13,21,19,14,19,7,14,2];
    for (var i=1 ; i<=config.maxStopovers ; i=i+1) {
        icons["stopover" + i] = icon;
    }
    icon = new GIcon();
        icon.image = "/cpc/images/default/start.png";
        icon.iconSize = new GSize(24, 38);
        icon.shadow = "/cpc/images/default/shadow.png";
        icon.shadowSize = new GSize(37, 34);
        icon.iconAnchor = new GPoint(10, 34);
        icon.infoWindowAnchor = new GPoint(11, 11);
        icon.transparent = "/cpc/images/default/markerTransparent.png";
        icon.printImage = "/cpc/images/default/start.png";
        icon.mozPrintImage = "/cpc/images/default/start.png";
        icon.printShadow = "/cpc/images/default/shadow.png";
        icon.imageMap=[7,2,2,7,2,14,8,21,9,24,10,29,10,34,11,34,11,29,12,24,13,21,19,14,19,7,14,2];
    icons["start"] = icon;
    icon = new GIcon();
        icon.image = "/cpc/images/default/destination.png";
        icon.iconSize = new GSize(35, 43);
        icon.shadow = "/cpc/images/default/destinationShadow.png";
        icon.shadowSize = new GSize(35, 43);
        icon.iconAnchor = new GPoint(8, 41);
        icon.infoWindowAnchor = new GPoint(27, 1);
        icon.transparent = "/cpc/images/default/markerTransparent.png";
        icon.printImage = "/cpc/images/default/destination.png";
        icon.mozPrintImage = "/cpc/images/default/destination.png";
        icon.printShadow = "/cpc/images/default/destinationShadow.png";
        icon.imageMap=[0,5,0,36,6,41,12,41,24,34,29,34,34,37,34,5,29,2,24,2,12,7,7,7];
    icons["destination"] = icon;
    for (var i=0 ; i<10 ; i++) {
        icon = new GIcon();
        icon.image = "/cpc/images/default/marker" + String.fromCharCode(65+i) + ".png";
        icon.iconSize = new GSize(24, 38);
        icon.shadow = "/cpc/images/default/shadow.png";
        icon.shadowSize = new GSize(37, 34);
        icon.iconAnchor = new GPoint(10, 34);
        icon.infoWindowAnchor = new GPoint(11, 11);
        icon.transparent = "/cpc/images/default/markerTransparent.png";
        icon.printImage = "/cpc/images/default/marker" + String.fromCharCode(65+i) + ".png";
        icon.mozPrintImage = "/cpc/images/default/marker" + String.fromCharCode(65+i) + ".png";
        icon.printShadow = "/cpc/images/default/shadow.png";
        icon.imageMap=[7,2,2,7,2,14,8,21,9,24,10,29,10,34,11,34,11,29,12,24,13,21,19,14,19,7,14,2];
        icons["searchResult" + i] = icon;
        icons["searchResult" + String.fromCharCode(65+i)] = icon;
    }
}

function handleAddOverlay(overlay) {
    // we have our own markers -> the GDirections markers can be hidden
    var numMarkers = gdir.getNumGeocodes()
    for (var i=0; i<numMarkers; i++) {
        var marker = gdir.getMarker(i);
        if (marker != null) marker.hide();
    } 
}

/**
 * Sends the currently given data to Google Maps. 
 * Possible errors are handled by the error handler. 
 * syntax: "from: city1 to: city2 to: city3 ..."
 */
function setDirections(mustUpdate) {
    // at least START and DESTINATION must be given
    var start = getGoogleMapsData("start");
    var destination = getGoogleMapsData("destination");
    if (start == null || destination == null) {
        gdir.clear();
        return;
    }

    // as long as any two cities are given, the query is performed
    var cntCities = 0;
    var query = "";
    var tmp = getGoogleMapsData("start");
    if (tmp != null) {
        cntCities ++;
        query = "from: " + tmp;
    }
    for (var i=1 ; i<=cntStopOver ; i=i+1) {
        tmp = getGoogleMapsData("stopover" + i);
        if (tmp != null) {
            cntCities ++;
            query = query + (cntCities == 1 ? " from: " : " to: ") + tmp;
        }
    }
    var tmp = getGoogleMapsData("destination");
    if (tmp != null) {
        cntCities ++;
            query = query + " to: " + tmp;
    }

    // alert(query);  // for testing purposes
    if (cntCities > 1) {
        gdir.load(query);
    } else if (mustUpdate) {
        centerMap();
        gdir.clear();
    }
}


/** 
 * Shows the map context menu within the Google Map
 */
function showContextMenu(pixel, tile) {
    clickedPixel = pixel;
    var x = pixel.x;
    var y = pixel.y;
    var contextMenu = getElement("mapContextMenu");
    if (x > map.getSize().width - 120) { x = map.getSize().width - 120 }
    if (y > map.getSize().height - 100) { y = map.getSize().height - 100 }
    var pos = new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(x,y));  
    pos.apply(contextMenu);
    contextMenu.style.visibility = "visible";
}

/**
 * Hides the context menu
 */ 
function hideContextMenu() {
    getElement("mapContextMenu").style.visibility="hidden";
}

function setStartingPoint(callback) {
    _setPoint("start", callback);
}

function setDestinationPoint(callback) {
    _setPoint("destination", callback);
}

function mapAddStopOver(callback, tagClass) {
    if (cntStopOver < config.maxStopovers) {
        addStopOver(tagClass);
        _setPoint("stopover" + cntStopOver, callback);
    }
    hideContextMenu();
}

/**
 * Adds a new marker with the given name at the stored position. 
 * Removes a previous marker, if set.
 * Updates form fields with geocoder information.
 */ 
function _setPoint(idx, callback) {
    // check if marker already exists, delete if necessary
    if (markers[idx]) {
        map.removeOverlay(markers[idx]);
    }

    var latlng = map.fromContainerPixelToLatLng(clickedPixel);
    if (latlng != null) {
        var marker = new GMarker(latlng, {draggable: true, bouncy: true, title: markerLabels[idx], 
                                          icon: icons[idx], zIndexProcess:getMarkerZIndex});
        GEvent.addListener(marker, "dragend", function() {
            updatePosition(idx, callback);
        }); 
        updateLatLng(idx, latlng);
        marker.importance = MARKER_IMPORTANCE_TOUR_POINT;
        map.addOverlay(marker);
        markers[idx] = marker;
        hideContextMenu();   
        action = idx;
        geocoder.getLocations(latlng, function(result) {
             updateAddressInformation(result);

            // another callback, if required
            if (callback) {
                callback();
            }
         });
    }
}

/**
 * Reads the given geocoder response and splits the address into pieces.
 */ 
function updateAddressInformation(response) {
    if (!action) {
        return; 
    }
    
    var city, zip, street, country;
    if (response && response.Status.code == 200) {
        var place = response.Placemark[0];
        city = searchNestedArray(place, "LocalityName");
        zip = searchNestedArray(place, "PostalCodeNumber");
        street = searchNestedArray(place, "ThoroughfareName");
        country = searchNestedArray(place, "CountryName");
    } 
    
    city = cleanAddressText(city);
    street = cleanAddressText(street);
    zip = cleanAddressText(zip);
    
    getElement(action + "_street").value = street ? street : "";
    getElement(action + "_postalcode").value = zip ? zip : "";
    getElement(action + "_city").value = city ? city : "";
    getElement(action + "_country").value = country ? country : "";
    action = null;
    setDirections();
}

/**
 * Clears the given text if unwanted content is contained.
 * null is returned if such content is found, otherwise the text is returned.
 */
function cleanAddressText(text) {
    if (text == undefined) {
        return null;
    } 
    return text;
}

/** 
 * Called after dragging a marker, updates all other information.
 */
function updatePosition(markerName, callback) {
    var marker = markers[markerName];
    var latlng = marker.getLatLng();
    action = markerName; 
    updateLatLng(markerName, latlng);
    geocoder.getLocations(latlng, function(result) {
        updateAddressInformation(result);
        
        if (callback) {
            callback();
        }
    });
}

/**
 * Removes the marker with the given name, if available
 * Returns true if the marker actually was removed, false if is was not available anyway
 */
function removeMarker(markerName) {
    if (markers[markerName]) {
        map.removeOverlay(markers[markerName]);
        markers[markerName] = null;
        return true;
    }
    return false;
}

/**
 * Removes all markers currently displayed in the map
 */
function removeAllMarkers() {
    map.clearOverlays();
    markers = new Array();
}

/**
 * Resets a form for the "Find" screens
 */
function resetForm(formName, callback) {
    // clear all form fields
    var elems = document.forms[formName].elements;
    for (var i=0 ; i<elems.length ; i++) {
        if (elems[i].name.indexOf("radius") == -1 && elems[i].type != "submit" && elems[i].type != "button") {
            elems[i].value = "";
        }
    }
    document.forms[formName].elements["newPage"].value = 1;
    document.forms[formName].elements["hash"].value = 1;
    
    // hide all stopovers, markers and displayed results
    removeAllStopOvers();
    removeAllMarkers();
    removeAllInfoWindows();
    // move map to initial state
    centerMap();
    gdir.clear();

    if (callback) {
        callback();
    }
}

/**
 * Removes all Info Windows used by the find screens from the HTML code
 */
function removeAllInfoWindows() {
    var container = getElement("templateInfoWindowContainer");
    var survivors = 0;
    while (survivors < container.childNodes.length) {
        var child = container.childNodes[survivors];
        if (child.nodeType == 1 && child.id != "templateInfoWindow") {
            container.removeChild(child);
        } else {
            survivors = survivors + 1;
        }
    }
}

var markerOptions = ["start", "destination", "stopover1", "stopover2", "stopover3", "stopover4"]; 

/**
 * Updates all markers
 */
function updateAllMarkers(callback) {
    for (var idx in markerOptions) {
        try {
            var name = markerOptions[idx];
            updateMarker(name, callback);
        } catch (err) {
            // ignore errors here... markers are not yet defined, no error
        }
    }
}

/**
 * After a manual address change the corresponding marker is updated to the new address
 * and the lat/long stored 
 */
function updateMarker(markerName, callback) {
    var markerRemoved = removeMarker(markerName);
    var address = getGoogleAddressString(markerName);
    
    // update only required if marker was just removed right now 
    if (address == null) {
        if (markerRemoved) {
            getElement(markerName + "_latitudeValue").value = "";
            getElement(markerName + "_longitudeValue").value = "";
            setDirections(true);
            if (callback) {
                callback();
            }
        }
        return;
    }
    
    geocoder.getLatLng(address, function(latlng) {
        if (!latlng) {
            updateLatLng(markerName, null);
            return;
        } 
    
        var marker = new GMarker(latlng, {draggable: true, bouncy: true, title: markerLabels[markerName], 
                                          icon: icons[markerName], zIndexProcess:getMarkerZIndex});
        markers[markerName] = marker;
        GEvent.addListener(marker, "dragend", function() {
            updatePosition(markerName, callback);
        }); 
        marker.importance = MARKER_IMPORTANCE_TOUR_POINT;
        map.addOverlay(marker);
        updateLatLng(markerName, latlng);
        setDirections();

        // another callback, if required
        if (callback) {
            callback();
        }
    });
}

/** 
 * Updates the hidden lat/lng fields with the given name to the given values
 */
function updateLatLng(markerName, latlng) {
    if (!markerName) {
        return;
    }
    
    if (!latlng) {
        getElement(markerName + "_latitudeValue").value = "";
        getElement(markerName + "_longitudeValue").value = "";
    } else {
        getElement(markerName + "_latitudeValue").value = latlng.lat();
        getElement(markerName + "_longitudeValue").value = latlng.lng();
    }    
}

/**
 * Based on the type of marker (result, start, stopover, destination) the zIndex is
 * returned. Search results are always lowest. 
 */ 
function getMarkerZIndex(marker, undef) {
    if (marker) {
        return marker.importance; 
    }
    return 0;
}

/** 
 * Creates a new GMarker with the given properties
 * Factored out as there are issues calling this from within a loop
 */
function createMarker(point, options, html) {
    var newMarker = new GMarker(point, options);
    if (html) {
        GEvent.addListener(newMarker, "click", function() {
            newMarker.openInfoWindowHtml(html);
        });
    }
    return newMarker;               
}

/**
 * Displays the info window for the marker with the given name
 */ 
function showInfoWindow(latlng, node, callback) {
    if (callback) {
        callback();
    }

    map.openInfoWindow(latlng, node);
}

/** 
 * Empty callback function
 */
function noop() {
}

/**
 * Compares the old form hash code with a newly calculated one. Returns
 * true if form content has been changed, false otherwise 
 */
function hasFormChanged(formName, hashElementName) {
    // use form hash code to find out if reload is required
    var oldHash = document.forms[formName].elements[hashElementName].value;
    var newHash = formHashCode(formName);
    if (oldHash == newHash) {
        return false;
    }
    document.forms[formName].elements[hashElementName].value = newHash;
    return true;
}

/**
 * Calculates a has value for all form input fields (text, hidden, selection) 
 */ 
function formHashCode(formName) {
    if (!formName) {
        return;
    }
    
    var elements, hash, len;
    hash = 0;
    elements = document.forms[formName].elements;
    len = elements.length;
    for (var i=0 ; i<len ; i++) {
        elem = elements[i];
        if (elem.name != "hash") {
            hash = 31*hash + hashCode(elem.value);
            hash = hash % 2147483648;
        }
    }
    return hash;
}

/**
 * Calculates and returns a hash code of the given text 
 */
function hashCode(text) {
    if (!text) {
        return -1;
    }
 
    var hash;
    hash = 0;
    for (var i=0; i<text.length; i++) {
        hash = 31*hash + text.charCodeAt(i);
        hash = hash % 2147483648;
    }
    return hash;
}

/**
 * Used by find templates to get the city string to display in hover box
 */ 
function getCityLabel(address) {
    if (!address) {
        return "";
    }
    var result = "";
    if (address.addrStreet) {
        result = address.addrStreet + ", ";
    } 
    return result + address.addrCity;
}

/** 
 * Helper function to fill empty properties (null) in an object with the given value.
 */
function fillNullValues(object, value) {
    for (var o in object) {
        if (object[o] == null) {
            object[o] = value; 
        }
    }
}    

/** 
 * Helper for the find screens to update the rating information for the given IDs
 */
function updateRating(idPrefix, idSuffix, tour, typeForDriver, even) {
	updateRatingForUser(idPrefix, idSuffix, tour.user, typeForDriver, even);
}

function updateRatingForUser(idPrefix, idSuffix, user, typeForDriver, even, horizontal) {
    var id = "";
    if (idPrefix === "") {
        id = "rating" + idSuffix;
    } else {
        id = idPrefix + "Rating" + idSuffix;
    }
    if (typeForDriver) {
        if (!user.ratingForDriver || user.ratingForDriver < 0) {   
        	dwr.util.byId(id).style.width = "100px";
            dwr.util.byId(id).className = "ratingSplit" + ((horizontal)?"Horizontal":"");
            dwr.util.byId(id).innerHTML = msg["Rating.without"];            
        } else {
            dwr.util.byId(id).style.width = (user.ratingForDriver * 20) + "px";
            dwr.util.byId(id).className = (even) ? "ratingEven" : "rating";
            dwr.util.byId(id).innerHTML = "";
        }
    } else {
        if (!user.ratingForCargoProvider || user.ratingForCargoProvider < 0) {            
            dwr.util.byId(id).style.width = "100px";
            dwr.util.byId(id).className = "ratingSplit"+ ((horizontal)?"Horizontal":"");
            dwr.util.byId(id).innerHTML = msg["Rating.without"];
        } else {
            dwr.util.byId(id).style.width = (user.ratingForCargoProvider * 20) + "px";
            dwr.util.byId(id).className = (even) ? "ratingEven" : "rating";
            dwr.util.byId(id).innerHTML = "";
        }
    }    
}

function updateRatingForTourCargo(idPrefix, idSuffix, tourCargo, typeForDriver, even, horizontal) {
    var id = "";
    if (idPrefix === "") {
        id = "rating" + idSuffix;
    } else {
        id = idPrefix + "Rating" + idSuffix;
    }
    if (typeForDriver) {
        if (!tourCargo.trcDriverRating || tourCargo.trcDriverRating < 0) {   
            dwr.util.byId(id).style.width = "100px";         
            dwr.util.byId(id).className = "ratingSplit" + ((horizontal)?"Horizontal":"");
            dwr.util.byId(id).innerHTML = msg["Rating.without"];            
        } else {
            dwr.util.byId(id).style.width = (tourCargo.trcDriverRating * 20) + "px";
            dwr.util.byId(id).className = (even) ? "ratingEven" : "rating";
            dwr.util.byId(id).innerHTML = "";
        }
    } else {
        if (!tourCargo.trcCargoRating || tourCargo.trcCargoRating < 0) {            
            dwr.util.byId(id).style.width = "100px";
            dwr.util.byId(id).className = "ratingSplit"+ ((horizontal)?"Horizontal":"");
            dwr.util.byId(id).innerHTML = msg["Rating.without"];
        } else {
            dwr.util.byId(id).style.width = (tourCargo.trcCargoRating * 20) + "px";
            dwr.util.byId(id).className = (even) ? "ratingEven" : "rating";
            dwr.util.byId(id).innerHTML = "";
        }
    }
}

/**
 * Helper to display updated result summary information per JavaScript
 */
function updateResultSummary(pager) {
    var html = "";
    if (pager.empty) {
        getElement("resultSummaryContainer").style.visibility = "hidden";
        html = ""; 
    } else {
        getElement("resultSummaryContainer").style.visibility = "visible";
        html = html + (pager.pageStartIndex + 1) + "&nbsp;-&nbsp;" + (pager.pageEndIndex + 1);
        html = html + "&nbsp;" + msg["Pager.of"] + "&nbsp;" + pager.totalSize;
    }
    getElement("resultSummary").innerHTML = html;
}

/**
 * Helper to display updated pager information per JavaScript
 */
function updatePager(pager, fct, pagerId) {
    if (!pagerId) pagerId = "pager";
    
    var html = "";
    if (pager.empty) {
        html = "&nbsp;";
    } else {
        var lnk = "anylink";    
        html = html + msg["Pager.page"] + "&nbsp;" + pager.currentPage + "&nbsp;" + msg["Pager.of"] + "&nbsp;" + pager.totalPages + "&nbsp;&nbsp;&nbsp;";
        if (pager.hasPreviousPage) {
            html = html + "<a onclick=" + fct + "(" + 1 + ")>|&lt;</a>&nbsp;&nbsp;";
            html = html + "<a onclick=" + fct + "(" + (pager.currentPage - 1) + ")>&lt;&lt;</a>";
        } else {
            html = html + "|&lt;&nbsp;&nbsp;&lt;&lt;";
        }
        html = html + "&nbsp;" 
        for (var i=pager.pagerFirstPage ; i<=pager.pagerLastPage ; i++) {
            html = html + "&nbsp;"
            if (i !== pager.currentPage) {
                html = html + "<a onclick=" + fct + "(" + i + ")>";
            }
            html = html + i;
            if (i !== pager.currentPage) {
                html = html + "</a>";
            }
            html = html + "&nbsp;"
        }
        html = html + "&nbsp;"
        if (pager.hasNextPage) {
            html = html + "<a onclick=" + fct + "(" + (pager.currentPage + 1) + ")>&gt;&gt;</a>&nbsp;&nbsp;";
            html = html + "<a onclick=" + fct + "(" + (pager.totalPages) + ")>&gt;|</a>";
        } else {
            html = html + "&gt;&gt;&nbsp;&nbsp;&gt;|";
        }
    }
    getElement(pagerId).innerHTML = html;
}

/**
 * Returns the current window height
 */
function getWindowHeight() {
    var y;
    if (self.innerHeight) { // all except Explorer
        y = self.innerHeight;
    } else if (document.documentElement && document.documentElement.clientHeight) {  // Explorer 6 Strict Mode
        y = document.documentElement.clientHeight;
    } else if (document.body) {  // other Explorers
        y = document.body.clientHeight;
    }
    return y;
}

/**
 * Returns the current viewport height
 */
function getViewportHeight() {
	var d = document;
    return (window.innerHeight && window.scrollMaxY) ? window.innerHeight + window.scrollMaxY
    : (d.body.scrollHeight > d.body.offsetHeight) ? d.body.scrollHeight
    : d.body.offsetHeight;    
}

function getScrollPosition() {
	return f_filterResults (
		window.pageYOffset ? window.pageYOffset : 0,
		document.documentElement ? document.documentElement.scrollTop : 0,
		document.body ? document.body.scrollTop : 0
	);
}

function f_filterResults(n_win, n_docel, n_body) {
	var n_result = n_win ? n_win : 0;
	if (n_docel && (!n_result || (n_result > n_docel)))
		n_result = n_docel;
	return n_body && (!n_result || (n_result > n_body)) ? n_body : n_result;
}
 
/**
 * Returns the current window width
 */
function getWindowWidth() {
    var x,y;
    if (self.innerHeight) { // all except Explorer
        x = self.innerWidth;
    } else if (document.documentElement && document.documentElement.clientHeight) {  // Explorer 6 Strict Mode
        x = document.documentElement.clientWidth;
    } else if (document.body) {  // other Explorers
        x = document.body.clientWidth;
    }
    return x;
}

/**
 * Required for IE6, add IFrame behind spans
 */
function addIFramesToPopups() {    
    var spans = document.getElementsByTagName("span");
    var size = spans.length;
    for (var i=0 ; i<size ; i++) {
        var span = spans[i];
        if (span.className == "popup") {
            var frame = document.createElement("iframe");
            frame.className = "popupIFrame";
            frame.src = "javascript:''";
            frame.style.width = span.offsetWidth;
            frame.style.height = span.offsetHeight;
            frame.style.top = span.offsetTop;
            frame.style.left = span.offsetLeft;
            frame.id = span.id + "_iframe";
            span.parentNode.insertBefore(frame, span);
        }
    }
}

/**
 * Helper for the find screens to update the input field classes 
 */
function addErrorClassToFields(errorFields) {
    _addErrorClassToFields(errorFields, "input", false);
    _addErrorClassToFields(errorFields, "textarea", false);
    _addErrorClassToFields(errorFields, "select", false);
    _addErrorClassToFields(errorFields, "div", true);   
}

function _addErrorClassToFields(errorFields, tagName, useCertainDivs) {
    var nodes = document.getElementsByTagName(tagName);
    var size = nodes.length;
    for (var i=0 ; i<size ; i++) {
        var child = nodes[i];
        var addErrorClass = false;
        
        // ignore all divs that do not contain input elements
        if (useCertainDivs) {
            if (child.id.indexOf("InputContainer_") == -1) {
                continue;
            }
            var id = child.id.substring(15, child.id.lastIndexOf("_"));
            if (errorFields[id])     addErrorClass = true;
        } else {
            if (errorFields[child.id])     addErrorClass = true;
        }
        
        // add or remove the error class
        if (addErrorClass) {
            child.className = child.className + " validationError";
        } else {
            if (child.className.indexOf(" ") > -1) {
                child.className = child.className.substring(0,  child.className.lastIndexOf(" "));
            } else  if (child.className == "validationError") {
                child.className = "";
            }
        }
    }
}

/**
 * Executes DWR login, relies on several IDs given to do its work
 */
function dwrLogin(formId) {
    var user = getElement("user").value;
    var passwd = getElement("passwd").value;

    UsersProvider.doLogin(user, passwd, {
        callback: function(loginMap) {

        if (loginMap.loginError == false) {
            dwr.util.setValue("user", "");
            getElement("loginError").style.display = "none";
            document.forms[formId].submit();
            hideLightbox("lbLogin");
        } else { 
            dwr.util.setValue("loginError", loginMap.errorMessage);
            getElement("loginError").style.display = "block";
        }
        dwr.util.setValue("password", "");
    }});
}


/** 
 * Callback for validation requests via DWR. 
 */
function dwrValidateCallback(validationResult, doColorInputFields, doShowLightbox) {
    if(validationResult == null || validationResult.messages.length == 0) {
        return true;
    }
    
    if (doColorInputFields) {
        var fields = validationResult.failedFields;
        var errorFields = new Array();
        for (var i=0 ; i<fields.length ; i++) {
            var field = fields[i];
            errorFields[field] = field;
        }
        addErrorClassToFields(errorFields);
    }
    if (doShowLightbox) {
        var content = "";
        for(var i=0; i < validationResult.messages.length; i++) {
            content += "<span class='validationErrorLine'>"+validationResult.messages[i]+"</span><br/>";
        }
        document.getElementById("validationErrors").innerHTML = content;
        showLightbox('lbValidationError');
    }
    return false;
}

function getHandshakeStatusText(isDriver, handshakeStatus) {
    if (isDriver) {
        if ((handshakeStatus & 32) > 0) {
            return msg["HandshakeStatus.32"];
        } else if ((handshakeStatus & 8) > 0) {
            return msg["HandshakeStatus.8"];
        }
    } else {
        if ((handshakeStatus & 4096) > 0) {
            return msg["HandshakeStatus.4096"];
        } else if ((handshakeStatus & 1024) > 0) {
            return msg["HandshakeStatus.1024"];
        }
    }
    
    // ok, this is one of the early states
    if ((handshakeStatus & 2) > 0) {
        return msg["HandshakeStatus.2"];
    } else if ((handshakeStatus & 65536) > 0) {
        return msg["HandshakeStatus.65536"];
    }
    return msg["HandshakeStatus.1"];
}

/**
 * Returns the value of the checked radio button in the button group with the 
 * given name in the given form.
 * Returns "_empty_", if all values are empty  
 */
function getRadioButtonValue(formName, radioGroupName) {
    var elem = document.forms[formName].elements[radioGroupName];
    for (var i=0; i < elem.length; i++) {
        if (elem[i].checked == true) {
            return elem[i].value;
        }
    }
    return "_empty_";
}

function decode(text) {
	return text.replace(/&#(\d+);/g, function(wholematch, parenmatch1) {
		return String.fromCharCode(+parenmatch1);
	});
}

/**
 * Used by the data lists (MyTours, ...).
 * Variable is used to guard double reloading of the content
 */
var _dataListReloaded = false;
function changeList() {
    if (_dataListReloaded) {
        return;
    }
    _dataListReloaded = true;
    var newUrl = dwr.util.getValue("query");
    window.location.href = newUrl;
}

/**
 * Removes the focus from all submit input elements.
 * This is required for proper operation of the lightboxes.
 */
function blurAllSubmitButtons() {    
    var inputs = document.getElementsByTagName("input");
    var size = inputs.length;
    for (var i=0 ; i<size ; i++) {
        var input = inputs[i];
        if (input.type == "submit") {
            input.blur();
        }
    }
}

/**
 * Opens a popup window
 */
function openPopupWindow(link, name, width) {
    if (!window.focus)  return true;

    var href;
    if (typeof(link) == 'string') {
        href = link;
    } else {
        href = link.href;
    }
    window.open(href, name, 'width=' + width + ',height=500,screenX=100,screenY=200,scrollbars=yes,resizable=yes');
    return false;
} 

/** 
 * Clears the value of a text-field that has had some values previously
 */ 
function clearInputValue(field, initialValue) {
    if (field.value == initialValue) {
        field.value = "";
    }
}

/** 
 * Sets the value to the given one, if the field has no value
 */
function resetInputValue(field, initialValue) {
    if (field.value == "") {
        field.value = initialValue;
    }
}
