/* 
 * Map Module (Using Google Maps v3 API)
 *
 * Author: Josh Drumm
 * Date: 28 Oct 2010
 *
 * Reference:
 * http://code.google.com/apis/maps/documentation/javascript/reference.html
 *
 */
var GMAP_ICON_BASE = 'http://maps.google.com/mapfiles/ms/micons/';
var GMAP_MARKER_NUM = 0;
var GMAPS_CALLBACK_FUNC = function() {};
var IS_GMAPS_LOADED = false;

// Class for Google Map Markers.  Creating a new one requires a map that refers
// to the map it's attached to.
//
var mapMarker = $.inherit({
    __constructor: function(map, title, lat, lng) {
        if ((!map)) {
            throw new Exception("Map not defined");
        }

        this.setMarkerID(++GMAP_MARKER_NUM);
        title = title || "New Marker " + ((GMAP_MARKER_NUM == 0) ? "" : GMAP_MARKER_NUM);
        this.setTitle(title);
        this.setDescription("Marker");
        this.setMap(map);

        if ((lat) && (lng)) {
            this.setPositionByLatLng(lat, lng);
        }
        else {
            this.setPosition(map.getGMapObj().getCenter());
        }
    },

    setMarkerID:    function(i) { this.markerid = i; },
    setMap:         function(m) { this.map = m; },
    setTitle:       function(t) { this.title = t; },
    setDescription: function(d) { this.description = d; },
    setLink:        function(l) { this.link = l;},
    setIcon:        function(i) { this.icon = i; },
    setPosition:    function(p) { this.position = p; },

    getMarkerID:    function() { return this.markerid; },
    getMap:         function() { return this.map; },
    getTitle:       function() { return this.title; },
    getDescription: function() { return this.description    },
    getLink:        function() { return this.link },
    getMarker:      function() { return this.marker;   },
    getIcon:        function() { return this.icon; },
    getPosition:    function() { return ((this.marker) ? this.marker.getPosition() : this.position); },

    showInfoBubble: function() {
        this.getMap().hideBubbles();
        this.infoBubble.display();
    },

    closeInfoBubble: function() {
        if (this.infoBubble) {
            this.infoBubble.hide();
        }
    },

    setPositionByLatLng: function(lat, lng) {
        this.setPosition(new google.maps.LatLng(lat, lng));
    },

    setLocationByAddr: function(searchString) {
        var _this = this;
        var gc = new google.maps.Geocoder();
        var st = google.maps.GeocoderStatus;

        // This makes an ajax call and therefore, asynchronous.
        // So this won't return a value properly.
        gc.geocode({"address": searchString}, function(result, stat) {
            switch (stat) {
                case st.OK:
                    var p = result[0].geometry.location;
                    NotifyQueue.addSuccessMessage("Location for: <br /> " + _this.marker.getTitle() + "<br />is now set to: <br />" + searchString);
                    _this.setPosition(p);
                    _this.marker.setPosition(p);
                    break;
                case st.ZERO_RESULTS:
                    NotifyQueue.addFailMessage("Unable to find: <br />" + searchString);
                    break;
                case st.ERROR:
                default:
                    NotifyQueue.addFailMessage("Unknown error searching for: <br />" + searchString);
                    break;
            }
        });
    },

    getIconPath: function() {
        if (this.getIcon()) {
            return GMAP_ICON_BASE + this.getIcon() + ".png";
        }
        else {
            return GMAP_ICON_BASE + "red-dot.png";
        }
    },

    resetIcon: function() {
        this.getMarker().setIcon(this.getIconPath());
    },

    display: function() {
        this.marker = new google.maps.Marker({
            map: this.getMap().getGMapObj(), 
            draggable: (this.getMap().isPublished) ? false : true,
            title: this.getTitle(),
            position: this.getPosition(),
            icon: this.getIconPath()
        });


        this.infoBubble = new mapInfoBubble(this);
        this.infoBubble.setTitle(this.getTitle());
        this.infoBubble.setDescription(this.getDescription());
        this.infoBubble.setLink(this.getLink());

        // Add the apparently propietary gmaps event handler for click on the marker
        var _this = this;
        google.maps.event.addListener(this.marker, "click", function() {
            _this.showInfoBubble();
        });
    },

    remove: function() {
        var _this = this;
        if (confirm("Are you sure you want to remove this marker?")) {
            // Setting the map to null removes the marker;
            this.getMap().ajaxPost("deleteMarker", {"marker_id": this.getMarkerID()}, function() {
                _this.closeInfoBubble();
                _this.marker.setMap(null);
                _this.marker = null;
            });
        }
    }
});


