// Helper methods
Function.prototype.scope = function (scope) {
    var fn = this;
    return function() {
        fn.apply(scope, arguments);
    }
};
var EasyTemplate = function (content) {
    this.content = content;
};
EasyTemplate.prototype.apply = function (data) {
    var c = this.content;
    for (var key in data) {
        c = c.replace(new RegExp('\\$' + key + '\\$', 'g'), data[key]);
    }
    return c;
};


/* events
 beforeinitialize
 initialized
 rootmarkerset(rootMarker)
 rootmarkericonset(rootMarkerIcon)
 markeradded(marker)
 markerremoved(marker)
 markericonset(markerIcon)
 infowindowopened(infoWindow)
 infowindowclosed(infoWindow)
 rootrestaurantloaded(restaurantLinkId, rootMarker, data)
 restaurantsloaded(addedMarkers, data)
 restaurantpositionset(retaurantId, marker, lagLng, data)
 geocodingfailed(request, status)
 filterset(regionId, filter)
*/

var DsGoogleMap = function (prefix, options) {
    //region DsGoogleMap: private vars
    var _gMap = null;                           // google.maps.Map object
    var _prefix = prefix;                       // prefix of the div's id
    var _cssMapIdentifier = '#'+prefix+'-Gmap';
    var _mapIdentifier = ''+prefix+'-Gmap';     // id to locate the div in which to display the map
    var _focusZoom = 13;                        // zoom level to use when calling focusRootRestaurant
    var _rootMarker = null;                     // marker which represents the root restaurant
    var _rootMarkerIcon = '/application/meinr/images/pin.png';          // image to use to display the root marker    // TODO change image
    var _markers = [];                          // list of markers repesenting nearby restaurants
    var _markerIcon = '/application/meinr/images/pin2.png';             // image to use to display list of markers    // TODO change icon
    var _infoWindow;                            // popup speech bubble to use to display information when marker is clicked
    var _geocoder;                              // google.maps.Geocoder object used to query coordinates when address is given
    var _filter;                                // if set, this is used as query term and searchRestaurants is called instead of loadRestaurants
    var _regionId;                              // regionId to use as parameter when calling searchRestaurants
    var _autoLoad = true;                       // whether or not to automatically load markers when a new viewport is set
    var _objectType = null;                     // get objecttype from URL (Restaurant, Company, Person)
    //endregion DsGoogleMap: private vars
    
    //region DsGoogleMap: privileged methods
    this.getPrefix = function () {
        return _prefix;
    };
    this.getMap = function () {
        return _gMap;
    };
    this.getMapIdentifier = function () {
        return _mapIdentifier;
    };
    this.getFocusZoom = function () {
        return _focusZoom;
    };
    this.setFocusZoom = function (zoom) {
        _focusZoom = zoom;
    };
    this.getRootMarker = function () {
        return _rootMarker;
    };
    this.setRootMarker = function (objectType, id, latLng) {
        if(id && latLng) {
            if(!_rootMarker) {
                _rootMarker = new google.maps.Marker({
                    position: latLng,
                    map: _gMap,
                    icon: this.getRootMarkerIcon()
                });
                _rootMarker.id = id;
                _rootMarker.objectType = objectType;
                google.maps.event.addListener(_rootMarker, 'dragend', function() {
                    this.setObjectPosition(_rootMarker.objectType, _rootMarker.id, _rootMarker.getPosition());
                }.scope(this));
            } else {
                _rootMarker.setPosition(latLng);
            }
        } else {
            if(_rootMarker) {
                _rootMarker.setMap(null);
                _rootMarker = null;
            }
        }
        $(this).trigger('rootmarkerset', _rootMarker);
        return _rootMarker;
    };
    this.getRootMarkerIcon = function () {
        return _rootMarkerIcon;
    };
    this.setRootMarkerIcon = function (icon) {
        _rootMarkerIcon = icon;
        if(_rootMarker)
            _rootMarker.setIcon(icon);
        $(this).trigger('rootmarkericonset', icon);
    };    
    this.getMarkers = function () {
        return _markers;
    };
    this.getMarkerById = function (id) {
        for (var i = 0; i < _markers.length; i++) {
            if (_markers[i].id == id)
                return _markers[i];
        }
        return null;
    };
    this.addMarker = function (id, latLng, title, infoWindowContent) {
        if (this.getMarkerById(id) == null) {
            // create marker
            var marker = new google.maps.Marker({
                position: latLng,
                map: _gMap,
                icon: this.getMarkerIcon(),
                title: title
            });
            marker.id = id;
            _markers.push(marker);
            
            marker.infoWindowContent = infoWindowContent;
            google.maps.event.addListener(marker, 'click', function() {
                this.openInfoWindow(marker.infoWindowContent, marker);
            }.scope(this));
            
            $(this).trigger('markeradded', marker);
            return marker;
        }
        return null;
    };
    this.removeMarker = function (marker) {
        for(var i = 0; i < _markers.length; i++) {
            var m = _markers[i];
            if(m === marker) {
                m.setMap(null);
                _markers.splice(i, 1);
                $(this).trigger('markerremoved', marker);
                return;
            }
        }
    };
    this.removeMarkerAt = function (index) {
        m.setMap(null);
        _markers.splice(i, 1);
        $(this).trigger('markerremoved', m); 
    };
    this.removeMarkerById = function (id) {
        for (var i = 0; i < _markers.length; i++) {
            var m = _markers[i];
            if (m.id == id) {
                _markers.splice(i, 1);
                $(this).trigger('markerremoved', m); 
                return;
            }
        }
    };
    this.clearMarkers = function () {
        for(var i = 0; i < _markers.length; i++) {
            var m = _markers[i];
            if(typeof m != 'undefined')
                m.setMap(null);
        }
        _markers = [];
    };
    this.getMarkerIcon = function () {
        return _markerIcon;
    };
    this.setMarkerIcon = function (icon) {
        _markerIcon = icon;
        for(var i = 0; i< _markers.length; i++)
            _markers[i].setIcon(icon);
        $(this).trigger('markericonset', icon);
    };    
    this.getInfoWindow = function () {
        return _infoWindow;
    };
    this.openInfoWindow = function (content, anchor) {
        if(!_infoWindow) {
            _infoWindow = this.createInfoWindow();
        }
        _infoWindow.setContent(content);
        _infoWindow.open(_gMap, anchor);
        $(this).trigger('infowindowopened', _infoWindow); 
    };
    this.closeInfoWindow = function () {
        _infoWindow.close();
        $(this).trigger('infowindowclosed', _infoWindow); 
    };
    this.getGeocoder = function () {
        if(!_geocoder) {
            _geocoder = new google.maps.Geocoder();
        }
        return _geocoder;
    };
    this.setFilter = function (regionId, filter) {
        _regionId = regionId;
        _filter = filter;
        $(this).trigger('filterset', regionId, filter);
        this.clearMarkers();
        this.loadViewportRestaurants();
    };
    this.clearFilter = function () {
        _regionId = null;
        _filter = null;
        $(this).trigger('filterset', _regionId, _filter);
        this.clearMarkers();
        this.loadViewportRestaurants();
    };
    this.getFilter = function () {
        if (!_regionId || !_filter)
            return null;
        return {regionId: _regionId, filter: _filter};
    };
    this.getAutoLoad = function () {
        return _autoLoad;
    };
    this.setAutoLoad = function (autoLoad) {
        _autoLoad = autoLoad;
        if(_autoLoad === true)
            this.loadViewportRestaurants();
    };
    //endregion DsGoogleMap: privileged methods
        
    //region DsGoogleMap: constructor
    this.initialize = function () {
        $(this).trigger('beforeinitialize');
        
        var mapOptions = $.extend(true, this.defaultMapOptions, options);
        _gMap = new google.maps.Map($(_cssMapIdentifier)[0], mapOptions);
        
        google.maps.event.addListener(_gMap, 'idle', function () {
            if(_autoLoad === true)
                switch(this._objectType) {
                    case 'restaurant':
                        this.loadViewportRestaurants();
                    break;
                }
        }.scope(this)); 
        
        $(this).trigger('initialized');  
    };
    //endregion DsGoogleMap: constructor
};

