/*****************************
 Handles showing places on Google Map.

 Places are loaded with ajax and positioned on the map.

 Usage:
    $.placesMap({
        map: new GMap2(...),
        placesGetURL: "{% url map_ajax_places %}"
    });


*****************************/
function PlacesMap(options) {
    this.opts = $.extend({
        polyline: {
            weight: 1,
            // zoomFactor: 18,
            // numLevels: 4,
            color: "black"
        },
        polygon: {
            fill: true,
            opacity: 0.01,
            outline: true
        },
        onPlaceInit: function(place) {},
        hlStyle: {
            color: "#D02A26",
            opacity: 0.3
        },
        getParameters: {},
        maxCachedTiles: 10
    }, options);
    this.visiblePlaces = {};
    this.places = {};
    this.alwaysVisible = [];
    this.tooltip = $("<div class=\"tooltip\">Tooltip</div>").hide();
    this.tilesCache = {};
    this.tilesCacheUsage = {};
    this.currentLocation = null;
}

PlacesMap.prototype.init = function() {
    var self = this,
        map = new GMap2(document.getElementById("map"));//,{ draggableCursor:"auto", draggingCursor:"move"});

    self.map = map;

    GEvent.addListener(map, 'zoomend', function() {
        self.updatePlaces();
    });
    GEvent.addListener(map, 'moveend', function() {
        self.updatePlaces();
    });

    GEvent.addListener(map, 'mouseout', function() {
        self.tooltip.hide();
    });

    self.tooltip.appendTo(map.getContainer());
    $(map.getContainer()).mousemove(function(e) {
        var container = $(this),
            pos = new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(
                e.pageX - container.offset().left + 10,
                e.pageY - container.offset().top  + 20));
        pos.apply(self.tooltip.get(0));
    });
    map.setCenter(self.opts.mapCenter, self.opts.mapZoom);
    map.setUIToDefault();
};

PlacesMap.prototype.getTile = function(latlng, zoom) {
    return new GPoint(
        Math.floor((latlng.lng() + 180) / (360 / Math.pow(2, zoom))),
        Math.floor((latlng.lat() + 85) / (170 / Math.pow(2, zoom)))
    );
};

PlacesMap.prototype.getTileBounds = function(bounds, zoom) {
    bounds = bounds || this.map.getBounds();
    zoom = zoom || this.map.getZoom();

    var bottomLeftPoint = bounds.getSouthWest(),
        topRightPoint = bounds.getNorthEast(),
        bottomLeftTile = this.getTile(bottomLeftPoint, zoom),
        topRightTile = this.getTile(topRightPoint, zoom);
    return {
        x: bottomLeftTile.x,
        y: bottomLeftTile.y,
        x1: topRightTile.x,
        y1: topRightTile.y,
        zoom: zoom
    };
};

PlacesMap.prototype.getPlacesForCurrentLocation = function(callback) {
    var self = this,
        tileBounds = self.getTileBounds(),
        newLocation = $.map(["x", "y", "x1", "y1", "zoom"], function(key) {
            return tileBounds[key];
        }).join("/");

    if (self.currentLocation == newLocation) {
        return;
    }

    self.currentLocation = newLocation;

    var tiles = [];
    for (var tile in self.tilesCache) tiles.push(tile);

    if (newLocation in self.tilesCache) {
        self.tilesCacheUsage[newLocation] += 1;
        callback(self.tilesCache[newLocation]);
    } else {
        $.get(self.opts.placesGetURL + "area/" + newLocation + "/" , function(data) {
            $.each(data.places, function() {
                self.initPlace(this);
            });
            self.tilesCache[newLocation] =  $.map(data.places, function(place) { return place.id; });
            self.tilesCacheUsage[newLocation] = new Date();
            callback(self.tilesCache[newLocation]);
        });
    }

    var cachedTilesNumber = 0;
    // for (var tile in self.tilesCache) cachedTilesNumber++;

    if (cachedTilesNumber > self.opts.maxCachedTiles) {
        // deleting unused cache
        var tiles = [];
        for (var tile in self.tilesCache) tiles.push(tile);
        tiles.sort(function(tile1, tile2){
            return self.tilesCacheUsage[tile1] < self.tilesCacheUsage[tile2];
        });
        $.each(tiles.splice(self.opts.maxCachedTiles), function(index, tile){
            delete self.tilesCache[tile];
        });

        // deleting places that are not used on cached tiles
        var neededPlaces = [];
        for (var tile in self.tilesCache) neededPlaces = neededPlaces.concat(self.tilesCache[tile]);

        var places = [];
        for (var id in self.places) places.push(parseInt(id));

        $.each(places, function(index, placeID) {
            if ($.inArray(placeID, neededPlaces) == -1 &&
                $.inArray(placeID, self.alwaysVisible)) {
                delete self.places[placeID];
            }
        });

    }
};

