/**
 * Toolkit class handling the map results/markers management
 */
function Results(map, mapType, markerManager)
{
    this.map = map;
    this.mapType = mapType || 'full';
    this.mm = markerManager;
    this.results = [];
    this.activeResultsIndeces = [];
    this.clusters = [];
    this.markers = [];
    this.resultListElement = document.getElementById("resultList");
    this.lastOpenInfo = false;
}

Results.prototype = new Object();

Results.prototype.setResults = function(val) {
    this.results = val;
}

/**
 * Appends a result to the total results array
 */
Results.prototype.pushResult = function(val) {
    val['index'] = this.results.length;
    this.results.push(val);
}

/**
 * Appends the input index to the list of active results
 */
Results.prototype.pushActiveResultIndex = function(index) {
    this.activeResultsIndeces.push(index);
    this.results[index]['number'] = this.activeResultsIndeces.length;
}

/**
 * Clears all results (for eg. when clicking the markers pagination and loading a new DB-provided set)
 */
Results.prototype.clearResults = function() {
    this.results = [];
    this.clearActiveResults();
}

Results.prototype.clearActiveResults = function() {
    this.activeResultsIndeces = [];
    for (var i = 0; i < this.results.length; i++) {
        this.results[i]['number'] = false;
    }
}

/**
 * Generates cluster markers to be used when showing close results
 */
Results.prototype.createClusters = function()
{
    // initialy, each grouping coresponds to one position
    var groupings = [];
    for (var i = 0; i < this.results.length; i++) {
        var grouping = [];
        var result = this.results[i];
        grouping[0] = this.map.fromLatLngToDivPixel(result[0]); // the pixel coordinates
        grouping[1] = result[0]; // the geographical coordinates
        grouping[2] = 1; // weight (initialy 1)
        grouping[3] = [result]; // map back to the results it contais
        grouping[4] = result['number']; // active result number
        groupings.push(grouping);
    }

    var changed = true;
    while (changed) {
        changed = false;
        for (var i = 0; i < groupings.length; i++) {
            iPixelCoords = groupings[i][0];
            for (var j = i + 1; j < groupings.length; j++) {
                jPixelCoords = groupings[j][0];
                if (    Math.abs(iPixelCoords.x - jPixelCoords.x) <= groupingLimit &&
                        Math.abs(iPixelCoords.y - jPixelCoords.y) <= groupingLimit) {
                    // Merge them; i will be the result of the merge, j will be deleted

                    // compute the new geographical coordinates as a weight average
                    var iWeight = groupings[i][2];
                    var jWeight = groupings[j][2];
                    geoLat =  (iWeight * groupings[i][1].lat() + jWeight * groupings[j][1].lat()) / (iWeight + jWeight);
                    geoLng =  (iWeight * groupings[i][1].lng() + jWeight * groupings[j][1].lng()) / (iWeight + jWeight);
                    groupings[i][1] = new GLatLng(geoLat, geoLng); // the geographical coordinates

                    groupings[i][0] = this.map.fromLatLngToDivPixel(groupings[i][1]); // the pixel coordinates
                    groupings[i][2] = groupings[i][2] + groupings[j][2]; // weight
                    groupings[i][3] = groupings[i][3].concat(groupings[j][3]); // result data
                    groupings[i][4] = false; // active result number is ignored for clustered results

                    // remove j
                    groupings.splice(j, 1);
                    changed = true;
                    break;
                }
            }
        }
    }

    // Create the returned array
    var clusters = [];
    for (var i = 0; i < groupings.length; i++) {
        var cluster = [];
        cluster['coord'] = groupings[i][1]; // geographical coordinates
        if (groupings[i][2] == 1) { // weight = 1 -> single position
            cluster['type'] = 'single';
            cluster['number'] = groupings[i][4]; // active result number
        }
        else {// weight > 1 -> grouped positions
            cluster['type'] = 'group';
            cluster['number'] = false; // active result number
        }
        var resultsInCluster = groupings[i][3]; // results it contains
        cluster['results'] = resultsInCluster;

        for (var j = 0; j < resultsInCluster.length; j++) {
            resultsInCluster[j]['cluster'] = cluster; // map back results to containing clusters
        }
        clusters.push(cluster);
    }

    return clusters;
}