//region DsGoogleMap: static vars
//alert(objecttype);
DsGoogleMap.prototype.tmpl = {
    showRestaurantImage: new EasyTemplate("<img src='$url$' alt='' />"),
    infoWindowContent: new EasyTemplate("<div class='gmapinfo'>$img$<a class='title' href='/restaurant/view/id/$linkId$'>$title$</a><br>" +
                                         "<span class='city'>$city$</span> <br>" +
                                         "<span class='city'>$street$</span>"),
    listRestarauntItem: new EasyTemplate("<li class='li_item' id='list_$counter$'><div id='type:restaurant-id:$restaurantId$-catlinkid:$linkId$' class='listelement bg$style$'>" +
                                            "<div class='itembox'><h3><a href='/restaurant/view/id/$linkId$'>$title$</a></h3>" +
                                            "<div>$city$</div>" +
                                            "<div>$street$</div>" +
                                            "<div class='contentclearer'></div>" +
                                            "</div>" +
				"           </div></li>"),
    getRestaurantsInAreaUrl: new EasyTemplate('/xtl/restaurant/getrestaurantsinarea/north/$north$/west/$west$/east/$east$/south/$south$'),
    searchRestaurantsUrl: new EasyTemplate('/xtl/restaurant/search/id/$id$?query=$query$&north=$north$&west=$west$&east=$east$&south=$south$'),
//    searchRestaurantsUrl: new EasyTemplate('/json/restaurant/search/id/$id$?query=$query$&north=$north$&west=$west$&east=$east$&south=$south$'),
    viewObjectUrl: new EasyTemplate('/xtl/$objectType$/view/id/$objectId$'),
    setPositionUrl: new EasyTemplate('/json/$objectType$/setposition/id/$id$')
};