PlacesMap.prototype.updatePlaces = function() {
    var self = this;

    self.getPlacesForCurrentLocation(function(placesIDs) {
        $.each(self.visiblePlaces, function() {
            if ($.inArray(this.id, placesIDs) == -1 &&
                $.inArray(this.id, self.alwaysVisible) == -1) {
                self.removeOverlay(this.id);
            }
        });
        $.each(placesIDs, function(){
            if (!(this in self.visiblePlaces)) {
                self.addOverlay(this);
            }
        });
    });
};

PlacesMap.prototype.initPlace = function(place) {
    if (place.id in this.places) {
        return false;
    }
    var polygon = this.createPolygon(place);

    place.polygon = polygon;
    this.places[place.id] = place;

    this.initPlaceHandlers(place);
    return place;
};

PlacesMap.prototype.initPlaceHandlers = function(place) {
    var polygon = place.polygon,
        self = this;

    GEvent.clearListeners(polygon, 'mouseover');
    GEvent.clearListeners(polygon, 'mouseout');

    GEvent.addListener(polygon, 'mouseover', function() {
        self.tooltip.html(self.getTooltipHtml(place));
        polygon.setFillStyle(self.getHighLightStyle(place, true));
        self.tooltip.show();
    });

    GEvent.addListener(polygon, 'mouseout', function() {
        self.tooltip.hide();
        polygon.setFillStyle(self.getHighLightStyle(place, false));
    });
};

PlacesMap.prototype.createPolygon = function(place) {
    return new GPolygon.fromEncoded($.extend({
        polylines: [ $.extend({
            points: place.poly,
            levels: place.levels,
            numLevels: place.num_levels,
            zoomFactor: place.zoom_factor
        }, this.opts.polyline)]
    }, this.opts.polygon));
};

PlacesMap.prototype.removeOverlay = function(placeID) {
    this.map.removeOverlay(this.places[placeID].polygon);
    delete this.visiblePlaces[placeID];
};

PlacesMap.prototype.addOverlay = function(placeID) {
    this.map.addOverlay(this.places[placeID].polygon);
    this.visiblePlaces[placeID] = this.places[placeID];
};

PlacesMap.prototype.getHighLightStyle = function(place, hover) {
    return hover ? this.opts.hlStyle : this.opts.polygon;
};

PlacesMap.prototype.getTooltipHtml = function(place) {
    if (place.full_name.length > 37) {
        return '<div class="map-pointer inline-huge"><div class="place">' + place.full_name + '</div></div>';
    } else {
        return '<div class="map-pointer small"><div class="place">' + place.full_name + '</div></div>';
    }
};

PlacesMap.prototype.loadPlace = function(id, callback) {
    var self = this;

    $.getJSON(
        this.opts.placesGetURL + id + "/", {geometry: true},
        function(data) {
            var place = self.initPlace(data);
            if (callback) callback(place);
        }
    );
}

PlacesMap.prototype.zoomToPlace = function(id, callback){
    var self = this,
        map = self.map,
        place = self.places[id];

    function zoom(place) {
        var bounds = place.polygon.getBounds();
        map.setCenter(bounds.getCenter(), map.getBoundsZoomLevel(bounds));
        if (callback) callback(place);
    }

    if (place) {
        zoom(place);
    } else {
        self.loadPlace(id, function(place){ zoom(place); });
    }
}

