import { Component, Host, Input, Output, EventEmitter,OnDestroy, OnInit, OnChanges, SimpleChanges, forwardRef } from '@angular/core';
import { LayerGroupComponent, MapComponent } from 'ngx-openlayers';
import { ItemService,IItem,AppConfig } from '@farmmaps/common';
import { IItemLayer, ITemporalItemLayer} from '../../../models/item.layer';
import { ILayerData} from '../../../models/layer.data';
import { IRenderoutputTiles,IRenderoutputImage,IGradientstop,ILayer,IHistogram,IColor} from '../../../models/color.map';
import {Extent} from 'ol/extent';
import Projection from 'ol/proj/Projection';
import * as proj from 'ol/proj';
import * as loadingstrategy from 'ol/loadingstrategy';
import * as style from 'ol/style';
import {Tile,Layer,Image} from 'ol/layer';
import {XYZ,ImageStatic,OSM,BingMaps,TileWMS,TileArcGISRest,TileJSON,Source} from 'ol/source';
import {Vector as VectorSource} from 'ol/source';
import { Vector as VectorLayer } from 'ol/layer';
import VectorTileSource from 'ol/source/VectorTile';
import VectorTileLayer from 'ol/layer/VectorTile';
import {GeoJSON,MVT} from 'ol/format';
import { Geometry } from 'ol/geom';
import BaseLayer from 'ol/layer/Base';

@Component({
  selector: 'fm-map-item-layers',
  template: `<ng-content></ng-content>`,
  providers: [
    { provide: LayerGroupComponent, useExisting: forwardRef(() => ItemLayersComponent) }
  ]
})

export class ItemLayersComponent extends LayerGroupComponent implements OnChanges, OnInit,OnDestroy {
  @Input() itemLayers: IItemLayer[];
  @Input() itemLayer: IItemLayer;
  @Output() onFeatureSelected: EventEmitter<any> = new EventEmitter<any>();
  @Output() onFeatureHover: EventEmitter<any> = new EventEmitter<any>();
  @Output() onPrerender: EventEmitter<any> = new EventEmitter<any>();
  private _apiEndPoint: string;
  private initialized:boolean = false;
  private mapEventHandlerInstalled = false;
  private topLayerPrerenderEventhandlerInstalled = false;
  private selectedFeatures = {};
  private selectionLayer:Layer<Source> = null;
  
  constructor(private itemService: ItemService, private map: MapComponent, public appConfig: AppConfig) {
    super(map);
    this._apiEndPoint = appConfig.getConfig("apiEndPoint");
  }

  private styleCache = {}

  componentToHex(c) {
    var hex = c.toString(16);
    return hex.length == 1 ? "0" + hex : hex;
  }

   rgbaToHex(r, g, b,a) {
     return "#" + this.componentToHex(r) + this.componentToHex(g) + this.componentToHex(b) + this.componentToHex(a);
  }

  getColorFromGradient(layer: ILayer, value: number): IColor {
    var gradient: IGradientstop[] = layer.renderer.colorMap.gradient;
    var histogram: IHistogram = layer.renderer.band.histogram;
    var index = (value - histogram.min) / histogram.max;
    var min = gradient[0];
    var max = gradient[gradient.length - 1];
    for (var n = 0; n < gradient.length; n++) {
      var s = gradient[n];
      if (s.relativestop <= index && min.relativestop < s.relativestop && n < gradient.length - 1) min = s;
      if (s.relativestop >= index && max.relativestop > s.relativestop && n > 0) max = s;
    }
    var i = index - min.relativestop;
    var size = max.relativestop - min.relativestop;
    var alpha = Math.round(min.color.alpha + ((max.color.alpha - min.color.alpha) * i / size));
    var red = Math.round(min.color.red + ((max.color.red - min.color.red) * i / size));
    var green = Math.round(min.color.green + ((max.color.green - min.color.green) * i / size));
    var blue = Math.round(min.color.blue + ((max.color.blue - min.color.blue) * i / size));

    return { alpha: alpha, red: red, green: green, blue: blue };
  }

