import { Component, OnInit, OnDestroy, HostListener, ViewChild, AfterViewInit, NgZone, ElementRef } from '@angular/core';
import { Location } from '@angular/common';
import { Observable, Subject, Subscription, from, of, EMPTY } from 'rxjs';
import { withLatestFrom, switchMap, skip } from 'rxjs/operators';
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
import { Store } from '@ngrx/store';
import { DeviceService } from '@farmmaps/common';
import { getRenderPixel } from 'ol/render';

// Map
import * as mapReducers from '../../reducers/map.reducer';
import * as mapActions from '../../actions/map.actions';
import { IMapState } from '../../models/map.state';
import { IClickedFeature } from '../../models/clicked.feature';
import { IQuery } from '../../reducers/map.reducer'
import { ISelectedFeatures } from '../../models/selected.features';
import { IItemLayer } from '../../models/item.layer';
import { IListItem, IQueryState } from '@farmmaps/common';
import { IPeriodState } from '../../models/period.state';
import { IStyles } from '../../models/style.cache';
import { IDroppedFile } from '../aol/file-drop-target/file-drop-target.component';
import { StateSerializerService } from '@farmmaps/common';
import { GeolocationService } from '../../services/geolocation.service';
import { GeolocatorService } from '@farmmaps/common';
import { DeviceOrientationService } from '../../services/device-orientation.service';

// AppCommon
import { ResumableFileUploadService, ItemTypeService } from '@farmmaps/common';
import { IItemType, IItem } from '@farmmaps/common';
import { commonReducers } from '@farmmaps/common';
import { commonActions } from '@farmmaps/common';

import { Feature } from 'ol';
import { Geometry, Point, Circle } from 'ol/geom';
import { Extent, createEmpty, extend } from 'ol/extent';
import { transform } from 'ol/proj';
import { tassign } from 'tassign';
import * as style from 'ol/style';


@Component({
    selector: 'fm-map-map',
    templateUrl: './map.component.html',
    styleUrls: ['./map.component.scss'],
    standalone: false
})

export class MapComponent implements OnInit, OnDestroy, AfterViewInit {
  title = 'Map';
  public openedModalName$: Observable<string> = this.store.select(commonReducers.selectOpenedModalName);
  public itemTypes$: Observable<{ [id: string]: IItemType }>;
  public mapState$: Observable<IMapState> = this.store.select(mapReducers.selectGetMapState);
  public features$: Observable<Array<Feature<Geometry>>> = this.store.select(mapReducers.selectGetFeatures);
  public overlayLayers$: Observable<Array<IItemLayer>> = this.store.select(mapReducers.selectGetOverlayLayers);
  public selectedOverlayLayer$: Observable<IItemLayer> = this.store.select(mapReducers.selectGetSelectedOverlayLayer);
  public selectedItemLayer$: Observable<IItemLayer> = this.store.select(mapReducers.selectGetSelectedItemLayer);
  public baseLayers$: Observable<Array<IItemLayer>> = this.store.select(mapReducers.selectGetBaseLayers);
  public selectedBaseLayer$: Observable<IItemLayer> = this.store.select(mapReducers.selectGetSelectedBaseLayer);
  public projection$: Observable<string> = this.store.select(mapReducers.selectGetProjection);
  public selectedFeatures$: Subject<ISelectedFeatures> = new Subject<ISelectedFeatures>();
  public droppedFile$: Subject<IDroppedFile> = new Subject<IDroppedFile>();
  private paramSub: Subscription;
  private itemTypeSub: Subscription;
  private stateSub: Subscription;
  private queryStateSub: Subscription;
  private querySub: Subscription;
  public parentCode$: Observable<string> = this.store.select(mapReducers.selectGetParentCode);
  public panelVisible$: Observable<boolean> = this.store.select(mapReducers.selectGetPanelVisible);
  public panelCollapsed$: Observable<boolean> = this.store.select(mapReducers.selectGetPanelCollapsed);
  public panelExtraWide$: Observable<boolean> = this.store.select(mapReducers.selectGetPanelExtraWide);
  public selectedFeature$: Observable<Feature<Geometry>> = this.store.select(mapReducers.selectGetSelectedFeature);
  public clickedFeature: Subject<Feature<Geometry>> = new Subject<Feature<Geometry>>();
  public selectedItem$: Observable<IItem> = this.store.select(mapReducers.selectGetSelectedItem);
  public parentItem$: Observable<IItem> = this.store.select(mapReducers.selectGetParentItem);
  public queryState$: Observable<IQueryState> = this.store.select(mapReducers.selectGetQueryState);
  public state$: Observable<{ mapState: IMapState, queryState: IQueryState }> = this.store.select(mapReducers.selectGetState);
  public period$: Observable<IPeriodState> = this.store.select(mapReducers.selectGetPeriod);
  public clearEnabled$: Observable<boolean> = this.store.select(mapReducers.selectGetClearEnabled);
  public searchCollapsed$: Observable<boolean> = this.store.select(mapReducers.selectGetSearchCollapsed);
  public searchMinified$: Observable<boolean> = this.store.select(mapReducers.selectGetSearchMinified);
  public showDataLayerSlide$: Observable<boolean> = this.store.select(mapReducers.selectGetShowdataLayerSlide);
  public menuVisible$: Observable<boolean>;
  public query$: Observable<IQuery> = this.store.select(mapReducers.selectGetQuery);
  public position$: Observable<GeolocationPosition> = this.geolocationService.getCurrentPosition();
  public compassHeading$: Observable<number> = this.deviceorientationService.getCurrentCompassHeading();
  public baseLayersCollapsed = true;
  public overlayLayersCollapsed = true;
  public extent$: Observable<Extent> = this.store.select(mapReducers.selectGetExtent);
  public styles$: Observable<IStyles> = this.store.select(mapReducers.selectGetStyles);
  public fullscreen$: Observable<boolean> = this.store.select(commonReducers.selectGetFullScreen);
  private lastUrl = "";
  private initialized = false;
  public noContent = false;
  public overrideSelectedItemLayer = false;
  public overrideOverlayLayers = false;
  public hideShowLayerValues = false;
  public const
  public dataLayerSlideValue = 50;
  public dataLayerSlideEnabled = false;
  private visibleAreaBottom = 0;
  private viewEnabled = true;