DsGoogleMap.prototype.defaultMapOptions = {
    zoom: 6,
    center: new google.maps.LatLng(47.635784, 13.590088), // AUT
    mapTypeId: google.maps.MapTypeId.ROADMAP,
    mapTypeControl: true, 
    mapTypeControlOptions: {
        mapTypeIds:[google.maps.MapTypeId.ROADMAP,google.maps.MapTypeId.TERRAIN],
        position:google.maps.ControlPosition.TOP_LEFT,
        style:google.maps.MapTypeControlStyle.HORIZONTAL_BAR
    },
    navigationControl: true, 
    navigationControlOptions: { 
        style: google.maps.NavigationControlStyle.SMALL, 
        position: google.maps.ControlPosition.LEFT 
    }
};
//endregion DsGoogleMap: static vars


//region DsGoogleMap: public methods

DsGoogleMap.prototype.createInfoWindow = function () {
    var infoWindow;
    if (typeof(InfoBox) !== 'undefined') {

        infoWindow = new InfoBox();
    } else {
        infoWindow= new google.maps.InfoWindow();
    }
    return infoWindow;
};

// Query web service for object within given bounds and display markers
DsGoogleMap.prototype.loadRootObject = function (objectType, objectId, callback) {
    $.ajax({
        type:'GET',
        url: this.tmpl.viewObjectUrl.apply({
            objectType: objectType,
            objectId: objectId
        }),
        dataType:'xml',
        success: function (data) {
            var marker = this.parseRootObjectXML(objectType, data);
            if (callback)
                callback(objectId, marker, data);
            $(this).trigger('rootobjectloaded', objectId, marker, data);
        }.scope(this)
    });
};
// Parse XML retrieved from web service (/xtl/restaurant/view) and display root marker
DsGoogleMap.prototype.parseRootObjectXML = function (objectType, xmlData) {
    var r = $(xmlData).find(objectType);
    var linkId = $(r).attr('linkID');
    var geo = $(r).find('adresse').find('geo');
    var latLng = new google.maps.LatLng(
                        parseFloat($(geo).find('lat').text()),
                        parseFloat($(geo).find('lon').text())
                     );
    
    return this.setRootMarker(objectType, linkId, latLng);
};

