/**
 * timeTracing
 */
function traceApplicationStepTime(eventName) {
    currentEndTime = (new Date()).getTime();
    //console.log(eventName + " took to previous: " + Math.abs((new Date()).setTime(currentStartTime - currentEndTime)) + "ms");
    currentStartTime = (new Date()).getTime();
}

function traceApplicationTotalTime() {
    /*console.log(
        "ApplicationTotalTime: " + Math.abs((new Date()).setTime(totalStartTime - totalEndTime)) + "ms"        
    );*/
}
var totalStartTime = (new Date()).getTime();
//console.log("totalStartTime: " + totalStartTime);
var currentStartTime = (new Date()).getTime();
var currentEndTime = null;
var totalEndTime = null;



/**
 * Helper in addition to the jQuery namespace 
 */
jQuery.fn.toIndexedArray = function (obj, extentionProperties) {
    var result = [];
    if (obj === undefined || obj === null) {
        return result;
    } 
    if ($.isArray(obj)) {
        result = obj;
    }
    if (obj.toArray) {
        result = obj.toArray();
    }
    if (typeof(obj) === "object") {
        for (var key in obj) {
            var value = obj[key];
            if (
                typeof(value) === "object" &&
                typeof(value["key"]) === "undefined"
            ) {
                value["key"] = key;
                for (var extentionProperty in extentionProperties) {
                    if (
                        typeof(value[extentionProperty] === "undefined") // to not to override already existing keys
                    ) {
                        value[extentionProperty] = extentionProperties[extentionProperty];
                    }
                }
            }
            result.push(value);
        }
    }
    return result;
};

var pinuts = pinuts || {};
pinuts.googleMaps = pinuts.googleMaps || {};

/**
 * general flags
 */
pinuts.googleMaps.apiAccessible = false;
pinuts.googleMaps.applicationNotReady = true;

/**
 * Config ... developper changeable
 */
pinuts.googleMaps.config = pinuts.googleMaps.config || {};

pinuts.googleMaps.config.defaults = pinuts.googleMaps.config.defaults || {
    lat: 0,
    lng: 0,
    zoom: 1,
    domElement: null
};

pinuts.googleMaps.config.defaults.addressFilter =
    pinuts.googleMaps.config.defaults.addressFilter || {};
pinuts.googleMaps.config.defaults.addressFilter.country = pinuts.googleMaps.config.defaults.addressFilter.country || "DE";
pinuts.googleMaps.config.defaults.addressFilter.administrativeAreas = pinuts.googleMaps.config.defaults.addressFilter.administrativeAreas || ["Berlin"];
pinuts.googleMaps.config.defaults.addressFilter.viewport = pinuts.googleMaps.config.defaults.addressFilter.viewport || 
    {
        southWest: {lat: 52.350440909192635, lng: 13.07098388671875}, 
        northEast: {lat: 52.65722696364054, lng: 13.75762939453125}
    };

/**
 * These is fix so far, maybe providing a method the make this configurable ???
 */
pinuts.googleMaps.config.domReferences = {};
pinuts.googleMaps.config.domReferences.baseRef = ".google-maps-container";
pinuts.googleMaps.config.domReferences.mapContainer = ".google-maps";
pinuts.googleMaps.config.domReferences.infoWindowTmp = {};
pinuts.googleMaps.config.domReferences.infoWindowTmp.ref =
    pinuts.googleMaps.config.domReferences.baseRef +
    " .google-maps-infowindow-tmp";

pinuts.googleMaps.config.domReferences.infoWindowTmp.content =
    pinuts.googleMaps.config.domReferences.infoWindowTmp.ref +
    " .google-maps-infowindow-content";

pinuts.googleMaps.config.domReferences.search = {};
pinuts.googleMaps.config.domReferences.search.ref =
    pinuts.googleMaps.config.domReferences.baseRef +
    " #google-maps-address-search-wrapper";

pinuts.googleMaps.config.domReferences.search.errorOverlay = {};
pinuts.googleMaps.config.domReferences.search.errorOverlay.ref =
    pinuts.googleMaps.config.domReferences.search.ref +
    " .google-maps-address-search-wrapper-error-overlay";

pinuts.googleMaps.config.domReferences.search.errorOverlay.text =
    pinuts.googleMaps.config.domReferences.search.errorOverlay.ref +
    " H5";

pinuts.googleMaps.config.domReferences.search.container = {};
pinuts.googleMaps.config.domReferences.search.container.ref = 
    pinuts.googleMaps.config.domReferences.search.ref +
    " #google-maps-address-search-container";
pinuts.googleMaps.config.domReferences.search.container.results = 
    pinuts.googleMaps.config.domReferences.search.ref +
    " .results";
pinuts.googleMaps.config.domReferences.search.container.form = 
    pinuts.googleMaps.config.domReferences.search.ref +
    " FORM";
pinuts.googleMaps.config.domReferences.search.container.fieldsetLabel = 
    pinuts.googleMaps.config.domReferences.search.ref +
    " FORM FIELDSET LABEL";
pinuts.googleMaps.config.domReferences.search.container.searchField = 
    pinuts.googleMaps.config.domReferences.search.ref +
    " FORM #google-maps-address-search-field";
pinuts.googleMaps.config.domReferences.search.container.selectablePoints = 
    pinuts.googleMaps.config.domReferences.search.ref +
    " FORM #google-maps-region-search-field";
pinuts.googleMaps.config.domReferences.search.container.distances = 
    pinuts.googleMaps.config.domReferences.search.ref +
    " FORM #google-maps-viewport-visible-radius";

pinuts.googleMaps.config.domReferences.overlay = {};
pinuts.googleMaps.config.domReferences.overlay.ref =
    pinuts.googleMaps.config.domReferences.baseRef +
    " .google-maps-overlay";
pinuts.googleMaps.config.domReferences.overlay.message =
    pinuts.googleMaps.config.domReferences.overlay.ref +
    " div.status span.status-message";
