import Cartesian2 from "../../Core/Cartesian2.js";
import defaultValue from "../../Core/defaultValue.js";
import defined from "../../Core/defined.js";
import DeveloperError from "../../Core/DeveloperError.js";
import EasingFunction from "../../Core/EasingFunction.js";
import SceneTransforms from "../../Scene/SceneTransforms.js";
import knockout from "../../ThirdParty/knockout.js";

var screenSpacePos = new Cartesian2();
var offScreen = "-1000px";

/**
 * The view model for {@link SelectionIndicator}.
 * @alias SelectionIndicatorViewModel
 * @constructor
 *
 * @param {Scene} scene The scene instance to use for screen-space coordinate conversion.
 * @param {Element} selectionIndicatorElement The element containing all elements that make up the selection indicator.
 * @param {Element} container The DOM element that contains the widget.
 */
function SelectionIndicatorViewModel(
  scene,
  selectionIndicatorElement,
  container
) {
  //>>includeStart('debug', pragmas.debug);
  if (!defined(scene)) {
    throw new DeveloperError("scene is required.");
  }

  if (!defined(selectionIndicatorElement)) {
    throw new DeveloperError("selectionIndicatorElement is required.");
  }

  if (!defined(container)) {
    throw new DeveloperError("container is required.");
  }
  //>>includeEnd('debug')

  this._scene = scene;
  this._screenPositionX = offScreen;
  this._screenPositionY = offScreen;
  this._tweens = scene.tweens;
  this._container = defaultValue(container, document.body);
  this._selectionIndicatorElement = selectionIndicatorElement;
  this._scale = 1;

  /**
   * Gets or sets the world position of the object for which to display the selection indicator.
   * @type {Cartesian3}
   */
  this.position = undefined;

  /**
   * Gets or sets the visibility of the selection indicator.
   * @type {Boolean}
   */
  this.showSelection = false;

  knockout.track(this, [
    "position",
    "_screenPositionX",
    "_screenPositionY",
    "_scale",
    "showSelection",
  ]);

  /**
   * Gets the visibility of the position indicator.  This can be false even if an
   * object is selected, when the selected object has no position.
   * @type {Boolean}
   */
  this.isVisible = undefined;
  knockout.defineProperty(this, "isVisible", {
    get: function () {
      return this.showSelection && defined(this.position);
    },
  });

  knockout.defineProperty(this, "_transform", {
    get: function () {
      return "scale(" + this._scale + ")";
    },
  });

  /**
   * Gets or sets the function for converting the world position of the object to the screen space position.
   *
   * @member
   * @type {SelectionIndicatorViewModel.ComputeScreenSpacePosition}
   * @default SceneTransforms.wgs84ToWindowCoordinates
   *
   * @example
   * selectionIndicatorViewModel.computeScreenSpacePosition = function(position, result) {
   *     return Cesium.SceneTransforms.wgs84ToWindowCoordinates(scene, position, result);
   * };
   */
  this.computeScreenSpacePosition = function (position, result) {
    return SceneTransforms.wgs84ToWindowCoordinates(scene, position, result);
  };
}

/**
 * Updates the view of the selection indicator to match the position and content properties of the view model.
 * This function should be called as part of the render loop.
 */
SelectionIndicatorViewModel.prototype.update = function () {
  if (this.showSelection && defined(this.position)) {
    var screenPosition = this.computeScreenSpacePosition(
      this.position,
      screenSpacePos
    );
    if (!defined(screenPosition)) {
      this._screenPositionX = offScreen;
      this._screenPositionY = offScreen;
    } else {
      var container = this._container;
      var containerWidth = container.parentNode.clientWidth;
      var containerHeight = container.parentNode.clientHeight;
      var indicatorSize = this._selectionIndicatorElement.clientWidth;
      var halfSize = indicatorSize * 0.5;

      screenPosition.x =
        Math.min(
          Math.max(screenPosition.x, -indicatorSize),
          containerWidth + indicatorSize
        ) - halfSize;
      screenPosition.y =
        Math.min(
          Math.max(screenPosition.y, -indicatorSize),
          containerHeight + indicatorSize
        ) - halfSize;

      this._screenPositionX = Math.floor(screenPosition.x + 0.25) + "px";
      this._screenPositionY = Math.floor(screenPosition.y + 0.25) + "px";
    }
  }
};

/**
 * Animate the indicator to draw attention to the selection.
 */
SelectionIndicatorViewModel.prototype.animateAppear = function () {
  this._tweens.addProperty({
    object: this,
    property: "_scale",
    startValue: 2,
    stopValue: 1,
    duration: 0.8,
    easingFunction: EasingFunction.EXPONENTIAL_OUT,
  });
};

/**
 * Animate the indicator to release the selection.
 */
SelectionIndicatorViewModel.prototype.animateDepart = function () {
  this._tweens.addProperty({
    object: this,
    property: "_scale",
    startValue: this._scale,
    stopValue: 1.5,
    duration: 0.8,
    easingFunction: EasingFunction.EXPONENTIAL_OUT,
  });
};

Object.defineProperties(SelectionIndicatorViewModel.prototype, {
  /**
   * Gets the HTML element containing the selection indicator.
   * @memberof SelectionIndicatorViewModel.prototype
   *
   * @type {Element}
   */
  container: {
    get: function () {
      return this._container;
    },
  },

  /**
   * Gets the HTML element that holds the selection indicator.
   * @memberof SelectionIndicatorViewModel.prototype
   *
   * @type {Element}
   */
  selectionIndicatorElement: {
    get: function () {
      return this._selectionIndicatorElement;
    },
  },

  /**
   * Gets the scene being used.
   * @memberof SelectionIndicatorViewModel.prototype
   *
   * @type {Scene}
   */
  scene: {
    get: function () {
      return this._scene;
    },
  },
});

/**
 * A function that converts the world position of an object to a screen space position.
 * @callback SelectionIndicatorViewModel.ComputeScreenSpacePosition
 * @param {Cartesian3} position The position in WGS84 (world) coordinates.
 * @param {Cartesian2} result An object to return the input position transformed to window coordinates.
 * @returns {Cartesian2} The modified result parameter.
 */
export default SelectionIndicatorViewModel;
