// Copyright 2009 nextstop.com
// A simple Javascript widget to use the nextstop API to display the content of a guide on a map.
// Please use, reuse, modify or do whatever you'd like with the code.

// Supports several options:
//     key: nextstop API key
//     guideId: The id of a guide (Get from the URL of a guide -- appears as /guide/[guideId]/guide-name
//     connectPoints: Set to true to draw a line between points on the map
//     mapLocation: The id of an element on the page where the map should be displayed
//     contentLocation: Set to the id of a container on the page to show the full size photo / text.
//     lineMode: "walking" or "driving"

GuideMap._requestQueue = {}; // Keeps track of API requests
GuideMap._baseApiUrl = "http://api.nextstop.com/guide/";
GuideMap._cssUrl = "/css/guide_map.css";
GuideMap._animationUrl = "http://www.nextstop.com/static/js/external/animation.compressed.js";

function GuideMap(opts) {
    this._options = opts;
    this._mapId = (new Date()).getTime();
    this._mapHandle = null;
    this._guideData = null;
    this._placeData = null;
    this._placesCount = 0;
    this._markers = [];
    this._selectedPlaceIndex = 0;
    this._contentImage = null;
    this._contentText = null;

    google.load("maps", 2, {"callback": GuideMap._bind(this, "show")});

    var directionsPanel = this._dirPanel = document.createElement("div");
}

GuideMap.prototype.show = function() {
    this._fetchGuideData(GuideMap._bind(this, "_initializeMapAndContent"));
    this._initializeScriptAndCss();
};


//-------- Internal Functions ---------------

GuideMap.prototype._initializeScriptAndCss = function() {
    var styleTag = document.createElement("link");
    styleTag.setAttribute("rel", "stylesheet");
    styleTag.setAttribute("type", "text/css");
    styleTag.setAttribute("href", GuideMap._cssUrl);
    document.getElementsByTagName("head")[0].appendChild(styleTag);

    var scriptTag = document.createElement("script");
    scriptTag.setAttribute("type", "text/javascript");
    scriptTag.setAttribute("src", GuideMap._animationUrl);
    document.getElementsByTagName("head")[0].appendChild(scriptTag);    
};

GuideMap._bind = function(obj, methodName) {
    var opt_args = Array.prototype.slice.call(arguments, 2);

    return function () {
        var args = [];
        args = args.concat(opt_args);

        // Arguments isn't a real Array -- args.concat(arguments) doesn't work        
        for (var i = 0; i < arguments.length; i++) {
            args.push(arguments[i]);
        }

        return obj[methodName].apply(obj, args);
    }
};


GuideMap._makeRequest = function(url, callback) {
    var rq = GuideMap._requestQueue;
    
    var requestId = (new Date()).getTime();
    
    rq[requestId] = function (response) {
        callback(response);

        var scriptTag = document.getElementById("GuideMapRequest-" + requestId);
        if (scriptTag) {
            scriptTag.parentNode.removeChild(scriptTag);
        }
        
        delete rq[requestId];
    };
    
    url += "callback=GuideMap._requestQueue[\"" + requestId + "\"]"
    
    // Insert script tag into document
    var scriptTag = document.createElement("script");
    scriptTag.setAttribute("id", "GuideMap-" + requestId);
    scriptTag.setAttribute("type", "text/javascript");
    scriptTag.setAttribute("src", url);
    document.getElementsByTagName("head")[0].appendChild(scriptTag);
};


GuideMap.prototype._getScopedId = function(stringId) {
    return stringId + "_" + this._mapId;
};


GuideMap.prototype._fetchGuideData = function(callback) {
    var url = GuideMap._baseApiUrl + this._options.guideId + "/?key=" + this._options.key + "&";

    GuideMap._makeRequest(url, callback);
};


