﻿Ext.namespace('TNRIS');
/**
 @class abstract class for any layer that can be added to a map
 @constructor
 @config {string} [name]
 @config {string} [teaser]
 @config {string} [description]
 @config {string} [id] a database id for the layer
 @config {string} [micronail_url]
 */
TNRIS.LayerBase = function(config) {
    this.config = {};
    Ext.apply(this.config, config, {
        'zindex': 1,
        'micronail_url': 'images/testServices/micronails/layer8-tn.png'
    });
    
    this.set('visible', true);

    TNRIS.LayerBase.superclass.constructor.call(this, {});
    
    this.addEvents({
        'beforeloadmap' : true,
        'loadmap' : true,
        'loadgrid' : true,
        'loadexception' : true,
        'metadatachange': true,
        'beforedataload':true, // fired when the layer is performing a time consuming operation
        'dataload':true, // fired when time consuming operation is completed
        'layer-refresh' : true // fired after a layer has successfully refreshed itself
    });

    /** true when metadata (name, teaser, etc) isn't saved on the server */
    this.dirtyMetadata = (this.layerId() == null);
};

Ext.extend(TNRIS.LayerBase, Ext.Component, 
    /** @scope TNRIS.LayerBase */
    {
    /***********************************
    * Map Functions
    ************************************/
    
    /** add this layer to the map.  This is an abstract method */
    addToMap: function(id, map, index, mapPanel) {
        this.fireEvent('loadException', "ERROR: LayerBase.addToMap should never be called - it is an abstract method");
    },
    
    /** remove this layer from the map.  This is an abstract method */
    removeFromMap: function() {
        this.map(null);
        this.mapPanel(null);
    },
    
    /** redraw the map - if the layer is invisible, ignore the call, otherwise refresh the view */
    redraw: function() {
        alert("ERROR: LayerBase.redraw should never be called - it is an abstract method");
    },

    /** toggle the visibility of the layer */
    toggleVisible: function() {
        return this.visible(!this.visible());
    },
    
    /** set a layers visibility.  If a layer becomes visible, the abstract redraw function is called */
    visible: function(value) {
        if (typeof(value) != 'undefined' && value != this.get('visible')) {
            this.set('visible', value);
            this.mapPanel() && this.mapPanel().fireEvent('state-change');
            this.redraw();
        }
        return this.get('visible');
    },
    
    opacity: function() {
        return 1.0;
    },
    
    /**************************************
    * Properties
    **************************************/

    /** by default, every publicly available property is wrapped in a method (a la C# properties).  If passed an argument, it becomes a set, otherwise it's a get
    @private 
    */
    defaultMethod: function(key, value) {
        if (typeof(value) == 'undefined') {
            return this.get(key);
        } else {
            return this.set(key, value);
        }
    },

    // legacy functions when hashes were used for a layer
    get: function(key) {
        return this.config[key];
    },
    
    set: function(key, value) {
        switch(key) {
            case 'name':
            case 'teaser':
            case 'description': 
            case 'keywords': 
                this.dirtyMetadata = true; break;
            default: break;
        }
        this.config[key] = value;
        return this.get(key);
    },
    
    /** index is only relevant to tiles layers but gets called on all layers.  This returns a default value */
    index: function(value) {
        return 10;
    },
    
    name: function(value) {
        return this.defaultMethod('name', value);
    },
    
    teaser: function(value) {
        return this.defaultMethod('teaser', value);
    },
    
    description: function(value) {
        return this.defaultMethod('description', value);
    },

    layerId: function(value) {
        return this.defaultMethod('layer_id', value);
    },
    
    micronail: function(value) {
        return this.defaultMethod('micronail_url', value);
    },
    
    mapId: function(value) {
        if (typeof(value) != 'undefined' && value != this._mapId) {
            this._mapId = value;
        }
        return this._mapId;
    },

    /** zoom level range */
    minZoomLevel: function(value) {
        return this.defaultMethod('minZoomLevel', value) || 1;
    },
    
    /** zoom level range */
    maxZoomLevel: function(value) {
        return this.defaultMethod('maxZoomLevel', value) || 19;
    },

    /** a virtual earth map referenc
    @param {VEMap} [value]
    @return {VEMap} the virtual earth map this layer is attached to
    */
    map: function(value) {
        if (typeof(value) != 'undefined' && value != this._map) {
            this._map = value;
        }
        return this._map;
    },
    
    mapPanel: function(value) {
        if (typeof(value) != 'undefined' && value != this._mapPanel) {
            this._mapPanel = value;
        }
        return this._mapPanel;
    },
    
    getShapeLayer: function() {
        return null;
    },

    /****************************************
    * Layer Qualities
    ****************************************/
    
    /** whether layers can be listed in the grid control - HACK */
    queryable: function(value) {
        return 0;
    },
    
    /** if a layer is searchable (to enable searching a feature when user click on the map) */
    searchable: function(value) {
        return this.defaultMethod('searchableLayer', value);
    },
    
    /** if a layer is refreshable (to determine if a refresh button should be added to a layer's legend item) */
    isRefreshable: function() {
        return false;
    },
    
    /** layers are readonly by default */
    isEditable: function() {
        return false;
    },
    
    /** editable layers which are restricted from layer deletion, metadata editing. presently used for nhd layer  */
    hasRestrictedEdit: function() {
        return false;
    },
    
    /** true if the metadata has been modified.  See LayerBase.commit */
    isModified: function() {
        // TODO implement accurately, based on modifications to metadata.
        return this.dirtyMetadata;
    },

    /** by default, layers can have their opacity altered */
    isOpacityVariable: function(value) {
        return true;
    },
    
    /** by default, layers can't be made into smart layers */
    smartable: function(value) {
        return 0;
    },
    
    isPersisted: function() {
        return (this.layerId() != null);
    },
    
    /*******************************************
    * Data Functions
    *******************************************/
    
    /* call to commit a layer to the database */
    commit: function(callback) {
        if (!this.isEditable()) {
            callback && callback("error",  'The ' + this.name() + ' Layer is not editable by this user.');
            return;
        }
        
        var unsavedLayer = !this.isPersisted();
        this.commitMetadata(function(status, message) {
            if (status == "error") {
                callback && callback("error", message);
                return;
            }
            if (status == "success") {
                if (unsavedLayer && this.getShapeStore() != null) {
                    // the first time a layer is saved, there aren't any modified or deleted records to save - only records to add
                    // commit will clear out the shape store of modified records (deletes are tracked in the delete method)
                    this.commitAllStores();
                }
                this.commitShapeData(function(status, message) {
                    if (status == "error") {
                        callback && callback("error", message);
                        return;
                    }
                    if (status == "success") {
                        this.reload();
                        callback && callback("success");
                    }
                }.bind(this));
            }
        }.bind(this));
    },
    
    commitMetadata: function(callback) {
        if (this.dirtyMetadata) {
            PageMethods.saveLayerMetadata(this.config, function(result) {
                if (result.error != null && result.error.length > 0) {
                    callback("error", result.error);
                } else {
                    this.dirtyMetadata = false;
                    if (null == this.layerId()) {
                        this.layerId(result.layer_id);
                    }
                    this.onMetadataChange(result);
                    this.fireEvent('metadatachange', this);
                    callback("success");
                }
            }.bind(this));
        } else {
            callback("success");
        }
    },
    
    commitAllStores: function() {
    
    },
    
    onMetadataChange: function(serverResults) {
    },
    
    commitShapeData: function(callback) {
        callback("success");
    },
    
    reload: function() {
        // derived layers could refresh from the server and refresh
    },

    /** add a shape to a layer - only supported by UserShapeLayer - note Virtual Earth naming convention to interchange with VEMap and VEShapeLayer */
    AddShape: function(shape) {
        throw new Exception("AddShape not supported");
    },
    
    /** add a shape to a layer - only supported by UserShapeLayer - note Virtual Earth naming convention to interchange with VEMap and VEShapeLayer */
    DeleteShape: function(shape) {
        throw new Exception("Delete Shape not supported");
    },
    
    modifyShapes: function(shapes) {
        // default behavior does nothing
    },

    /** finds the corresponding record for a shape */
    getRecordByShape: function(shape) {
        // default behavior does nothing
    },
    
    dispose: function() {
        // console.log('Layer ' + this._id + " disposed");
    }

});