pinuts.googleMaps.config.domReferences.overlay.percent =
    pinuts.googleMaps.config.domReferences.overlay.ref +
    " div.status span.status-bar span.bar-percent";

/**
 * data storage
 */
pinuts.googleMaps.rawData = {};
pinuts.googleMaps.data = {};

/**
 * googleMap api object instances...
 */
pinuts.googleMaps.googleMapInstance = null;
pinuts.googleMaps.googleGeoCoderInstance = null;
pinuts.googleMaps.googleGeoCoderFilterViewport = null;

pinuts.googleMaps.defaultLocationMarkerOptions = null;
pinuts.googleMaps.userLocationMarkerOptions = null;

/**
 * Event names. Meant as constants ...
 */
pinuts.googleMaps.events = {};
pinuts.googleMaps.events.onBeforeDataLoading = "onBeforeDataLoading";
pinuts.googleMaps.events.onAfterDataLoaded = "onAfterDataLoaded";
pinuts.googleMaps.events.onDataLoadingError = "onDataLoadingError"; /* Error! */

pinuts.googleMaps.events.onBeforeDataParsing = "onBeforeDataParsing";
pinuts.googleMaps.events.onAfterDataParsed = "onAfterDataParsed";
pinuts.googleMaps.events.onDataParsingError = "onDataParsingError"; /* Error! */

pinuts.googleMaps.events.onMapApiLoadingError = "onMapApiLoadingError"; /* Error! */
pinuts.googleMaps.events.onMapBeforeInitializing = "onMapBeforeInitializing";
pinuts.googleMaps.events.onMapIsReady = "onMapIsReady"; // load()
pinuts.googleMaps.events.onMapIsLoaded = "onMapIsLoaded"; // tilesLoaded()
pinuts.googleMaps.events.onMapIsInitialyLoaded = "onMapIsInitialyLoaded";


pinuts.googleMaps.events.onSearchUserRequest = "onSearchUserRequest";
pinuts.googleMaps.events.onBeforeSearch = "onBeforeSearch";
pinuts.googleMaps.events.onAfterSearch = "onAfterSearch";
pinuts.googleMaps.events.onSearchError = "onSearchError"; /* Error! */
pinuts.googleMaps.events.onSearchNoAddressFoundError = "onSearchNoAddressFoundError"; /* Error! */

pinuts.googleMaps.events.onSelectablePointSelect = "onSelectablePointSelect";
pinuts.googleMaps.events.onDistanceFieldChange = "onDistanceFieldChange";


pinuts.googleMaps.tmp = {};

/**
 * Functionality
 */
pinuts.googleMaps.tools = pinuts.googleMaps.tools || {};
pinuts.googleMaps.tools.defaultEventHandler = function (evnt, args) {
    //alert(evnt.type);
    //console.log(evnt.type);
    if (args !== undefined) {        
        //console.dir(args);
    }
    //console.dir(arguments);
}
pinuts.googleMaps.tools.defaultErrorHandler = function (evnt, args) {
    pinuts.googleMaps.tools.overlay.setStatus(pinuts.googleMaps.tools.getErrorTextByType(evnt.type) + (!!arguments[1] ? ": (" + arguments[1] + ")" : ""), 0, true);
}

pinuts.googleMaps.tools.setConfig = function (customConfig) {
    $.extend(
        true, // deep copy
        pinuts.googleMaps.config.defaults,
        customConfig
    );
};

/**
 * obsolete eo far ... but nice;-)
 */
pinuts.googleMaps.tools.getConfigValue = function (referenceString) {
    var referenceChain = referenceString.split(".");
    var configValue = pinuts.googleMaps.config[referenceChain[0]];
    for (var iii = 1; iii < referenceChain.length; iii++) {
        configValue = configValue[referenceChain[iii]];
    }
    return configValue;
};


pinuts.googleMaps.tools.loadRawData = function () {
    $(window).triggerHandler(pinuts.googleMaps.events.onBeforeDataLoading);
    try {
        $.ajax(
            {
                type    : "GET",
                cache   : false,
                url     : pinuts.googleMaps.config.dataUris.googleMapsPoints,
                dataType: 'text',
                success: function(data, successType, xhr) {
                    try {
                        pinuts.googleMaps.rawData = eval('{' + data + '}');
                        $(window).triggerHandler(pinuts.googleMaps.events.onAfterDataLoaded);
                    } catch (exp) {
                        $(window).triggerHandler(pinuts.googleMaps.events.onDataLoadingError, [exp]);
                    }
                },
                error: function (xhr, errorType, exp) {
                    $(window).triggerHandler(pinuts.googleMaps.events.onDataLoadingError, [exp, xhr, errotype]);
                }
            }
        );
    } catch (exp) {
        $(window).triggerHandler(pinuts.googleMaps.events.onDataLoadingError, [exp]);
    }
};
pinuts.googleMaps.tools.parseData = function () {
    $(window).triggerHandler(pinuts.googleMaps.events.onBeforeDataParsing);
    try {


        pinuts.googleMaps.data.selectablePoints = $.fn.toIndexedArray(
            pinuts.googleMaps.rawData.selectablePoints
        );

        pinuts.googleMaps.data.selectablePoints.sort(
            function (a, b) {
                var compA = ("" + a.name).toLowerCase();
                var compB = ("" + b.name).toLowerCase();
                return (compA < compB) ? -1 : (compA > compB) ? 1 : 0;

                // the following is the usage according to the specs, but don't work in IE!
                //return b.name < a.name;
            }
        );

        $(window).triggerHandler(pinuts.googleMaps.events.onAfterDataParsed);
    } catch (exp) {
        $(window).triggerHandler(pinuts.googleMaps.events.onDataParsingError, [exp]);
    }
};
pinuts.googleMaps.tools.createSelectablePoints = function () {
    $.each(
        pinuts.googleMaps.data.selectablePoints,
        function (idx, selectablePoint) {
            pinuts.googleMaps.tools.createSelectablePointOption(selectablePoint);
        }
    );
    $(pinuts.googleMaps.config.domReferences.search.container.selectablePoints).change(
        function () {
            $(window).triggerHandler(pinuts.googleMaps.events.onSelectablePointSelect, [$(this).val()]);
        }
    );
};
pinuts.googleMaps.tools.createSelectablePointOption = function (selectablePoint) {
    var option = $('<option value="' + selectablePoint.key + '">' + selectablePoint.name + '</option>');
    $(pinuts.googleMaps.config.domReferences.search.container.selectablePoints).append(
        option
    );
};
/**
 * This is a approximation(!!!), which works for google maps zoom level
 * 4 to 19 (max). The constant 155905.08 was calculated with three systematic
 * tests and achieving the arithmetic average of the results per zoom level ...
 * Kinda reverse engineering!
 * Unfortunately the distance per google maps pane (256px * 256px) @ zoom level
 * doesn't seem to be documented ... or i simply didn't find it.
 * Maybe it isn't a exponential function... My guess was:
 * meters per pixel multiplicated with (<anybase>)^zoomLevel = Constant ... and
 * so i found this:
 * meters / px * 2^zoomLevel = 155905.08
 * which is supposable adequate for the purpose ...
 */