  getColorForValue(layer: ILayer, value: number): IColor {
    var color: IColor =  { alpha:0,red:0,green:0,blue:0};
    if(layer.renderer.colorMap.entries.length>0) {
      color=layer.renderer.colorMap.noValue;
    }
    layer.renderer.colorMap.entries.forEach((entry) => {
        if(entry.value==value) {
          color =entry.color;
          return;
        }
    });

    return color;
  }

  getColor(item: IItem, layer: ILayer, feature): style.Style {
    var value = layer.indexKey ? feature.get(layer.indexKey) : feature.get(layer.name);
    var key = item.code + "_" + value;
    if (!this.styleCache[key]) {
      var color: IColor;
      if(layer.renderer.colorMap.colormapType == "manual") {
        color = this.getColorForValue(layer, value);
      } else {
        color = this.getColorFromGradient(layer, value);
      }

      this.styleCache[key] = new style.Style(
        {
          image: new style.Circle({
            fill: new style.Fill({
              color: this.rgbaToHex(color.red, color.green, color.blue, color.alpha)
            }),
            radius: 3
          }),
          fill: new style.Fill({
            color: this.rgbaToHex(color.red, color.green, color.blue, color.alpha)
          }),
          stroke: new style.Stroke({
            color: this.rgbaToHex(color.red, color.green, color.blue, 255),
            width: 1.25
          }),
        });
    }
    return this.styleCache[key];
  }

  createGeotiffLayer(item:IItem,itemLayer:IItemLayer):Layer<Source> {
    var layerIndex = -1;
    var layer: Layer<Source> = null;  
    layerIndex = itemLayer.layerIndex != -1 ? itemLayer.layerIndex : item.data.layers[0].index;
    let source = new XYZ({ maxZoom: 19, minZoom: 1, url: `${this._apiEndPoint}/api/v1/items/${item.code}/tiles/${layerIndex}/{z}/{x}/{y}.png?v=${Date.parse(item.updated)}` });
    layer = new Tile({ source: source });
    var data = item.data;
    var l = (data && data.layers && data.layers.length > 0) ? data.layers[0] : null;
    if (l && l.rendering && l.rendering.renderoutputType == "Tiles") {
      var rt = l.rendering as IRenderoutputTiles;
      let source = new XYZ({crossOrigin: 'use-credentials', maxZoom: rt.maxzoom, minZoom: rt.minzoom, url: `${this._apiEndPoint}/api/v1/items/${item.code}/tiles/${layerIndex}/{z}/{x}/{y}.png?v=${Date.parse(item.updated)}` });
      layer = new Tile({ source: source });
    }
    if (l && l.rendering && l.rendering.renderoutputType == "Image") {
      var ri = l.rendering as IRenderoutputImage;  
      // convert to EPSG:4326 asworkaround for cesium   
      var projectedExtent = proj.transformExtent( ri.extent, 'EPSG:3857','EPSG:4326');
      let source = new ImageStatic({ imageExtent:projectedExtent,projection:'EPSG:4326',  crossOrigin: 'use-credentials', url: `${this._apiEndPoint}/api/v1/items/${item.code}/mapimage/${layerIndex}?v=${Date.parse(item.updated)}` });
      layer = new Image({ source: source });
    }
    return layer;
  }

