'use strict'

import { Container } from '@pixi/display';

import { Effect } from './effect';
import { Vector } from '../vector';

/**
 * A zooming effect that involves the camera zooming in to a particular point on the container.
 */
export class ZoomTo extends Effect {
  /**
   * The zoom level to zoom to with values larger than 1 being zoomed in and values smaller than 1 being zoomed out.
   * 
   * @private
   * 
   * @property {Vector}
   */
  private _desiredZoomLevel: Vector;

  /**
   * A reference to the easing function to use for this effect.
   * 
   * @private
   * 
   * @property {Function}
   */
  private _easing: Function;

  /**
   * A reference to the initial zoom level.
   * 
   * @private
   * 
   * @property {Vector}
   */
  private _initialZoomLevel: Vector;

  /**
   * Indicates whether the desired x zoom level is greater than the current zoom level or not.
   * 
   * @private
   * 
   * @property {boolean}
   */
  private _xIsGreater = false;

  /**
   * Indicates whether the desired y zoom level is greater than the current zoom level or not.
   * 
   * @private
   * 
   * @property {boolean}
   */
  private _yIsGreater = false;

  /**
   * @param {Container} container A reference to the container to apply the zoomto effect to.
   * @param {number} xZoomLevel The zoom level to zoom horizontally with values larger than 1 being zoomed in and values smaller than 1 being zoomed out.
   * @param {number} yZoomLevel The zoom level to zoom vertically with values larger than 1 being zoomed in and values smaller than 1 being zoomed out.
   * @param {number} duration The amount of time, in milliseconds, that the effect should take.
   * @param {Function} [easing] The easing function that should be used.
   */
  constructor(container: Container, xZoomLevel: number, yZoomLevel: number, duration: number, easing?: Function) {
    super(container);

    this.duration = duration;
    this._desiredZoomLevel = { x: xZoomLevel, y: yZoomLevel };
    this._easing = easing || this.easeLinear;

    this._initialZoomLevel = { x: this.container.scale.x, y: this.container.scale.y };

    if (this._desiredZoomLevel.x > this._initialZoomLevel.x) this._xIsGreater = true;
    if (this._desiredZoomLevel.y > this._initialZoomLevel.y) this._yIsGreater = true;
  }

  /**
   * Updates the status of this effect on a frame by frame basis.
   */
  update() {
    if (this.criteriaMet() || this.current > this.duration) {
      this.finished.dispatch();
      return;
    }

    this.current = performance.now();

    const timeDiffPercentage: number = (this.current - this.started) / this.duration;
    const percentageThroughAnimation: number = this._easing(timeDiffPercentage);

    const xZoomAmount: number = this._desiredZoomLevel.x * percentageThroughAnimation;
    const yZoomAmount: number = this._desiredZoomLevel.y * percentageThroughAnimation;

    this.container.scale.x = this._xIsGreater ? this._initialZoomLevel.x + xZoomAmount / 2 : this._initialZoomLevel.x - xZoomAmount;
    this.container.scale.y = this._yIsGreater ? this._initialZoomLevel.y + yZoomAmount / 2 : this._initialZoomLevel.y - yZoomAmount;

    if (this.useRAF) this.id = requestAnimationFrame(() => this.update());
  }

  /**
   * Checks to see if the container's current zoom level is very close to the desired zoom level.
   * 
   * We can't use container zoom == desired zoom because with the game loop we might miss that exact moment so we check a very small window.
   * 
   * @private
   * 
   * @returns {boolean} Returns true if the zoom criteria is met or false otherwise.
   */
  criteriaMet(): boolean {
    if (
      (this.container.scale.x > this._desiredZoomLevel.x - 0.01 && this.container.scale.x < this._desiredZoomLevel.x + 0.01) &&
      (this.container.scale.y > this._desiredZoomLevel.y - 0.01 && this.container.scale.y < this._desiredZoomLevel.y + 0.01) 
    ) return true;

    return false;
  }
}