/**
 * A preliminary Map implementation
 *
 * Displays a Google map or ESRI map using Leaflet
 *
 * @since 23.1
 * @author Jeff Martin
 */
Ext.define('Map.component.Map', {
    extend: 'Ext.Component',

    xtype: 'abmap',

    /**
     * @property {Object} map The Leaflet map
     */
    map: null,

    /**
     * @property {Object[L.layergroup]} basemapLayerGroup The basemap layer group.
     */
    basemapLayerGroup: null,

    /**
     * @property {Array[Object]} basemapLayerGroup An array of basemap name-value pairs.
     */
    basemapLayerList: null,

    // holds a reference to the leaflet reference layer group
    markerLayerGroup: null,

    // Ext.util.MixedCollection
    // holds a list of datasource-MarkerProperty pairs
    // key is the dataSource, value is the corresponding LeafletMarkerProperty
    dataSourceMarkerPairs: null,

    // the marker action callback function
    markerActionCallback: null,



    config: {

        /**
         * @cfg {Number[]} center The center of the map
         */
        mapCenter: [37.30028, -98.26172],

        /**
         * @cfg {Number} zoom The map zoom level
         */
        mapZoom: 3

    },

    template: [
        {
            tag: 'div',
            reference: 'mapEl',
            cls: 'ab-map'
        }
    ],

    /**
     *
     * Map display methods.
     */
    setLatLonAndZoom: function (lat, lon, zoom) {
        var me = this;

        me.setView([lat, lon], zoom);
    },

    setCenterAndZoom: function (center, zoom) {
        var me = this;

        me.setView(center, zoom);
    },

    setView: function (center, zoom) {
        var me = this;

        if (me.map) {
            me.map.setView(center, zoom);
        }
    },

    initialize: function () {
        var me = this;

        // initialize marker pairs
        me.dataSourceMarkerPairs = new Ext.util.MixedCollection();

        me.on('painted', me.onPainted, me, {single: true});

        me.on('mapLoaded', me.onMapLoaded, me, {single: true});

        me.on('markerClick', me.onMarkerClck, me);
    },

    /**
     * The onPainted function is implemented in the Esri or Google map classes
     */
    onPainted: Ext.emptyFn,

    /**
     * The onMapLoaded function is implemented in the Esri or Google map classes
     */
    onMapLoaded: Ext.emptyFn,

    /**
     * Get the layer names for all available basemap layers
     */
    getBasemapLayerList: function () {
        return this.basemapLayerList.keys;
    },

    /*
     * Clear the marker layers.
     */
    clearMarkers: function () {
        this.markerLayerGroup.clearLayers();
    },

    /*
     *  create marker definition for the specified datasource with the specified marker properties
     */
    createMarkers: function (storeId, keyFields, geometryFields, titleField, contentFields, markerOptions) {
        var me = this,
            markerProperties,
            renderer = markerOptions.renderer || 'simple',
            markerConfig = {
                storeId: storeId,
                keyFields: keyFields,
                geometryFields: geometryFields,
                titleField: titleField,
                contentFields: contentFields,
                markerOptions: markerOptions
            },
            markerRenderers = {
                'simple': 'Simple',
                'thematic-class-breaks': 'Thematic',
                'thematic-unique-values': 'Thematic',
                'graduated-class-breaks': 'Thematic',
                'thematic-graduated-class-breaks': 'Thematic',
                'thematic-graduated-unique-values': 'Thematic',
                'proportional': 'Thematic',
                'thematic-proportional-unique-values': 'Thematic',
                'thematic-proportional-class-breaks': 'Thematic'
            },
            markerClass;

        Log.log('Map -> Create Markers...', 'debug');

        if (markerRenderers.hasOwnProperty(renderer)) {
            markerClass = 'Map.component.' + markerRenderers[renderer] + 'Marker';
        } else {
            Log.log('Error - Unsupported marker renderer : ' + renderer, 'error');
        }

        markerProperties = Ext.create(markerClass, markerConfig);

        me.updateMarkerPropertiesByStoreId(storeId, markerProperties);

    },

    /**
     *
     * Show current location methods
     */
    addLocationMarker: function (lat, lon) {
        var me = this,
            markerOptions = {
                radius: 10,
                fillColor: '#377eb8',
                fillOpacity: 1.0,
                stroke: true,
                color: '#fff',
                weight: 3,
                layerId: 'currentLocationLayer'
            },
            popupContent = '<div class="ab-map-popup-content-fields" id="abMapPopupContentFields">' +
                'Current location: <br>' + lat + ', ' + lon +
                '</div>';

        me.createMarkerGroupLayer(lat, lon, markerOptions, popupContent);
    },

    clearLocationMarker: function () {
        this.clearMarkerGroupLayer('currentLocationLayer');
    },

    /*
     * Common marker group methods
     */
    createMarkerGroupLayer: function (lat, lon, markerOptions, popupContent) {
        var me = this,
            layer;

        if (Ext.isEmpty(lat) || Ext.isEmpty(lon)) {
            Log.log('Cannot create marker layer', 'error');
        } else {
            layer = L.circleMarker([lat, lon], markerOptions).bindPopup(popupContent);
            me.markerLayerGroup.addLayer(layer);
        }
    },

    clearMarkerGroupLayer: function (layerName) {
        var me = this,
            layer,
            layers = me.markerLayerGroup._layers,
            layerId;

        for (layerId in layers) {
            layer = layers[layerId];
            if (layer.hasOwnProperty('options')) {
                if (layer.options.layerId === layerName) {
                    me.markerLayerGroup.removeLayer(layer._leaflet_id);
                }
            }
        }
    },

    createFeatureGroup: function(locations) {
        var markers = [];

        locations.forEach(function(location) {
            if(location.icon) {
                markers.push(L.marker([location.lat, location.lon],{icon: location.icon}));
            } else {
                markers.push(L.marker([location.lat, location.lon]));
            }

        });

        return L.featureGroup(markers);
    },

    /**
     *
     * Locate asset methods.
     */
    startLocateAsset: function (lat, lon) {
        var me = this,
            mapCenter;

        // add the locate asset marker
        if (Ext.isEmpty(lat) || Ext.isEmpty(lon)) {
            mapCenter = me.map.getCenter();
            lat = mapCenter.lat;
            lon = mapCenter.lng;
        }
        me.addLocateAssetMaker(lat, lon);
        me.setView([lat, lon], 16);

        me.map.on('click', me.onAssetLocateMapClick, this);
    },

    onAssetLocateMapClick: function (evt) {
        var me = this,
            latLng = evt.latlng,
            lat = latLng.lat,
            lon = latLng.lng;

        me.moveLocateAssetMarker(lat, lon);
    },

    addLocateAssetMaker: function (lat, lon) {
        var me = this,
            markerOptions = {
                radius: 10,
                fillColor: '#ffd700',
                fillOpacity: 1.0,
                stroke: true,
                color: '#fff',
                weight: 3,
                layerId: 'locateAssetLayer'
                //riseOnHover: _markerOptions.riseOnHover,
                //title: feature.id,
                //content: feature.properties
            },
            popupContent = '<div class="ab-map-popup-content-fields" id="abMapPopupContentFields">' +
                'Asset location: <br>' + lat.toFixed(7) + ', ' + lon.toFixed(7) + '<br><br>' +
                'Click to relocate this asset.' +
                '</div>';

        me.createMarkerGroupLayer(lat, lon, markerOptions, popupContent);
    },

    moveLocateAssetMarker: function (lat, lon) {
        var me = this;

        me.clearMarkerGroupLayer('locateAssetLayer');
        me.addLocateAssetMaker(lat, lon);
    },

    finishLocateAsset: function () {
        var me = this,
            lat, lon,
            layers = me.markerLayerGroup._layers,
            layerId,
            layer;

        // get final lat-lon
        for (layerId in layers) {
            layer = layers[layerId];
            if (layer.hasOwnProperty('options')) {
                if (layer.options.layerId === 'locateAssetLayer') {
                    lat = layer._latlng.lat;
                    lon = layer._latlng.lng;
                    break;
                }
            }
        }

        // clear the locate asset layer
        me.clearMarkerGroupLayer('locateAssetLayer');

        // remove map click event
        me.map.off('click', me.onAssetLocateMapClick, this);

        // return the lat-lon
        return [lat, lon];
    },

    /*
     *  show markers for the specified store and filter
     */
    showMarkers: function (storeId, filter) {
        var me = this,
            markerProperties = me.getMarkerPropertiesByStoreId(storeId),
            _markerOptions;

        Log.log('Map -> Show Markers...', 'debug');

        if (markerProperties) {
            _markerOptions = markerProperties.getMarkerOptions();
            if (_markerOptions.markerActionTitle) {
                //TODO
                me.markerActionCallback = markerProperties.getMarkerOptions().markerActionCallback;
            }

            // we have to get the datasource/records first, then resume creating markers
            me.getMarkerRecordsFromStore(storeId, filter);

        } else {
            // return, display and log error
            Log.log('Marker definition does not exist for storeId : ' + storeId, 'error');
        }

    },

    /**
     * @private
     * @param storeId
     * @param filters
     */
    getMarkerRecordsFromStore: function (storeId, filters) {
        var me = this,
            store = Ext.getStore(storeId);

        Log.log('Map -> Get Marker Records From Store...', 'debug');

        if (filters.length > 0) {
            store.setDisablePaging(false);
            store.setFilters(filters);
        } else {
            store.setDisablePaging(true);
            store.clearFilter();
        }


        store.load(function (records) {
            me.onGetMarkerRecordsFromStore(storeId, records);
        });
    },

    /**
     * @private
     * @param storeId
     * @param records
     */
    onGetMarkerRecordsFromStore: function (storeId, records) {
        var me = this;

        // create marker data from records
        var markerData = me.createMarkerData(storeId, records);

        Log.log('Map -> On Get Marker Records From Store...', 'debug');

        // display the markers
        if (markerData.features.length > 0) {
            me.displayMarkers(storeId, markerData);
        }
    },


    /*
     *  return the markerProperties for given ds
     *  @private
     *  @param dataSource. The dataSource name
     */
    getMarkerPropertiesByStoreId: function (storeId) {
        var me = this;
        Log.log('Map -> Get Marker Properties By StoreId...', 'debug');

        return me.dataSourceMarkerPairs.get(storeId);
    },

    /**
     * @private
     * @param storeId
     * @param markerProperties
     */
    updateMarkerPropertiesByStoreId: function (storeId, markerProperties) {
        var me = this;

        if (me.getMarkerPropertiesByStoreId(storeId) === null) {
            me.dataSourceMarkerPairs.add(storeId, markerProperties);
        }
        else {
            this.dataSourceMarkerPairs.replace(storeId, markerProperties);
        }
    },

    /*
     *  Helper / Convenience Methods
     */

    /**
     *  get map data
     *  @private
     *  @param dataSource. The dataSource name.
     *  @param restriction. The Restriction.
     *  @return. The map data (geoJson).
     */
    createMarkerData: function (storeId, records) {
        // convert record data to geoJson

        var markerData = this.recordsToGeoJson(storeId, records);
        Log.log('Map -> Create Marker Data...', 'debug');

        return markerData;
    },

    //TODO refactor 
    recordsToGeoJson: function (storeId, records) {
        var markerData = {},
            features = [],
            markerProperties = this.getMarkerPropertiesByStoreId(storeId),
            lonField = markerProperties.getGeometryFields()[0],
            latField = markerProperties.getGeometryFields()[1],
            keyFields = markerProperties.getKeyFields(),
            contentFields = markerProperties.getContentFields(),
            record,
            lat,
            lon,
            r, j, k,
            feature,
            geometry,
            coordinates,
            properties,
            fieldTitle,
            fieldValue,
            titleFieldValue,
            popupTitle,
            popupAction,
            popupContent,
            keyValues,
            keyValue;

        // create feature GeoJson for each record
        var recordsLength = records.length;

        Log.log('Map -> Records To GeoJson...', 'debug');
        for (r = 0; r < recordsLength; r++) {
            record = records[r];

            lon = record.get(lonField);
            lat = record.get(latField);

            if (Ext.isEmpty(lon) || Ext.isEmpty(lat)) {
                //TODO
                // assetKey = '';
                // for (l = 0; l < keyFields.length; l++) {
                //     keyValue = record.get(keyFields[l]);
                //     if (l === 0) {
                //         assetKey = keyValue;
                //     } else {
                //         assetKey += '-' + keyValue;
                //     }
                // }
                // What can we do here...
                // Log.log('Asset has no location data. Asset key: ' + assetKey,'debug');
            } else {

                feature = {};

                // type
                feature.type = 'Feature';

                // geometry
                geometry = {};
                geometry.type = 'Point';

                coordinates = [
                    record.get(lonField),
                    record.get(latField)
                ];
                geometry.coordinates = coordinates;

                feature.geometry = geometry;

                // properties
                properties = {};
                popupContent = '<div class="ab-map-popup-content-fields" id="abMapPopupContentFields">';

                for (j = 0; j < contentFields.length; j++) {
                    fieldTitle = this.getFieldTitle(storeId, contentFields[j]);
                    fieldValue = record.get(contentFields[j]) || '';
                    popupContent += '<b>' + fieldTitle + '</b>: ' + fieldValue + '</br>';
                    properties[contentFields[j]] = fieldValue;
                }
                popupContent += "</div>";
                feature.properties = properties;

                // format the title based on the title field and/or its lookup field
                titleFieldValue = record.get(markerProperties.get('titleField'));

                popupTitle = '<span class="ab-map-popup-content-title" id="abMapPopupContentTitle">';
                popupTitle += titleFieldValue;
                popupTitle += '</span>';
                feature.properties.popupTitle = popupTitle;

                //TODO
                //add marker action to popup
                if (markerProperties.getMarkerOptions().markerActionTitle && markerProperties.getMarkerOptions().markerActionCallback) {
                    //<a class="action" id="actionLink" href="javascript: void(0);">Show Details</a>
                    popupAction = '<span class="ab-map-popup-action" id="abMapPopupAction"><a href="javascript: void(0);">';
                    popupAction += markerProperties.getMarkerOptions().markerActionTitle;
                    popupAction += '</a></span>';

                    popupContent += popupAction;
                }
                feature.properties.popupContent = popupContent;

                keyValues = '';
                for (k = 0; k < keyFields.length; k++) {
                    keyValue = record.get(keyFields[k]);
                    if (k === 0) {
                        keyValues = keyValue;
                    } else if (k > 0) {
                        keyValues += '|' + keyValue;
                    }
                }
                feature.properties.keyValues = keyValues;

                // add to features
                features.push(feature);

            }

        }

        markerData.type = 'Feature Collection';
        markerData.features = features;

        return markerData;
    },

    /**
     * @private
     * @param storeId
     * @param markerData
     */
    displayMarkers: function (storeId, markerData) {
        var me = this,
            markerProperties,
            _markerOptions,
            geoJsonLayer,
            markerClusters,
            usePopup;

        // clear the marker layer
        // clear markers for this datasource only
        me.removeLayerFromLayerGroup(me.markerLayerGroup, storeId);

        // get the marker properties
        markerProperties = me.getMarkerPropertiesByStoreId(storeId);
        _markerOptions = markerProperties.getMarkerOptions();
        usePopup = _markerOptions.usePopup;
        geoJsonLayer = L.geoJson(markerData, {
            getStoreId: function () {
                return storeId;
            },
            pointToLayer: function (feature, latlng) {
                var markerOptions = {
                    radius: getMarkerRadius(feature, markerProperties),
                    fillColor: getMarkerFillColor(feature, markerProperties),
                    fillOpacity: _markerOptions.fillOpacity,
                    stroke: _markerOptions.stroke,
                    color: _markerOptions.color,
                    weight: _markerOptions.weight,
                    riseOnHover: _markerOptions.riseOnHover,
                    //title: feature.id,
                    content: feature.properties
                };

                var marker;
                switch (_markerOptions.renderer) {
                    case 'proportional':
                    case 'thematic-proportional-unique-values':
                    case 'thematic-proportional-class-breaks':
                        marker = L.circle(latlng, markerOptions.radius, markerOptions);
                        break;

                    default:
                        marker = L.circleMarker(latlng, markerOptions);
                        break;
                }
                return marker;
            },

            onEachFeature: function (feature, layer) {
                var assetId;
                if (usePopup === true) {
                    layer.bindPopup(
                        feature.properties.popupTitle +
                        feature.properties.popupContent
                    );
                }

                layer.on('click', function (evt) {
                    feature = evt.target.feature;
                    assetId = feature.properties.keyValues;
                    me.fireEvent('markerClick', assetId, feature);
                });
            }
        });

        //TODO ?
        if (_markerOptions.useClusters === true) {
            markerClusters = L.markerClusterGroup({

                polygonOptions: {
                    fillColor: '#000',
                    color: '#000',
                    weight: 2,
                    opacity: 1,
                    fillOpacity: 0.25
                },

                iconCreateFunction: function (cluster) {
                    var count = cluster.getChildCount();
                    return L.divIcon({
                        html: '<div style="ab-map-div-cluster-marker-label">' + (count) + '</div>',
                        className: 'ab-map-div-cluster-marker',
                        iconSize: [25, 25]
                    });
                }
            });
            markerClusters.addLayer(geoJsonLayer);
            me.map.addLayer(markerClusters);
            markerClusters.bringToFront();
            me.map.fitBounds(markerClusters.getBounds());
        } else {
            me.markerLayerGroup.addLayer(geoJsonLayer);
            geoJsonLayer.bringToFront();
            me.map.fitBounds(geoJsonLayer.getBounds());
        }

        function getMarkerFillColor(feature, markerProperties) {
            var fillColor,
                featureValue,
                i;

            _markerOptions = markerProperties.getMarkerOptions();

            switch (_markerOptions.renderer) {

                case 'simple':
                case 'graduated-class-breaks':
                case 'proportional':
                    fillColor = _markerOptions.fillColor;
                    break;

                case 'thematic-unique-values':
                case 'thematic-graduated-unique-values':
                case 'thematic-proportional-unique-values':
                    featureValue = feature.properties[_markerOptions.thematicField];
                    for (i = 0; i < _markerOptions.thematicRenderer.length; i++) {
                        if (featureValue === _markerOptions.thematicRenderer[i].uniqueValue) {
                            fillColor = _markerOptions.thematicRenderer[i].color;
                        }
                    }
                    break;

                case 'thematic-class-breaks':
                case 'thematic-graduated-class-breaks':
                case 'thematic-proportional-class-breaks':
                    featureValue = feature.properties[_markerOptions.thematicField];
                    for (i = 0; i < _markerOptions.thematicRenderer.length; i++) {
                        if (i === 0) {
                            // first class break
                            if (featureValue < _markerOptions.thematicRenderer[0].maxValue) {
                                fillColor = _markerOptions.thematicRenderer[0].color;
                                break;
                            }
                        } else if (i === _markerOptions.thematicRenderer.length) {
                            // last class break
                            if (featureValue >= _markerOptions.thematicRenderer[i].minValue) {
                                fillColor = _markerOptions.thematicRenderer[i].color;
                                break;
                            }
                        }
                        else {
                            // intermediate class break
                            if (featureValue >= _markerOptions.thematicRenderer[i].minValue && featureValue < _markerOptions.thematicRenderer[i].maxValue) {
                                fillColor = _markerOptions.thematicRenderer[i].color;
                                break;
                            }
                        }
                    }
                    break;

                default:
                    break;
            }
            Log.log('getMarkerFillColor-> value: ' + featureValue + ' color: ' + fillColor, 'debug');
            return fillColor;

        }

        function getMarkerRadius(feature, markerProperties) {
            var markerRadius,
                _markerOptions = markerProperties.getMarkerOptions(),
                radius,
                graduatedRenderer,
                graduatedField,
                featureValue,
                proportionalField,
                i;

            switch (_markerOptions.renderer) {

                case 'graduated-class-breaks':
                case 'thematic-graduated-unique-values':
                case 'thematic-graduated-class-breaks':
                    radius = _markerOptions.radius;
                    graduatedRenderer = _markerOptions.graduatedRenderer;
                    graduatedField = _markerOptions.graduatedField;
                    featureValue = feature.properties[graduatedField];

                    for (i = 0; i < graduatedRenderer.length; i++) {
                        if (i === 0) {
                            // first class break
                            if (featureValue < graduatedRenderer[0].maxValue) {
                                markerRadius = graduatedRenderer[0].radius;
                                break;
                            }

                        } else if (i === graduatedRenderer.length) {
                            // last class break
                            if (featureValue >= graduatedRenderer[i].minValue) {
                                markerRadius = graduatedRenderer[i].radius;
                                break;
                            }
                        }
                        else {
                            // intermediate class break
                            if (featureValue >= graduatedRenderer[i].minValue && featureValue < graduatedRenderer[i].maxValue) {
                                markerRadius = graduatedRenderer[i].radius;
                                break;
                            }
                        }
                    }

                    break;

                case 'proportional':
                case 'thematic-proportional-unique-values':
                case 'thematic-proportional-class-breaks':
                    proportionalField = _markerOptions.proportionalField;
                    featureValue = feature.properties[proportionalField];
                    markerRadius = parseFloat(featureValue);
                    break;

                default:
                    markerRadius = _markerOptions.radius;
                    break;
            }
            Log.log('getMarkerRadius -> value: ' + featureValue + ' radius: ' + markerRadius, 'debug');
            return markerRadius;
        }

        //TODO
        // update the legend
        //this._updateLegendContent(markerProperties);
    },

    /**
     *  Gets the field title from the dataSource
     *  @private
     *  @param {String} dataSource The data source name.
     *  @param {String} field The field name; e.g. 'bl.bl_id'
     */
    getFieldTitle: function (storeName, fieldName) {
        var fieldTitle = '',
            store = Ext.getStore(storeName),
            tableName = store.serverTableName,
            fieldCollection = TableDef.getTableDefFieldCollection(tableName),
            fieldTitles = fieldCollection.get(fieldName).multiLineHeadings,
            i;

        Log.log('Map -> Get Field Title...', 'debug');

        for (i = 0; i < fieldTitles.length; i++) {
            if (i === 0) {
                fieldTitle = fieldTitles[0];
            } else {
                fieldTitle += ' ' + fieldTitles[i];
            }
        }

        return fieldTitle;
    },

    /**
     * @private
     * @param layerGroup
     * @param storeId
     */
    removeLayerFromLayerGroup: function (layerGroup, storeId) {
        var layers,
            layerId,
            layer;

        if (layerGroup) {
            layers = layerGroup._layers;
            for (layerId in layers) {
                layer = layers[layerId];
                if (layer.hasOwnProperty('options')) {
                    if (layer.options.getStoreId() === storeId) {
                        layerGroup.removeLayer(layer._leaflet_id);
                    }
                }
            }
        }
    },

    /* 
     * Sample functions added by JM.
     */
    addMarker: function (lat, lon) {
        var me = this;

        if (me.map) {
            L.marker([lat, lon]).addTo(me.map);
        }
    },

    panToLocation: function (lat, lon) {
        var me = this;

        if (me.map) {
            me.map.panTo(new L.LatLng(lat, lon));
        }
    }

});