/* (C) GeoBasis-DE/LGB
 * Copyright by Landesvermessung und Geobasisinformation Brandenburg (LGB)
 *
 * Software engineering by Intevation GmbH
 *
 * This file is Free Software under the GNU GPL (>=v3)
 * and comes with ABSOLUTELY NO WARRANTY! Check out the
 * LICENSE for details.
 */

/**
 * Loader for OpenLayers.Layer
 *
 * Creates layers from valid OpenLayers.Layer configurations.
 * The following layer types are supported by the loader:
 *  - WMS
 *  - WFS (V1.0.0,V1.1.0, V2.0)
 *  - KML
 *
 * The loader can parse additional attributes to style the layer(WFS/KML) and
 * create groups and folders.
 * For WFS layers the attribute 'search' defines a set of search filters and
 * result sets.
 *
 * Example WMS layer configuration:
 *
    var layers = [{
        name: "Standard",
        url: "http://isk.geobasis-bb.de/ows/dnm.php",
        type: "wms",
        params:{
            layers: "bg,vegetation,gewaesser,siedlung,bln,brb,transport,verkehrsobjekte,strassennamen,ortsnamen,gewaessernamen",
            format: "image/png",
            BGCOLOR: '0xFFFFFF'
        },
        options: {
            isBaseLayer: true,
            buffer: 0,
            visibility: true,
            "TILED": true,
            group: 'Kartenpräsentation',
            level: 'Geobasisdaten'
        }
    }, {
 *
 * Example WFS layer configuration:
 *
        name: "Öbvi-WFS",
        url: "http://isk.geobasis-bb.de/ows/oebvi.php",
        type: "wfs",
        options: {
            featureType: ['Alle', 'PotsdamMittelmark'],
            version: '1.1.0',
            visibility: false,
            group: 'WFS',
            level: 'Geobasisdaten'
        },
        search: {
            types: [{
                featureType: 'Alle',
                filter: ['nachname', 'plz'],
                result: [{
                    display: 'Vorname',
                    name: 'vorname'
                }, {
                    display: 'Nachname',
                    name: 'nachname'
                }, {
                    display: 'PLZ',
                    name: 'plz'
                }]
            }]
        }
    }, {
 *
 * Example for KML layer configuration:
 *

        name: "Test",
        url: "test.kml",
        type: "kml",
        options: {
            featureSelect: true,
            showLabel: true,
            labelStyle: {
                fontColor: '#0000ff',
                fontOutlineColor: '#00ff00',
                fontOutlineWidth: 2,
                fontSize: '10px',
                labelAlign: 'cl'
            },
            visibility: false,
            group: 'KML',
            level: 'Zusätzliche'
        }
    }]
 *
 * To load layers from a configuration like seen above use:
 *
    var loader = Ext.create('LGB.ext4map.util.LayerLoader',{});
    loader.projection = 'EPSG:25833'; // Set the default projection
    var layerObjects = loader.load(layers);
 */
