/*
 * Copyright (C) 2018 Robert Bosch GmbH Copyright (C) 2018 Robert Bosch Manufacturing Solutions GmbH, Germany. All rights reserved.
 */

import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core';
import { BOSCH_ICONS } from '@bci-web-core/common-styles/fonts/bosch-ic';
import { BOSCH_DDS_COLORS } from '@bci-web-core/core';
import {
  DefaultInteractions,
  DrawInteractionMode,
  GeometryType,
  InteractionsManager,
  OpenLayersLayerFactory,
  OpenLayersMapComponent,
  OpenLayersStyleFactory,
  PolyGuidesInteractionMode,
  Projections,
  SnapInteractionMode,
} from '@bci-web-core/maps';
import { Coordinate } from 'ol/coordinate';
import { GeoJSON } from 'ol/format';
import { GeoJSONFeatureCollection } from 'ol/format/GeoJSON';
import { createBox, createRegularPolygon, DrawEvent, GeometryFunction } from 'ol/interaction/Draw';
import { ModifyEvent } from 'ol/interaction/Modify';
import { TranslateEvent } from 'ol/interaction/Translate';
import { Vector as VectorLayer } from 'ol/layer';
import { merge, Subject } from 'rxjs';
import { debounceTime, takeUntil } from 'rxjs/operators';
import { GEOJSON_BOSCH_FE701 } from './map-editor.data';

class Layer {
  constructor(public title: string, public mapLayer: VectorLayer) {}

  get visible() {
    return this.mapLayer.getVisible();
  }

  set visible(value: boolean) {
    this.mapLayer.setVisible(value);
  }

  toggleVisibility(): void {
    this.visible = !this.visible;
  }
}

@Component({
  selector: 'bci-map-editor',
  templateUrl: './map-editor.component.html',
  styleUrls: ['./map-editor.component.scss'],
})
export class MapEditorComponent implements OnDestroy, AfterViewInit {
  readonly ICONS = BOSCH_ICONS;

  @ViewChild(OpenLayersMapComponent)
  map: OpenLayersMapComponent;

  viewProjection = Projections.EPSG3857_WGS84PROJECTED;

  lat = 48.81485;
  lon = 9.16957;
  viewCenter: Coordinate = [this.lon, this.lat];

  drawLayers: Layer[] = [];
  selectedDrawLayer: Layer;

  lastChangedFeature: any;
  drawnFeaturesAsGeoJSON: GeoJSONFeatureCollection;

  private geoJSONReader: GeoJSON;

  private interactionManager: InteractionsManager;
  private defaultInteractions = new DefaultInteractions();
  private _interactionMode: string;

  private ngUnsubscribe = new Subject<void>();

  constructor(private openLayersLayerFactory: OpenLayersLayerFactory) {}

  get interactionMode() {
    return this._interactionMode;
  }

  set interactionMode(value: string) {
    switch (value) {
      case 'select': {
        this.interactionManager.switchMode(this.defaultInteractions.selection);
        break;
      }
      case 'edit': {
        const mode = this.getSnapInteractionModeForAllVectorLayers().extends(this.defaultInteractions.modification);
        this.interactionManager.switchMode(mode);
        break;
      }
      case 'dnd': {
        this.interactionManager.switchMode(this.defaultInteractions.translation);
        break;
      }
      case 'polygon': {
        this.enterDrawMode(GeometryType.POLYGON);
        break;
      }
      case 'linestring': {
        this.enterDrawMode(GeometryType.LINE_STRING);
        break;
      }
      case 'point': {
        this.enterDrawMode(GeometryType.POINT);
        break;
      }
      case 'circle': {
        this.enterDrawMode(GeometryType.CIRCLE);
        break;
      }
      case 'rectangle': {
        this.enterDrawMode(GeometryType.CIRCLE, createBox());
        break;
      }
      case 'square': {
        this.enterDrawMode(GeometryType.CIRCLE, createRegularPolygon(4));
        break;
      }
      default: {
        alert('Undefined interactionManager mode.');
      }
    }
    this._interactionMode = value;
  }

  ngOnDestroy(): void {
    this.interactionManager.destroy();
    this.ngUnsubscribe.next();
    this.ngUnsubscribe.complete();
  }