GuideMap.prototype._initializeMapAndContent = function(response) {
    if (response.status == 200) {
        this._guideData = response.data;
        this._placeData = response.data.guideContent.places;
        this._placesCount = this._placeData.length;
        
        this._initializeMap();
        this._initializeContent();
        
        this._setSelectedPlace(0);
    }
};


GuideMap.prototype._initializeContent = function() {
    var html = ""
    html += "<div class='ns_gm_container'>";
    html += "<a class='ns_gm_prev_link' id='" + this._getScopedId("guide-map_prev") + "'></a>"
    html += "<a class='ns_gm_next_link' id='" + this._getScopedId("guide-map_next") + "'></a>"
    html += "<img id='" + this._getScopedId("guide-map_image") + "' class='ns_gm_image'/>";
    html += "<div id='" + this._getScopedId("guide-map_photocredit") + "' class='ns_gm_photocredit'></div>";
    html += "<div id='" + this._getScopedId("guide-map_text") + "' class='ns_gm_textcontent'></div>";
    html += "<div class='ns_gm_guidelabel'>From the guide <a target='_blank' href='" + this._guideData.permalink + "'/>" + this._guideData.name + "</a> on <a href='http://www.nextstop.com/'>nextstop.com</a>.</div>";
    html += "</div>";

    var content = document.getElementById(this._options.contentLocation);
    content.innerHTML = html;
    
    this._prevLink = document.getElementById(this._getScopedId("guide-map_prev"));
    this._nextLink = document.getElementById(this._getScopedId("guide-map_next"));
    this._contentImage = document.getElementById(this._getScopedId("guide-map_image"));
    this._contentText = document.getElementById(this._getScopedId("guide-map_text"));
    this._photoCredit = document.getElementById(this._getScopedId("guide-map_photocredit"));

    this._prevLink.onclick = GuideMap._bind(this, "_onClickPrev");
    this._nextLink.onclick = GuideMap._bind(this, "_onClickNext");
};


GuideMap.prototype._setSelectedPlace = function(index) {
    var oldIndex = this._selectedPlaceIndex;
    var oldMarker = this._markers[oldIndex];

    oldMarker.setImage(GuideMap._genNumberIconUrl(oldIndex + 1));

    this._selectedPlaceIndex = index;
    
    var newMarker = this._markers[index];
    newMarker.setImage(GuideMap._genSelectedNumberIconUrl(index + 1));

    this._mapHandle.panTo(newMarker.getLatLng());
    this._updatePlaceDisplay();

    if (index < this._placesCount - 1) {
        this._nextLink.className = "ns_gm_next_link";
    } else {
        this._nextLink.className = "ns_gm_next_link ns_gm_next_link_disabled";
    }

    if (index > 0) {
        this._prevLink.className = "ns_gm_prev_link";
    } else {
        this._prevLink.className = "ns_gm_prev_link ns_gm_prev_link_disabled";
    }
};


GuideMap.prototype._updatePlaceDisplay = function() {
    var img = this._contentImage;
    var contentText = this._contentText;
    var currentPlace = this._placeData[this._selectedPlaceIndex];
    var currentRec = currentPlace.recommendationsInfo.guideRecommendation;
    var photoCredit = this._photoCredit;
    var currentNumber = this._selectedPlaceIndex + 1;
    
    img.setAttribute("src", currentRec.largeCachedImageUrl);
    var html = currentNumber + ". <a target='_blank' class='ns_gm_title' href='" + currentRec.permalink + "'>";
    html += currentRec.name + "</a>";
    html += ":&nbsp;&nbsp;<span class='ns_gm_text'>" + currentRec.fullText + "</span>";
    
    photoCredit.innerHTML = "<a target='_blank' href='" + currentRec.originalImageSource + "'>" + currentRec.originalImageSource + "</a>";
    
    contentText.innerHTML = html;
};


GuideMap.prototype._onClickPrev = function() {
    if (this._selectedPlaceIndex > 0) {
        this._setSelectedPlace(this._selectedPlaceIndex - 1);
    }
    
    return false;
};