  @ViewChild('map') map;
  @ViewChild('contentDiv') contentDiv: ElementRef;

  constructor(private store: Store<mapReducers.State | commonReducers.State>,
    private route: ActivatedRoute,
    private router: Router,
    private uploadService: ResumableFileUploadService,
    private serializeService: StateSerializerService,
    public itemTypeService: ItemTypeService,
    private location: Location,
    private geolocationService: GeolocationService,
    private geolocaterService: GeolocatorService,
    private zone: NgZone,
    private deviceorientationService: DeviceOrientationService,
    public devicesService: DeviceService) {
    if (route && route.snapshot && route.snapshot.data && route.snapshot.data["fm-map-map"]) {
      const params = route.snapshot.data["fm-map-map"];
      this.overrideSelectedItemLayer = params["overrideSelectedItemlayer"] ? params["overrideSelectedItemlayer"] : false;
      this.overrideOverlayLayers = params["overrideOverlayLayers"] ? params["overrideOverlayLayers"] : false;
      this.hideShowLayerValues = params["hideShowLayerValues"] ? params["hideShowLayerValues"] : false;
    }
    this.querySub = this.query$.pipe(skip(1), withLatestFrom(this.mapState$)).subscribe(([query, mapState]) => {
      if (query && query.querystate) {
        let newQueryState = tassign(mapReducers.initialQueryState);
        //console.debug(`Do Query`);
        const urlparts = [];
        if (query.querystate.itemCode && query.querystate.itemCode != "") {
          if (query.querystate.itemType && query.querystate.itemType != "") {
            const itemType = this.itemTypeService.itemTypes[query.querystate.itemType];
            if (itemType && itemType.viewer && itemType.viewer == "edit_in_editor" && itemType.editor) {
              urlparts.push('/editor');
              urlparts.push(itemType.editor);
              urlparts.push('item');
              urlparts.push(query.querystate.itemCode);
            }
          }
        } else {
          newQueryState = query.querystate;
        }
        if (urlparts.length == 0) {
          newQueryState.itemCode = query.querystate.itemCode;
          this.zone.run(() => {
            this.replaceUrl(mapState, newQueryState, query.replace);
          })
        } else {
          this.router.navigate(urlparts);
        }
      }
    });
    this.store.dispatch(new mapActions.Init());
    // this.store.select(commonReducers.getRootItems).subscribe((l) => {
    //    if(l && l.length>0) {
    //      this.store.dispatch(new mapActions.Init());
    //    }
    // });
  }

  @HostListener('document:keyup', ['$event'])
  escapeClose(event: KeyboardEvent) {
    const x = event.keyCode;
    if (x === 27) {
      this.handleCloseModal()
    }
  }