pinuts.googleMaps.tools.getZoomLevelByDistanceForContainerDimension = function (distance, shorterViewportDimension) {
    if (!isNaN(distance / 1) && !isNaN(shorterViewportDimension / 1)) {
        var zoomLevel = Math.round( ( Math.log( 155905.08 * shorterViewportDimension / distance ) / Math.log(2) ) );
        return zoomLevel;
    } else {
        return 10; // well, it's the middle of available zoom levels, so in case of emergency, this hopefully is a quite good choice
    }
};
pinuts.googleMaps.tools.initializeMarkerOptions = function () {
    
    var defaultIcon = new GIcon(G_DEFAULT_ICON, pinuts.googleMaps.config.imageUris.defaultLocationMarker);
    defaultIcon.iconSize = new GSize(14, 26);
    defaultIcon.shadow = pinuts.googleMaps.config.imageUris.defaultLocationMarkerShadow;
    defaultIcon.shadowSize = new GSize(20, 26);
    defaultIcon.imageMap = pinuts.googleMaps.config.imageUris.defaultLocationImageMapCoords;

    pinuts.googleMaps.defaultLocationMarkerOptions = {icon: defaultIcon};;

    if (pinuts.googleMaps.config.imageUris.userLocationMarker !== "") {
        var userIcon = new GIcon(G_DEFAULT_ICON, pinuts.googleMaps.config.imageUris.userLocationMarker);
        userIcon.iconSize = new GSize(34, 28);
        userIcon.shadow = "";
        userIcon.shadowSize = new GSize(0, 0);
        userIcon.imageMap = [0, 0, 0, 0];
        
        pinuts.googleMaps.userLocationMarkerOptions = {icon: userIcon};
    }
};
pinuts.googleMaps.tools.setMarkers = function () {
    pinuts.googleMaps.googleMapInstance.clearOverlays();
    for (var markerPointId in pinuts.googleMaps.rawData.markerPoints) {

        var markerPoint = pinuts.googleMaps.rawData.markerPoints[markerPointId];
        if (markerPoint.geoCoords !== undefined) {

            var marker = new GMarker(
                new GLatLng(markerPoint.geoCoords.lat, markerPoint.geoCoords.lng),
                $.extend(
                    {},
                    pinuts.googleMaps.defaultLocationMarkerOptions,
                    {title: markerPoint.name}
                )
            );
            marker.personalId = markerPointId;
            markerPoint.googleMapsMarkerInstance = marker;
            pinuts.googleMaps.googleMapInstance.addOverlay(marker);
        }
    }
};
pinuts.googleMaps.tools.initializeMap = function () {

    $(window).triggerHandler(pinuts.googleMaps.events.onMapBeforeInitializing);

    pinuts.googleMaps.googleMapInstance = new google.maps.Map2(pinuts.googleMaps.config.defaults.domElement);
    
    // routing the google maps events to custom events:
    GEvent.addListener(
        pinuts.googleMaps.googleMapInstance,
        "load",
        function () {
            $(window).triggerHandler(pinuts.googleMaps.events.onMapIsReady, arguments);
        }
    );
    GEvent.addListener(
        pinuts.googleMaps.googleMapInstance,
        "tilesloaded",
        function () {
            $(window).triggerHandler(pinuts.googleMaps.events.onMapIsLoaded, arguments);
        }
    );

    pinuts.googleMaps.googleMapInstance.addControl(new GLargeMapControl3D());
    //pinuts.googleMaps.googleMapInstance.addControl(new GSmallZoomControl3D());


    //pinuts.googleMaps.googleMapInstance.addControl(new GNavLabelControl());
    //pinuts.googleMaps.googleMapInstance.addControl(new GScaleControl());

    // TODO: G_PHYSICAL_MAP is wanted
    pinuts.googleMaps.googleMapInstance.setMapType(G_NORMAL_MAP);


    try{
        GEvent.addListener(
            pinuts.googleMaps.googleMapInstance,
            "infowindowopen",
            function () {
                window.setTimeout(
                    function () {
                        pinuts.googleMaps.tools.showCurrentInfoWindow();
                /* * /
                        
                        var infoWindowPixelPosition = pinuts.googleMaps.googleMapInstance.fromLatLngToDivPixel(
                            pinuts.googleMaps.googleMapInstance.getInfoWindow().getPoint()
                        );
                        var infoWindowPixelOffset = pinuts.googleMaps.googleMapInstance.getInfoWindow().getPixelOffset();

                        var realInfoWindowAnchorPoint = pinuts.googleMaps.googleMapInstance.fromDivPixelToLatLng(
                            new GPoint(infoWindowPixelPosition.x + infoWindowPixelOffset.width, infoWindowPixelPosition.y + infoWindowPixelOffset.height)
                        );
                        /* * /
                        pinuts.googleMaps.googleMapInstance.getInfoWindow()
                            .reset(
                                realInfoWindowAnchorPoint,
                                pinuts.googleMaps.googleMapInstance.getInfoWindow().getTabs(),
                                new GSize(290, 130)
                            );
                        /* * /
                /* */
                    },
                    //1
                    0
                );
            }
        );
        
        GEvent.addListener(
            pinuts.googleMaps.googleMapInstance,
            "click",
            function (overlay, latLng, overlayLatLng) {
                if (
                    overlay !== null &&
                    typeof(overlay.getTitle) === "function"
                ) {
                    var markerPointId = overlay.personalId;
                    if (!!markerPointId) {
                        pinuts.googleMaps.tools.openInfoWindowForMarkerPointId(markerPointId);
                    } else {
                        //console.dir(latLng); // TODO: do something with a click on the map, which hits no marker point???
                    }
                }
            }
        );
        GEvent.addListener(
            pinuts.googleMaps.googleMapInstance,
            "zoomend",
            function (oldLevel, newLevel) {
                // because of the resetting of the infowindow, the getting close
                // to the marker point is destroyed...
                // so - as far as time and money si available - it'll be closed...
                // !!! DON'T WORK!!!
                // BIG TODO:!
                //pinuts.googleMaps.googleMapInstance.closeInfoWindow();
            }
        );
    } catch (exp) {
        //console.dir(exp); // TODO: can this be ignoerd?
    }

    pinuts.googleMaps.googleMapInstance.setCenter(
        new google.maps.LatLng(
            pinuts.googleMaps.config.defaults.lat,
            pinuts.googleMaps.config.defaults.lng
        ),
        pinuts.googleMaps.config.defaults.zoom
    );
    pinuts.googleMaps.googleGeoCoderInstance = new GClientGeocoder();
    pinuts.googleMaps.googleGeoCoderInstance.setBaseCountryCode(pinuts.googleMaps.config.defaults.addressFilter.country);
    pinuts.googleMaps.googleGeoCoderFilterViewport = new GLatLngBounds(
        new GLatLng(
            pinuts.googleMaps.config.defaults.addressFilter.viewport.southWest.lat,
            pinuts.googleMaps.config.defaults.addressFilter.viewport.southWest.lng
        ),
        new GLatLng(
            pinuts.googleMaps.config.defaults.addressFilter.viewport.northEast.lat,
            pinuts.googleMaps.config.defaults.addressFilter.viewport.northEast.lng
        )
    );
    pinuts.googleMaps.googleGeoCoderInstance.setViewport(
        pinuts.googleMaps.googleGeoCoderFilterViewport
    );

    pinuts.googleMaps.tools.createSearchControlClass();
    var searchControlInstance = new pinuts.googleMaps.tools.searchControl();
    pinuts.googleMaps.googleMapInstance.addControl(searchControlInstance);

};

