/*	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 ol_interaction_Interaction from 'ol/interaction/Interaction.js'
import ol_style_Style_defaultStyle from '../style/defaultStyle.js'
import ol_Collection from 'ol/Collection.js'
import ol_layer_Vector from 'ol/layer/Vector.js'
import ol_source_Vector from 'ol/source/Vector.js'
import ol_geom_Circle from 'ol/geom/Circle.js'
import {fromCircle as ol_geom_Polygon_fromCircle} from 'ol/geom/Polygon.js'
import ol_geom_Point from 'ol/geom/Point.js'
import ol_geom_LineString from 'ol/geom/LineString.js'
import ol_geom_Polygon from 'ol/geom/Polygon.js'
import ol_Feature from 'ol/Feature.js'

/** Interaction rotate
 * @constructor
 * @extends {ol_interaction_Interaction}
 * @fires drawstart, drawing, drawend, drawcancel
 * @param {olx.interaction.TransformOptions} options
 *  @param {Array<ol.Layer>} options.source Destination source for the drawn features
 *  @param {ol.Collection<ol.Feature>} options.features Destination collection for the drawn features 
 *  @param {ol.style.Style | Array.<ol.style.Style> | ol.style.StyleFunction | undefined} options.style style for the sketch
 *  @param {integer} options.sides number of sides, default 0 = circle
 *  @param { ol.events.ConditionType | undefined } options.condition A function that takes an ol.MapBrowserEvent and returns a boolean that event should be handled. By default module:ol/events/condition.always.
 *  @param { ol.events.ConditionType | undefined } options.squareCondition A function that takes an ol.MapBrowserEvent and returns a boolean to draw square features. Default test shift key
 *  @param { ol.events.ConditionType | undefined } options.centerCondition A function that takes an ol.MapBrowserEvent and returns a boolean to draw centered features. Default check Ctrl key
 *  @param { bool } options.canRotate Allow rotation when centered + square, default: true
 *  @param { string } [options.geometryName=geometry] 
 *  @param { number } options.clickTolerance click tolerance on touch devices, default: 6
 *  @param { number } options.maxCircleCoordinates Maximum number of point on a circle, default: 100
 */