  handlePanelResize(resizeTop: number) {
    if (resizeTop == 100 || !this.devicesService.IsMobile()) {
      this.visibleAreaBottom = 0;
    } else {
      this.visibleAreaBottom = 100 - resizeTop;
      if (this.visibleAreaBottom > 60) {
        this.visibleAreaBottom = 60;
      }
    }
  }

  bottom(panelVisible: boolean) {
    if (panelVisible) {
      return this.visibleAreaBottom + '%';
    } else {
      return "0%";
    }
  }

  handleOpenModal(modalName: string) {
    this.store.dispatch(new commonActions.OpenModal(modalName));
  }

  handleCloseModal() {
    this.store.dispatch(new commonActions.CloseModal());
  }

  handleFileDropped(droppedFile: IDroppedFile) {
    this.uploadService.addFiles(droppedFile.files, droppedFile.event, { parentCode: droppedFile.parentCode, geometry: droppedFile.geometry });
  }

  handleFeatureClick(feature: Feature<Geometry>) {
    this.store.dispatch(new mapActions.ClickFeature(feature));
    this.clickedFeature.next(feature);
  }

  handleFeatureHover(feature: Feature<Geometry>) {
    this.store.dispatch(new mapActions.SelectFeature(feature));
  }

  handleSearch(queryState: IQueryState) {
    this.store.dispatch(new mapActions.DoQuery(queryState));
  }

  handleSidepaneloutletActivate(component: any) {
    if (component && component.hasOwnProperty('clickedFeature')) {
      (component as IClickedFeature).clickedFeature = this.clickedFeature;
    }
    if (component && component.hasOwnProperty('extrawide')) {
      this.store.dispatch(new mapActions.SetPanelExtraWide(true));
    }
  }

  handleSidepaneloutletDeactivate(component: any) {
    if (component && component.hasOwnProperty('clickedFeature')) {
      (component as IClickedFeature).clickedFeature = null;
    }
    if (component && component.hasOwnProperty('extrawide')) {
      this.store.dispatch(new mapActions.SetPanelExtraWide(false));
    }
  }

  handlePrerender(event: any) {
    if (!this.dataLayerSlideEnabled) return;
    const ctx = event.context;
    const mapSize = this.map.instance.getSize();
    const width = mapSize[0] * (this.dataLayerSlideValue / 100);
    const tl = getRenderPixel(event, [width, 0]);
    const tr = getRenderPixel(event, [mapSize[0], 0]);
    const bl = getRenderPixel(event, [width, mapSize[1]]);
    const br = getRenderPixel(event, mapSize);

    ctx.save();
    ctx.beginPath();
    ctx.moveTo(tl[0], tl[1]);
    ctx.lineTo(bl[0], bl[1]);
    ctx.lineTo(br[0], br[1]);
    ctx.lineTo(tr[0], tr[1]);
    ctx.closePath();
    ctx.clip();
  }

  handleSlideChange(event: any) {
    this.dataLayerSlideValue = event.target.value;
    this.map.instance.render();
  }

  ngOnInit() {
    this.initialized = false;
    //console.debug("Init");
    this.store.dispatch(new mapActions.Clear());
    this.selectedFeatures$.next({ x: 0, y: 0, features: [] });
    this.selectedFeatures$.next(null);
  }

  initCustomStyles() {
    this.store.dispatch(new mapActions.SetStyle('vnd.farmmaps.itemtype.layer', new style.Style({
      stroke: new style.Stroke({
        color: 'red',
        lineDash: [5, 5],
        width: 1
      }),
      geometry: (feature) => feature.getGeometry()
    })));
    this.store.dispatch(new mapActions.SetStyle('vnd.farmmaps.itemtype.layer_selected', new style.Style({
      stroke: new style.Stroke({
        color: 'red',
        lineDash: [5, 5],
        width: 3
      }),
      geometry: (feature) => feature.getGeometry()
    })));
  }

  round(value: number, decimals: number): number {
    const d = Math.pow(10, decimals);
    return Math.round((value + Number.EPSILON) * d) / d;
  }

  getMapStateFromUrl(params: ParamMap): IMapState {
    const hasUrlmapState = params.has("xCenter") && params.has("yCenter");
    if (hasUrlmapState) {
      const xCenter = parseFloat(params.get("xCenter"));
      const yCenter = parseFloat(params.get("yCenter"));
      const zoom = parseFloat(params.get("zoom"));
      const rotation = parseFloat(params.get("rotation"));
      const baseLayer = params.get("baseLayer") ? params.get("baseLayer") : "";
      const newMapState = { zoom: zoom, rotation: rotation, xCenter: xCenter, yCenter: yCenter, baseLayerCode: baseLayer };
      return newMapState;
    } else {
      return null;
    }
  }