pinuts.googleMaps.tools.openInfoWindowForMarkerPointId = function (markerPointId) {
    if (!!markerPointId) {
        var markerPoint = pinuts.googleMaps.rawData.markerPoints[markerPointId];
        if (markerPoint !== undefined) {
    
            //pinuts.googleMaps.tools.panToBestPositionForInfoWindow(markerPoint.googleMapsMarkerInstance.getLatLng());
            $.ajax(
                {
                    url: markerPoint.infoUri,
                    success: function (data, httpStatus, xhr) {
                        if (pinuts.googleMaps.config.isDummy) {
                            data = data
                                        .replace(/<h5>[^<]*<\/h5>/g, "<h5>" + markerPoint.name + "</h5>")
                                        .replace(/\?outletid\=id_ID-intern1003_/g, "?outletid=" + markerPointId);
                        }
                        $(pinuts.googleMaps.config.domReferences.infoWindowTmp.content).html(data);
                        $(pinuts.googleMaps.config.domReferences.infoWindowTmp.content + " a[href^='http']").attr("target", "_blank");
                        markerPoint.googleMapsMarkerInstance.openInfoWindow(
                            $(".google-maps-container .google-maps-infowindow-sizer").clone().css("display", "block")[0]
                        );
                        //pinuts.googleMaps.tools.showCurrentInfoWindow(markerPoint.googleMapsMarkerInstance);
                        /*
                        pinuts.googleMaps.tmp.currentMoveEndListener = GEvent.addListener(
                            pinuts.googleMaps.googleMapInstance,
                            "moveend",
                            function () {
                                pinuts.googleMaps.tools.showCurrentInfoWindow(markerPoint.googleMapsMarkerInstance);
                            }
                        );
                        */
                    }
                }
            );
        }
    }
};

pinuts.googleMaps.tools.showCurrentInfoWindow = function (overlay) {
    /*overlay.openInfoWindow(
        $(pinuts.googleMaps.config.domReferences.infoWindowTmp.content).clone()[0]
    );
    */
    $(".google-maps-container .google-maps .google-maps-infowindow-content").html(
        $(".google-maps-container .google-maps-infowindow-tmp .google-maps-infowindow-content").html()
    );
    //$(".google-maps-container .google-maps .google-maps-infowindow-content .google-maps-markerpoint-infowindow").show();
    //$(pinuts.googleMaps.config.domReferences.infoWindowTmp.content)
    //$(pinuts.googleMaps.config.domReferences.infoWindowTmp.content).html(pinuts.googleMaps.config.i18nTexts[currentPageLanguage].standardLoading);
    //GEvent.removeListener(pinuts.googleMaps.tmp.currentMoveEndListener);
};

