/**
 * @license
 * Copyright 2018 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview Methods animating a block on connection and disconnection.
 * @author fenichel@google.com (Rachel Fenichel)
 */
'use strict';

goog.provide('Blockly.blockAnimations');

goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.Svg');


/**
 * PID of disconnect UI animation.  There can only be one at a time.
 * @type {number}
 * @private
 */
Blockly.blockAnimations.disconnectPid_ = 0;

/**
 * SVG group of wobbling block.  There can only be one at a time.
 * @type {Element}
 * @private
 */
Blockly.blockAnimations.disconnectGroup_ = null;

/**
 * Play some UI effects (sound, animation) when disposing of a block.
 * @param {!Blockly.BlockSvg} block The block being disposed of.
 * @package
 */
Blockly.blockAnimations.disposeUiEffect = function(block) {
  var workspace = block.workspace;
  var svgGroup = block.getSvgRoot();
  workspace.getAudioManager().play('delete');

  var xy = workspace.getSvgXY(svgGroup);
  // Deeply clone the current block.
  var clone = svgGroup.cloneNode(true);
  clone.translateX_ = xy.x;
  clone.translateY_ = xy.y;
  clone.setAttribute('transform', 'translate(' + xy.x + ',' + xy.y + ')');
  workspace.getParentSvg().appendChild(clone);
  clone.bBox_ = clone.getBBox();
  // Start the animation.
  Blockly.blockAnimations.disposeUiStep_(clone, workspace.RTL, new Date,
      workspace.scale);
};

/**
 * Animate a cloned block and eventually dispose of it.
 * This is a class method, not an instance method since the original block has
 * been destroyed and is no longer accessible.
 * @param {!Element} clone SVG element to animate and dispose of.
 * @param {boolean} rtl True if RTL, false if LTR.
 * @param {!Date} start Date of animation's start.
 * @param {number} workspaceScale Scale of workspace.
 * @private
 */
Blockly.blockAnimations.disposeUiStep_ = function(clone, rtl, start,
    workspaceScale) {
  var ms = new Date - start;
  var percent = ms / 150;
  if (percent > 1) {
    Blockly.utils.dom.removeNode(clone);
  } else {
    var x = clone.translateX_ +
        (rtl ? -1 : 1) * clone.bBox_.width * workspaceScale / 2 * percent;
    var y = clone.translateY_ + clone.bBox_.height * workspaceScale * percent;
    var scale = (1 - percent) * workspaceScale;
    clone.setAttribute('transform', 'translate(' + x + ',' + y + ')' +
        ' scale(' + scale + ')');
    setTimeout(Blockly.blockAnimations.disposeUiStep_, 10, clone, rtl, start,
        workspaceScale);
  }
};

/**
 * Play some UI effects (sound, ripple) after a connection has been established.
 * @param {!Blockly.BlockSvg} block The block being connected.
 * @package
 */
Blockly.blockAnimations.connectionUiEffect = function(block) {
  var workspace = block.workspace;
  var scale = workspace.scale;
  workspace.getAudioManager().play('click');
  if (scale < 1) {
    return;  // Too small to care about visual effects.
  }
  // Determine the absolute coordinates of the inferior block.
  var xy = workspace.getSvgXY(block.getSvgRoot());
  // Offset the coordinates based on the two connection types, fix scale.
  if (block.outputConnection) {
    xy.x += (block.RTL ? 3 : -3) * scale;
    xy.y += 13 * scale;
  } else if (block.previousConnection) {
    xy.x += (block.RTL ? -23 : 23) * scale;
    xy.y += 3 * scale;
  }
  var ripple = Blockly.utils.dom.createSvgElement(
      Blockly.utils.Svg.CIRCLE,
      {
        'cx': xy.x,
        'cy': xy.y,
        'r': 0,
        'fill': 'none',
        'stroke': '#888',
        'stroke-width': 10
      },
      workspace.getParentSvg());
  // Start the animation.
  Blockly.blockAnimations.connectionUiStep_(ripple, new Date, scale);
};

/**
 * Expand a ripple around a connection.
 * @param {!SVGElement} ripple Element to animate.
 * @param {!Date} start Date of animation's start.
 * @param {number} scale Scale of workspace.
 * @private
 */
Blockly.blockAnimations.connectionUiStep_ = function(ripple, start, scale) {
  var ms = new Date - start;
  var percent = ms / 150;
  if (percent > 1) {
    Blockly.utils.dom.removeNode(ripple);
  } else {
    ripple.setAttribute('r', percent * 25 * scale);
    ripple.style.opacity = 1 - percent;
    Blockly.blockAnimations.disconnectPid_ = setTimeout(
        Blockly.blockAnimations.connectionUiStep_, 10, ripple, start, scale);
  }
};

/**
 * Play some UI effects (sound, animation) when disconnecting a block.
 * @param {!Blockly.BlockSvg} block The block being disconnected.
 * @package
 */
Blockly.blockAnimations.disconnectUiEffect = function(block) {
  block.workspace.getAudioManager().play('disconnect');
  if (block.workspace.scale < 1) {
    return;  // Too small to care about visual effects.
  }
  // Horizontal distance for bottom of block to wiggle.
  var DISPLACEMENT = 10;
  // Scale magnitude of skew to height of block.
  var height = block.getHeightWidth().height;
  var magnitude = Math.atan(DISPLACEMENT / height) / Math.PI * 180;
  if (!block.RTL) {
    magnitude *= -1;
  }
  // Start the animation.
  Blockly.blockAnimations.disconnectUiStep_(
      block.getSvgRoot(), magnitude, new Date);
};
/**
 * Animate a brief wiggle of a disconnected block.
 * @param {!SVGElement} group SVG element to animate.
 * @param {number} magnitude Maximum degrees skew (reversed for RTL).
 * @param {!Date} start Date of animation's start.
 * @private
 */
Blockly.blockAnimations.disconnectUiStep_ = function(group, magnitude, start) {
  var DURATION = 200;  // Milliseconds.
  var WIGGLES = 3;  // Half oscillations.

  var ms = new Date - start;
  var percent = ms / DURATION;

  if (percent > 1) {
    group.skew_ = '';
  } else {
    var skew = Math.round(
        Math.sin(percent * Math.PI * WIGGLES) * (1 - percent) * magnitude);
    group.skew_ = 'skewX(' + skew + ')';
    Blockly.blockAnimations.disconnectGroup_ = group;
    Blockly.blockAnimations.disconnectPid_ =
        setTimeout(Blockly.blockAnimations.disconnectUiStep_, 10, group,
            magnitude, start);
  }
  group.setAttribute('transform', group.translate_ + group.skew_);
};

/**
 * Stop the disconnect UI animation immediately.
 * @package
 */
Blockly.blockAnimations.disconnectUiStop = function() {
  if (Blockly.blockAnimations.disconnectGroup_) {
    clearTimeout(Blockly.blockAnimations.disconnectPid_);
    var group = Blockly.blockAnimations.disconnectGroup_;
    group.skew_ = '';
    group.setAttribute('transform', group.translate_);
    Blockly.blockAnimations.disconnectGroup_ = null;
  }
};