  createShapeLayer(item:IItem,itemLayer:IItemLayer):Layer<Source> {
    var layerIndex = -1;
    var layer: Layer<Source> = null; 
    layerIndex = itemLayer.layerIndex != -1 ? itemLayer.layerIndex : item.data.layers[0].index;
    var data = item.data;
    var l:ILayer = (data && data.layers && data.layers.length > 0) ? data.layers[layerIndex] : null;
    if (l && l.rendering && l.rendering.renderoutputType == "VectorTiles") {
      var rt = l.rendering as IRenderoutputTiles;
      layer = new VectorTileLayer({
        declutter: true,
        source: new VectorTileSource({
          maxZoom: rt.maxzoom,
          minZoom: rt.minzoom,
          format: new MVT(),
          url: `${this._apiEndPoint}/api/v1/items/${item.code}/vectortiles/{z}/{x}/{y}.pbf?v=${Date.parse(item.updated)}`
        }),
        style: (feature) => {
          return this.getColor(item,l, feature);
        }
      })
    } else if (l && l.rendering && l.rendering.renderoutputType == "Tiles") {
      var rt = l.rendering as IRenderoutputTiles;
      layer = new Tile({
        source: new XYZ({
          maxZoom: rt.maxzoom,
          minZoom: rt.minzoom,
          url: `${this._apiEndPoint}/api/v1/items/${item.code}/vectortiles/image_tiles/${layerIndex}/{z}/{x}/{y}.png?v=${Date.parse(item.updated)}`
        })
      });
    } else {
      let __this = this;
      let format = new GeoJSON();
      let source = new VectorSource({
        strategy: loadingstrategy.bbox,
        loader: function (extent: Extent, resolution: number, projection: Projection) {
          var source = this as VectorSource<Geometry>;
          __this.itemService.getItemFeatures(item.code, extent, projection.getCode(), layerIndex).subscribe(function (data) {
            var features = format.readFeatures(data);
            for (let f of features) {
              if (f.get("code")) {
                f.setId(f.get("code"));
              }
            }
            source.addFeatures(features);
          });
        }
      });
      layer = new VectorLayer({
        source: source,
        style: (feature) => {
          var key =feature.get("code") + "_" + feature.get("color");
          if (!this.styleCache[key]) {
            var color = feature.get("color");
            this.styleCache[key] = new style.Style(
              {
                fill: new style.Fill({
                  color: color
                }),
                stroke: new style.Stroke({
                  color: color,
                  width: 1.25
                }),
                image: new style.Circle({
                  fill: new style.Fill({
                    color: color
                  }),
                  stroke: new style.Stroke({
                    color: color,
                    width: 1.25
                  }),
                  radius: 5
                }),
              }
            )
          }
          return this.styleCache[key];
        }
      });
    }
    if(l.minzoom) {
      layer.setMinZoom(l.minzoom);
    }
    if(l.maxzoom) {
      layer.setMaxZoom(l.maxzoom);
    }
    return layer;
  }

  createSelectionLayer(itemLayer:IItemLayer):Layer<Source> {
    var layerIndex = -1;
    var layer: Layer<Source> = null; 
    layerIndex = itemLayer.layerIndex != -1 ? itemLayer.layerIndex : itemLayer.item.data.layers[0].index;
    var data = itemLayer.item.data;
    var l:ILayer = (data && data.layers && data.layers.length > 0) ? data.layers[layerIndex] : null;
    if (l && l.rendering && l.rendering.renderoutputType == "VectorTiles") {
      return new VectorTileLayer({
        renderMode: 'vector',
        source: (itemLayer.layer as VectorTileLayer).getSource(),
        style: (feature) => {
          if (feature.getId() in this.selectedFeatures) {

            return new style.Style(
              {               
                stroke: new style.Stroke({
                  color: 'red',
                  width: 2
                })
              }
            );
          }
        },
        minZoom: itemLayer.layer.getMinZoom(),
        maxZoom: itemLayer.layer.getMaxZoom()        
      });
    }
    return null;
  }

  createExternalLayer(item:IItem,itemLayer:IItemLayer):Layer<Source> {
    let data = item.data as ILayerData;
    var layer: Layer<Source> = null; 
    switch (data.interfaceType) {
      case 'OSM': {
        let source = new OSM();
        layer = new Tile({ source: source });
        break;
      }
      case 'BingMaps': {
        let source = new BingMaps(data.options);
        layer = new Tile({ source: source });
        break;
      }
      case 'TileWMS': {
        let source = new TileWMS(data.options);
        layer = new Tile({ source: source });
        break;
      }
      case 'TileJSON': {
        let source = new TileJSON(data.options);
        layer = new Tile({ source: source });
        break;
      }
      case 'TileArcGISRest': {
        let source = new TileArcGISRest(data.options);
        layer = new Tile({ source: source });
        break;
      }
      case 'VectorWFSJson': {
        let source = new VectorSource({
          format: new GeoJSON(),
          url: function (extent) {
            return (
              data.options.url + '&srsname=' + data.projection +
              '&bbox=' +
              extent.join(',') +
              ',EPSG:3857'
            );
          },
          strategy: loadingstrategy.bbox,
        });
        layer = new VectorLayer({ source: source });
        break;
      }
      default: {
        break;
      }
    }
    return layer;
  }