  ngAfterViewInit(): void {
    // create interaction manager and set initial interaction mode to selection
    this.interactionManager = new InteractionsManager(this.map.map);
    setTimeout(() => (this.interactionMode = 'select'));

    // update GeoJSON export of last edited feature
    merge(
      this.defaultInteractions.modification.modified.pipe(debounceTime(250)),
      this.defaultInteractions.translation.translated.pipe(debounceTime(250))
    )
      .pipe(takeUntil(this.ngUnsubscribe))
      .subscribe((event: ModifyEvent | TranslateEvent) => {
        this.lastChangedFeature = this.geoJSONReader.writeFeaturesObject(event.features.getArray());
      });

    this.geoJSONReader = this.openLayersLayerFactory.createGeoJSONReader(this.map.projection);

    // OSM layer as backdrop
    this.map.addOSM(1, 0.33);

    // A drawing layer with some existing GeoJSON features
    {
      const geoJsonLayer = this.openLayersLayerFactory.createGeoJSONLayer(GEOJSON_BOSCH_FE701, this.map.projection, 10);
      geoJsonLayer.setStyle(
        OpenLayersStyleFactory.featureStyle(
          BOSCH_DDS_COLORS.boschGray40,
          BOSCH_DDS_COLORS.boschGray40,
          'rgba(255,255,255,0.5)'
        )
      );
      this.map.addLayer(geoJsonLayer);
      this.drawLayers.push(new Layer('GeoJSON Layer', geoJsonLayer));
    }

    // Another drawing layer
    {
      const vectorLayer = this.openLayersLayerFactory.createVectorLayer(
        this.openLayersLayerFactory.createVectorSource(),
        20
      );
      vectorLayer.setStyle(
        OpenLayersStyleFactory.featureStyle(
          BOSCH_DDS_COLORS.boschBlue35,
          BOSCH_DDS_COLORS.boschGreen25,
          'rgba(200,230,255,0.5)'
        )
      );
      this.map.addLayer(vectorLayer);
      this.selectedDrawLayer = new Layer('Drawing Layer', vectorLayer);
      this.drawLayers.push(this.selectedDrawLayer);
    }

    this.drawLayers.sort((a, b) => b.mapLayer.getZIndex() - a.mapLayer.getZIndex());

    // update GeoJSON export when the draw layer has been changed
    const drawLayerChanged = new Subject<void>();
    this.selectedDrawLayer.mapLayer
      .getSource()
      .on(['addfeature', 'changefeature', 'removefeature'],
        /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
       (evt) => drawLayerChanged.next());
    drawLayerChanged.pipe(debounceTime(250), takeUntil(this.ngUnsubscribe)).subscribe(() => {
      this.drawnFeaturesAsGeoJSON = this.geoJSONReader.writeFeaturesObject(
        this.selectedDrawLayer.mapLayer.getSource().getFeatures()
      );
    });
  }

  geoJSONFeatureChanged(event: any) {
    // import GeoJSON feature collection from text
    try {
      this.selectedDrawLayer.mapLayer.getSource().clear();
      if (event.target.value.length > 0) {
        this.selectedDrawLayer.mapLayer.getSource().addFeatures(this.geoJSONReader.readFeatures(event.target.value));
      }
    } catch (e) {
      alert('Please enter valid GeoJson');
    }
  }

  private enterDrawMode(type, geometryFunction?: GeometryFunction) {
    // create a draw interaction mode for the desired geometry
    const drawInteractionMode = new DrawInteractionMode({
      source: this.selectedDrawLayer.mapLayer.getSource(),
      type: type,
      geometryFunction: geometryFunction,
    }).extends(this.getSnapInteractionModeForAllVectorLayers());

    // register for created features
    drawInteractionMode.created.pipe(takeUntil(this.ngUnsubscribe)).subscribe((event: DrawEvent) => {
      this.lastChangedFeature = this.geoJSONReader.writeFeatureObject(event.feature);
    });

    // add poly drawing guides for polygons and polylines
    if (!geometryFunction) {
      drawInteractionMode.extends(new PolyGuidesInteractionMode(drawInteractionMode.drawInteraction));
    }

    this.interactionManager.switchMode(drawInteractionMode);
  }

  private getSnapInteractionModeForAllVectorLayers() {
    // build a snap interaction mode with a snap interaction for every layer
    return new SnapInteractionMode(
      this.drawLayers
        .map((layer) => layer.mapLayer)
        .map((layer) => {
          return {
            source: layer.getSource(),
          };
        })
    );
  }
}
