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

import { AfterViewInit, Directive } from '@angular/core';
import { Feature } from 'ol';
import { GeoJSON } from 'ol/format';
import { Point } from 'ol/geom';
import { Vector as VectorSource } from 'ol/source';
import { OpenLayersLayerFactory } from '../../generic-maps/open-layers-map/open-layers-layer-factory';
import { OpenLayersMapComponent } from '../../generic-maps/open-layers-map/open-layers-map.component';
import { Projections } from '../../generic-maps/projection.utils';
import { LsLocation } from '../model/lsLocation';
import { LocationMapService } from './location-map.service';
import { LocationStyleService } from './location-style.service';

@Directive({
  selector: '[bciLocationRenderer]',
})
/**
 * renders locations on a given layer
 */
export class LocationRendererDirective implements AfterViewInit {
  protected geoJSONReader: GeoJSON;

  constructor(
    private locationMapService: LocationMapService,
    private locationStyleService: LocationStyleService,
    public mapComponent: OpenLayersMapComponent,
    private openLayersLayerFactory: OpenLayersLayerFactory
  ) {}

  ngAfterViewInit(): void {
    this.geoJSONReader = this.openLayersLayerFactory.createGeoJSONReader(this.mapComponent.projection);
  }

  /**
   * Update styles considering current selection.
   * @param selectedLocations Currently selected locations.
   */
  public updateStyles(selectedLocations: LsLocation[]): void {
    const selectedLocationIds = new Set(selectedLocations.map((location) => location.id));
    this.locationMapService.featureToEntityMap.forEachFeature((features, locationId) => {
      if (!!features.length) {
        // Determine style of location
        const featureStyle = this.locationStyleService.getStyle(
          this.locationMapService.featureToEntityMap.getEntityOfFeature(features[0]),
          { isSelected: selectedLocationIds.has(locationId) }
        );
        features.forEach((feature) => feature.setStyle(featureStyle));
      }
    });
  }

  /**
   * renders locations on locationVectorSource layer by creating features derived from locations
   * @param locationVectorSource the layer to render on
   * @param locations the locations to be rendered
   */
  public render(locationVectorSource: VectorSource, locations: LsLocation[]): void {
    if (!this.geoJSONReader) {
      this.ngAfterViewInit();
    }
    // Clear Map
    this.locationMapService.featureToEntityMap.clearFeatures(locationVectorSource.getFeatures());
    locationVectorSource.clear();

    locations.map((location) => {
      this.renderLocation(locationVectorSource, location);
    });
  }

  /**
   * renders a single location by creating features
   * @param locationVectorSource the layer to render on
   * @param location the location to be rendered
   */
  protected renderLocation(locationVectorSource: VectorSource, location) {
    const features = [];

    // Render boundary of location if set
    const boundaryFeature = this.renderBoundaryAndLabel(location);
    // Add to map layer
    if (boundaryFeature !== undefined) {
      locationVectorSource.addFeature(boundaryFeature);
      features.push(boundaryFeature);
    }

    // Render position of location if set
    const positionFeature = this.renderPosition(location);
    // Add to map layer
    if (positionFeature !== undefined) {
      locationVectorSource.addFeature(positionFeature);
      features.push(positionFeature);
    }

    this.locationMapService.featureToEntityMap.set(features, location);
  }

  /**
   * takes the boundary from the location and creates a feature.
   * If no boundary is contained in the location no feature is created.
   * The zIndex is derived from the locations altitude attribute if provided.
   * Otherwise it is 10.
   * @param location the location that provides the boundary to be drawn
   */
  protected renderBoundaryAndLabel(location: LsLocation): Feature | undefined {
    const boundary = location.boundary;
    const position = location.geoCoordinate;
    if (!!boundary) {
      const boundaryFeature = new Feature(this.geoJSONReader.readGeometry(boundary));
      boundaryFeature.setId(location.id + '.boundary');
      if (!position) {
        boundaryFeature.set('label', location.name);
      }
      boundaryFeature.set('zIndex', !!position && !!position.altitude ? Math.floor(position.altitude) : 10);
      return boundaryFeature;
    }
  }

  /**
   * takes the geoCoordinate of the location and creates a feature for it.
   * If no geoCoordinate is contained in the location no feature is created.
   * The zIndex is derived from the locations altitude attribute if provided.
   * Otherwise it is 10.
   * @param location the location that provides the geoCoordinate to be drawn
   */
  protected renderPosition(location: LsLocation): Feature | undefined {
    const position = location.geoCoordinate;
    if (
      !!position &&
      position.longitude !== null &&
      position.longitude !== undefined &&
      position.latitude !== null &&
      position.latitude !== undefined
    ) {
      const positionFeature = new Feature(
        this.mapComponent.projectionFrom(Projections.EPSG4326_WGS84)(new Point([position.longitude, position.latitude]))
      );
      positionFeature.setId(location.id + '.position');
      positionFeature.set('label', location.name);
      positionFeature.set('zIndex', !!position && !!position.altitude ? Math.floor(position.altitude) : 10);
      return positionFeature;
    }
  }
}