  createLayer(itemLayer: IItemLayer): Layer<Source> {
    var layer: Layer<Source> = null;   
    var layerIndex = -1;
    if (itemLayer.item.itemType == 'vnd.farmmaps.itemtype.geotiff.processed') {
      layer = this.createGeotiffLayer(itemLayer.item,itemLayer);
    } else if (itemLayer.item.itemType == 'vnd.farmmaps.itemtype.shape.processed') {
      layer = this.createShapeLayer(itemLayer.item,itemLayer);
    } else if (itemLayer.item.itemType == 'vnd.farmmaps.itemtype.layer') {
      layer = this.createExternalLayer(itemLayer.item,itemLayer);
    }
    if (layer) {
      let geometry = new GeoJSON().readGeometry(itemLayer.item.geometry);
      let extent = geometry ? proj.transformExtent(geometry.getExtent(), 'EPSG:4326', 'EPSG:3857') : null;
      if (extent) layer.setExtent(extent);
    }

    return layer;
  }

  ngOnInit() {
    super.ngOnInit();
    if(this.itemLayers) {
      this.updateLayers(this.itemLayers);
    } else if(this.itemLayer) {
      if(this.getItemlayer(this.itemLayer).item.itemType == 'vnd.farmmaps.itemtype.shape.processed') {
        this.installMapEventHandler();
      }
      this.updateLayers([this.itemLayer])
    } else {
      this.updateLayers([]);
    }
    this.initialized=true;
  }

  installMapEventHandler() {
    if(!this.mapEventHandlerInstalled) {
      this.map.instance.on(['click', 'pointermove'],this.mapEventHandler);
      this.mapEventHandlerInstalled=true;
    }
  }

  unInstallMapEventHandler() {
    if(this.mapEventHandlerInstalled) {
      this.map.instance.un(['click', 'pointermove'],this.mapEventHandler);
      this.mapEventHandlerInstalled=false;
    }
  }

  installTopLayerPrerenderEventhandler() {
    if(!this.topLayerPrerenderEventhandlerInstalled  && this.onPrerender.observers.length > 0 ) {
      if(this.instance.getVisible()) {
        var olLayers = this.instance.getLayers().getArray().filter(l=> l.getVisible());
        if(olLayers.length >0) {
          var topLayer = olLayers[0] as any;
          topLayer.on('prerender',this.topLayerPrerenderEventhandler);
          topLayer.on('postrender',this.topLayerPostrenderEventhandler);
          this.topLayerPrerenderEventhandlerInstalled = true;
        }  
      }
    }
  }

  unInstallTopLayerPrerenderEventhandler() {
    if(this.topLayerPrerenderEventhandlerInstalled && this.onPrerender.observers.length > 0 )
    {
      if(this.instance.getVisible()) {
        var olLayers = this.instance.getLayers().getArray().filter(l=> l.getVisible());
        if(olLayers.length >0) {
          var topLayer = olLayers[0] as any;
          topLayer.un('prerender',this.topLayerPrerenderEventhandler);
          topLayer.un('postrender',this.topLayerPostrenderEventhandler);
          this.topLayerPrerenderEventhandlerInstalled = false;
        }  
      }
    }
  }


  addOrUpdateOlLayer(itemLayer:IItemLayer,index:number):Layer<Source> {
    if(!itemLayer) return null;
    var olLayers = this.instance.getLayers();
    var layer = itemLayer.layer;
    let olIndex = olLayers.getArray().indexOf(layer);
    if (olIndex < 0) {
      // New layer: we add it to the map
      layer = this.createLayer(itemLayer);
      if (layer) {
        olLayers.insertAt(index, layer);
      }
    } else if (index !== olIndex) {
      // layer has moved inside the layers list
      olLayers.removeAt(olIndex);
      olLayers.insertAt(index, layer);
    }
    if(layer) {
      itemLayer.layer = layer;
      layer.setOpacity(itemLayer.opacity);
      layer.setVisible(itemLayer.visible); 
    }
    return layer;
  }