// Pan to root marker and set zoom level to value of focusZoom
DsGoogleMap.prototype.focusRootObject = function () {
    var root = this.getRootMarker();
    if (root) {
        this.centerMarker(root);
        this.getMap().setZoom(this.getFocusZoom());    
    }
};

// Pan to the position of given marker
DsGoogleMap.prototype.centerMarker = function (marker) {
    this.getMap().panTo(marker.getPosition());    
};

// Call loadRestaurants or searchRestaurants for current viewport
DsGoogleMap.prototype.loadViewportRestaurants = function() {
    var m = this.getMap();
    var bounds = m.getBounds();
    if (bounds) {
        var filter = this.getFilter();
        if (filter == null) {
            this.loadRestaurants(bounds);
        }
        else {
            this.searchRestaurants(filter.regionId, filter.filter, bounds);
        }
    }
};

// Query web service for restaurants within given bounds and display markers
DsGoogleMap.prototype.loadRestaurants = function (bounds, callback) {
    var sw = bounds.getSouthWest();
    var ne = bounds.getNorthEast();
    $.ajax({
        type:'GET',
        url: this.tmpl.getRestaurantsInAreaUrl.apply({
            north: ne.lat(),
            west: sw.lng(),
            east: ne.lng(),
            south: sw.lat()
        }),
        dataType:'xml',
        success: function (data) {
            var markers = this.parseRestaurantsXML(data);
            if (callback)
                callback(bounds, markers, data);
            $(this).trigger('restaurantsloaded', markers, data);
        }.scope(this)
    });
};

// Parse XML retrieved from web service (/xtl/restaurant/getrestaurantsinarea) and display markers
DsGoogleMap.prototype.parseRestaurantsXML = function (xmlData) {
    var restaurants = $(xmlData).find('restaurant');
        
    var searchresultlist = $('#searchresultlist');
    searchresultlist.empty();
    var markers = [];
    restaurants.each(function(i, r) {
        var id = $(r).attr('id');
        var linkId = $(r).find('linkid').text();
        var addrNode = $(r).find('adresse');
        var geoNode = $(addrNode).find('geo');
        
        var o = {
            counter: i,
            style: i%2 ? "light" : "dark",
            restaurantId: id,
            linkId: linkId,
            title: $(r).find('titel').text(),
            city: $(addrNode).find('plz').text() + ' ' + $(addrNode).find('ort').text(),
            street: $(addrNode).find('strasse').text() + ' ' + $(addrNode).find('hausnummer').text(),
            img: ''
        }
        if(typeof $(r).find('imageteaserimage').attr('url') != 'undefined')
            o.img = this.tmpl.showRestaurantImage.apply({url:$(r).find('imageteaserimage').attr('url')});
        var latLng = new google.maps.LatLng(
                            parseFloat($(geoNode).find('lat').text()),
                            parseFloat($(geoNode).find('lon').text())
                         );
         var infoWindowContent = this.tmpl.infoWindowContent.apply(o);
         var listRestarauntItem = $(this.tmpl.listRestarauntItem.apply(o));
         listRestarauntItem.appendTo(searchresultlist);
         var marker = this.addMarker(linkId, latLng, o.title, infoWindowContent);
         if(marker)
            markers.push(marker);
    }.scope(this));
    
    return markers;
};

// Query web service for restaurants matching the criteria within given bounds and display markers
DsGoogleMap.prototype.searchRestaurants = function (regionId, query, bounds, callback) {
    //this.clearFilter(); // TODO
    var sw = bounds.getSouthWest();
    var ne = bounds.getNorthEast();
    if(!regionId)
        regionId = '10';
    $.ajax({
        type:'GET',
        url: this.tmpl.searchRestaurantsUrl.apply({
            id: regionId,
            query: query,
            north: ne.lat(),
            west: sw.lng(),
            east: ne.lng(),
            south: sw.lat()
        }),
        dataType:'xml',
        success: function (data) {
//            var markers = this.parseRestaurantsJson(data); !!!!!!!!!!!!
            var markers = this.parseRestaurantsXML(data);
            if (callback)
                callback(bounds, markers, data);
            $(this).trigger('restaurantsloaded', markers, data);
        }.scope(this)
    });    
};