// InfoBubble (in Google terms: "InfoWindow") for the mapMarker
// This handles all the popup info and editing options for the marker
//
var mapInfoBubble = $.inherit({
    __constructor: function(marker) {
        this.setMarker(marker);
        this.bubble = new google.maps.InfoWindow({content: ""});
    },

    setMarker:      function(m) { this.marker = m; },
    setTitle:       function(t) { this.title = t; this.marker.setTitle(t); },
    setDescription: function(d) { this.description = d; this.marker.setDescription(d) },
    setLink:        function(l) { this.link = l; this.marker.setLink(l) },

    getMarker:      function() { return this.marker; },
    getTitle:       function() { return this.title; },
    getDescription: function() { return this.description; },
    getLink:        function() { return this.link },

    display: function() {
        this.bubble.open(this.getMarker().getMap().getGMapObj(), this.getMarker().marker);
        this.displayInfo();
    },

    hide: function() {
        this.bubble.close();
    },

    displayInfo: function() {
        var _this = this;
        var l = this.getLink();
        var $content = $("<div></div>", {
            "class": "description",
            "css": { height: "100px", width: "300px", overflow: "hidden"}
            });

        if (l) {
            $content.append($('<h3><a href="' + l + '">' + this.getTitle() + "</a></h3>"));
        }
        else {
            $content.append($("<h3>" + this.getTitle() + "</h3>"));
        }

        $content.append($("<p>" + this.getDescription() + "</p>"));

        if (!this.getMarker().getMap().isPublished) {
            var $f = $('<div></div>', { 
                css: {
                    position: 'absolute',
                    bottom: '0'
                }
            });
            var $editBtn = $('<a></a>', {
                href: "#",
                text: "Edit",
                click: function() { _this.editInfo(); }
            });
            var $editLocBtn = $('<a></a>', {
                href: "#",
                text: "Change Location",
                click: function() { _this.editLocation(); }
            });
            var $changeIcon = $('<a></a>', {
                href: "#",
                text: "Change Icon",
                click: function() { _this.changeIcon(); }
            });
            var $removeBtn = $('<a></a>', {
                href: "#",
                text: "Remove",
                click: function() { _this.getMarker().remove(); }
            });
            var $span = $("<span> | </span>");

            $f.append($editBtn).append($span.clone(false))
              .append($editLocBtn).append($span.clone(false))
              .append($changeIcon).append($span.clone(false))
              .append($removeBtn);
            $content.append($f);
        }
        this.bubble.setContent($content[0]);
    },

    changeIcon: function() {
        var _this = this;
        var spots = new Array('blue-dot','red-dot','green-dot','ltblue-dot','yellow-dot','purple-dot','pink-dot','blue','red','green','lightblue','yellow','purple','pink','blue-pushpin','red-pushpin','grn-pushpin','ltblu-pushpin','ylw-pushpin','purple-pushpin','pink-pushpin','restaurant','coffeehouse','bar','snack_bar','tram','lodging','wheel_chair_accessible','shopping','movies','grocerystore','convienancestore','arts','homegardenbusiness','electronics','mechanic','partly_cloudy','realestate','salon','dollar','parkinglot','gas','cabs','bus','truck','rail','plane','ferry','helicopter','subway','info','flag','earthquake','webcam','postoffice-us','police','firedept','hospitals','info_circle','phone','caution','fallingrocks','camera','tree','campfire','picnic','campground','rangerstation','toilets','POI','hiker','cycling','motorcycling','horsebackriding','sportvenue','golfer','trail','water','snowflake_simple','marina','fishing','sailing','swimming','ski','woman','man','rainy','volcano','sunny','euro','yen');
        var width = 34;
        var height = 34;
        var cols = 7;
        var x1, x2, y1, y2;

        var $content = $("<div></div>", { 
            "class": "info-bubble-changeIcon-container",
            css: {
                width: "280px",
                height: "280px"
            }
        });
        var $iconWindow = $("<div></div>", { "class": "iconWindow-container" });
        $iconWindow.append($("<div></div>", { "class": "iconWindow icons-bg" }));

        var $block = $("<div></div>", { "class": "iconWindow" });

        for (i in spots) {
            x1 = (i % cols) * width;
            x2 = x1 + width;
            y1 = Math.floor(i / cols) * height;
            y2 = y1 + height;

            var $a = $("<a></a>", {
                "class": "change-icon-block",
                data: {"icon": spots[i] },
                click: function() {
                    _this.getMarker().setIcon($(this).data('icon'));
                    _this.getMarker().resetIcon();
                    _this.displayInfo();
                }
            });
            $block.append($a);
        }
        $iconWindow.append($block);
        $content.append($iconWindow);

        // Add link to go back.
        var $links = $("<div></div>", {"class": "buttonBox"});
        $links.append($("<a></a>", {
            html: "Cancel",
            href: "#",
            click: function() {
                _this.displayInfo();
            }
        }));
        $content.append($links);

        this.bubble.setContent($content[0]);

    },

    editLocation: function() {
        var _this = this;
        var $f = $('<form></form>', {
            css: {
                width: "250px",
                height: "150px"
            },
            action: "return false;"
        });
        $f.append($("<label></label>", { html: "Location", "for": "addrInput"}));
        var $addrInput = $("<input />", {
            "class": "textInput",
            name: "addrInput",
            id: "addrInput",
            type: "text",
            value: ""
        });
        $f.append($addrInput);
        $f.append($("<p>Enter an address or place and the marker will reposition itself there.</p>"));


        // Add Cancel Button
        var $cancelBtn = $("<input />", {
            type: "button",
            value: "Cancel",
            click: function() {
                _this.displayInfo();
            }
        });
        // Add Save Button
        var $saveBtn = $("<input />", {
            type: "submit",
            value: "Set Location",
            click: function() {
                $(this).val("Searching...");
                _this.getMarker().setLocationByAddr($addrInput.val());
                _this.displayInfo();
            }
        });

        $btnBox = $("<div></div>", {"class": "btnBox"});
        $btnBox.append($cancelBtn).append($saveBtn);
        $f.append($btnBox);

        this.bubble.setContent($f[0]);
    },

    editInfo: function() {
        var _this = this;

        var $f = $('<form></form>', {
            action: "return false;",
            css: {
                width: "250px",
                height: "250px"
            }
        });
        $f.append($("<label></label>", { html: "Title", "for": "titleText", "class": "bubbleLabel"}));
        var $titleInput = $("<input />", {
            "class": "textInput",
            name: "titleText",
            id: "titleText",
            type: "text",
            value: this.getTitle()
        });
        $f.append($titleInput);

        $f.append($("<label></label>", { html: "Description", "for": "descriptionText"}));
        var $descriptionInput = $("<textarea></textarea>", {
            "class": "textInput",
            name: "descriptionText",
            id: "descriptionText",
            html: this.getDescription()
        });
        $f.append($descriptionInput);

        $f.append($("<label></label>", { html: "Link", "for": "linkText"}));
        var $linkInput = $("<input />", {
            "class": "linkInput",
            name: "linkText",
            id: "linkText",
            type: "text",
            value: this.getLink()
        });
        $f.append($linkInput);

        // Add Cancel Button
        var $cancelBtn = $("<input />", {
            type: "button",
            value: "Cancel",
            click: function() {
                _this.displayInfo();
            }
        });
        // Add Save Button
        var $saveBtn = $("<input />", {
            type: "submit",
            value: "Save",
            click: function() {
                _this.setTitle($titleInput.val());
                _this.setDescription($descriptionInput.val());
                _this.setLink($linkInput.val());
                _this.displayInfo();
            }
        });

        $btnBox = $("<div></div>", {"class": "btnBox"});
        $btnBox.append($cancelBtn).append($saveBtn);
        $f.append($btnBox);


        this.bubble.setContent($f[0]);
    }
});



