﻿/*

Map Trakker Base Application
    
*/

// Register the MapTrakker namespace.
Ext.ns('Trakkware.MapTrakker');

// Create the map application.
// mapUID:  The unique clientID of the Element that will contain the map.
// overlayUID:  The unique clientID of the element that will be used for map overlays.
Trakkware.MapTrakker.Application = function(config, mapUID, overlayUID, decorationUID) {

    // A bool which indicates whether this map application is in a valid state.
    this.isValid = true;

    try {
        // This application's name.
        this.applicationName = "MapTrakker.BaseApplication";

        // Capture the map element
        this.mapEl = Ext.get(mapUID);
        if (this.mapEl == undefined) {
            alert(this.applicationName + ':  The map element UID provided does not map to a known element (' + mapUID + ')');
            this.isValid = false;
            return;
        }

        // Capture the map overlay element.
        this.overlayEl = Ext.get(overlayUID);
        if (this.overlayEl == undefined) {
            alert(this.applicationName + ':  The map-overlay element UID provided does not map to a known element (' + overlayUID + ')');
            this.isValid = false;
            return;
        }

        // Capture the decoration element.
        this.decorationEl = Ext.get(decorationUID);
        if (Ext.isEmpty(this.decorationEl)) {
            alert(this.applicationName + ':  The map-decoration element UID provided does not map to a known element (' + decorationUID + ')');
            this.isValid = false;
            return;
        }

        // Apply the configuration file.
        this.Configuration = new function() { };
        Ext.apply(this.Configuration, config);

        // Apply map height and width.
        var width = this.Configuration.VisualDetails.WidthInPixels;
        if (width == undefined) {
            alert(this.applicationName + ': Width was not provided for this map!  I cannot continue.');
            this.isValid = false;
            return;
        }
        this.mapEl.setWidth(width);

        var height = this.Configuration.VisualDetails.HeightInPixels;
        if (height == undefined) {
            alert(this.applicationName + ': Height was not provided for this map!  I cannot continue.');
            this.isValid = false;
            return;
        }
        this.mapEl.setHeight(height);

        // Set copyright information.
        this.copyCollection = new GCopyrightCollection(this.Configuration.CopyrightDetails.CopyrightPrefix);
        this.copyCollection.addCopyright(new GCopyright(1, new GLatLngBounds(new GLatLng(-90, -240), new GLatLng(90, 240)), 0, this.Configuration.CopyrightDetails.CopyrightText));

        // gather zoom levels for easier access.
        var start = this.Configuration.ZoomDetails.StartingZoomLevel;
        var low = this.Configuration.ZoomDetails.LowestZoomLevel;
        var high = this.Configuration.ZoomDetails.HighestZoomLevel;

        // Setup titles
        this.tileLayers = [new GTileLayer(this.copyCollection, low, high)];
        this.tileLayers[0].getTileUrl = this.getTileImageUrl.createDelegate(this);

        // Start an empty marker set.
        this.markers = {};

        // The marker that is currently being shown.
        this.activeMarker = null;

        // Initial Pin settings.
        this.defaultIcon = new GIcon();

        // Please note:  The capability to have different pin gfx on each zoom level is not complete, so
        // i am using the pin gfx from the initial zoom level.
        var baseurl = this.Configuration.WebsiteBaseUrl;
        var pinbase = this.Configuration.VisualDetails.AdvancedSettings.MapPinBasePath;
        var pinset = this.Configuration.VisualDetails.PinSetShim;
        var pinfile = 'pin' + this.Configuration.VisualDetails.PinImageFormatShim;
        var finalpin = baseurl + pinbase + pinset + '/' + start + '/' + pinfile;

        this.defaultIcon.image = finalpin;

        var pinsize = this.Configuration.VisualDetails.PinSizeInPixels;
        this.defaultIcon.iconSize = new GSize(pinsize, pinsize);
        this.defaultIcon.iconAnchor = new GPoint(0, 8);
        delete this.defaultIcon.shadow;

        // Setup the google map.
        this.googleMap = new GMap2(this.mapEl.dom, { mapTypes: [new GMapType(this.tileLayers, new GMercatorProjection(high + 1), "Chart", { errorMessage: this.Configuration.ZoomDetails.ZoomLevelErrorText })], backgroundColor: this.Configuration.VisualDetails.BackgroundColorInHex });
        this.googleMap.setCenter(new GLatLng(this.Configuration.InitialLatitude, this.Configuration.InitialLongitude), low);

        // Drag settings.  Map dragging is enabled by default.
        this.googleMap.enableDragging();
        var dragging = this.Configuration.DisableDragging;
        if (!Ext.isEmpty(dragging)) {
            if (dragging == true)
                this.googleMap.disableDragging();
        }

        // Zoomability settings.  Zooming is enabled by default.
        var allowzoom = this.Configuration.DisableZooming;
        if (!Ext.isEmpty(allowzoom)) {
            if (allowzoom == true) {
                this.googleMap.disableDoubleClickZoom();
                this.googleMap.disableScrollWheelZoom();
                this.googleMap.disablePinchToZoom();
            }
        }

        // Show a zoom control if instructed to.
        var showzoom = this.Configuration.ShowZoomControl;
        if (!Ext.isEmpty(showzoom)) {
            this.googleMap.addControl(new GSmallZoomControl());
        }

        // Hide the overlay el on zoom changes..
        GEvent.addListener(this.googleMap, "zoomend", this.afterZoomed.createDelegate(this));
    } catch (e) {
        alert('An exception occurred during map setup: ' + e);
        this.isValid = false;
    }
};