pinuts.googleMaps.tools.panToBestPositionForInfoWindow = function (overlayLatLng) {
    
    var overlayDivPixel = pinuts.googleMaps.googleMapInstance.fromLatLngToContainerPixel(overlayLatLng);

    // get the horizantal delta to the center of the map
    var deltaX = (
        $(pinuts.googleMaps.config.defaults.domElement).width() / 2 - overlayDivPixel.x
    );
    // get the vertical delta to 10px above the map bottom
    var deltaY = (
        $(pinuts.googleMaps.config.defaults.domElement).height() - 10 - overlayDivPixel.y
    );

    pinuts.googleMaps.googleMapInstance.panBy(new GSize(deltaX, deltaY));
};

pinuts.googleMaps.tools.createSearchControlClass = function () {

    pinuts.googleMaps.tools.searchControl = function () {};
    pinuts.googleMaps.tools.searchControl.prototype = new GControl(false, true);
    pinuts.googleMaps.tools.searchControl.prototype.initialize = function (map) {
        var searchControlElement = $(pinuts.googleMaps.config.domReferences.search.ref).show()[0];
        map.getContainer().appendChild(searchControlElement);
        return searchControlElement;
    };
    pinuts.googleMaps.tools.searchControl.prototype.getDefaultPosition = function () {
        return new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(15, 14));
    };
};


pinuts.googleMaps.tools.searchUserLocationByAddress = function (evnt, address) {
    $(window).triggerHandler(pinuts.googleMaps.events.onBeforeSearch);
    pinuts.googleMaps.tools.overlay.show();
    $("#google-maps-address-search-container .results").hide();
    try {
        address += ", " + pinuts.googleMaps.config.defaults.addressFilter.country;
        //console.log("will be looked for the address: " + address);
        //pinuts.googleMaps.googleGeoCoderInstance.setBaseCountryCode(pinuts.googleMaps.config.defaults.addressFilter.country);
        //pinuts.googleMaps.googleGeoCoderInstance.setViewport(
        //    pinuts.googleMaps.googleGeoCoderFilterViewport
        //);
        pinuts.googleMaps.googleGeoCoderInstance.getLocations(
            address,
            function (response) {
                if (!response || response.Status.code !== 200) {
                    // error while connecting the google web service, or API returned an error
                    pinuts.googleMaps.tools.overlay.hide();
                    $(window).triggerHandler(pinuts.googleMaps.events.onSearchError, [response]);
                } else {
                    /* * /
                    console.group("unfiltered google response:")
                    console.dir(response.Placemark);
                    console.groupEnd();
                    /* */
                    placemarks = pinuts.googleMaps.tools.filterAndSortAddresses(response.Placemark);
                    /* * /
                    console.group("filtered google response:")
                    console.dir(placemarks);
                    console.groupEnd();
                    /* */

                    if (placemarks.length > 1) { // google found more alternatives for the address, showing the results
                        pinuts.googleMaps.tools.showMultipleUserLocations(placemarks);
                    } else if (placemarks.length == 1) { // cool, just one address is found, center the map:
                        pinuts.googleMaps.tools.centerMapToSelectedPointAndSetZoomByGeoCoords(
                            placemarks[0].Point.coordinates[1],
                            placemarks[0].Point.coordinates[0]
                        );
                    } else { // nothing valuable found ... showing error:
                        pinuts.googleMaps.tools.overlay.hide();
                        $(window).triggerHandler(pinuts.googleMaps.events.onSearchNoAddressFoundError);
                    }
                }
                $(window).triggerHandler(pinuts.googleMaps.events.onAfterSearch, [response]);
            }
        );
    } catch (exp) {
        // fatal error when trying to recieve google maps data
        pinuts.googleMaps.tools.overlay.hide();
        $(window).triggerHandler(pinuts.googleMaps.events.onSearchError, [exp]);
    }
};
pinuts.googleMaps.tools.filterAndSortAddresses = function (googleMapsApiPlacemarks) {

/*
    ------------------------------
    enum GGeoAddressAccuracy:
        0  Unknown location.
        1  Country level accuracy.
        2  Region (state, province, prefecture, etc.) level accuracy.
        3  Sub-region (county, municipality, etc.) level accuracy.
        4  Town (city, village) level accuracy.
        5  Post code (zip code) level accuracy.
        6  Street level accuracy.
        7  Intersection level accuracy.
        8  Address level accuracy.
        9  Premise (building name, property name, shopping center, etc.) level accuracy.
    ------------------------------
    ## We only are interested in accuracies from 3 to 8 ...
*/
    return $.grep(
        googleMapsApiPlacemarks,
        function (placemark, idx) {
            // the google maps api response structure is too different - so the quite hard fallback is needed ...
            try {
                if (!placemark.AddressDetails) {
                    return false;
                }
                if (placemark.AddressDetails.Country.CountryNameCode !== pinuts.googleMaps.config.defaults.addressFilter.country) {
                    return false;
                }
                if (placemark.AddressDetails.Accuracy > 8 || placemark.AddressDetails.Accuracy < 3) {
                    return false;
                }
                /* * /
                console.log("value: " + placemark.AddressDetails.Country.AdministrativeArea.AdministrativeAreaName);
                console.log("administrativeAreas: " + pinuts.googleMaps.config.defaults.addressFilter.administrativeAreas);
                console.log($.inArray(placemark.AddressDetails.Country.AdministrativeArea.AdministrativeAreaName, pinuts.googleMaps.config.defaults.addressFilter.administrativeAreas))
                /* */
                if (
                    placemark.AddressDetails.Country &&
                    placemark.AddressDetails.Country.AdministrativeArea &&
                    $.inArray(placemark.AddressDetails.Country.AdministrativeArea.AdministrativeAreaName, pinuts.googleMaps.config.defaults.addressFilter.administrativeAreas) === -1
                ) {
                    return false;
                }
                return true;
            } catch (exp) {
                return false;
            }
        }
    ).sort( // sorting by accuracy
        function (a, b) {
            var compA = a.AddressDetails.Accuracy + 0;
            var compB = b.AddressDetails.Accuracy + 0;
            return (compA < compB) ? -1 : (compA > compB) ? 1 : 0;
        }
    ).slice(0, 10); // and finally grabbing just the top ten...
};
pinuts.googleMaps.tools.showMultipleUserLocations = function (placemarks) {
    var resultHTML = $("<ul />").append($("<li><strong>" + pinuts.googleMaps.config.i18nTexts[currentPageLanguage].resultsListStartText + "</strong></li>"));
    $.each(
        placemarks,
        function (idx, placemark) {
            var district = placemark.AddressDetails.Country &&
                           placemark.AddressDetails.Country.AdministrativeArea &&
                           placemark.AddressDetails.Country.AdministrativeArea.SubAdministrativeArea &&
                           placemark.AddressDetails.Country.AdministrativeArea.SubAdministrativeArea.Locality &&
                           placemark.AddressDetails.Country.AdministrativeArea.SubAdministrativeArea.Locality.DependentLocality &&
                           placemark.AddressDetails.Country.AdministrativeArea.SubAdministrativeArea.Locality.DependentLocality.DependentLocalityName !== undefined ?
                               placemark.AddressDetails.Country.AdministrativeArea.SubAdministrativeArea.Locality.DependentLocality.DependentLocalityName : "";
            resultHTML.append(
                $("<li />")
                    .append(
                        $("<a>" + placemark.address.replace(/,\s[^,]*?\s?Deutschland,?/gi, "").replace(/,\sGermany,?/gi, "") + (district != "" ? ", " + district : "") + "</a>")
                        .data("placemark", placemark)
                        .click(
                            function () {
                                $(pinuts.googleMaps.config.domReferences.search.container.results).hide("blind", {}, 500);
                                pinuts.googleMaps.tools.centerMapToSelectedPointAndSetZoomByGeoCoords(
                                    $(this).data("placemark").Point.coordinates[1],
                                    $(this).data("placemark").Point.coordinates[0]
                                );
                            }
                        )
                    )
            );
        }
    );
    $(pinuts.googleMaps.config.domReferences.search.container.results)
        .html("")
        .append(resultHTML)
        .show(
            "blind",
            {},
            500,
            function () {
                pinuts.googleMaps.tools.overlay.hide();
            }
        );
};
pinuts.googleMaps.tools.searchFailedHandler = function (evnt, args) {
    $(pinuts.googleMaps.config.domReferences.search.errorOverlay.text)
        .text(pinuts.googleMaps.tools.getErrorTextByType(evnt.type))
        .parent()
        .show();
    $(pinuts.googleMaps.config.domReferences.search.ref)
        .css(
            "left",
            $(pinuts.googleMaps.config.domReferences.search.ref).offset().left -
            parseInt($(pinuts.googleMaps.config.domReferences.search.ref).css("right")) -
            10 // moving offset needs to be subdacted
        )
        .effect(
            "shake",
            {
                times: 3,
                distance: 10
            },
            100,
            function () {
                window.setTimeout(
                    function () {
                        $(pinuts.googleMaps.config.domReferences.search.ref).css("left", "auto");
                        $(pinuts.googleMaps.config.domReferences.search.errorOverlay.ref)
                            .hide()
                            .children()
                            .text("");
                    },
                    500
                );
            }
        );
};
pinuts.googleMaps.tools.getErrorTextByType = function (errorType) {
    var errorText = pinuts.googleMaps.config.i18nTexts[currentPageLanguage].errorUnknown;
    switch (errorType) {
        case pinuts.googleMaps.events.onSearchError:
            errorText = pinuts.googleMaps.config.i18nTexts[currentPageLanguage].errorSearchError;
        break;
        case pinuts.googleMaps.events.onSearchNoAddressFoundError:
            errorText = pinuts.googleMaps.config.i18nTexts[currentPageLanguage].errorSearchNoAddressFound;
        break;
        case pinuts.googleMaps.events.onMapApiLoadingError:
            errorText = pinuts.googleMaps.config.i18nTexts[currentPageLanguage].errorMapApiLoading;
        break;
        case pinuts.googleMaps.events.onDataLoadingError:
            errorText = pinuts.googleMaps.config.i18nTexts[currentPageLanguage].errorDataLoading;
        break;
        case pinuts.googleMaps.events.onDataParsingError:
            errorText = pinuts.googleMaps.config.i18nTexts[currentPageLanguage].errorDataParsing;
        break;
        default:
    }
    return errorText;
}