// Parse Json retrieved from web service (/json/restaurant/search) and display markers
DsGoogleMap.prototype.parseRestaurantsJson = function (jsonData) {
    var markers = [];
    var searchresultlist = $('#searchresultlist');
    searchresultlist.empty();
    for(var i = 0; i < jsonData.length; i++) {
        var r = jsonData[i];
        var id = r.restaurantid;
        
        var o = {
            counter: i,
            style: i%2 ? "light" : "dark",
            restaurantId: r.restaurantid,
            linkId: r.linkid,
            title: r.name,
            city: r.postal + ' ' + r.city,
            street: r.street + ' ' + r.streetnumber,
            img: ''
        };
        var latLng = new google.maps.LatLng(
                            parseFloat(r.latitude),
                            parseFloat(r.longitude)
                         );
         var infoWindowContent = this.tmpl.infoWindowContent.apply(o);
         var listRestarauntItem = $(this.tmpl.listRestarauntItem.apply(o));
         listRestarauntItem.appendTo(searchresultlist);
         var marker = this.addMarker(id, latLng, o.title, infoWindowContent);
         if(marker)
            markers.push(marker);
    }
    
    return markers;
};

// Post coordinates to web service (/json/setposition) to set position of given restaurant, 
// move marker to that position when web service call was successful
DsGoogleMap.prototype.setObjectPosition = function (objectType, objectId, latLng, callback) {
    $.ajax({
        type: 'POST',
        url: this.tmpl.setPositionUrl.apply({
            objectType: objectType,
            id: objectId
        }),
        data: {
            latitude: latLng.lat(),
            longitude: latLng.lng()
        },
        success: function (data) {
            var marker = this.getMarkerById(objectId) || this.getRootMarker();
            if (marker && marker.id == objectId) {
                marker.setPosition(latLng);
            } else {
                marker = null;
            }
            if (callback)
                callback(objectId, marker, latLng, data);
            $(this).trigger('objectpositionset', objectId, marker, latLng, data);
        }.scope(this)
    });        
}
// Get coordinates for given address and post coordinates to web service (/json/setposition) to set position of given restaurant, 
// move marker to that position when web service call was successful
//DsGoogleMap.prototype.setObjectPositionByAddress = function (restaurantId, country, postal, city, street, streetNumber, callback) {
DsGoogleMap.prototype.setObjectPositionByAddress = function (objectType, objectId, callback) {
    
    var geocoder = this.getGeocoder();
    var request = {
        // address:  country + ', ' + postal + ' ' + city + ', ' + street + ' ' + streetNumber
        address:  'austria, '+$('#'+this.getPrefix()+'-postal').val()+' '+$('#'+this.getPrefix()+'-city').val()+', '+$('#'+this.getPrefix()+'-street').val()+' '+$('#'+this.getPrefix()+'-streetnumber').val()
    };
    geocoder.geocode(request, function (results, status) {
        if (status == google.maps.GeocoderStatus.OK) {
            if(results.length > 0) {
                var geo = results[0].geometry;
                this.setObjectPosition(objectType,objectId, geo.location, function (rid, marker, latLng, data) {
                    if(this._rootMarker == null)
                        this.setRootMarker(objectType, objectId, latLng);
                    this.getMap().fitBounds(geo.viewport);
                    if (callback)
                        callback(rid, marker, latLng, data);
                }.scope(this));
            }
        } else {
            $(this).trigger('geocodingfailed', request, status);
        }
    }.scope(this));
}

// Use geolocation API if available to retrieve the user's position
DsGoogleMap.prototype.getClientLocation = function (success, failure) {
    if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(success.scope(this), failure ? failure.scope(this) : null);
        return true;
    } else {
        return false;
    }
};

// Call getClientLocation to retrieve the user's position and move viewport to that point
DsGoogleMap.prototype.focusClientLocation = function () {
    return this.getClientLocation(function (position) {
        var latLng = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
        var map = this.getMap();
        map.panTo(latLng);
        map.setZoom(this.getFocusZoom());    
    }.scope(this));
};
//endregion DsGoogleMap: public methods

