/*	Copyright (c) 2016 Jean-Marc VIGLINO, 
  released under the CeCILL-B license (French BSD license)
  (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt).
*/

import {unByKey as ol_Observable_unByKey} from 'ol/Observable.js'
import ol_control_Control from 'ol/control/Control.js'
import ol_geom_Polygon from 'ol/geom/Polygon.js'
import ol_geom_Point from 'ol/geom/Point.js'
import ol_interaction_Pointer from 'ol/interaction/Pointer.js'
import ol_Map from 'ol/Map.js'
import ol_Collection from 'ol/Collection.js'
import ol_View from 'ol/View.js'
import ol_source_Vector from 'ol/source/Vector.js'
import ol_style_Style from 'ol/style/Style.js'
import ol_style_Circle from 'ol/style/Circle.js'
import ol_style_Fill from 'ol/style/Fill.js'
import ol_style_Stroke from 'ol/style/Stroke.js'
import ol_layer_Vector from 'ol/layer/Vector.js'
import ol_Feature from 'ol/Feature.js'

/**
 * OpenLayers 3 Layer Overview Control.
 * The overview can rotate with map. 
 * Zoom levels are configurable.
 * Click on the overview will center the map.
 * Change width/height of the overview trough css.
 *
 * @constructor
 * @extends {ol_control_Control}
 * @param {Object=} options Control options.
 *  @param {ol.ProjectionLike} options.projection The projection. Default is EPSG:3857 (Spherical Mercator).
 *  @param {Number} options.minZoom default 0
 *  @param {Number} options.maxZoom default 18
 *  @param {boolean} options.rotation enable rotation, default false
 *  @param {top|bottom-left|right} options.align position
 *  @param {Array<ol.layer>} options.layers list of layers
 *  @param {ol.style.Style | Array.<ol.style.Style> | undefined} options.style style to draw the map extent on the overveiw
 *  @param {bool|elastic} options.panAnimation use animation to center map on click, default true
 */