Ext.extend(Trakkware.MapTrakker.Application, Ext.util.Observable, {

    // Delegate used to get the URL for map tiles.
    getTileImageUrl: function(location, zoom) {
        var basepath = this.Configuration.VisualDetails.AdvancedSettings.MapTitleBasePath;
        var tilepath = this.Configuration.VisualDetails.MapSetShim;
        var format = this.Configuration.VisualDetails.MapImageFormatShim;

        var vurl = basepath + tilepath + '/' + zoom + '/' + location.x + '_' + location.y + format;
        return vurl;
    },

    // Event fires after zooming in or out.
    afterZoomed: function(fromzoom, tozoom) {
        this.overlayEl.update('');
        this.overlayEl.show(false);
        this.decorationEl.update('');
        this.decorationEl.show(false);
    },

    // Called during .onReady..  Sets all the pin information we will need.
    initialMarkers: function(locations) {
        // store location data.
        this.locations = locations;

        // send it off to have markers added.
        this.addMarker(locations);
    },

    // config is google GIcon + title, zindex, lat, lng, id, uiData
    // you can also pass in an array [] of configs
    addMarker: function(config) {

        // if an array build marker for each item
        if (Ext.isArray(config)) {
            for (var item in config) { if (typeof (config[item]) == 'object') this.addMarker(config[item]); }
            return;
        }

        // Start a new icon based on the default icon and configure it.
        var icon = new GIcon(this.defaultIcon);
        this.configPin(icon, config);

        // If the config section has no id, create one.
        if (config.id == undefined) {
            config.id = Ext.id();
        }

        // Force all IDs to lower.
        config.id = config.id.toLowerCase();

        // Set up a marker for this config.. store the config data in marker.location for later access.
        var marker = this.markers[config.id] = new GMarker(new GLatLng((config.lat || 0), (config.lng || 0)), { icon: icon, title: (config.title || ''), zindex: (config.zIndexProcess || 0) });

        // Create a config property on this marker and apply config to it.
        marker.Configuration = new function() { };
        Ext.apply(marker.Configuration, config);

        // attach events
        GEvent.addListener(marker, "click", this.markerClicked.createDelegate(this, [config.id]));

        // Add the marker to the map.
        this.googleMap.addOverlay(marker);

        return marker;
    },

    // Send an icon and config, and the pin is configured.
    configPin: function(icon, config) {

        // Calculate the center offset and then set it and icon size.
        var iconsize = this.Configuration.VisualDetails.PinSizeInPixels;
        if (!Ext.isEmpty(iconsize)) {
            var half = Math.floor(parseFloat(iconsize) / 2);
            icon.iconSize = new GSize(iconsize, iconsize);
            icon.iconAnchor = new GPoint(half, half);
        }

        return icon;
    },

    // call this method and supply an ID and the corresponding marker will behave as if it
    // has been clicked.
    markerClicked: function(id, forcezoom) {

        // lowercase ID before making a comparison.
        id = id.toLowerCase();

        // Get the marker that was clicked.
        var marker = this.markers[id];

        // If it does not exist, say as such and bail.
        if (marker == undefined) {
            // TODO: Option for showing this error.
            // alert('The marker clicked provided an ID that did not match a known marker\'s ID. (' + id + ')');
            return;
        }

        // Get the marker's Configuration data.
        var config = marker.Configuration;
        if (!Ext.isEmpty(config)) {

            // Check if there is a click action for this marker for this zoom level.
            var currentzoom = this.googleMap.getZoom();

            // Check if a forcezoom value was provided.  This causes the PinClick setup to fire for the forcezoom level 
            // instead of the zoom level we are currently at.
            if (!Ext.isEmpty(forcezoom)) {
                currentzoom = forcezoom;
            }

            // We are in markerClicked, so ClickMode needs to be 0 (OnOpen).
            var method = 0;

            // Move through the pin actions list and perform the actions of the PinAction that is appropriate.
            var actions = this.Configuration.PinActions;
            if (!Ext.isEmpty(actions) && actions.length > 0) {

                for (var jj = 0; jj < actions.length; jj++) {
                    var pinclick = actions[jj];

                    // If the zoomlevels match, use this click configuration.
                    if (pinclick.ApplyToZoomLevel == currentzoom && pinclick.ClickMode == method) {

                        // Check if this pin disables dragging..
                        var dragging = pinclick.DisableDragging;
                        if (!Ext.isEmpty(dragging)) {
                            if (dragging == true) this.googleMap.disableDragging();
                        }

                        // Check for re-centering on the pin.
                        var recenter = pinclick.Recenter;
                        if (!Ext.isEmpty(recenter)) {
                            if (recenter == true) {
                                var lat = config.lat;
                                var offsetx = pinclick.RecenterOffsetLatitude;
                                if (!Ext.isEmpty(lat) && !Ext.isEmpty(offsetx)) lat += offsetx;

                                var lng = config.lng;
                                var offsety = pinclick.RecenterOffsetLongitude;
                                if (!Ext.isEmpty(lng) && !Ext.isEmpty(offsety)) lng += offsety;

                                this.googleMap.setCenter(new GLatLng(lat, lng));
                            }
                        }

                        // Check if we need to re-zoom the map.
                        var newzoom = pinclick.ChangeZoomTo;
                        if (!Ext.isEmpty(newzoom)) {
                            this.googleMap.setZoom(newzoom);
                        }

                        // hide the overlay el to start with.  Will be shown further on if told to.
                        this.overlayEl.show(false);

                        // Check if we need to show the pin.
                        var showpin = pinclick.ShowPin;
                        if (!Ext.isEmpty(showpin)) {
                            if (showpin == true) {

                                // Since we are showing a pin, set the active marker so it can be used
                                // when we close this pin info window.
                                this.activeMarker = marker;

                                // A placeholder for pin html.
                                var pinhtml = '';

                                // Get this location's pin details for this zoom level..
                                var marker_config = marker.Configuration;
                                if (!Ext.isEmpty(marker_config)) {
                                    var marker_details = marker_config.MapLocationDetails;
                                    if (!Ext.isEmpty(marker_details)) {
                                        for (var mm = 0; mm < marker_details.length; mm++) {
                                            var location_detail = marker_details[mm];
                                            if (location_detail.ZoomLevel == currentzoom) {
                                                pinhtml = unescape(location_detail.PinHTML);
                                                break;
                                            }
                                        }
                                    }
                                }

                                // Set the pin's html block.
                                this.overlayEl.update(pinhtml);

                                // Get absolute position for this pin.
                                var abs_top = pinclick.AbsoluteTop;
                                var abs_left = pinclick.AbsoluteLeft;
                                var pinwidth = pinclick.Width;
                                var pinheight = pinclick.Height;

                                // Find the google map's box for absolute positioning.
                                var mapbox = this.mapEl.getBox(false, false);

                                if (!Ext.isEmpty(abs_top) && !Ext.isEmpty(abs_left)) {

                                    this.overlayEl.setBox({ x: mapbox.x + abs_left, y: mapbox.y + abs_top, width: pinwidth, height: pinheight });

                                    // Fade the overlay into existance.
                                    this.overlayEl.fadeIn(50);
                                }

                                // See if we need to show a pin decoration.
                                var showdec = pinclick.ShowPinDecoration;
                                if (!Ext.isEmpty(showdec)) {

                                    // See if we need to show a decoration.
                                    if (showdec == true) {

                                        // Get the decoration's anchor point.
                                        var anchor = pinclick.PinInfoWindowDecorationAnchor;
                                        if (!Ext.isEmpty(anchor)) {

                                            // We need the pin's XY vis-a-vis it's lng/lat... thanks google!
                                            var point = this.googleMap.fromLatLngToContainerPixel(marker.getPoint());

                                            point.x += mapbox.x;
                                            point.y += mapbox.y;

                                            // Get the decoration overlay's box.
                                            this.setDecorationAnchorAndImage(anchor, point, pinclick);
                                        }
                                    }
                                }
                            }
                        }

                        // Since we found an applicable pinclick, don't bother finishing the loop.
                        break;
                    }
                }
            }
        }
    },

    // Calculates and sets the decorationEl's box based on overlayEl and anchor.
    setDecorationAnchorAndImage: function(anchor, pin, pinclick) {

        // Initialize box-size vars.
        var x, y, width, height = 0;

        // Get the overlayEl's properties for use in calculations.
        var box = this.overlayEl.getBox(false, false);

        // Heights need an offset added to them.
        var oh = this.overlayEl.dom.offsetHeight / 2;

        // An anchor direction that gets appended to the decoration's filename....
        var dir = '';

        switch (anchor) {
            case 1:     // North
                dir = 'N';
                break;
            case 10:     // South
                dir = 'S';
                break;

            case 100:    // East
                dir = 'E';
                x = box.x + box.width;
                y = box.y;
                width = pin.x - x;
                height = box.height + oh;
                break;

            case 1000:   // West
                dir = 'W';
                break;

            case 101:   // Northeast
                dir = 'NE';
                break;

            case 1001:   // Northwest
                dir = 'NW';
                break;

            case 110:   // Southeast
                dir = 'SE';
                break;

            case 1010:  // Southwest
                dir = 'SW';
                break;

            default:    // Unknown anchor.
                alert('An unknown anchor was provided (' + anchor + ').');
                return;
                break;
        }

        // Position the decoration el.
        this.decorationEl.setBox({ x: x, y: y, width: width, height: height });

        // Set the decorationEl's image.        
        var baseurl = this.Configuration.WebsiteBaseUrl;
        var pinbase = this.Configuration.VisualDetails.AdvancedSettings.MapPinBasePath;
        var pinset = this.Configuration.VisualDetails.PinSetShim;
        var pinext = pinclick.PinDecorationImageFormatShim;

        // Check if we should override the file extension for IE6 ..
        if (Ext.isIE6) {
            var override = pinclick.PinDecorationImageUseGIFForIE6;
            if (!Ext.isEmpty(override)) {
                if (override == true) {
                    pinext = '.gif';
                }
            }
        }

        var zoom = this.googleMap.getZoom();
        var pinfile = pinclick.PinDecorationFilenameBase + '_' + dir + pinext;
        var finalpin = baseurl + pinbase + pinset + '/' + zoom + '/' + pinfile;

        this.decorationEl.applyStyles('background-color:transparent; background-image:url(' + finalpin + ');background-repeat:no-repeat;background-position:center center;position:absolute;');
        this.decorationEl.show(true);
    },

    // What follows are some public methods which can be called from PinHTMLs to perform basic map operations.
    // To get a reference to this map application, be sure to include {MapTrakkerBaseApplication} in your pinHTML.
    // An example of what I mean:  <blah onclick="{MapTrakkerBaseApplication}.hidePinInfo();">Close [x]</blah>

    // Hides the overlay element.
    hidePinInfo: function() {

        // Hide the pin info window.
        this.overlayEl.fadeOut(50);
        this.decorationEl.fadeOut(50);
        this.overlayEl.update('');
        this.overlayEl.show(false);
        this.decorationEl.show(false);
        this.overlayEl.setBox({ x: 0, y: 0, height: 0, width: 0 });
        this.decorationEl.setBox({ x: 0, y: 0, height: 0, width: 0 });

        // Move through the pin actions list and perform the actions of the PinAction that is appropriate.
        var actions = this.Configuration.PinActions;
        if (!Ext.isEmpty(actions) && actions.length > 0) {

            var currentzoom = this.googleMap.getZoom();
            var method = 1;  // this is a ClickMode.OnClose
            for (var jj = 0; jj < actions.length; jj++) {

                var pinclick = actions[jj];

                // If the zoomlevels match, use this click configuration.
                if (pinclick.ApplyToZoomLevel == currentzoom && pinclick.ClickMode == method) {

                    // Check if this pin disables dragging..
                    var dragging = pinclick.DisableDragging;
                    this.googleMap.enableDragging();
                    if (!Ext.isEmpty(dragging)) {
                        if (dragging == true) this.googleMap.disableDragging();
                    }

                    // Check for re-centering on the pin.
                    var recenter = pinclick.Recenter;
                    if (!Ext.isEmpty(recenter)) {
                        if (recenter == true) {
                            var lat = config.lat;
                            var offsetx = pinclick.RecenterOffsetLatitude;
                            if (!Ext.isEmpty(lat) && !Ext.isEmpty(offsetx)) lat += offsetx;

                            var lng = config.lng;
                            var offsety = pinclick.RecenterOffsetLongitude;
                            if (!Ext.isEmpty(lng) && !Ext.isEmpty(offsety)) lng += offsety;

                            this.googleMap.setCenter(new GLatLng(lat, lng));
                        }
                    }

                    // Check if we are to recenter to the map's default position.
                    var recenterdefault = pinclick.RecenterToMapDefault;
                    if (!Ext.isEmpty(recenterdefault)) {
                        this.googleMap.setCenter(new GLatLng(this.Configuration.InitialLatitude, this.Configuration.InitialLongitude));
                    }

                    // Check if we need to re-zoom the map.
                    var newzoom = pinclick.ChangeZoomTo;
                    if (!Ext.isEmpty(newzoom)) {
                        this.googleMap.setZoom(newzoom);
                    }

                    // We found a good click record, so no need to finish the loop.
                    break;
                }
            }
        }

        // Finally, Reset the active marker to null.
        this.activeMarker = null;
    }
});