pinuts.googleMaps.tools.centerMapToSelectedPointAndSetZoomBySelectablePointId = function (evnt, selectablePointId) {
    if (selectablePointId !== "") { // the "Please choose..."-item has am empty value
        var selectablePoint = pinuts.googleMaps.rawData.selectablePoints[selectablePointId];
        if ( // can't happen normally; "Angst-Stahl" my father would call it...
            selectablePoint !== undefined &&
            selectablePoint !== null
        ) {
            pinuts.googleMaps.tools.centerMapToSelectedPointAndSetZoomByGeoCoords(
                selectablePoint.geoCoords.lat,
                selectablePoint.geoCoords.lng
            );
        }
    }
};
pinuts.googleMaps.tools.centerMapToSelectedPointAndSetZoomByGeoCoords = function (lat, lng) {
    if (!isNaN(lat / 1) && !isNaN(lng / 1)) { // are valid values passed?

        pinuts.googleMaps.tools.overlay.show();
        pinuts.googleMaps.tools.changeMapZoom(null);
        pinuts.googleMaps.googleMapInstance.panTo(
            new google.maps.LatLng(
                lat,
                lng
            )
        );
    }
};
pinuts.googleMaps.tools.changeMapZoom = function (evnt) {
    /* the resetting of the infoWindow leads to incorrect position for all
     * other zoomLevels, so hiding it, if shown and redraw
     * it later on (see some lines below...)
     */
    var infoWindowWasVisible = false;
    var infoWindow = pinuts.googleMaps.googleMapInstance.getInfoWindow();
    if (!infoWindow.isHidden()) {
        infoWindow.hide();
        infoWindowWasVisible = true;
    }

    pinuts.googleMaps.googleMapInstance.setZoom(
        pinuts.googleMaps.tools.getZoomLevelByDistanceForContainerDimension(
           pinuts.googleMaps.tools.getDesiredDistance(),
           pinuts.googleMaps.tools.getMapMinimalDimension()
        )
    );
    /*
     * this is "later on" ;-)
     * redraw the info window
     */
    if (infoWindowWasVisible) {
        infoWindow.show();
        infoWindow.redraw();
    }

    window.setTimeout(pinuts.googleMaps.tools.overlay.hide, 5000);
};
pinuts.googleMaps.tools.getDesiredDistance = function () {
    return parseInt($(pinuts.googleMaps.config.domReferences.search.container.distances).val()) * 2;
};
pinuts.googleMaps.tools.getMapMinimalDimension = function () { 
    return (
        $(pinuts.googleMaps.config.defaults.domElement).height() <=
        $(pinuts.googleMaps.config.defaults.domElement).width() ?
            $(pinuts.googleMaps.config.defaults.domElement).height() :
            $(pinuts.googleMaps.config.defaults.domElement).width()
    );
};