var ol_control_Overview = class olcontrolOverview extends ol_control_Control {
  constructor(options) {
    options = options || {}
    var element = document.createElement("div");

    super({
      element: element,
      target: options.target
    })

    var self = this

    // API
    this.minZoom = options.minZoom || 0
    this.maxZoom = options.maxZoom || 18
    this.rotation = options.rotation

    if (options.target) {
      this.panel_ = options.target
    } else {
      element.classList.add('ol-overview', 'ol-unselectable', 'ol-control', 'ol-collapsed')
      if (/top/.test(options.align))
        element.classList.add('ol-control-top')
      if (/right/.test(options.align))
        element.classList.add('ol-control-right')
      var button = document.createElement("button")
      button.setAttribute('type', 'button')
      button.addEventListener("touchstart", function (e) { self.toggleMap(); e.preventDefault() })
      button.addEventListener("click", function () { self.toggleMap() })
      element.appendChild(button)
      this.panel_ = document.createElement("div")
      this.panel_.classList.add("panel")
      element.appendChild(this.panel_)
    }

    // Create a overview map
    this.ovmap_ = new ol_Map({
      controls: new ol_Collection(),
      interactions: new ol_Collection(),
      target: this.panel_,
      view: new ol_View({
        zoom: 2,
        center: [0, 0],
        projection: options.projection
      }),
      layers: options.layers
    })

    this.oview_ = this.ovmap_.getView()

    // Cache extent
    this.extentLayer = new ol_layer_Vector({
      name: 'Cache extent',
      source: new ol_source_Vector(),
      style: options.style || [new ol_style_Style({
        image: new ol_style_Circle({
          fill: new ol_style_Fill({
            color: 'rgba(255,0,0, 1)'
          }),
          stroke: new ol_style_Stroke({
            width: 7,
            color: 'rgba(255,0,0, 0.8)'
          }),
          radius: 5
        }),
        stroke: new ol_style_Stroke({
          width: 5,
          color: "rgba(255,0,0,0.8)"
        })
      }
      )]
    })
    this.ovmap_.addLayer(this.extentLayer)

    /** Elastic bounce
    *	@param {Int} bounce number of bounce
    *	@param {Number} amplitude amplitude of the bounce [0,1]
    *	@return {Number}
    * /
    var bounceFn = function (bounce, amplitude){
      var a = (2*bounce+1) * Math.PI/2;
      var b = amplitude>0 ? -1/amplitude : -100;
      var c = - Math.cos(a) * Math.pow(2, b);
      return function(t) {
        t = 1-Math.cos(t*Math.PI/2);
        return 1 + Math.abs( Math.cos(a*t) ) * Math.pow(2, b*t) + c*t;
      }
    }
  
    /** Elastic bounce
    *	@param {Int} bounce number of bounce
    *	@param {Number} amplitude amplitude of the bounce [0,1]
    *	@return {Number}
    */
    var elasticFn = function (bounce, amplitude) {
      var a = 3 * bounce * Math.PI / 2
      var b = amplitude > 0 ? -1 / amplitude : -100
      var c = Math.cos(a) * Math.pow(2, b)
      return function (t) {
        t = 1 - Math.cos(t * Math.PI / 2)
        return 1 - Math.cos(a * t) * Math.pow(2, b * t) + c * t
      }
    }

    // Click on the preview center the map
    this.ovmap_.addInteraction(new ol_interaction_Pointer({
      handleDownEvent: function (evt) {
        if (options.panAnimation !== false) {
          if (options.panAnimation == "elastic" || options.elasticPan) {
            self.getMap().getView().animate({
              center: evt.coordinate,
              easing: elasticFn(2, 0.3),
              duration: 1000
            })
          } else {
            self.getMap().getView().animate({
              center: evt.coordinate,
              duration: 300
            })
          }
        }
        else
          self.getMap().getView().setCenter(evt.coordinate)
        return false
      }
    }))
  }
  /** Get overview map
  *	@return {ol.Map}
  */
  getOverviewMap() {
    return this.ovmap_
  }
  /** Toggle overview map
  */
  toggleMap() {
    this.element.classList.toggle("ol-collapsed")
    this.ovmap_.updateSize()
    this.setView()
  }
  /** Set overview map position
  *	@param {top|bottom-left|right}
  */
  setPosition(align) {
    if (/top/.test(align))
      this.element.classList.add("ol-control-top")
    else
      this.element.classList.remove("ol-control-top")
    if (/right/.test(align))
      this.element.classList.add("ol-control-right")
    else
      this.element.classList.remove("ol-control-right")
  }
  /**
   * Set the map instance the control associated with.
   * @param {ol.Map} map The map instance.
   */
  setMap(map) {
    if (this._listener) {
      for (let i in this._listener) {
        ol_Observable_unByKey(this._listener[i])
      }
    }
    this._listener = {}

    super.setMap(map)

    if (map) {
      this._listener.map = map.on('change:view', function () {
        if (this._listener.view)
          ol_Observable_unByKey(this._listener.view)
        if (map.getView()) {
          this._listener.view = map.getView().on('propertychange', this.setView.bind(this))
          this.setView()
        }
      }.bind(this))
      this._listener.view = map.getView().on('propertychange', this.setView.bind(this))
      this.setView()
    }

  }
  /** Calculate the extent of the map and draw it on the overview
  */
  calcExtent_(extent) {
    var map = this.getMap()
    if (!map)
      return

    var source = this.extentLayer.getSource()
    source.clear()
    var f = new ol_Feature()

    var size = map.getSize()
    var resolution = map.getView().getResolution()
    var rotation = map.getView().getRotation()
    var center = map.getView().getCenter()
    if (!resolution)
      return

    var dx = resolution * size[0] / 2
    var dy = resolution * size[1] / 2
    var res2 = this.oview_.getResolution()
    if (dx / res2 > 5 || dy / res2 > 5) {
      var cos = Math.cos(rotation)
      var sin = Math.sin(rotation)
      var i, x, y
      extent = [[-dx, -dy], [-dx, dy], [dx, dy], [dx, -dy]]
      for (i = 0; i < 4; ++i) {
        x = extent[i][0]
        y = extent[i][1]
        extent[i][0] = center[0] + x * cos - y * sin
        extent[i][1] = center[1] + x * sin + y * cos
      }
      f.setGeometry(new ol_geom_Polygon([extent]))
    } else {
      f.setGeometry(new ol_geom_Point(center))
    }
    source.addFeature(f)
  }
  /**
  *	@private
  */
  setView(e) {
    if (!e) {
      // refresh all
      this.setView({ key: 'rotation' })
      this.setView({ key: 'resolution' })
      this.setView({ key: 'center' })
      return
    }
    // Set the view params
    switch (e.key) {
      case 'rotation': {
        if (this.rotation)
          this.oview_.setRotation(this.getMap().getView().getRotation())
        else if (this.oview_.getRotation())
          this.oview_.setRotation(0)
        break
      }
      case 'center': {
        var mapExtent = this.getMap().getView().calculateExtent(this.getMap().getSize())
        var extent = this.oview_.calculateExtent(this.ovmap_.getSize())
        if (mapExtent[0] < extent[0] || mapExtent[1] < extent[1]
          || mapExtent[2] > extent[2] || mapExtent[3] > extent[3]) {
          this.oview_.setCenter(this.getMap().getView().getCenter())
        }
        break
      }
      case 'resolution': {
        //var z = Math.round(this.getMap().getView().getZoom()/2)*2-4;
        var z = Math.round(this.oview_.getZoomForResolution(this.getMap().getView().getResolution()) / 2) * 2 - 4
        z = Math.min(this.maxZoom, Math.max(this.minZoom, z))
        this.oview_.setZoom(z)
        break
      }
      default: break
    }
    this.calcExtent_()
  }
}

export default ol_control_Overview