GuideMap.prototype._onClickNext = function() {
    if (this._selectedPlaceIndex < this._placesCount - 1) {
        this._setSelectedPlace(this._selectedPlaceIndex + 1);
    }
    
    return false;
};

GuideMap.prototype._initializeMap = function() {
    var map = this._mapHandle = new google.maps.Map2(document.getElementById(this._options.mapLocation));
    map.addControl(new google.maps.LargeMapControl());

    var place_count = this._placeData.length;
    var mapBounds = new google.maps.LatLngBounds();
    var lineVertices = []
    var useInfoWindows = this._options.contentLocation == "internal";

    for (var i = 0; i < place_count; i++) {
        var place = this._placeData[i];
        var marker = this._createMarker(place, useInfoWindows, i);

        var coords = new google.maps.LatLng(place.lat, place.lng);
        mapBounds.extend(coords);
        map.addOverlay(marker);
        lineVertices.push(place.lat + "," + place.lng);
        this._markers.push(marker);
    }
    
    if (this._options.connectPoints) {
        var travelMode = null;
        if (this._options.lineMode == "walking") {
            travelMode = G_TRAVEL_MODE_WALKING;
        } else {
            travelMode = G_TRAVEL_MODE_DRIVING;
        }
        var opts = {"travelMode": travelMode,
                    "getPolyline": true};

        while (lineVertices.length > 0) {
            var currentSlice = lineVertices.slice(0, 25);
            lineVertices = lineVertices.slice(24);
            this._addDirectionsPolyline(currentSlice, opts);
        }
    }

    var zoom = map.getBoundsZoomLevel(mapBounds, map.getSize());
    if (this._options.defaultZoom == "default") {
            zoomFactor = 14;
        } else {
            zoomFactor = (this._options.defaultZoom);
        }
    var zoom = (zoomFactor);
    map.setCenter(mapBounds.getCenter(), zoom + 2);
};

GuideMap.prototype._addDirectionsPolyline = function(vertices, opts) {
    var directions = new google.maps.Directions(null, this._dirPanel);
    var self = this;
    
    directions.loadFromWaypoints(vertices, opts);
    google.maps.Event.addListener(directions, "load", function() {
        var directionsPline = directions.getPolyline();
        self._mapHandle.addOverlay(directionsPline);
    });
};


GuideMap.prototype._createMarker = function (place, useInfoWindow, num) {
    var opts = {title: place.name,
                icon: GuideMap._createNumberIcon(num)};
                
    var coords = new google.maps.LatLng(place.lat, place.lng);
    
    var marker = new google.maps.Marker(coords, opts);
    var self = this;
    google.maps.Event.addListener(marker, "click", function() {
        if (useInfoWindow) {
            var itemHtml = GuideMap._genInfoWindowHtml(place);
            
            map.openInfoWindowHtml(marker.getLatLng(), itemHtml);
        } else {
            self._setSelectedPlace(num);
        }
    });
    
    return marker;
};


GuideMap._createNumberIcon = function(i) {
    var icon = new google.maps.Icon();
    
    icon.image = icon.printImage = icon.mozPrintImage = GuideMap._genNumberIconUrl(i + 1);

    icon.shadow = icon.pringShadow = "http://www.nextstop.com/static/images/mapmarkers/number_icons/shadow.png";
    icon.iconSize = new google.maps.Size(20, 20);
    icon.shadowSize = new google.maps.Size(30, 20);
    icon.iconAnchor = new google.maps.Point(10, 20);
    
    return icon;
};


GuideMap._genNumberIconUrl = function(i) {
    return "http://www.nextstop.com/static/images/mapmarkers/number_icons/" + i + ".png";
};

GuideMap._genSelectedNumberIconUrl = function(i) {
    return "http://www.nextstop.com/static/images/mapmarkers/number_icons_blue/blueicons" + i + ".png";
};