  normalizeMapState(mapState: IMapState): IMapState {
    if (!mapState) return null;
    return {
      zoom: this.round(mapState.zoom, 0),
      rotation: this.round(mapState.rotation, 2),
      xCenter: this.round(mapState.xCenter, 5),
      yCenter: this.round(mapState.yCenter, 5),
      baseLayerCode: mapState.baseLayerCode
    };
  }

  serializeMapState(mapState: IMapState): string {
    return JSON.stringify(this.normalizeMapState(mapState));
  }

  getQueryStateFromUrl(params: ParamMap): IQueryState {
    if (params.has("queryState")) {
      const queryState = params.get("queryState");
      let newQueryState = tassign(mapReducers.initialQueryState);
      if (queryState != "") {
        newQueryState = this.serializeService.deserialize(queryState);
      }
      return newQueryState;
    } else {
      return null;
    }
  }

  ngAfterViewInit() {
    //console.debug("View init");
    this.noContent = true;
    this.route.children.forEach((entry) => {
      if (entry.outlet == "primary") {
        this.noContent = false;
      }
    });

    this.initCustomStyles();

    // url to state

    const urlMapState = this.getMapStateFromUrl(this.route.snapshot.paramMap);
    const urlQueryState = this.getQueryStateFromUrl(this.route.snapshot.paramMap);
    if (urlQueryState && urlMapState && this.noContent) {
      this.store.dispatch(new mapActions.SetState(urlMapState, urlQueryState));
      window.localStorage.setItem("FarmMapsCommonMap_mapState", this.serializeMapState(urlMapState));
    } else if (urlQueryState && this.noContent) {
      this.store.dispatch(new mapActions.SetQueryState(urlQueryState));
    } else {
      this.store.dispatch(new mapActions.SetReplaceUrl(true));
    }

    this.paramSub = this.route.paramMap.pipe(withLatestFrom(this.state$), switchMap(([params, state]) => {
      if (this.initialized && this.noContent) {
        const urlQueryState = this.getQueryStateFromUrl(params);
        if (this.serializeService.serialize(state.queryState) != this.serializeService.serialize(urlQueryState)) {
          return of(new mapActions.SetState(state.mapState, urlQueryState));
        }
      }
      return EMPTY;
    })).subscribe((action) => {
      if (action) {
        this.zone.run(() => {
          //console.debug("Url to state");
          this.store.dispatch(action);
        });
      }
    });

    // state to url

    this.stateSub = this.state$.pipe(switchMap((state) => {
      const newUrl = this.serializeMapState(state.mapState) + "_" + this.serializeService.serialize(state.queryState);
      if (this.lastUrl != newUrl) {
        this.lastUrl = newUrl;
        return of(state);
      }
      else {
        return of(null);
      }
    })).subscribe((newUrlState: any) => {
      if (newUrlState) {
        //console.debug(`State to url`);
        this.replaceUrl(newUrlState.mapState, newUrlState.queryState, newUrlState.replaceUrl);
      }
    });
    this.initialized = true;
    this.showDataLayerSlide$.subscribe((v) => {
      this.dataLayerSlideEnabled = v;
      this.map.instance.render();
    });
    this.store.select(mapReducers.selectGetViewEnabled).subscribe((v) => {
      this.viewEnabled = v;
    });
  }

  handleSearchCollapse(event) {
    this.store.dispatch(new mapActions.CollapseSearch());
  }

  handleSearchExpand(event) {
    this.store.dispatch(new mapActions.ExpandSearch());
  }

  handleToggleMenu(event) {
    this.store.dispatch(new commonActions.ToggleMenu());
  }

  handleToggleBaseLayers(event: MouseEvent) {
    this.baseLayersCollapsed = !this.baseLayersCollapsed;
    event.preventDefault();
  }

  handleToggleOverlayLayers(event: MouseEvent) {
    this.overlayLayersCollapsed = !this.overlayLayersCollapsed;
    event.preventDefault();
  }

  handlePredefinedQuery(event: MouseEvent, query: any) {
    event.preventDefault();
    const queryState = tassign(mapReducers.initialQueryState, query);
    this.store.dispatch(new mapActions.DoQuery(queryState));
  }