var ol_interaction_DrawRegular = class olinteractionDrawRegular extends ol_interaction_Interaction {
  constructor(options) {
    options = options || {}
    
    super({
      handleEvent: function(e) { return self.handleEvent_(e) }
    })

    var self = this;

    this.squaredClickTolerance_ = options.clickTolerance ? options.clickTolerance * options.clickTolerance : 36
    this.maxCircleCoordinates_ = options.maxCircleCoordinates || 100

    // Collection of feature to transform 
    this.features_ = options.features
    // List of layers to transform 
    this.source_ = options.source
    // Square condition
    this.conditionFn_ = options.condition
    // Square condition
    this.squareFn_ = options.squareCondition
    // Centered condition
    this.centeredFn_ = options.centerCondition
    // Allow rotation when centered + square
    this.canRotate_ = (options.canRotate !== false)
    // Specify custom geometry name
    this.geometryName_ = options.geometryName || 'geometry'

    // Number of sides (default=0: circle)
    this.setSides(options.sides)

    // Style
    var defaultStyle = ol_style_Style_defaultStyle(true)

    // Create a new overlay layer for the sketch
    this.sketch_ = new ol_Collection()
    this.overlayLayer_ = new ol_layer_Vector({
      source: new ol_source_Vector({
        features: this.sketch_,
        useSpatialIndex: false
      }),
      name: 'DrawRegular overlay',
      displayInLayerSwitcher: false,
      style: options.style || defaultStyle
    })
  }
  /**
   * Remove the interaction from its current map, if any,  and attach it to a new
   * map, if any. Pass `null` to just remove the interaction from the current map.
   * @param {ol.Map} map Map.
   * @api stable
   */
  setMap(map) {
    if (this.getMap()) this.getMap().removeLayer(this.overlayLayer_)
    super.setMap(map)
    this.overlayLayer_.setMap(map)
  }
  /**
   * Activate/deactivate the interaction
   * @param {boolean}
   * @api stable
   */
  setActive(b) {
    this.reset()
    super.setActive(b)
  }
  /**
   * Reset the interaction
   * @api stable
   */
  reset() {
    if (this.overlayLayer_) this.overlayLayer_.getSource().clear()
    this.started_ = false
  }
  /**
   * Set the number of sides.
   * @param {int} number of sides.
   * @api stable
   */
  setSides(nb) {
    nb = parseInt(nb)
    this.sides_ = nb > 2 ? nb : 0
  }
  /**
   * Allow rotation when centered + square
   * @param {bool}
   * @api stable
   */
  canRotate(b) {
    if (b === true || b === false)
      this.canRotate_ = b
    return this.canRotate_
  }
  /**
   * Get the number of sides.
   * @return {int} number of sides.
   * @api stable
   */
  getSides() {
    return this.sides_
  }
  /** Get geom of the current drawing
  * @return {ol.geom.Polygon | ol.geom.Point}
  */
  getGeom_() {
    this.overlayLayer_.getSource().clear()
    if (!this.center_)
      return false

    var g
    if (this.coord_) {
      var center = this.center_
      var coord = this.coord_

      // Specific case: circle
      var d, dmax, r, circle, centerPx
      if (!this.sides_ && this.square_ && !this.centered_) {
        center = [(coord[0] + center[0]) / 2, (coord[1] + center[1]) / 2]
        d = [coord[0] - center[0], coord[1] - center[1]]
        r = Math.sqrt(d[0] * d[0] + d[1] * d[1])
        circle = new ol_geom_Circle(center, r, 'XY')
        // Optimize points on the circle
        centerPx = this.getMap().getPixelFromCoordinate(center)
        dmax = Math.max(100, Math.abs(centerPx[0] - this.coordPx_[0]), Math.abs(centerPx[1] - this.coordPx_[1]))
        dmax = Math.min(this.maxCircleCoordinates_, Math.round(dmax / 3))
        return ol_geom_Polygon_fromCircle(circle, dmax, 0)
      } else {
        var hasrotation = this.canRotate_ && this.centered_ && this.square_
        d = [coord[0] - center[0], coord[1] - center[1]]
        if (this.square_ && !hasrotation) {
          //var d = [coord[0] - center[0], coord[1] - center[1]];
          var dm = Math.max(Math.abs(d[0]), Math.abs(d[1]))
          coord = [
            center[0] + (d[0] > 0 ? dm : -dm),
            center[1] + (d[1] > 0 ? dm : -dm)
          ]
        }
        r = Math.sqrt(d[0] * d[0] + d[1] * d[1])
        if (r > 0) {
          circle = new ol_geom_Circle(center, r, 'XY')
          var a
          if (hasrotation)
            a = Math.atan2(d[1], d[0])
          else
            a = this.startAngle[this.sides_] || this.startAngle['default']

          if (this.sides_) {
            g = ol_geom_Polygon_fromCircle(circle, this.sides_, a)
          } else {
            // Optimize points on the circle
            centerPx = this.getMap().getPixelFromCoordinate(this.center_)
            dmax = Math.max(100, Math.abs(centerPx[0] - this.coordPx_[0]), Math.abs(centerPx[1] - this.coordPx_[1]))
            dmax = Math.min(this.maxCircleCoordinates_, Math.round(dmax / (this.centered_ ? 3 : 5)))
            g = ol_geom_Polygon_fromCircle(circle, dmax, 0)
          }

          if (hasrotation)
            return g

          // Scale polygon to fit extent
          var ext = g.getExtent()
          if (!this.centered_)
            center = this.center_
          else
            center = [2 * this.center_[0] - this.coord_[0], 2 * this.center_[1] - this.coord_[1]]
          var scx = (center[0] - coord[0]) / (ext[0] - ext[2])
          var scy = (center[1] - coord[1]) / (ext[1] - ext[3])
          if (this.square_) {
            var sc = Math.min(Math.abs(scx), Math.abs(scy))
            scx = Math.sign(scx) * sc
            scy = Math.sign(scy) * sc
          }
          var t = [center[0] - ext[0] * scx, center[1] - ext[1] * scy]

          g.applyTransform(function (g1, g2, dim) {
            for (var i = 0; i < g1.length; i += dim) {
              g2[i] = g1[i] * scx + t[0]
              g2[i + 1] = g1[i + 1] * scy + t[1]
            }
            return g2
          })
          return g
        }
      }
    }

    // No geom => return a point
    return new ol_geom_Point(this.center_)
  }
  /** Draw sketch
  * @return {ol.Feature} The feature being drawn.
  */
  drawSketch_(evt) {
    this.overlayLayer_.getSource().clear()
    if (evt) {
      this.square_ = this.squareFn_ ? this.squareFn_(evt) : evt.originalEvent.shiftKey
      this.centered_ = this.centeredFn_ ? this.centeredFn_(evt) : evt.originalEvent.metaKey || evt.originalEvent.ctrlKey
      var g = this.getGeom_()
      if (g) {
        var f = this.feature_

        //f.setGeometry (g);
        if (g.getType() === 'Polygon')
          f.getGeometry().setCoordinates(g.getCoordinates())
        this.overlayLayer_.getSource().addFeature(f)
        if (this.coord_
          && this.square_
          && ((this.canRotate_ && this.centered_ && this.coord_) || (!this.sides_ && !this.centered_))) {
          this.overlayLayer_.getSource().addFeature(new ol_Feature(new ol_geom_LineString([this.center_, this.coord_])))
        }
        return f
      }
    }
  }
  /** Draw sketch (Point)
  */
  drawPoint_(pt, noclear) {
    if (!noclear) {
      this.overlayLayer_.getSource().clear()
    }
    this.overlayLayer_.getSource().addFeature(new ol_Feature(new ol_geom_Point(pt)))
  }
  /**
   * @param {ol.MapBrowserEvent} evt Map browser event.
   */
  handleEvent_(evt) {
    var dx, dy
    // Event date time
    this._eventTime = new Date();
    switch (evt.type) {
      case "pointerdown": {
        if (this.conditionFn_ && !this.conditionFn_(evt)) break
        this.downPx_ = evt.pixel
        this.start_(evt)
        // Test long touch
        var dt = 500
        this._longTouch = false
        setTimeout(function () {
          this._longTouch = (new Date() - this._eventTime > .9 * dt)
          if (this._longTouch)
            this.handleMoveEvent_(evt)
        }.bind(this), dt)
        this.lastEvent = evt.type;
        break
      }
      case "pointerup": {
        // Started and fisrt move
        if (this.started_ && this.coord_) {
          dx = this.downPx_[0] - evt.pixel[0]
          dy = this.downPx_[1] - evt.pixel[1]

          if (dx * dx + dy * dy <= this.squaredClickTolerance_) {
            // The pointer has moved
            if (this.lastEvent == "pointerdown" || this.lastEvent == "pointermove" || this.lastEvent == "keydown") {
              this.end_(evt)
            }

            // On touch device there is no move event : terminate = click on the same point
            else {
              dx = this.upPx_[0] - evt.pixel[0]
              dy = this.upPx_[1] - evt.pixel[1]
              if (dx * dx + dy * dy <= this.squaredClickTolerance_) {
                this.end_(evt)
              } else {
                this.handleMoveEvent_(evt)
                this.drawPoint_(evt.coordinate, true)
              }
            }
          }
        }
        this.upPx_ = evt.pixel
        break
      }
      case "pointerdrag": {
        if (this.started_) {
          var centerPx = this.getMap().getPixelFromCoordinate(this.center_)
          dx = centerPx[0] - evt.pixel[0]
          dy = centerPx[1] - evt.pixel[1]
          if (dx * dx + dy * dy <= this.squaredClickTolerance_) {
            this.reset()
          }
        }
        return !this._longTouch
        // break;
      }
      case "pointermove": {
        if (this.started_) {
          dx = this.downPx_[0] - evt.pixel[0]
          dy = this.downPx_[1] - evt.pixel[1]
          if (dx * dx + dy * dy > this.squaredClickTolerance_) {
            this.handleMoveEvent_(evt)
            this.lastEvent = evt.type
          }
        } else {
          this.drawPoint_(evt.coordinate)
        }
        break
      }
      default: {
        // Prevent zoom or other event on click/singleclick/dblclick
        if (this.started_ && (evt.type === 'click' || evt.type === 'singleclick' || evt.type === 'dblclick')) {
          //evt.stopPropagation();
          return false
        }
        this.lastEvent = evt.type

        break
      }
    }
    return true
  }
  /** Stop drawing.
   */
  finishDrawing() {
    if (this.started_ && this.coord_) {
      this.end_({ pixel: this.upPx_, coordinate: this.coord_ })
    }
  }
  /**
   * @param {ol.MapBrowserEvent} evt Event.
   */
  handleMoveEvent_(evt) {
    if (this.started_) {
      this.coord_ = evt.coordinate
      this.coordPx_ = evt.pixel
      var f = this.drawSketch_(evt)
      this.dispatchEvent({
        type: 'drawing',
        feature: f,
        pixel: evt.pixel,
        startCoordinate: this.center_,
        coordinate: evt.coordinate,
        square: this.square_,
        centered: this.centered_
      })
    } else {
      this.drawPoint_(evt.coordinate)
    }
  }
  /** Start an new draw
   * @param {ol.MapBrowserEvent} evt Map browser event.
   * @return {boolean} `false` to stop the drag sequence.
   */
  start_(evt) {
    if (!this.started_) {
      this.started_ = true
      this.center_ = evt.coordinate
      this.coord_ = null
      var f = this.feature_ = new ol_Feature({})
      f.setGeometryName(this.geometryName_ || 'geometry')
      f.setGeometry(new ol_geom_Polygon([[evt.coordinate, evt.coordinate, evt.coordinate]]))
      this.drawSketch_(evt)
      this.dispatchEvent({ type: 'drawstart', feature: f, pixel: evt.pixel, coordinate: evt.coordinate })
    } else {
      this.coord_ = evt.coordinate
    }
  }
  /** End drawing
   * @param {ol.MapBrowserEvent} evt Map browser event.
   * @return {boolean} `false` to stop the drag sequence.
   */
  end_(evt) {
    this.coord_ = evt.coordinate
    this.started_ = false
    if (this.coord_ && (this.center_[0] !== this.coord_[0] || this.center_[1] !== this.coord_[1])) {
      var f = this.feature_

      f.setGeometry(this.getGeom_())
      if (this.source_)
        this.source_.addFeature(f)
      else if (this.features_)
        this.features_.push(f)
      this.dispatchEvent({ type: 'drawend', feature: f, pixel: evt.pixel, coordinate: evt.coordinate, square: this.square_, centered: this.centered_ })
    } else {
      this.dispatchEvent({ type: 'drawcancel', feature: null, pixel: evt.pixel, coordinate: evt.coordinate, square: this.square_, centered: this.centered_ })
    }

    this.center_ = this.coord_ = null
    this.drawSketch_()
  }
}

/** Default start angle array for each sides
*/
ol_interaction_DrawRegular.prototype.startAngle = {
  'default':Math.PI/2,
  3: -Math.PI/2,
  4: Math.PI/4
};

export default ol_interaction_DrawRegular