/**
 * Generates the GIcon instance associated to the result's cluser type (ie. regular marker, inactive marker or cluster)
 */
Results.prototype.makeIcon = function(number, clusterType)
{
    var icon = new GIcon();
    if (clusterType == 'single') {
        if (number != false) {
            // Active single result
            icon.image = "/images/icons/marker_" + number + ".png";
            icon.iconSize = new GSize(30, 30);
            icon.iconAnchor = new GPoint(1, 30);
            icon.infoWindowAnchor = new GPoint(27, 4);
        }
        else {
            // Not-active singlre result
            icon.image = "/images/icons/marker_simple.png";
            icon.iconSize = new GSize(15, 15);
            icon.iconAnchor = new GPoint(1, 15);
            icon.infoWindowAnchor = new GPoint(12, 4);
        }
    }
    else {
        // Cluster of results
        icon.image = "/images/icons/cluster.png";
        icon.iconSize = new GSize(36, 36);
        icon.iconAnchor = new GPoint(1, 36);
        icon.infoWindowAnchor = new GPoint(33, 4);
    }

    return icon;
}

/**
 * Generates a GMarker instance associated to a result (single or cluser), and registeres the event listeners for showing/closing
 * the info bubble
 */
Results.prototype.makeMarker = function(cluster)
{
    var icon = this.makeIcon(cluster['number'], cluster['type']); // 1 -> cluster type
    var marker = new GMarker(cluster['coord'], icon); // 0 -> position
    cluster['marker'] = marker;

    var self = this;

    if (cluster['number'] == false && cluster['type'] == 'single') {
        GEvent.addListener(marker, 'click', function() {
            myMap.resultsPaginator.setPageForResultIndex(cluster['results'][0]['index']);
            self.popResultInfo(cluster['results'][0]);
            self.map.panTo(marker.getLatLng());
        });
    }
    else {
        GEvent.addListener(marker, 'click', function() {
            self.popClusterInfo(cluster);
            self.map.panTo(marker.getLatLng());
        });
    }

    return marker;
}

/**
 * Various HTML snippet templates for displaying the info bubble/window
 */
Results.prototype.makeResultPopupSimpleHtml = function(result)
{
    var resultData = result[1];
    return "<div style='float:none'><a href='" + resultData['url'] + "'><strong>" + resultData['name'] + "</strong></a></div>" +
        "<p>" + resultData['address'] + "</p>" +
        "<p>" + resultData['desc'] + "</p>" + 
        (resultData['phone'].length ? "<p>Phone: " + resultData['phone'] + "</p>" : "" );
}

Results.prototype.makeResultPopupHtml = function(result)
{
    return "<div class='info_wrapper'>" + this.makeResultPopupSimpleHtml(result) + "</div>";
}

Results.prototype.makeClusterPopupHtml = function(cluster)
{
    var popupHtmlArray = [];
    var resultsInCluster = cluster['results'];
    for (var i = 0; i < resultsInCluster.length; i++) {
        var result = resultsInCluster[i];
        var number = '&nbsp;';
        if (result['number'] != false) {
            number = result['number'];
        }
        popupHtmlArray.push("<div align='center' style='float:left; width:15px; height:15px; background-color:#f05300; color:white'>" + number + "</div><div style='float:left; width:3px'>&nbsp;</div>" + this.makeResultPopupSimpleHtml(result));
    }
    var popupHtml = "<div class='info_wrapper'>" + popupHtmlArray.join("<div class='spacer'>&nbsp;</div>") + "</div>";

    return popupHtml
}

/**
 * Pops an info bubble associated to a cluster (ie. a list of the info for each merged marker)
 */
Results.prototype.popClusterInfo = function(cluster) {
    if (cluster['results'].length == 1) {
        // Exactly one result
        this.popResultInfo(cluster['results'][0]);
        return;
    }

    var marker = cluster['marker'];
    this.lastOpenInfo = {'type' : 'cluster', 'location': marker.getPoint()};
    this.popHtmlInfo(marker, this.makeClusterPopupHtml(cluster))
}