  replaceUrl(mapState: IMapState, queryState: IQueryState, replace = true) {
    if (this.noContent) {
      const newMapState = this.serializeMapState(mapState);
      const newQueryState = this.serializeService.serialize(queryState);
      const currentMapState = this.serializeMapState(this.getMapStateFromUrl(this.route.snapshot.paramMap));
      const urlQueryState = this.getQueryStateFromUrl(this.route.snapshot.paramMap);
      const currentQueryState = urlQueryState == null ? "" : this.serializeService.serialize(urlQueryState);
      if (mapState.baseLayerCode != "" && ((newMapState != currentMapState) || (newQueryState != currentQueryState))) {
        const parts = ["."];
        parts.push(mapState.xCenter.toFixed(5));
        parts.push(mapState.yCenter.toFixed(5));
        parts.push(mapState.zoom.toFixed(0));
        parts.push(mapState.rotation.toFixed(2));
        parts.push(mapState.baseLayerCode);
        parts.push(this.serializeService.serialize(queryState));

        //console.debug("Replace url",parts);
        this.router.navigate(parts, { replaceUrl: replace, relativeTo: this.route.parent });
      }
    }
  }

  handleOnMoveEnd(event) {
    if (this.initialized && this.viewEnabled) {
      this.zone.run(() => {
        //console.debug("Move end");
        const map = event.map;
        const view = map.getView();
        const rotation = view.getRotation();
        const zoom = view.getZoom();
        const center = transform(view.getCenter(), view.getProjection(), "EPSG:4326");
        const viewExtent = view.calculateExtent(this.map.instance.getSize());
        const mapState: IMapState = { xCenter: center[0], yCenter: center[1], zoom: zoom, rotation: rotation, baseLayerCode: null };
        const state = { mapState: mapState, viewExtent: viewExtent };
        //console.debug("Center: ",center[0],center[1] );
        const source = from([state]);
        source.pipe(withLatestFrom(this.selectedBaseLayer$)).subscribe(([state, baselayer]) => {
          if (mapState && baselayer) { // do not react on first move
            const newMapState = tassign(state.mapState, { baseLayerCode: baselayer.item.code });
            this.store.dispatch(new mapActions.SetMapState(newMapState));
            this.store.dispatch(new mapActions.SetViewExtent(state.viewExtent));
          }
        });
      });
    }
  }

  handleOnMouseDown(event: MouseEvent) {
    event.stopPropagation();
    this.zone.run(() => {
      this.store.dispatch(new commonActions.CloseAll());
    });
  }

  handleShowLayerValues(event: MouseEvent) {
    if (!this.hideShowLayerValues) {
      event.stopPropagation();
      this.zone.run(() => {
        this.store.dispatch(new mapActions.ToggleLayerValuesEnabled());
      });
    }
  }

  handleOnDownload(event) {

  }

  handleClearSearch(event) {
    this.store.dispatch(new mapActions.Clear());
  }

  handleOnDelete(itemLayer: IItemLayer) {
    this.store.dispatch(new mapActions.RemoveLayer(itemLayer));
  }

  handleOnToggleVisibility(itemLayer: IItemLayer) {
    this.store.dispatch(new mapActions.SetVisibility(itemLayer, !itemLayer.visible));
  }

  handleOnSetOpacity(event: { layer: IItemLayer, opacity: number }) {
    this.store.dispatch(new mapActions.SetOpacity(event.layer, event.opacity));
  }

  handleZoomToExtent(itemLayer: IItemLayer) {
    const extent = createEmpty();
    extend(extent, itemLayer.layer.getExtent());
    if (extent) {
      this.store.dispatch(new mapActions.SetExtent(extent));
    }
  }

  handleSelectBaseLayer(itemLayer: IItemLayer) {
    this.store.dispatch(new mapActions.SelectBaseLayer(itemLayer));
  }

  handleSelectOverlayLayer(itemLayer: IItemLayer) {
    this.store.dispatch(new mapActions.SelectOverlayLayer(itemLayer));
  }

  handlePeriodChange(period: IPeriodState) {
    this.store.dispatch(new mapActions.SetPeriod(period));
  }

  handleCitySearch(location: string) {
    this.geolocaterService.geocode(location).subscribe(locations => {
      if (locations.length > 0) {
        const point = new Point([locations[0].coordinates.lon, locations[0].coordinates.lat]);
        point.transform('EPSG:4326', 'EPSG:3857');
        const circle = new Circle(point.getCoordinates(), 5000);// 
        const extent = createEmpty();
        extend(extent, circle.getExtent());
        this.store.dispatch(new mapActions.SetExtent(extent))
      }
    });
  }

  ngOnDestroy() {
    if (this.paramSub) this.paramSub.unsubscribe();
    if (this.itemTypeSub) this.itemTypeSub.unsubscribe();
    if (this.stateSub) this.stateSub.unsubscribe();
    if (this.queryStateSub) this.queryStateSub.unsubscribe();
    if (this.querySub) this.querySub.unsubscribe();
  }
}
