/*
  Copyright (c) 2015 Jean-Marc VIGLINO,
  released under the CeCILL-B license (http://www.cecill.info/).

  ol_layer_AnimatedCluster is a vector layer that animate cluster
*/
import ol_layer_Vector from 'ol/layer/Vector.js'
import ol_source_Vector from 'ol/source/Vector.js'
import ol_Feature from 'ol/Feature.js'
import {easeOut as ol_easing_easeOut} from 'ol/easing.js'
import {buffer as ol_extent_buffer} from 'ol/extent.js'
import ol_geom_Point from 'ol/geom/Point.js'
import ol_render_getVectorContext from '../util/getVectorContext.js';
import ol_ext_getVectorContextStyle from '../util/getVectorContextStyle.js'

/**
 *  A vector layer for animated cluster
 * @constructor 
 * @extends {ol.layer.Vector}
 * @param {olx.layer.AnimatedClusterOptions=} options extend olx.layer.Options
 *  @param {Number} options.animationDuration animation duration in ms, default is 700ms 
 *  @param {ol.easingFunction} animationMethod easing method to use, default ol.easing.easeOut
 */
var ol_layer_AnimatedCluster = class ollayerAnimatedCluster extends ol_layer_Vector {
  constructor(opt_options) {
    var options = opt_options || {}

    super(options)

    this.oldcluster = new ol_source_Vector()
    this.clusters = []
    this.animation = { start: false }
    this.set('animationDuration', typeof (options.animationDuration) == 'number' ? options.animationDuration : 700)
    this.set('animationMethod', options.animationMethod || ol_easing_easeOut)

    // Animate the cluster
    this.on(['precompose', 'prerender'], this.animate.bind(this))
    this.on(['postcompose', 'postrender'], this.postanimate.bind(this))
  }
  /** Set the cluster source
   * @param {ol_source_Vector} source
   */
  setSource(source) {
    if (!this._saveClusterFn) this._saveClusterFn = this.saveCluster.bind(this)
    // Save cluster before change
    if (this.getSource()) this.getSource().un('change', this._saveClusterFn)
    ol_layer_Vector.prototype.setSource.call(this, source)
    if (this.getSource()) this.getSource().on('change', this._saveClusterFn)
  }
  /** save cluster features before change
   * @private
   */
  saveCluster() {
    if (this.oldcluster) {
      this.oldcluster.clear()
      if (!this.get('animationDuration'))
        return
      var features = this.getSource().getFeatures()
      if (features.length && features[0].get('features')) {
        this.oldcluster.addFeatures(this.clusters)
        this.clusters = features.slice(0)
        this.sourceChanged = true
      }
    }
  }
  /**
   * Get the cluster that contains a feature
   * @private
  */
  getClusterForFeature(f, cluster) {
    for (var j = 0, c; c = cluster[j]; j++) {
      var features = c.get('features')
      if (features && features.length) {
        for (var k = 0, f2; f2 = features[k]; k++) {
          if (f === f2) {
            return c
          }
        }
      }
    }
    return false
  }
  /**
   * Stop animation
   * @private
   */
  stopAnimation() {
    this.animation.start = false
    this.animation.cA = []
    this.animation.cB = []
  }
  /**
   * animate the cluster
   * @private
   */
  animate(e) {
    var duration = this.get('animationDuration')
    if (!duration)
      return
    var resolution = e.frameState.viewState.resolution
    // var ratio = e.frameState.pixelRatio;
    var i, c0, a = this.animation
    var time = e.frameState.time

    // Start a new animation, if change resolution and source has changed
    if (a.resolution != resolution && this.sourceChanged) {
      var extent = e.frameState.extent
      if (a.resolution < resolution) {
        extent = ol_extent_buffer(extent, 100 * resolution)
        a.cA = this.oldcluster.getFeaturesInExtent(extent)
        a.cB = this.getSource().getFeaturesInExtent(extent)
        a.revers = false
      } else {
        extent = ol_extent_buffer(extent, 100 * resolution)
        a.cA = this.getSource().getFeaturesInExtent(extent)
        a.cB = this.oldcluster.getFeaturesInExtent(extent)
        a.revers = true
      }
      a.clusters = []
      for (i = 0, c0; c0 = a.cA[i]; i++) {
        var f = c0.get('features')
        if (f && f.length) {
          var c = this.getClusterForFeature(f[0], a.cB)
          if (c)
            a.clusters.push({ f: c0, pt: c.getGeometry().getCoordinates() })
        }
      }
      // Save state
      a.resolution = resolution
      this.sourceChanged = false

      // No cluster or too much to animate
      if (!a.clusters.length || a.clusters.length > 1000) {
        this.stopAnimation()
        return
      }
      // Start animation from now
      time = a.start = (new Date()).getTime()
    }

    // Run animation
    if (a.start) {
      var vectorContext = e.vectorContext || ol_render_getVectorContext(e)
      var d = (time - a.start) / duration
      // Animation ends
      if (d > 1.0) {
        this.stopAnimation()
        d = 1
      }
      d = this.get('animationMethod')(d)
      // Animate
      var style = this.getStyle()
      var stylefn = (typeof (style) == 'function') ? style : style.length ? function () { return style } : function () { return [style] }
      // Layer opacity
      e.context.save()
      e.context.globalAlpha = this.getOpacity()
      for (i = 0, c; c = a.clusters[i]; i++) {
        var pt = c.f.getGeometry().getCoordinates()
        var dx = pt[0] - c.pt[0]
        var dy = pt[1] - c.pt[1]
        if (a.revers) {
          pt[0] = c.pt[0] + d * dx
          pt[1] = c.pt[1] + d * dy
        } else {
          pt[0] = pt[0] - d * dx
          pt[1] = pt[1] - d * dy
        }
        // Draw feature
        var st = stylefn(c.f, resolution, true)
        if (!Array.isArray(st)) {
          st = [st]
        }
        // If one feature: draw the feature
        if (c.f.get("features").length === 1 && !dx && !dy) {
          f = c.f.get("features")[0]
        } else {
          // else draw a point
          var geo = new ol_geom_Point(pt)
          f = new ol_Feature(geo)
        }
        for (var k = 0, s; s = st[k]; k++) {
          // Multi-line text
          if (s.getText() && /\n/.test(s.getText().getText())) {
            var offsetX = s.getText().getOffsetX()
            var offsetY = s.getText().getOffsetY()
            var rot = s.getText().getRotation() || 0
            var fontSize = Number((s.getText().getFont() || '10px').match(/\d+/)) * 1.2
            var str = s.getText().getText().split('\n')
            var dl, nb = str.length - 1
            var s2 = s.clone()
            // Draw each lines
            str.forEach(function (t, i) {
              if (i == 1) {
                // Allready drawn
                s2.setImage()
                s2.setFill()
                s2.setStroke()
              }
              switch (s.getText().getTextBaseline()) {
                case 'alphabetic':
                case 'ideographic':
                case 'bottom': {
                  dl = nb
                  break
                }
                case 'hanging':
                case 'top': {
                  dl = 0
                  break
                }
                default: {
                  dl = nb / 2
                  break
                }
              }
              s2.getText().setOffsetX(offsetX - Math.sin(rot) * fontSize * (i - dl))
              s2.getText().setOffsetY(offsetY + Math.cos(rot) * fontSize * (i - dl))
              s2.getText().setText(t)
              vectorContext.drawFeature(f, ol_ext_getVectorContextStyle(e, s2))
            })
          } else {
            vectorContext.drawFeature(f, ol_ext_getVectorContextStyle(e, s))
          }
          /* OLD VERSION OL < 4.3
          // Retina device
          var ratio = e.frameState.pixelRatio;
  
          var sc;
          // OL < v4.3 : setImageStyle doesn't check retina
          var imgs = ol_Map.prototype.getFeaturesAtPixel ? false : s.getImage();
          if (imgs)
          {	sc = imgs.getScale();
            imgs.setScale(sc*ratio);
          }
          // OL3 > v3.14
          if (vectorContext.setStyle)
          {	// If one feature: draw the feature
            if (c.f.get("features").length===1 && !dx && !dy) {
              vectorContext.drawFeature(c.f.get("features")[0], s);
            }
            // else draw a point
            else {
              vectorContext.setStyle(s);
              vectorContext.drawGeometry(geo);
            }
          }
          // older version
          else
          {	vectorContext.setImageStyle(imgs);
            vectorContext.setTextStyle(s.getText());
            vectorContext.drawPointGeometry(geo);
          }
          if (imgs) imgs.setScale(sc);
          */
        }
      }
      e.context.restore()
      // tell ol to continue postcompose animation
      e.frameState.animate = true

      // Prevent layer drawing (clip with null rect)
      e.context.save()
      e.context.beginPath()
      e.context.rect(0, 0, 0, 0)
      e.context.clip()
      this.clip_ = true
    }

    return
  }
  /**
   * remove clipping after the layer is drawn
   * @private
   */
  postanimate(e) {
    if (this.clip_) {
      e.context.restore()
      this.clip_ = false
    }
  }
}

export default ol_layer_AnimatedCluster