/**
 * Pops the info bubble associated to a single result
 */
Results.prototype.popResultInfo = function(result) {
    var marker = result['cluster']['marker'];
    // if we zoom, we want to folow the position of the wanted result, not that of the cluster which will become less accurate with zooming
    this.lastOpenInfo = {'type' : 'result', 'location': result[0]};
    myMap.addRecentSelection(result);
    this.popHtmlInfo(marker, this.makeResultPopupHtml(result));
}

/**
 * Actually adds the bubble markup to the map using the EWindow toolkit
 */
Results.prototype.popHtmlInfo = function(marker, html) {
    // handle full/mini map sizes
    var popupWidth = 300;
    if (this.mapType == 'collection') {
      popupWidth = 100;
    }
	myMap.eW.openOnMarker(marker, html);
    //marker.openInfoWindow(html, {maxWidth:popupWidth});
}

/**
 * Pops the info bubble for a result specified by its index (used by the map's results list)
 */
Results.prototype.popResultAtIndexInfo = function(resultIndex) {
    var result = this.results[resultIndex];
    this.popResultInfo(result);
}

/**
 * Closes any open info bubble
 */
Results.prototype.closeResultInfo = function()
{
    this.lastOpenInfo = false; // unset the last open info saved data
//    this.infoWindow.hide();
    this.map.closeInfoWindow();
}

/**
 * Keeps the current open bubble reference before closing it in order to restore later (for eg. when panning the map)
 */
Results.prototype.saveAndCloseResultInfo = function()
{
    // do not unset the last open info saved data, so it can be used with restoreLastOpenInfo
//    this.infoWindow.hide();
    this.map.closeInfoWindow();
}

/**
 * Restores the previously closed info bubble
 */
Results.prototype.restoreLastOpenInfo = function()
{
    if (this.lastOpenInfo == false) {
        // Nothing to restore
        return;
    }

    var lastLocation = this.lastOpenInfo['location'];
    if (this.lastOpenInfo['type'] == 'result') {
        for (var i = 0; i < this.results.length; i++) {
            var result = this.results[i];
            var latLng = result[0];
            if (latLng.x == lastLocation.x && latLng.y == lastLocation.y) {
                // We found our result
                this.popResultInfo(result);
            }
        }

        // The result is no longer in the current view, nothing to restore
        return;
    }

    if (this.lastOpenInfo['type'] == 'cluster') {
        for (var i = 0; i < this.clusters.length; i++) {
            var cluster = this.clusters[i];
            var latLng = cluster['marker'].getPoint();
            if (latLng.x == lastLocation.x && latLng.y == lastLocation.y) {
                // We found our cluster
                this.popClusterInfo(cluster);
            }
        }

        // The cluster is no longer visible or it has changed is position and components, nothing to restore
        return;
    }
}

/**
 * Generates markers for all result clusters
 */
Results.prototype.createMarkers = function(clusters)
{
    var markers = [];
    for (var i = 0; i < this.clusters.length; i++) {
        markers.push(this.makeMarker(this.clusters[i]));
    }

    return markers;
}

/**
 * Adds all generated markers to the marker manager
 */
Results.prototype.addMarkers = function()
{
    for (var i = 0; i < this.markers.length; i++) {
    	this.mm.addMarker(this.markers[i],0,17);
        //this.map.addOverlay(this.markers[i]);
    }
}

/**
 * Removes all markers from the marker manager and closes the currently open info bubble
 */
Results.prototype.removeMarkers = function()
{
    this.map.closeInfoWindow();
    for (var i = 0; i < this.markers.length; i++) {
        this.mm.removeMarker(this.markers[i]);
    }
}

/**
 * Refreshes the results (clears any markers currently available, regenerates and adds them back to the markers manager)
 */
Results.prototype.refresh = function(restoreLastOpenInfo)
{
    this.removeMarkers();
    this.clusters = this.createClusters();
    this.markers = this.createMarkers();
    this.addMarkers();

    if (restoreLastOpenInfo) {
        this.restoreLastOpenInfo();
     }
}
