/* (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.
 */

/**
 * The controller for the display/save local file tool.
 *
 * The controller defines the logic for locally displaying and saving
 * several geodata file formats.
 * Note that this requires additional external libraries
 * (e.g. shapefile-js and FileSaver.js, see Readme.md).
 *
 * To use the display file tool add the controller to the controllers
 * array in your app and activate the tool in the
 * {@link LGB.ext4map.view.Toolbar toolbar}.
 *
    Ext.application({
        classicControllers: [
            'DisplayFile'
            ...
        ],
        ...
    });
 */
Ext.define('LGB.ext4map.controller.DisplayFile', {
    extend: 'Ext.app.Controller',

    map: null,

    layerID: 2000,

    /**
     * @private
     * Initialize the controller and map the events to function.
     */
    init: function() {
        var me = this;
        this.control({
            'geofiledisplay combobox': {
                change: me.formatChanged
            },
            'geofiledisplay button[action=showfile]': {
                click: me.showFile
            },
            'geofilesave button[action=savefile]': {
                click: me.saveFile
            }
        });
    },

    setMap: function(map) {
        this.map = map.map;
    },

    formatChanged: function(combobox, newValue, oldValue, scope) {
        var oldPanel = combobox.up('window').down('panel[name='+oldValue+'Attributes]');
        var newPanel = combobox.up('window').down('panel[name='+newValue+'Attributes]');
        oldPanel.hide();
        newPanel.show();
    },

    showFile: function(button) {
        var format = button.up('window').down('combobox').getValue();
        var currentPanel = button.up('window').down('panel[name='+format+'Attributes]');
        var projection = currentPanel.down('textfield[name=projection]').getValue();
        var file = button.up('window').down('filefield').fileInputEl.dom.files[0];
        if (!file) {
            return;
        }
        var fileName = file.name.toLowerCase();
        if (format === 'csv' && fileName.match('.csv')) {
            var lat = currentPanel.down('textfield[name=lat]').getValue();
            var lon = currentPanel.down('textfield[name=lon]').getValue();
            this.displayCSV(file, projection, lat, lon);
        }
        else if (format === 'kml' && fileName.match('.kml')) {
            this.displayKML(file, projection);
        }
        else if (format === 'shp' &&
            (fileName.match('.shp') || fileName.match('.zip'))) {
            this.displayShape(file, projection);
        }
        else if (format === 'json' &&
            (fileName.match('.json') || fileName.match('.gjson') ||
                fileName.match('.geojson'))) {
            this.displayGeoJSON(file, projection);
        }
        else if (format === 'txt' &&
            (fileName.match('.txt') || fileName.match('.wkt'))) {
            this.displayWKT(file, projection);
        }
        else {
            Ext.Msg.alert('Fehler!',
                'Dateiformat oder -endung passt nicht zum ausgewählten Format.');
        }
        button.up('window').close();
    },

    displayCSV: function(file, projection, lat, lon) {
        var me = this;
        var reader = new FileReader();
        reader.addEventListener('load', function() {
            var content = reader.result;
            var gj = csv2geojson.csv2geojson(content, {
                latfield: lat,
                lonfield: lon,
                delimiter: ','
            }, function(err, data) {
                if (err) {
                    Ext.Msg.alert('Fehler!',
                        'Die angegebene CSV-Datei konnte nicht verarbeitet werden.');
                    return;
                }
                me.createLayerFromJSON(data, file.name, projection);
            });
        });
        reader.readAsText(file);
    },

    displayKML: function(file, proj) {
        var me = this;
        var reader = new FileReader();
        reader.addEventListener('load', function() {
            var content = reader.result;
            var style = new ol.style.Style({
                fill: new ol.style.Fill({
                    color: 'rgba(255, 255, 255, 0.2)'
                }),
                stroke: new ol.style.Stroke({
                    color: 'rgba(255, 130, 37, 1)',
                    width: 2
                }),
                image: new ol.style.Circle({
                    radius: 6,
                    fill: new ol.style.Fill({
                        color: 'rgba(255, 130, 37, 0.2)'
                    }),
                    stroke: new ol.style.Stroke({
                        color: 'rgba(255, 130, 37, 1)'
                    })
                })
            });
            var source = new ol.source.Vector({
                features: (new ol.format.KML({
                    extractStyles: false,
                    defaultStyle: style
                })).readFeatures(content)
            });
            var layer = new ol.layer.Vector({
                name: file.name,
                source: source,
                layerID: me.layerID,
                options: {}
            });
            me.layerID++;
            me.map.addLayer(layer);
            var tree = Ext.ComponentQuery.query('layertree')[0];
            var store = tree.store;
            var opts = {
                visibility: true,
                printable: false
            };
            var mapProj = me.map.getView().getProjection().getCode()
            if (proj && proj !== '' && proj !== mapProj) {
                me.transformFeatures(layer, proj, mapProj)
            }
            me.validateAndAdd(layer, store, file.name, opts, me);
        });
        reader.readAsText(file);
    },

    displayShape: function(file, projection) {
        var me = this;
        var reader = new FileReader();
        reader.addEventListener('load', function() {
            shp(reader.result).then(function(geojson) {
                if (geojson instanceof Array) {
                    for (var i = 0; i < geojson.length; i++) {
                        var tmp = file.name;
                        tmp = tmp + '/' + geojson[i].fileName;
                        me.createLayerFromJSON(geojson[i], tmp, projection);
                    }
                }
                else {
                    me.createLayerFromJSON(geojson, file.name, projection);
                }
            });
        });
        reader.readAsArrayBuffer(file);
    },

    displayGeoJSON: function(file, projection) {
        var me = this;
        var reader = new FileReader();
        reader.addEventListener('load', function() {
            var content = reader.result;
            me.createLayerFromJSON(content, file.name, projection);
        });
        reader.readAsText(file);
    },

    displayWKT: function(file, proj) {
        var me = this;
        var reader = new FileReader();
        reader.addEventListener('load', function() {
            var content = reader.result;
            var source = new ol.source.Vector({
                features: (new ol.format.WKT()).readFeatures(content)
            });
            var style = new ol.style.Style({
                fill: new ol.style.Fill({
                    color: 'rgba(255, 255, 255, 0.2)'
                }),
                stroke: new ol.style.Stroke({
                    color: 'rgba(255, 130, 37, 1)',
                    width: 2
                }),
                image: new ol.style.Circle({
                    radius: 6,
                    fill: new ol.style.Fill({
                        color: 'rgba(255, 130, 37, 0.2)'
                    }),
                    stroke: new ol.style.Stroke({
                        color: 'rgba(255, 130, 37, 1)'
                    })
                })
            });
            var layer = new ol.layer.Vector({
                name: file.name,
                source: source,
                layerID: me.layerID,
                style: style,
                options: {}
            });
            this.layerID++;
            me.map.addLayer(layer);
            var tree = Ext.ComponentQuery.query('layertree')[0];
            var store = tree.store;
            var opts = {
                visibility: true,
                printable: false
            };
            var mapProj = me.map.getView().getProjection().getCode()
            if (proj && proj !== '' && proj !== mapProj) {
                me.transformFeatures(layer, proj, mapProj)
            }
            me.validateAndAdd(layer, store, file.name, opts, me);
        });
        reader.readAsText(file);
    },

    createLayerFromJSON: function(json, file, proj) {
        var source = new ol.source.Vector({
            features: (new ol.format.GeoJSON()).readFeatures(json)
        });
        var layer = new ol.layer.Vector({
            name: file,
            source: source,
            layerID: this.layerID,
            options: {}
        });
        this.layerID++;
        this.map.addLayer(layer);
        var tree = Ext.ComponentQuery.query('layertree')[0];
        var store = tree.store;
        var opts = {
            visibility: true,
            printable: false
        };
        var mapProj = this.map.getView().getProjection().getCode()
        if (proj && proj !== '' && proj !== mapProj) {
            this.transformFeatures(layer, proj, mapProj)
        }
        this.validateAndAdd(layer, store, file, opts, this);
    },

    transformFeatures: function(layer, srcProj, destProj) {
        var features = layer.getSource().getFeatures();
        for (var i = 0; i < features.length; i++) {
            features[i].getGeometry().transform(srcProj, destProj);
        }
    },

    validateAndAdd: function(layer, store, name, opts, me) {
        var maxExtent;
        var conf = me.application.appParams.map;
        var proj = me.map.getView().getProjection().getCode();
        for (var i = 0; i < conf.length; i++) {
            if (conf[i].switcherValue === proj) {
                maxExtent = conf[i].maxExtent;
            }
        }
        var validator = Ext.create('LGB.ext4map.util.CoordinateValidator', {
            epsg: proj.replace('EPSG:', ''),
            bounds: maxExtent
        });
        var extent = layer.getSource().getExtent()
        var fit = validator.validate([extent[0], extent[1]])
            && validator.validate([extent[2], extent[3]]);
        if (fit) {
            store.addLayer(layer, name, opts, store.getRootNode());
            me.map.getView().fit(layer.getSource().getExtent(), me.map.getSize());
            me.application.fireEvent('layerstatechanged', layer);
        }
        else {
            Ext.Msg.alert('Fehler!',
                'Die Projektion der Daten ist nicht korrekt oder<br> die Daten liegen ausserhalb des Anzeigebereichs.');
            me.map.removeLayer(layer);
        }
    },

    saveFile: function(button) {
        var win = button.up('window');
        var items = win.down('panel[name=layerchoose]').items.items;
        var layers = [];
        var features = [];
        for (var i = 0; i < items.length; i++) {
            features = features.concat(items[i].layer.getSource().getFeatures());
        }
        var format = win.down('combobox').getValue();
        if (format === 'csv') {
            this.saveCSV(features);
        }
        else if (format === 'kml') {
            this.saveKML(features);
        }
        else if (format === 'shp') {
            this.saveSHP(features, this.map.getView().getProjection().getCode());
        }
        else if (format === 'wkt' || format === 'txt') {
            this.saveWKT(features);
        }
        else if (format === 'json') {
            this.saveGeoJSON(features);
        }
        win.close();
    },

    saveCSV: function(features) {
        var heads = [];
        heads.push('x');
        heads.push('y');
        for (var i = 0; i < features.length; i++) {
            var properties = features[i].getKeys();
            for (var j = 0; j < properties.length; j++) {
                if (properties[j] === 'geometry' ||
                    heads.includes(properties[j])) {
                    continue;
                }
                heads.push(properties[j]);
            }
        }
        lines = [];
        lines.push(heads);
        for (var i = 0; i < features.length; i++) {
            if (!(features[i].getGeometry() instanceof ol.geom.Point)) {
                continue;
            }
            var items = [];
            items.push(features[i].getGeometry().getCoordinates()[0]);
            items.push(features[i].getGeometry().getCoordinates()[1]);
            for (var j = 2; j < heads.length; j++) {
                var value = features[i].get(heads[j]);
                if (!value) {
                    items.push('');
                }
                else {
                    items.push(value);
                }
            }
            var stringItems = items.join(',');
            lines.push(stringItems);
        }
        var stringLines = lines.join('\r\n');
        this.saveAs(stringLines, {type: 'text/plain;charset=utf-8'}, 'csv');
    },

    saveKML: function(features) {
        var kmlFormat = new ol.format.KML();
        var kml = kmlFormat.writeFeatures(features);
        this.saveAs(kml, {type: 'text/plain;charset=utf-8'}, 'kml');
    },

    saveSHP: function(features, proj) {
        if (proj != 'EPSG:4326') {
            for (var i = 0; i < features.length; i++) {
                features[i].getGeometry().transform(proj, 'EPSG:4326');
            }
        }
        var jsonFormat = new ol.format.GeoJSON();
        var json = jsonFormat.writeFeaturesObject(features);
        var content = shpwrite.zip(json);
        var byteString = atob(content);
        var ab = new ArrayBuffer(byteString.length);
        var ia = new Uint8Array(ab);
        for (var i = 0; i < byteString.length; i++) {
            ia[i] = byteString.charCodeAt(i);
        }
        this.saveAs(ab, {type: 'application/zip'}, 'zip');
    },

    saveWKT: function(features) {
        var wktFormat = new ol.format.WKT();
        var wkt = wktFormat.writeFeatures(features);
        this.saveAs(wkt, {type: 'text/plain;charset=utf-8'}, 'txt');
    },

    saveGeoJSON: function(features) {
        var jsonFormat = new ol.format.GeoJSON();
        var json = jsonFormat.writeFeatures(features);
        this.saveAs(json, {type: 'text/plain;charset=utf-8'}, 'json');
    },

    saveAs: function(content, type, format) {
        var blob = new Blob([content], type);
        saveAs(blob, 'bbviewer.' + format);
    }
});