  updateLayers(itemLayers: IItemLayer[]) {
    this.unInstallTopLayerPrerenderEventhandler();
    let newLayers: Layer<Source>[] = [];
    if (itemLayers) {
      itemLayers.forEach((itemLayer, index) => {
        if(itemLayer.item.itemType ==  'vnd.farmmaps.itemtype.temporal') {
          let il = itemLayer as ITemporalItemLayer;
          let previousLayer = this.addOrUpdateOlLayer(il.previousItemLayer,newLayers.length);          
          if(previousLayer) newLayers.push(previousLayer);
          let selectedLayer = this.addOrUpdateOlLayer(il.selectedItemLayer,newLayers.length);
          if(selectedLayer) newLayers.push(selectedLayer);
          let nextLayer = this.addOrUpdateOlLayer(il.nextItemLayer,newLayers.length);
          if(nextLayer) newLayers.push(nextLayer);
        } else {
          let layer = this.addOrUpdateOlLayer(itemLayer,newLayers.length);
          if(layer) newLayers.push(layer);           
        }
      });
      // Remove the layers that have disapeared from childrenLayers
      var olLayers = this.instance.getLayers();
      while(olLayers.getLength() > newLayers.length) {
        olLayers.removeAt(newLayers.length);
      }
      this.selectionLayer=null;
      if(this.mapEventHandlerInstalled && itemLayers.length==1 && this.getItemlayer(itemLayers[0]).item.itemType == 'vnd.farmmaps.itemtype.shape.processed') {
        this.selectionLayer = this.createSelectionLayer(this.getItemlayer(itemLayers[0]));
        if(this.selectionLayer) olLayers.push(this.selectionLayer)
      }
    }
    this.installTopLayerPrerenderEventhandler();
  }

  topLayerPrerenderEventhandler = (event) => {
     this.onPrerender.emit(event);
  }

  topLayerPostrenderEventhandler = (event) => {
    const ctx = event.context;
    ctx.restore();
  }


  mapEventHandler = (event) => {
    // select only when having observers
    if(event.type === 'click' && !this.onFeatureSelected.observers.length) return;
    if(event.type === 'pointermove' && !this.onFeatureHover.observers.length) return;    
    let itemLayer= this.getItemlayer(this.itemLayer);
    if(itemLayer && itemLayer.layer) {
       this.selectedFeatures = {};
       if(itemLayer.layer ) {
        let minZoom = itemLayer.layer.getMinZoom();
        let currentZoom = this.map.instance.getView().getZoom();
        if(currentZoom>minZoom) {
            itemLayer.layer.getFeatures(event.pixel).then((features) => {      
            if(!features.length) {
              this.onFeatureHover.emit(null);
              return;
            }
           let fid = features[0].getId();
           let feature = features[0];
           if(event.type === 'pointermove') {
             this.selectedFeatures[fid] = features[0];
             this.onFeatureHover.emit({ "feature": feature,"itemCode":itemLayer.item.code });
           } else {
             this.onFeatureSelected.emit({ "feature": feature,"itemCode":itemLayer.item.code });
           }
          })
          if(this.selectionLayer) this.selectionLayer.changed(); 
        }      
       }
    }    
  }

  getItemlayer(itemLayer:IItemLayer):IItemLayer {     
     if((itemLayer as ITemporalItemLayer).selectedItemLayer) return (itemLayer as ITemporalItemLayer).selectedItemLayer;
     return itemLayer;
  }

  ngOnChanges(changes: SimpleChanges) {
    if (this.instance && this.initialized) {
      if (changes['itemLayers']) {
        var itemLayers = changes['itemLayers'].currentValue as IItemLayer[];
        this.updateLayers(itemLayers);
      }
      if (changes['itemLayer']) {      
        var itemLayer = changes['itemLayer'].currentValue as IItemLayer;
        this.itemLayer = itemLayer
        if(itemLayer) {
          if(this.getItemlayer(this.itemLayer).item.itemType == 'vnd.farmmaps.itemtype.shape.processed') {
            this.installMapEventHandler();
          }
          this.updateLayers([itemLayer]);
        } else {
          this.unInstallMapEventHandler();
          this.updateLayers([]);          
        }
      }
    }
  }

  ngOnDestroy() {
    this.unInstallMapEventHandler();
    super.ngOnDestroy();
  }
}
