import GeometryType from 'ol/geom/GeometryType';
import Layer from 'ol/layer/Layer';
import Source from 'ol/source/Source';
import VectorSource from 'ol/source/Vector';
import Geometry from 'ol/geom/Geometry';
import Feature from 'ol/Feature';
import { ProjectionLike } from 'ol/proj';
import LayerGroup from 'ol/layer/Group';
import LayerColorScale from '../model/LayerColorScale';
import NumericalParameter from '../model/Parameter';
import TemporalImageLayer from '../model/TemporalImageLayer';
import VectorLayer from 'ol/layer/Vector';
import GeoJSON from 'ol/format/GeoJSON';
import NetcdfRasterSource from '../model/NetcdfRasterSource';
import getBackendModule from '../../dataAcquisition/RasterBackendModule';
import FileUtils from '../../util/FileUtils';
import Demap from '../Demap.vue';

export default class LayerUtils {

    /**
     * Extracts the first 'valid' geometry from the given layer and returns it in the given targetCRS.
     * 
     * @param layer 
     * @param targetCRS 
     * @param allowedGeometryTypes 
     */
    public static extractGeometryFromLayer(layer: Layer, targetCRS: string = 'EPSG:4326', allowedGeometryTypes: string[] = [GeometryType.POLYGON, GeometryType.MULTI_POLYGON, GeometryType.LINEAR_RING]): Geometry | undefined {
        let source: Source = layer.getSource();

        if (source && (typeof source['getFeatures'] === "function") || source instanceof VectorSource) {
            // get features from source
            let features: Feature[] = source['getFeatures']();
            if (features && features.length) {
                for (let feature of features) {
                    // get the features geometry
                    let geom: Geometry = feature.getGeometry();

                    // assert geometry type and return geometry
                    if (allowedGeometryTypes.includes(geom.getType())) {
                        let geomTarget = geom.clone();

                        if (targetCRS && targetCRS.length > 0) {
                            // target projection given
                            let srcProj: ProjectionLike = source.getProjection();
                            // if (!srcProj) {
                            //     // check layer
                            //     srcProj = layer.getProjection();
                            // }
                            if (!srcProj) {
                                // assume web mercator
                                srcProj = 'EPSG:3857';
                            }

                            if (srcProj != targetCRS) {
                                // transform the geometry from map-view projection to wgs84
                                geomTarget.transform(srcProj, targetCRS);
                            }
                        }

                        // return the geometry
                        return geomTarget;
                    }
                }
            }
        }

        // either not a vector source or an empty source or not containing a polygon
        return undefined;
    }

    public static containsGeometry(layer: Layer, allowedGeometryTypes: string[] = [GeometryType.POLYGON, GeometryType.MULTI_POLYGON, GeometryType.LINEAR_RING]): boolean {
        return this.extractGeometryFromLayer(layer) != undefined;
    }

    public static downloadAsGeojson(layer: VectorLayer): void {
        let features: Feature<Geometry>[] = [];
        (layer.getSource() as VectorSource).forEachFeature((f: Feature<Geometry>) => {
            features.push(new Feature(f.getGeometry().clone().transform('EPSG:3857', 'EPSG:4326')));
        });

        FileUtils.forceFileDownload(layer.get('title') + '.geojson', new GeoJSON().writeFeatures(features, { decimals: 6 }));
    }

    public static downloadAsNetcdf(layer: TemporalImageLayer | ArrayBuffer, filename?: string): void {
        if(layer instanceof TemporalImageLayer) {
            let src = layer.getSource();
            if (src instanceof NetcdfRasterSource) {
                getBackendModule().requestNetcdfBytes((status: string, netcdf: ArrayBuffer | string) => {
                    if (status == 'success') {
                        // we received the netcdf bytestream - force file download
                        if(!filename) {
                            // use layer title
                            filename = layer.get('title') + '.nc';
                        }
                        FileUtils.forceFileDownload(filename, netcdf);
                    } else {
                        throw new Error(netcdf as string);
                    }
                }, src);
            }
        } else if(layer instanceof ArrayBuffer) {
            // we already have an array buffer given - force download directly
            if(!filename) {
                filename = 'raster.nc';
            }
            FileUtils.forceFileDownload(filename, layer);
        } else {
            throw new Error('unsupported argument type for netcdf download');
        }
        
    }

    public static downloadLayer(layer: Layer): void {
        if (layer instanceof VectorLayer) {
            LayerUtils.downloadAsGeojson(layer as VectorLayer);
        } else if (layer instanceof TemporalImageLayer) {
            LayerUtils.downloadAsNetcdf(layer as TemporalImageLayer);
        }
        else {
            throw Error('download of this layer type is not yet implemented');
        }
    }

    public static splitIntoPropertyLayers(layer: Layer, group: LayerGroup): void {
        // console.log('splitting layer ', layer);

        let colorScale: LayerColorScale = layer.get(LayerColorScale.KEY);

        if (colorScale === undefined) {
            console.warn('cannot split layers without existing colorscale');
            return;
        }

        let layerTitle: string = layer.get('title');
        let parameters: NumericalParameter[] = colorScale.getAvailableParameters();

        let demap: Demap = layer.get('demap') as Demap;

        for (let param of parameters) {
            if (layer instanceof TemporalImageLayer) {
                // split temporal image layer
                let source = layer.getSource();
                if (source instanceof NetcdfRasterSource) {
                    let splitSource: NetcdfRasterSource = source.cloneParameterSource(param);
                    let splitLayer: TemporalImageLayer = new TemporalImageLayer({
                        title: param.getName() + "::" + layerTitle,
                        source: splitSource
                    });

                    demap.addLayer(splitLayer);
                } else {
                    console.warn('splitting not yet supported for given source type', source);
                    return;
                }
            } else if (layer instanceof VectorLayer) {
                console.warn('splitting of vector layers is not yet supported');
            }
        }
    }

}