// Main map module class.  This will handle the creation of any mapMarkers/mapInfoBubbles
// for the map.
//
var map = $.inherit(Module, {
    __constructor: function(div) {
        this.__base(div);
        this.type = "map";
        this.keepAspectRatio = false;

        if (!IS_GMAPS_LOADED) {
            GMAPS_CALLBACK_FUNC = createRef(this, this.initialize);
            $.getScript("http://maps.google.com/maps/api/js?sensor=false&callback=mapsjsloaded");
        }
    },

    getGMapObj: function() {
        return this.gmap;
    },

    initialize: function() {
        this.mapDiv = $("#map_" + this.id + "_0")[0];
        this.gmap = null;
        this.markerList = [];
        var opts = this.getOptions();
        var _this = this;

        if (IS_GMAPS_LOADED) {
            var o = {
                zoom: parseInt(opts.zoom),
                center: new google.maps.LatLng(parseFloat(opts.lat), parseFloat(opts.lng)),
                mapTypeId: google.maps.MapTypeId.ROADMAP,
                mapTypeControlOptions: {
                    mapTypeIds: [google.maps.MapTypeId.ROADMAP, google.maps.MapTypeId.TERRAIN],
                    style:google.maps.MapTypeControlStyle.DEFAULT
                },
                draggable: opts.draggable,
                streetViewControl: false
            };

            _this.gmap = new google.maps.Map(_this.mapDiv, o);

            if (!_this.isPublished) {
                _this.setCustomControls();
            }

            _this.loadMarkers();
        }
    },

    loadModuleCallback: function(data) {
        this.container.html(data.html);
        this.initialize();
        this.addDragHandle(data);
        this.addResizing();

        // If this module has no pre-existing size styling, give it some height
        if (this.element.height() < 80) {
            this.element.css('height', '80px');
        }
    },

    getOptions: function() {
        var $f = this.container.find("#WEBON_MAP_OPTIONS_" + this.id);
        var opts = {
            zoom: $f.find("input#WEBON_MAP_" + this.id + "_ZOOM").val(),
            lat: $f.find("input#WEBON_MAP_" + this.id + "_LAT").val(),
            lng: $f.find("input#WEBON_MAP_" + this.id + "_LNG").val(),
            draggable: ($f.find("input#WEBON_MAP_" + this.id + "_DRAGGABLE").val() == '1') ? true : false
        }
        return opts;
    },

    loadMarkers: function() {
        // Function to populate the markers on the map.
        var populateMarkers = function(data) {
                var markers = data.response.markers;
                // Reset the global marker num for when we add more markers
                GMAP_MARKER_NUM = data.response.maxMarkerId;
                for (i in markers) {
                    this.addMarker(markers[i]);
                }
        }

        // Make the ajax call to get them from the DB.
        this.ajaxPost("loadMarkers", {}, createRef(this, populateMarkers));
    },

    /* The "resize" event must be triggered when the map's containing div is resized.
     * For more, see: http://youngdutchdesign.com/goolemaps-v3-checkresize-equivalent
     */
    onResize: function() {
       google.maps.event.trigger(this.getGMapObj(), 'resize');
    },

    addMarker: function(markerObj) {
        var m;
        if (markerObj) {
            m = new mapMarker(this, markerObj.title, markerObj.lat, markerObj.lng);
            m.setMarkerID(markerObj.id);
            m.setDescription(markerObj.description);
            m.setLink(markerObj.url);
            m.setIcon(markerObj.icon);
        }
        else {
            m = new mapMarker(this);
        }
        m.display();
        this.markerList.push(m);
    },

    hideBubbles: function() {
        for (i in this.markerList) {
            this.markerList[i].closeInfoBubble();
        }
    },


    // Adds the block for custom controls (really only addMarker)
    //
    setCustomControls: function() {
        var _this = this;
        var $buttonsContainer = $("<div></div>", {
            css: {
                "padding" : "5px"
            }
        });

        var $addMarkerBtn = $("<div></div>", {
            html: "Add Marker",
            css: {
                "background-color": "#FFF",
                "color": "#000",
                "border": "1px solid #666",
                "padding" : "3px",
                "cursor": "pointer",
                "font": "12px Arial, Helvetica, Sans-serif"
            }
        }).appendTo($buttonsContainer);

        $addMarkerBtn.click(function() {
            _this.addMarker();
        });

        this.gmap.controls[google.maps.ControlPosition.TOP_RIGHT].push($buttonsContainer[0]);
    },

    hideControls: function() {
        var opts = {
            navigationControl: false,
            mapTypeControl: false,
            scaleControl: false
        };

        this.gmap.setOptions(opts);
    },

    showControls: function() {
        var opts = {
            navigationControl: true,
            mapTypeControl: true,
            scaleControl: true
        };

        this.gmap.setOptions(opts);
    },

    // This needs to be overridden because saving should prompt
    // the saving of the module status in a separate JSON call.
    saveModule: function() {
        var saveInfo = {};

        // Save the options of the map (zoom, center, type)
        saveInfo.zoom = this.gmap.zoom;
        saveInfo.lat = this.gmap.getCenter().lat();
        saveInfo.lng = this.gmap.getCenter().lng();
        saveInfo.mtype = this.gmap.getMapTypeId();
        saveInfo.markers = [];

        for (i in this.markerList) {
            if (this.markerList[i].getMarker()) {
                saveInfo.markers.push({
                    id: this.markerList[i].getMarkerID(),
                    lat: this.markerList[i].getPosition().lat(),
                    lng: this.markerList[i].getPosition().lng(),
                    title: this.markerList[i].getTitle(),
                    icon: this.markerList[i].getIcon(),
                    url: this.markerList[i].getLink(),
                    description: this.markerList[i].getDescription()
                });
            }
        }

        this.ajaxPost("saveMarkers", saveInfo);
        return "[ map, " + this.id + " ]";
    }
});


function mapsjsloaded() {
    IS_GMAPS_LOADED = true;
    GMAPS_CALLBACK_FUNC();
}


