import ol_interaction_Clip from './Clip.js'
import ol_coordinate_cspline from '../render/Cspline.js'

/** Blob interaction to clip layers in a blob
 * @constructor
 * @extends {ol_interaction_Clip}
 * @param {*} options blob  options
 *  @param {number} options.radius radius of the clip, default 100
 *	@param {ol.layer|Array<ol.layer>} options.layers layers to clip
 *	@param {number} [options.stiffness=20] spring stiffness coef, default 20
 *	@param {number} [options.damping=7] spring damping coef
 *	@param {number} [options.mass=1] blob mass
 *	@param {number} [options.points=10] number of points for the blob polygon
 *	@param {number} [options.tension=.5] blob polygon spline tension 
 *	@param {number} [options.fuss] bob fussing factor
 *	@param {number} [options.amplitude=1] blob deformation amplitude factor
 */
var ol_interaction_Blob = class olinteractionBlob extends ol_interaction_Clip {
  constructor(options) {
    super(options);
  }
  /** Animate the blob
   * @private
   */
  precompose_(e) {
    if (!this.getActive())
      return;
    var ctx = e.context;
    var ratio = e.frameState.pixelRatio;

    ctx.save();
    if (!this.pos) {
      ctx.beginPath();
      ctx.moveTo(0, 0);
      ctx.clip();
      return;
    }

    var pt = [this.pos[0], this.pos[1]];
    var tr = e.inversePixelTransform;
    if (tr) {
      pt = [
        (pt[0] * tr[0] - pt[1] * tr[1] + tr[4]),
        (-pt[0] * tr[2] + pt[1] * tr[3] + tr[5])
      ];
    } else {
      pt[0] *= ratio;
      pt[1] *= ratio;
    }

    // Time laps
    if (!this.frame)
      this.frame = e.frameState.time;
    var dt = e.frameState.time - this.frame;
    this.frame = e.frameState.time;
    // Blob position
    pt = this._getCenter(pt, dt);
    // Blob geom
    var blob = this._calculate(dt);
    // Draw
    var p = blob[0];
    ctx.beginPath();
    ctx.moveTo(pt[0] + p[0], pt[1] + p[1]);
    for (var i = 1; p = blob[i]; i++) {
      ctx.lineTo(pt[0] + p[0], pt[1] + p[1]);
    }
    ctx.clip();
    e.frameState.animate = true;
  }
  /** Get blob center with kinetic
   * @param {number} dt0 time laps
   * @private
   */
  _getCenter(pt, dt0) {
    if (!this._center) {
      this._center = pt;
      this._velocity = [0, 0];
    } else {
      var k = this.get('stiffness') || 20; // stiffness
      var d = -1 * (this.get('damping') || 7); // damping
      var mass = Math.max(this.get('mass') || 1, .1);
      var dt = Math.min(dt0 / 1000, 1 / 30);

      var fSpring = [
        k * (pt[0] - this._center[0]),
        k * (pt[1] - this._center[1])
      ];
      var fDamping = [
        d * this._velocity[0],
        d * this._velocity[1]
      ];

      var accel = [
        (fSpring[0] + fDamping[0]) / mass,
        (fSpring[1] + fDamping[1]) / mass
      ];

      this._velocity[0] += accel[0] * dt;
      this._velocity[1] += accel[1] * dt;

      this._center[0] += this._velocity[0] * dt;
      this._center[1] += this._velocity[1] * dt;
    }
    return this._center;
  }
  /** Calculate the blob geom
   * @param {number} dt time laps
   * @returns {Array<ol_coordinate>}
   * @private
   */
  _calculate(dt) {
    var i, nb = this.get('points') || 10;
    if (!this._waves || this._waves.length !== nb) {
      this._waves = [];
      for (i = 0; i < nb; i++) {
        this._waves.push({
          angle: Math.random() * Math.PI,
          noise: Math.random()
        });
      }
    }
    var blob = [];
    var speed = (this._velocity[0] * this._velocity[0] + this._velocity[1] * this._velocity[1]) / 500;
    this._rotation = (this._rotation || 0) + (this._velocity[0] > 0 ? 1 : -1) * Math.min(.015, speed / 70000 * dt);
    for (i = 0; i < nb; i++) {
      var angle = i * 2 * Math.PI / nb + this._rotation;
      var radius = this.radius + Math.min(this.radius, speed);
      var delta = Math.cos(this._waves[i].angle) * radius / 4 * this._waves[i].noise * (this.get('amplitude') || 1);
      blob.push([
        (this.radius + delta) * Math.cos(angle),
        (this.radius + delta) * Math.sin(angle)
      ]);
      // Add noise
      this._waves[i].angle += (Math.PI + Math.random() + speed / 200) / 350 * dt * (this.get('fuss') || 1);
      this._waves[i].noise = Math.min(1, Math.max(0, this._waves[i].noise + (Math.random() - .5) * .1 * (this.get('fuss') || 1)));
    }
    blob.push(blob[0]);
    return ol_coordinate_cspline(blob, { tension: this.get('tension') });
  }
}

export default ol_interaction_Blob