pinuts.googleMaps.tools.overlay = {};
pinuts.googleMaps.tools.overlay.toggle = function () {
    $(pinuts.googleMaps.config.domReferences.overlay.ref).toggle();
};
pinuts.googleMaps.tools.overlay.hide = function () {
    $(pinuts.googleMaps.config.domReferences.overlay.ref).hide();
};
pinuts.googleMaps.tools.overlay.show = function () {
    $(pinuts.googleMaps.config.domReferences.overlay.ref).show();
};
pinuts.googleMaps.tools.overlay.setStatus = function (msg, percent, isError) {
    
    $(pinuts.googleMaps.config.domReferences.overlay.message).text(msg);
    if (!isError) {
        $(pinuts.googleMaps.config.domReferences.overlay.percent).css(
            "width",
            parseInt($(pinuts.googleMaps.config.domReferences.overlay.percent).css("max-width")) / 100 * percent + "%"
        );
    } else {
        $(pinuts.googleMaps.config.domReferences.overlay.percent).css(
            {
                "width": "100%",
                "height": "30px",
                "background-image": "url('" + pinuts.googleMaps.config.imageUris.statusError + "')",
                "background-position": "center center"
            }
        ).parent().css("height", "30px");
    }
};


pinuts.googleMaps.tools.initializeCounter = 0;
pinuts.googleMaps.tools.initialize = function () {

    pinuts.googleMaps.config.defaults.domElement = $(pinuts.googleMaps.config.domReferences.mapContainer)[0]

    if (google !== undefined && google.maps !== undefined) {
        pinuts.googleMaps.tools.initializeMap();
    } else if (pinuts.googleMaps.tools.initializeCounter < 10){
        pinuts.googleMaps.tools.initializeCounter++;
        window.setTimeout(pinuts.googleMaps.tools.initializeMap, 32);
    } else {
        $(window).triggerHandler(pinuts.googleMaps.events.onMapApiLoadingError);
    }
};

