import { ElementRef, Input, ViewChild, Directive, HostListener, OutputRefSubscription } from '@angular/core'

import { MatButton } from '@angular/material/button'

import { Position } from 'geojson'
import distance from '@turf/distance'
import { Units } from '@turf/helpers'
import { MapService } from '@maplibre/ngx-maplibre-gl'
import { GeoJSONSource, MapMouseEvent } from 'maplibre-gl'


const LAYER_LINE = 'gisaf-ruler-line'
const LAYER_SYMBOL = 'gisaf-ruler-symbol'
const LAYER_DISTANCE = 'gisaf-ruler-dist'
const LAYER_MARKER = 'gisaf-ruler-marker'
const SOURCE_LINE = 'gisaf-ruler-line-source'
const SOURCE_SYMBOL = 'gisaf-ruler-symbol-source'

@Directive({
  selector: '[gisafRuler]'
})
export class GisafRulerDirective {
  segments: Position[][] = []
  startingPoint: Position
  labels: string[]
  distances: number[]
  // XXX: remove typings (maplibre 2.x)
  //lineSource: GeoJSONSource
  //symbolSource: GeoJSONSource
  lineSource: any
  symbolSource: any
  active: boolean = false
  @Input() color: string = '#4264fb'
  @Input() units: Units = <Units>'kilometers'
  @Input() font: string[] = ['Noto Sans Regular']
  @Input() fontSize: number = 14
  @Input() markerSize: number = 3
  @Input() lineWidth: number = 1
  @Input() secondaryColor: string = 'white'
  @Input() fontHalo: number = 2
  @ViewChild(MatButton) button: MatButton
  clickSubscription: OutputRefSubscription
  lineDrawn: boolean

  constructor(
    public elementRef: ElementRef,
    private mapService: MapService
  ) { }

  @HostListener('click')
  onClick() {
    this.active ? this.stop() : this.start()
  }

  start() {
    this.active = true
    this.elementRef.nativeElement.classList.add('-active')
    this.segments = []
    this.labels = []
    this.distances = []
    this.lineDrawn = false
    this.startingPoint = undefined
    this.addLayers()
    this.mapService.mapCreated$.subscribe(
      () => {
        this.clickSubscription = this.mapService.mapEvents.mapClick.subscribe(
          evt => this.onMapClick(evt)
        )
      }
    )
  }

  stop() {
    this.active = false
    this.elementRef.nativeElement.classList.remove('-active')
    this.clickSubscription.unsubscribe()
    this.removeLayers()
  }

  /*
  updateLabels() {
    let sum = 0
    this.labels = this.segments.map((coordinate, index) => {
      if (index === 0) return this.labelFormat(0)
      sum += distance(this.segments[index - 1], coordinate, {units: this.units})
      return this.labelFormat(sum)
    })
  }
  */

  /*
  updateDistances() {
    this.distances = []
    this.segments.map((coordinate, index) => {
      if (index === 0) return
      this.distances.push(
        distance(this.segments[index - 1], coordinate, {units: this.units})
      )
    })
  }
  */

  labelFormat(n) {
    return n == 0 ? '0' : n < 1 ? `${(n * 1000).toFixed(2)} m` : `${n.toFixed(3)} km`
  }

  onMapClick(evt: MapMouseEvent) {
    const newCoordinate = [evt.lngLat.lng, evt.lngLat.lat]

    if (this.lineDrawn) {
      if (evt.originalEvent.ctrlKey) {
        this.segments.push([this.startingPoint, newCoordinate])
      }
      else {
        this.startingPoint = newCoordinate
        this.lineDrawn = false
      }
    }
    else {
      if (this.startingPoint) {
        this.segments.push([this.startingPoint, newCoordinate])
        this.lineDrawn = true
      }
      else {
        this.startingPoint = newCoordinate
      }
    }

    //else {
    //  if (!this.startingPoint || this.multiLineMode) {
    //    this.startingPoint = newCoordinate
    //  }
    //  else {
    //    this.segments.push([this.startingPoint, newCoordinate])
    //    this.startingPoint = undefined
    //  }
    //  this.multiLineMode = false
    //}

    //this.updateLabels()
    //this.updateDistances()

    this.lineSource.setData(this.getLineStringFeatures())
    this.symbolSource.setData(this.getPointFeatureCollection())
  }

  addLayers() {
    const map = this.mapService.mapInstance
    map.addSource(SOURCE_LINE, {
      type: 'geojson',
      data: this.getLineStringFeatures()
    }
    )

    map.addSource(SOURCE_SYMBOL, {
      type: 'geojson',
      data: this.getPointFeatureCollection()
    })

    map.addLayer({
      id: LAYER_LINE,
      type: 'line',
      source: SOURCE_LINE,
      paint: {
        'line-color': this.color,
        'line-width': this.lineWidth,
        'line-dasharray': [2, 2]
      },
    })

    /*
    map.addLayer({
      id: LAYER_SYMBOL,
      type: 'symbol',
      source: SOURCE_SYMBOL,
      layout: {
        'text-field': '{text}',
        'text-font': this.font,
        'text-anchor': 'top',
        'text-size': this.fontSize,
        'text-offset': [0, 0.8],
        'text-allow-overlap': true,
      },
      paint: {
        'text-color': this.color,
        //'text-halo-color': this.secondaryColor,
        //'text-halo-width': this.fontHalo,
      },
    })
    */

    map.addLayer({
      id: LAYER_DISTANCE,
      type: 'symbol',
      source: SOURCE_LINE,
      layout: {
        'text-field': ['get', 'dist'],
        'text-font': this.font,
        //'text-anchor': 'top',
        'text-size': this.fontSize,
        //'text-offset': [0, 0.8],
        'text-allow-overlap': true,
        'symbol-placement': 'line-center'
      },
      paint: {
        'text-color': this.color,
        'text-halo-color': this.secondaryColor,
        'text-halo-width': this.fontHalo,
      },
    })

    map.addLayer({
      id: LAYER_MARKER,
      type: 'circle',
      source: SOURCE_SYMBOL,
      paint: {
        'circle-radius': this.markerSize,
        'circle-color': this.color,
      },
    })

    this.lineSource = <GeoJSONSource>map.getSource(SOURCE_LINE)
    this.symbolSource = <GeoJSONSource>map.getSource(SOURCE_SYMBOL)
  }

  removeLayers() {
    const map = this.mapService.mapInstance
    map.removeLayer(LAYER_LINE)
    //map.removeLayer(LAYER_SYMBOL)
    map.removeLayer(LAYER_DISTANCE)
    map.removeLayer(LAYER_MARKER)
    map.removeSource(SOURCE_LINE)
    map.removeSource(SOURCE_SYMBOL)
  }

  getLineStringFeatures(): GeoJSON.FeatureCollection {
    return {
      type: 'FeatureCollection',
      features: this.segments.map(
        segment => ({
          type: 'Feature',
          properties: {
            dist: this.labelFormat(distance(segment[0], segment[1]))
          },
          geometry: {
            type: 'LineString',
            coordinates: segment,
          }
        })
      )
    }
  }

  getPointFeatureCollection(): GeoJSON.FeatureCollection {
    let points: Position[] = []
    this.segments.forEach(
      s => {
        points.push(s[0], s[1])
      }
    )
    if (!!this.startingPoint) {
      points.push(this.startingPoint)
    }
    return {
      type: 'FeatureCollection',
      features: points.map(
        p => ({
          type: 'Feature',
          properties: {},
          geometry: {
            type: 'Point',
            coordinates: p,
          }
        })
      )
    }
  }
}
