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

import { AfterViewInit, Component, 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 {
  OpenLayersLayerFactory,
  OpenLayersMapComponent,
  OpenLayersProjectionsService,
  OpenLayersStyleFactory,
  Projections,
} from '@bci-web-core/maps';
import { Feature } from 'ol';
import { Circle, Geometry, LineString, MultiPoint, Point, Polygon } from 'ol/geom';
import { circular as circularPolygon } from 'ol/geom/Polygon';
import { fromLonLat } from 'ol/proj';
import { Circle as CircleStyle, Fill, Stroke, Style } from 'ol/style';

@Component({
  selector: 'bci-open-street-maps',
  templateUrl: './open-street-maps.component.html',
  styleUrls: ['./open-street-maps.component.scss'],
})
export class OpenStreetMapsComponent implements AfterViewInit {
  @ViewChild(OpenLayersMapComponent) map: OpenLayersMapComponent;
  olMaxZoom = OpenLayersMapComponent.OL_MAXZOOM;
  olMinZoom = OpenLayersMapComponent.OL_MINZOOM;
  maxZoom = 20;
  minZoom = 2;
  zoom = 11;
  lat = 48.812943;
  lon = 9.1785234;
  center: [number, number] = [this.lon, this.lat];
  rotation = 0;
  viewCenter: [number, number] = this.center;
  private readonly defaultProjection = Projections.EPSG3857_WGS84PROJECTED;
  viewProjection = this.defaultProjection;
  private readonly localProjection = 'OSMDemoLocalCrs:' + Date.now();

  constructor(projectionService: OpenLayersProjectionsService, private openLayersLayerFactory: OpenLayersLayerFactory) {
    // define location coordinate reference system (rotation unfortunately does not work yet)
    projectionService.addProjectionLocalCartesian(this.localProjection, this.center);

    // local coordinate reference system could also be used for the map
    // this.viewProjection = this.localProjection; this.viewCenter = [0, 0];
  }

  ngAfterViewInit(): void {
    this.map.addOSM();

    this.addGeoJsonDemoLayer();
    this.addLocalCrsDemoLayer();
  }

  createStyleOptionsForPolygonVertices() {
    return {
      image: new CircleStyle({
        radius: 3,
        fill: new Fill({
          color: BOSCH_DDS_COLORS.boschBlack,
        }),
      }),
      geometry: function (feature) {
        const geometry = feature.getGeometry();
        if (geometry instanceof Polygon) {
          // return the coordinates of the vertices of all rings of the polygon
          const coordinates = [].concat(...(<Polygon>geometry).getCoordinates());
          return new MultiPoint(coordinates);
        } else {
          return new MultiPoint([]);
        }
      },
    };
  }

  onLonChange(lon) {
    this.lon = lon;
    this.center = [this.lon, this.lat];
  }

  onLatChange(lat) {
    this.lat = lat;
    this.center = [this.lon, this.lat];
  }

  private fromLocalCrs<T extends Geometry>(geometry: T): T {
    return this.map.projectionFrom<T>(this.localProjection)(geometry);
  }

  private addLocalCrsDemoLayer() {
    const pointOffsetInMeters = 5000;

    // points in map coordinate reference system
    // should be moved 5km, but is translated significantly less due to distortion towards the polws,
    // compare https://stackoverflow.com/questions/32202944/openlayers-3-circle-radius-in-meters

    // point in the map reference system at the origin of the local reference system
    const pointMapCrsAtOriginOfLocalCRS = new Point(fromLonLat([this.lon, this.lat]));
    const markerMapOriginLocalCRS = new Feature(pointMapCrsAtOriginOfLocalCRS);
    markerMapOriginLocalCRS.setStyle(
      new Style({
        ...OpenLayersStyleFactory.markerOptions(
          BOSCH_DDS_COLORS.boschGray40,
          OpenLayersStyleFactory.boschFontIconOptions(BOSCH_ICONS.BoschIcHome, BOSCH_DDS_COLORS.boschWhite, false, 15),
          50
        ),
        zIndex: -10,
      })
    );

    // point that should have been translated 5km in map coordinate reference system
    // WRONG due to distortion towards the poles, compare https://stackoverflow.com/questions/32202944/openlayers-3-circle-radius-in-meters
    // actual distance can be computed using the haversineDistance of the sphere
    const pointMapTranslatedX = pointMapCrsAtOriginOfLocalCRS.clone();
    pointMapTranslatedX.translate(pointOffsetInMeters, 0);
    const markerMapTranslatedX = new Feature(pointMapTranslatedX);
    markerMapTranslatedX.setStyle(
      new Style({
        ...OpenLayersStyleFactory.markerOptions(
          BOSCH_DDS_COLORS.boschGray40,
          OpenLayersStyleFactory.boschFontIconOptions(BOSCH_ICONS.BoschIcArrowRight, BOSCH_DDS_COLORS.boschWhite, true),
          50
        ),
        zIndex: -10,
      })
    );

    // points in locale coordinate reference system

    // origin of local coordinate reference system
    const markerLocalCenter = new Feature(this.fromLocalCrs(new Point([0, 0])));
    markerLocalCenter.setStyle(
      new Style(
        OpenLayersStyleFactory.markerOptions(
          BOSCH_DDS_COLORS.boschBlue35,
          OpenLayersStyleFactory.boschFontIconOptions(BOSCH_ICONS.BoschIcHome, BOSCH_DDS_COLORS.boschWhite)
        )
      )
    );

    // point that has been moved 5km in local coordinate reference system
    const markerLocalTranslatedY = new Feature(this.fromLocalCrs(new Point([0, -pointOffsetInMeters])));
    markerLocalTranslatedY.setStyle(
      new Style(
        OpenLayersStyleFactory.markerOptions(
          BOSCH_DDS_COLORS.boschBlue50,
          OpenLayersStyleFactory.boschFontIconOptions(BOSCH_ICONS.BoschIcArrowDown, BOSCH_DDS_COLORS.boschWhite, true)
        )
      )
    );

    // point that has been moved 5km in local coordinate reference system
    const markerLocalTranslatedX = new Feature(this.fromLocalCrs(new Point([pointOffsetInMeters, 0])));
    markerLocalTranslatedX.setStyle(
      new Style(
        OpenLayersStyleFactory.markerOptions(
          BOSCH_DDS_COLORS.boschBlue50,
          OpenLayersStyleFactory.boschFontIconOptions(BOSCH_ICONS.BoschIcArrowRight, BOSCH_DDS_COLORS.boschWhite, true)
        )
      )
    );

    const markers = [
      markerLocalCenter,
      markerLocalTranslatedY,
      markerLocalTranslatedX,
      markerMapOriginLocalCRS,
      markerMapTranslatedX,
    ];
    const markerLayer = this.openLayersLayerFactory.createVectorLayer(
      this.openLayersLayerFactory.createVectorSource(markers),
      15
    );
    this.map.addLayer(markerLayer);

    //// some geometries
    const geometryStyle = new Style({
      stroke: new Stroke({
        color: BOSCH_DDS_COLORS.boschGray40,
        width: 3,
      }),
      fill: new Fill({
        color: 'rgba(255,255,255,0.5)',
      }),
    });

    // circle with correct radius
    // should be 5km, corrected for distortion towards the poles,
    // compare https://stackoverflow.com/questions/32202944/openlayers-3-circle-radius-in-meters
    const circleFeatureCorrected = new Feature(
      this.map.projectionFrom(Projections.EPSG4326_WGS84)(circularPolygon([this.lon, this.lat], 5000, 64))
    );
    circleFeatureCorrected.setStyle(
      new Style({
        stroke: new Stroke({
          color: BOSCH_DDS_COLORS.boschSignalGreen,
          width: 10,
        }),
      })
    );

    // circle with incorrect radius (due to distortion)
    const circleFeature = new Feature(this.fromLocalCrs(new Circle([0, 0], 5000)));

    const lineFeature = new Feature(
      this.fromLocalCrs(
        new LineString([
          [-15000, 1000],
          [-12000, 5000],
          [-12000, 7000],
          [-10000, 6000],
        ])
      )
    );

    const polygonFeature = new Feature(this.fromLocalCrs(new Polygon([this.createTriangle(-15000, 0, 8000, 4000)])));

    const triangleOutline = this.createTriangle(-10000, 10000, 4000, 4000);
    const triangularHole = this.createTriangle(-9000, 11000, 2000, 2000);
    const polygonWithHolesFeature = new Feature(this.fromLocalCrs(new Polygon([triangleOutline, triangularHole])));

    const geometriesLayer = this.openLayersLayerFactory.createVectorLayer(
      this.openLayersLayerFactory.createVectorSource([
        circleFeatureCorrected,
        circleFeature,
        lineFeature,
        polygonFeature,
        polygonWithHolesFeature,
      ]),
      10,
      <any>{
        style: [geometryStyle],
      }
    );
    this.map.addLayer(geometriesLayer);
  }

  private createTriangle(xOrigin: number, yOrigin: number, width: number, height: number): [number, number][] {
    return [
      [xOrigin, yOrigin],
      [xOrigin + width / 2, yOrigin + height],
      [xOrigin + width, yOrigin],
      [xOrigin, yOrigin], // last coordinate == first coordinate seems to be optional in openlayer
    ];
  }

  private addGeoJsonDemoLayer() {
    const geojsonObject = {
      type: 'FeatureCollection',
      features: [
        {
          type: 'Feature',
          properties: {},
          geometry: {
            type: 'Polygon',
            coordinates: [
              [
                [9.034881591796875, 48.71452483966837],
                [9.099240112304688, 48.71452483966837],
                [9.099240112304688, 48.74573966784039],
                [9.034881591796875, 48.74573966784039],
                [9.034881591796875, 48.71452483966837],
              ],
              [
                [9.0692138671875, 48.73756880394769],
                [9.06672477722168, 48.73326655588754],
                [9.072818756103516, 48.73077561241299],
                [9.078226089477539, 48.733662830971156],
                [9.079513549804688, 48.73654988373337],
                [9.079084396362305, 48.73881412282983],
                [9.0692138671875, 48.73756880394769],
              ],
            ],
          },
        },
      ],
    };

    const vectorLayer = this.openLayersLayerFactory.createGeoJSONLayer(geojsonObject, this.map.projection, 5);
    vectorLayer.setStyle([
      new Style({
        stroke: new Stroke({
          color: BOSCH_DDS_COLORS.boschGray80,
          width: 3,
        }),
        fill: new Fill({
          color: 'rgba(255,255,255,0.5)',
        }),
      }),
      new Style(this.createStyleOptionsForPolygonVertices()),
    ]);
    this.map.addLayer(vectorLayer);
  }
}