pinuts.googleMaps.tools.bindEventHandler = function () {

/*
    onBeforeDataLoading
    * onDataLoadingError
    onAfterDataLoaded
    onBeforeDataParsing
    * onDataParsingError
    onMapBeforeInitializing
    onMapIsReady
    onAfterDataParsed
    onMapIsInitialyLoaded
    onMapIsLoaded
*/

    $(window).bind(
        pinuts.googleMaps.events.onBeforeDataLoading,
        function (evnt, args) {
            pinuts.googleMaps.tools.overlay.setStatus(pinuts.googleMaps.config.i18nTexts[currentPageLanguage].overlayBeforeDataLoading, 24);
            pinuts.googleMaps.tools.defaultEventHandler(evnt, args);
        }
    );

    $(window).bind(pinuts.googleMaps.events.onDataLoadingError, pinuts.googleMaps.tools.defaultErrorHandler);

    $(window).bind(
        pinuts.googleMaps.events.onAfterDataLoaded,
        function (evnt, args) {
            pinuts.googleMaps.tools.overlay.setStatus(pinuts.googleMaps.config.i18nTexts[currentPageLanguage].overlayAfterDataLoaded, 27);
            pinuts.googleMaps.tools.defaultEventHandler(evnt, args);
            pinuts.googleMaps.tools.parseData();
        }
    );

    $(window).bind(
        pinuts.googleMaps.events.onBeforeDataParsing,
        function (evnt, args) {
            pinuts.googleMaps.tools.overlay.setStatus(pinuts.googleMaps.config.i18nTexts[currentPageLanguage].overlayBeforeDataParsing, 30);
            pinuts.googleMaps.tools.defaultEventHandler(evnt, args);
        }
    );

    $(window).bind(pinuts.googleMaps.events.onDataParsingError, pinuts.googleMaps.tools.defaultErrorHandler);
    
    $(window).bind(
        pinuts.googleMaps.events.onMapIsReady,
        function (evnt, args) {
            pinuts.googleMaps.tools.overlay.setStatus(pinuts.googleMaps.config.i18nTexts[currentPageLanguage].overlayMapIsReady, 55);
            pinuts.googleMaps.tools.defaultEventHandler(evnt, args);
        }
    );


    $(window).bind(
        pinuts.googleMaps.events.onAfterDataParsed,
        function (evnt, args) {
            pinuts.googleMaps.tools.overlay.setStatus(pinuts.googleMaps.config.i18nTexts[currentPageLanguage].overlayAfterDataParsed, 40);
            /**
             * Middle of Schleswig-Holstein
             *     lat: 54.21707306629117,
             *     lng: 9.7723388671875
             *     zoom: 8 // to have all of Schleswig-Holstein in viewport ...
             */
            pinuts.googleMaps.tools.initialize();
            $(pinuts.googleMaps.config.domReferences.search.container.distances).change(
                function (evnt, args) {
                    pinuts.googleMaps.tools.overlay.show();
                    pinuts.googleMaps.tools.changeMapZoom();
                    //$(window).triggerHandler(pinuts.googleMaps.events.onDistanceFieldChange);
                }
            );
            $(pinuts.googleMaps.config.domReferences.search.container.distances).keyup(
                function (evnt) {
                    if (
                        !evnt.metaKey &&
                        !evnt.altKey &&
                        !evnt.ctrlKey &&
                        (
                            evnt.keyCode === 38 || // cursor up key
                            evnt.keyCode === 40    // cursor dopwn key
                        )
                    ) {
                        $(this).triggerHandler("change");
                    }
                }
            );
            pinuts.googleMaps.tools.createSelectablePoints();
            pinuts.googleMaps.tools.initializeMarkerOptions();
            pinuts.googleMaps.tools.setMarkers();
            pinuts.googleMaps.tools.defaultEventHandler(evnt, args);
        }
    );

    $(window).bind(
        pinuts.googleMaps.events.onMapIsInitialyLoaded,
        function (evnt, args) {
            if (self.location.search.indexOf("mpid=") !== -1) {
                // get the value of this parameter:
                var markerPointId = unescape(self.location.search.replace(/\?.*?mpid=([^\&]*)\&?/g, "$1"));
                if (!!markerPointId) {
                    pinuts.googleMaps.tools.openInfoWindowForMarkerPointId(markerPointId);
                }
            }
            pinuts.googleMaps.tools.defaultEventHandler(evnt, args);
        }
    );

    $(window).bind(pinuts.googleMaps.events.onMapBeforeInitializing, pinuts.googleMaps.tools.defaultEventHandler);

    $(window).bind(
        pinuts.googleMaps.events.onMapIsLoaded,
        function (evnt, args) {
            pinuts.googleMaps.tools.overlay.setStatus(pinuts.googleMaps.config.i18nTexts[currentPageLanguage].overlayMapIsLoaded, 100);

            pinuts.googleMaps.tools.overlay.hide();
            $(pinuts.googleMaps.config.domReferences.overlay.percent).css(
                {
                    "width": "100%",
                    "background-image": "url('" + pinuts.googleMaps.config.imageUris.statusBusy + "')",
                    "background-position": "center center"
                }
            );
            pinuts.googleMaps.tools.overlay.setStatus(pinuts.googleMaps.config.i18nTexts[currentPageLanguage].standardLoading, 100);
            if (pinuts.googleMaps.applicationNotReady) {
                pinuts.googleMaps.applicationNotReady = false;
                $(window).triggerHandler(
                    pinuts.googleMaps.events.onMapIsInitialyLoaded
                );
            }
            pinuts.googleMaps.tools.defaultEventHandler(evnt, args);
        }
    );
    
    $(pinuts.googleMaps.config.domReferences.search.container.form).submit(
        function () {
            $(window).triggerHandler(pinuts.googleMaps.events.onSearchUserRequest, [$(pinuts.googleMaps.config.domReferences.search.container.searchField).val()])
            return false;
        }
    );

    $(window).bind(
        pinuts.googleMaps.events.onBeforeSearch,
        function (evnt, args) {
            //pinuts.googleMaps.googleMapInstance.closeInfoWindow();
            pinuts.googleMaps.tools.defaultEventHandler(evnt, args);
        }
    );

    $(window).bind(
        pinuts.googleMaps.events.onSearchUserRequest,
        function (evnt, args) {
            pinuts.googleMaps.tools.searchUserLocationByAddress(evnt, args);
            pinuts.googleMaps.tools.defaultEventHandler(evnt, args);
        }
    );

    $(window).bind(
        pinuts.googleMaps.events.onSearchNoAddressFoundError,
        pinuts.googleMaps.tools.searchFailedHandler
    );

    $(window).bind(
        pinuts.googleMaps.events.onSearchError,
        pinuts.googleMaps.tools.searchFailedHandler
    );

    $(window).bind(pinuts.googleMaps.events.onAfterSearch, pinuts.googleMaps.tools.defaultEventHandler);

    $(window).bind(
        pinuts.googleMaps.events.onSelectablePointSelect,
        pinuts.googleMaps.tools.centerMapToSelectedPointAndSetZoomBySelectablePointId
    );

    $(window).bind(pinuts.googleMaps.events.onDistanceFieldChange, pinuts.googleMaps.tools.defaultEventHandler);

    $(pinuts.googleMaps.config.domReferences.search.container.fieldsetLabel).click(
        function () {
            $(pinuts.googleMaps.config.domReferences.search.container.fieldsetLabel).removeClass("active").each(
                function (idx, elm) {
                    $("#" + $(elm).attr("for")).parent().removeClass("active");
                }
            );
            $(this).addClass("active");
            $("#" + $(this).attr("for")).parent().addClass("active");
        }
    );

};

pinuts.googleMaps.start = function (customConfig) {

    $(window).bind(pinuts.googleMaps.events.onMapApiLoadingError, pinuts.googleMaps.tools.defaultErrorHandler);

    if (pinuts.googleMaps.apiAccessible) {
        pinuts.googleMaps.tools.bindEventHandler();
    
        if (
            customConfig !== undefined &&
            typeof(customConfig) === "object"
        ) {
            pinuts.googleMaps.tools.setConfig(customConfig);
        }
        pinuts.googleMaps.tools.loadRawData();

    } else { // !googleApiAccessible
        $(window).triggerHandler(pinuts.googleMaps.events.onMapApiLoadingError);
    }
};

if (
    typeof(google) !== "undefined" &&
    typeof(google.load) !== "undefined"
) {
    try {
        google.load("maps", "2.x");
        pinuts.googleMaps.apiAccessible = true;
    } catch(exp) {
        /* ignore here, will be shown, when document is loaded... */
    }
}


//var applicationNotReady = true;
$(document).ready(
    function () {


    }
);

$(document).unload(
    function () {
        pinuts.googleMaps.googleMapInstance.GUnload();
    }
);