/**
@class A factory to create the appropriate layer object based on the type of layer.
@static
*/
TNRIS.LayerFactory = function() {
    var layers = {};
    var untitledLayerCount = 0;
    
        // LayerRecord is built from server callback (GridData.getClientFields())
    this.LayerRecord = Ext.data.Record.create([
        {name: 'id'}
    ]);

    var layerReader = new Ext.data.ArrayReader({
        id: 0                     // The subscript within row Array that provides an ID for the Record (optional)
    }, this.LayerRecord);

    var systemLayersLoaded = false;
    layerStore = new Ext.data.Store({
        id: 'layerStore',
        proxy: new TNRIS.FunctionProxy({findAll: 'findAllLayers', findBy: 'findLayersByTagID'}),
        reader: layerReader,
        autoLoad: false,
        listeners: { 'load': {fn: function() {systemLayersLoaded = true; }}}    });
    
    return {
        /** @scope TNRIS.LayerFactory */
        init: function() {
            layerStore.load();
        },
        
        loaded: function() {
            return userLayersLoaded && systemLayersLoaded;
        },

        getLayerById: function(id) {
            // todo: instantiate on cache miss
            var layer = layers[id];
            if (null == layer) {
                var idx = layerStore.find('layer_id', id);
                if (idx != -1) {
                    layer = TNRIS.LayerFactory.createLayerFromRecord(layerStore.getAt(idx));
                } else {
                    idx = userLayerStore.find('layer_id', id);
                    if (idx != -1) {
                        layer = TNRIS.LayerFactory.createLayerFromRecord(userLayerStore.getAt(idx));
                    }
                }
            }
            return layer;
        },
        
        /**
        Given a record, create the appropriate type of layer and populate it with data
        @param {Ext.data.Record} record
        @return {TNRIS.LayerBase} the correct child class of LayerBase
        */
        createLayerFromRecord: function(record) {
            var layer = layers[record.id];
            if (layer == null) {
                layer = TNRIS.LayerFactory.createLayer(record.data);
                layer.layerId(record.id);
                
                layers[record.id] = layer;
            }
            return layer;
        }, 
        
        /**
        Create a layer using the same arguments for TNRIS.LayerBase
        */
        createLayer: function(config) {
            var layer = layers[config.layer_id];
            if (layer == null) {
                if (config.georss_url != null && config.georss_url.length > 0) {
                    layer = new TNRIS.GeoRSSLayer(config);
                } else {
                    switch (config.source_type) {
                        case "vetile": layer = new TNRIS.TileLayer(config); break;
                        case "wms": layer = new TNRIS.WMSLayer(config); break;
                        case "relational": layer = new TNRIS.ShapeLayer(config); break;
                        case "cache": layer = new TNRIS.TileLayer(config); break;
                        case "json": layer = new TNRIS.JSONLayer(config); break;
                        case "xml":
                            switch(config.xmlLayerType){
                                case "NHDUserLayer":
                                    layer = new TNRIS.NhdXmlShapeLayer(config);
                                    break;
                                case "NFIPUserLayer":
                                    layer = new TNRIS.NfipXmlShapeLayer(config);
                                    break;
                                default:
                                    layer = new TNRIS.XmlShapeLayer(config); 
                                    break;
                            }
                        break;
                        case "kml": layer = new TNRIS.ImportLayer(config); break;
                        case "odm": layer = new TNRIS.ODMLayer(config); break;
                        case "hiscentral": layer = new TNRIS.HISCentral(config); break;
                        case "txdottraffic": layer = new TNRIS.TxDotTrafficLayer(config); break;
                        default: return new TNRIS.TileLayer(config); break;
                    }
                }
                if (layer.name() == null) {
                    untitledLayerCount++;
                    layer.name('Untitled ' + untitledLayerCount);
                }
                
                // cache the layer
                var id = layer.layerId();
                if (id != null) {
                    layers[id] = layer;
                }
            }
            return layer;
        }
    };
}();

if (typeof(Sys) !== "undefined") { Sys.Application.notifyScriptLoaded(); }