Ext.define('LGB.ext4map.util.LayerLoader', {
    requires: [
        'LGB.ext4map.util.StyleFunctionFactory',
        'LGB.ext4map.util.Toolbox'
    ],

    /**
     * The layers.
     */
    layers: [],

    toolbox: null,

    /**
     * The current projection.
     */
    projection: 'EPSG:4326',

    mapOptions: [],

    layerCounter: 0,

    hasVisibleBaseLayer: false,

    permalinkIDs: new Ext.util.HashMap(),

    statics: {
        layerGroups:{},
        baseLayers: [],

        addToGroup: function(layer, group) {
            if (Ext.isArray(this.layerGroups[group])) {
                LGB.ext4map.util.LayerLoader.layerGroups[group].push(layer);
                return;
            }
            LGB.ext4map.util.LayerLoader.layerGroups[group] = [];
            LGB.ext4map.util.LayerLoader.layerGroups[group].push(layer);
        },

        addBaseLayer: function(layer) {
            LGB.ext4map.util.LayerLoader.baseLayers.push(layer);
        },

        getLayerGroups: function() {
            return LGB.ext4map.util.LayerLoader.layerGroups;
        },

        getBaseLayers: function() {
            return LGB.ext4map.util.LayerLoader.baseLayers;
        }
    },

    /**
     * Replaces a placeholder object inside the layer list or the layer tree if already created
     */
    asyncLayerLoaded: function(scope, id, layer) {
        //
        scope.layers[id] = layer;
        //If views are already created, insert layer directly
        var layertree = Ext.getCmp('layertree');
        var map = Ext.getCmp('mapId');

        //If layertree already exists, add async loaded layer there, else try to add to map
        if (layertree) {
            var options = layer.get('options');
            layertree.addCustomLayer(layer, layer.get('name'), options.level, options.group, false, true);
        } else if (map) {
            map.layers.push(layer);
        }

    },

    /**
     * Checks if the given permalinkID is already in use.
     * Returns true if id is available, else false
     */
    isPermalinkIdAvailable: function(permalinkID) {
        return !this.permalinkIDs.get(permalinkID);
    },

    /**
     * Get the layers loaded via the {@link LGB.ext4map.util.LayerLoader#load}
     * method.
     */
    getLayers: function() {
        return this.layers;
    },

    /**
     * Load the layers from a json config.
     *
     * Each config item contains a typical OpenLayers layer configuration.
     */
    load: function(config, scope) {
        var me;
        if (scope) {
            me = scope;
        } else {
            me = this;
        }
        var layerconfig = config.layers;
        var nextSublayerId = layerconfig.length + 1;
        this.toolbox = Ext.create('LGB.ext4map.util.Toolbox');
        this.toolbox.initProjections(this.mapOptions);
        if (this.mapOptions.length > 0) {
            this.projection = this.mapOptions[0].projection;
            this.mapOption = this.mapOptions[0];
        }
        for (var i = 0; i < layerconfig.length; i++) {
            var cfg = layerconfig[i];
            var ndx = cfg.options.isBaseLayer ? 0 : (cfg.options.index | 3);

            //Check if there are sublayers to load
            if (cfg.options && cfg.options.sublayers) {
                subCfg = cfg.options.sublayers;
                cfg.options.sublayers = [];
                for (var j = 0; j < subCfg.length; j++) {
                    subCfg[j].options.isBaseLayer = false;
                    subCfg[j].options.displayInLayerSwitcher = false;
                    subCfg[j].options.visibility = cfg.options.visibility;
                    subCfg[j].options.layerGroup = undefined;
                    var subLayers = this.loadLayers(subCfg[j], nextSublayerId, ndx - 1, config.map[0]);
                    nextSublayerId++;
                    for (var k = 0; k < subLayers.length; k++) {
                        subLayers[k].set('visible', cfg.options.visibility);
                        cfg.options.sublayers.push(subLayers[k]);
                    }
                }
            }
            var layerId = i + 1;
            var layers = this.loadLayers(cfg, layerId, ndx, config.map[0]);
            for (var j = 0; j < layers.length; j++) {
                me.layers.push(layers[j]);
            }
        }
        return me.layers;
    },

    loadLayers: function(cfg, layerId, ndx, mapCfg) {
        var layers = [];
        if (cfg.type === 'wms') {
            layers.push(this.loadWMS(cfg, layerId, ndx, mapCfg));
        }
        else if (cfg.type === 'wmts') {
            layers.push(this.loadWMTS(cfg, layerId, ndx));
        }
        else if (cfg.type === 'wfs') {
            layers = this.loadWFS(cfg, layerId, ndx);
        }
        else if (cfg.type === 'csv') {
            layers.push(this.loadCSV(cfg, layerId, ndx));
        }
        else if (cfg.type === 'kml') {
            layers.push(this.loadKML(cfg, layerId, ndx));
        }
        return layers;
    },

    loadWMS: function(config, layerId, ndx, mapCfg) {
        var me = this;
        if (config.options.TILED) {
            var gridOpts = {
                extent: mapCfg.maxExtent
            };
            var resolutions = [];
            if (mapCfg.resolutions !== undefined) {
                resolutions = this.mapOption.resolutions;
                gridOpts.resolutions = resolutions;
            }
            else if (this.mapOption.scales !== undefined) {
                for (var j = 0; j < this.mapOption.scales.length; j++) {
                    resolutions.push(
                        this.toolbox.getResolutionFromScale(this.mapOption.scales[j],
                            this.mapOption.units));
                }
                gridOpts.resolutions = resolutions;
            }
            var layer = new ol.layer.Tile({
                source: new ol.source.TileWMS({
                    url: config.url,
                    params: config.params,
                    wrapX: false,
                    noWrap: true,
                    tileGrid: new ol.tilegrid.TileGrid(gridOpts)
                }),
                name: config.name,
                zIndex: ndx,
                options: config.options,
                visible: config.options.visibility,
                layerID: layerId
            });

            layer.getSource().on('tileloadstart', function() {
                me.layerCounter++;
                me.showLoadingIcon();
            });
            layer.getSource().on('tileloadend', function() {
                me.layerCounter--;
                me.hideLoadingIcon();
            });

        }
        else {
            var layer = new ol.layer.Image({
                source: new ol.source.ImageWMS({
                    url: config.url,
                    params: config.params
                }),
                name: config.name,
                zIndex: ndx,
                options: config.options,
                visible: config.options.visibility,
                layerID: layerId
            });
            layer.getSource().on('imageloadstart', function() {
                me.layerCounter++;
                me.showLoadingIcon();
            });
            layer.getSource().on('imageloadend', function() {
                me.layerCounter--;
                me.hideLoadingIcon();
            });
        }
		if (config.options.opacity){
			layer.setOpacity(config.options.opacity);
		}
		
        if (config.options.isBaseLayer &&
            config.options.visibility
        ) {
            if (!me.hasVisibleBaseLayer) {
                me.hasVisibleBaseLayer = true;
            }
            else {
                layer.setVisible(false);
            }
        }
        this.setPermalinkID(layer, config);
        if (config.options.layerGroup) {
            LGB.ext4map.util.LayerLoader.addToGroup(layer, config.options.layerGroup);
        }
        return layer
    },

    loadWMTS: function(config, layerId, ndx) {
        var me = this;
        var layer = new ol.layer.Tile({
            extent: config.params.extent,
            source: new ol.source.WMTS({
                url: config.url,
                layer: config.params.layer,
                matrixSet: config.params.matrixSet,
                projection: 'EPSG:25833', // TODO WHAAAT? Why hardcoded?
                format: config.params.format,
                tileGrid: new ol.tilegrid.WMTS({
                    origin: ol.extent.getTopLeft(config.params.extent),
                    tileSize: config.params.tileSize,
                    resolutions: config.params.resolutions,
                    matrixIds: config.params.matrixIds
                }),
                style: config.params.style
            }),
            name: config.name,
            options: config.options,
            visible: config.options.visibility,
            layerID: layerId
        });
        this.setPermalinkID(layer, config);
        layer.getSource().on('tileloadstart', function() {
            me.layerCounter++;
            me.showLoadingIcon();
        });
        layer.getSource().on('tileloadend', function() {
            me.layerCounter--;
            me.hideLoadingIcon();
        });
        if (config.options.isBaseLayer &&
            config.options.visibility
        ) {
            if (!me.hasVisibleBaseLayer) {
                me.hasVisibleBaseLayer = true;
            }
            else {
                layer.setVisible(false);
            }
        }
        if (config.options.layerGroup) {
            LGB.ext4map.util.LayerLoader.addToGroup(layer, config.options.layerGroup);
        }
        return layer;
    },

    loadWFS: function(config, layerId, ndx) {
        var me = this;
        var version = '1.1.0';
        if (config.options.version) {
            version = config.options.version;
        }
        var layers = [];
        for (var j = 0; j < config.options.featureType.length; j++) {
            var type = config.options.featureType[j];
            var namespace;
            if (config.options.featureNS) {
                namespace = config.options.featureNS;
            } else {
                namespace = 'http://mapserver.gis.umn.edu/mapserver';
            }
            var str = (config.url.indexOf("?") != -1 ? config.url + "&" : config.url + "?") +
                "SERVICE=WFS&VERSION=" + version +
                "&REQUEST=GetFeature&TYPENAME=" + type +
                "&SRSNAME=";
            var source = new ol.source.Vector({
                format: new ol.format.WFS({
                    featureNS: namespace,
                    featureType: type,
                    gmlFormat: new ol.format.GML3({
                        featureNS: namespace,
                        featureType: type
                    })
                }),
                url: function() {
                    var srcUrl = str;
                    return function(bounds, resolution, projection) {
                        var encoded = srcUrl + encodeURIComponent(projection.getCode());
                        return encoded;
                    };
                }()
            });
            var stylecfg = config.options.style;
            var styleFunction = undefined;
            //If style is defined, create style function
            if (stylecfg) {
                var functionFactory = Ext.create('LGB.ext4map.util.StyleFunctionFactory');
                var styleFunctionObj = functionFactory.createStyleFunction(stylecfg);
                styleFunction = styleFunctionObj.styleFunction;
                //Put all available styles in the layer object for later use in the legend
                config.options.styleObj = {
                    defaultStyle: styleFunctionObj.defaultStyle,
                    styleRules: styleFunctionObj.styleRules
                }
            }

            config.options.trueType = config.options.featureType[j];
            config.options.format = config.type;
            var layer = new ol.layer.Vector({
                name: config.name,
                source: source,
                visible: config.options.visibility,
                zIndex: config.options.index | 1,
                options: config.options,
                group: config.options.group,
                level: config.options.level,
                search: config.search,
                layerID: layerId + 1000 + j,
                style: styleFunction
            });

            this.setPermalinkID(layer, config);

            if (config.options.layerGroup) {
                LGB.ext4map.util.LayerLoader.addToGroup(layer, config.options.layerGroup);
            }
            layers.push(layer);
        }
        return layers;
    },

    loadCSV: function(config, layerId, ndx) {
        var me = this;
        var url = config.url;
        config.options.format = config.type;
        config.options.url = url;
        var source = new ol.source.Vector({
            loader: function(extend, resolution, proj) {
                Ext.Ajax.request({
                    url: url,
                    success: function(response) {
                        var gj = csv2geojson.csv2geojson(response.responseText, {
                            latfield: config.options.lat,
                            lonfield: config.options.lon,
                            delimiter: config.options.delimiter
                        }, function(err, data) {
                            if (err) {
                                Ext.Msg.alert('Fehler!',
                                    'Die angegebene CSV-Datei konnte nicht verarbeitet werden.');
                                return;
                            }
                            source.addFeatures((new ol.format.GeoJSON()).readFeatures(data));
                        });
                    },
                    failure: function(response) {
                        Ext.Msg.alert('Fehler!',
                            'Die angegebene CSV-Datei konnte nicht geladen werden.');
                    }
                });
            }
        });
        var layer = new ol.layer.Vector({
            name: config.name,
            source: source,
            visible: config.options.visibility,
            zIndex: config.options.index | 1,
            options: config.options,
            group: config.options.group,
            level: config.options.level,
            layerID: layerId
        });
        this.setPermalinkID(layer, config);
        if (config.options.layerGroup) {
            LGB.ext4map.util.LayerLoader.addToGroup(layer, config.options.layerGroup);
        }
        return layer;
    },

    loadKML: function(config, layerId, ndx) {
        var url = config.url;
        var extractStyles = true;
        var showPointNames = true;
        if (config.options.hasOwnProperty('extractStyles')) {
            extractStyles = config.options.extractStyles;
        }
        if (config.options.hasOwnProperty('showPointNames')) {
            showPointNames = config.options.showPointNames;
        }
        var format = new ol.format.KML({
            extractStyles: extractStyles,
			showPointNames: showPointNames
        });
        format.defaultDataProjection = ol.proj.get(config.projection);
        var source = new ol.source.Vector({
            format: format,
            url: url
        });
        config.options.format = config.type;
        config.options.url = url;
        var layer = new ol.layer.Vector({
            name: config.name,
            source: source,
            visible: config.options.visibility,
            zIndex: config.options.index | 1,
            options: config.options,
            group: config.options.group,
            type: config.type,
            level: config.options.level,
            layerID: layerId
        });
        this.setPermalinkID(layer, config);
        if (config.options.layerGroup) {
            LGB.ext4map.util.LayerLoader.addToGroup(layer, config.options.layerGroup);
        }

        if (layer.getVisible() == true) {
            layer.once('render', function(e){
                var map = Ext.getCmp('mapId');
                if (map.zoomToKml == true) {
                    map.renderedKmlFeatures.push(e.target.getSource().getFeatures());
                    map.kmlLayersRendered++;
                    map.zoomToKmlLayer();
                }
            });
        }
        return layer;
    },

    setPermalinkID: function(layer, config) {
        if (!config.options.permalinkID) {
            Ext.raise('No permalinkID set for layer "' + layer.get('name') + '"');
        }
        if (!this.isPermalinkIdAvailable(layer.get('options').permalinkID)) {
            Ext.raise('permalinkID "' + layer.get('options').permalinkID + '" is already used.')
        } else {
            this.permalinkIDs.add(layer.get('options').permalinkID, 1);
        }
    },

    loadStart: function() {
        this.layerCounter++;
        this.showLoadingIcon();
    },

    loadEnd: function() {
        this.layerCounter--;
        this.hideLoadingIcon();
    },

    showLoadingIcon: function() {
        var loadingIcon = Ext.ComponentQuery.query('container[id=idLayerLoading]')[0];
        if (loadingIcon) {
            loadingIcon.show();
        }
    },

    hideLoadingIcon: function() {
        if (this.layerCounter === 0) {
            var loadingIcon = Ext.ComponentQuery.query(
                'container[id=idLayerLoading]')[0];
            if (loadingIcon) {
                loadingIcon.hide();
            }
        }
    },

    /**
     * Get all layers with the attribute 'printable = false'.
     */
    getPrintBlacklist: function() {
        var blacklist = [];
        for (var i = 0; i < this.layers.length; i++) {
            if (this.layers[i].get('options').printable != undefined && this.layers[i].get('options').printable == false) {
                blacklist.push(this.layers[i]);
            }
        }
        return blacklist;
    },

    /**
     * Remove all layers previously loaded.
     */
    clear: function() {
        this.layers = [];
    }
});