import { RAD_TO_DEG, DEG_TO_RAD } from "./const";
import { Bounds } from "./bounds";
import { Point } from "./point";
import { Transform } from "./transform";
import { Rectangle } from "./rectangle";

export class DisplayObject {

  tempDisplayObjectParent?:DisplayObject;

transform = new Transform();

  visible = true;

  parent?:any;

  alpha = 1;

  /**
   * The multiplied alpha of the displayObject.
   */
  worldAlpha = 1;

  /**
   * Which index in the children array the display component was before the previous zIndex sort.
   * Used by containers to help sort objects with the same zIndex, by using previous array index as the decider.
   */
  _lastSortedIndex = 0;

  /**
   * The zIndex of the displayObject.
   * A higher value will mean it will be rendered on top of other displayObjects within the same container.
   */
  _zIndex = 0;

  /**
   * The bounds object, this is used to calculate and store the bounds of the displayObject.
   */
  _bounds = new Bounds();
  _boundsID = 0;
  _lastBoundsID = -1;
  _boundsRect?:Rectangle;

  _localBoundsRect?:Rectangle;

  /**
   * If the object has been destroyed via destroy(). If true, it should not be used.
   */
  _destroyed = false;

  get _tempDisplayObjectParent() {
    if (!this.tempDisplayObjectParent) {
      this.tempDisplayObjectParent = new DisplayObject();
    }

    return this.tempDisplayObjectParent as DisplayObject;
  }

  /**
   * Updates the object transform for rendering.
   * TODO - Optimization pass!
   */
  updateTransform() {
    if (this.parent) {
      this.transform.updateTransform(this.parent.transform);
      // multiply the alphas..
      this.worldAlpha = this.alpha * this.parent.worldAlpha;
    } else {
      this.transform.updateTransform(this._tempDisplayObjectParent.transform);
      this.worldAlpha = this.alpha;
    }

    this._boundsID++;
  }

  /**
   * Recursively updates transform of all objects from the root to this one
   * internal function for toLocal()
   */
  _recursivePostUpdateTransform() {
    if (this.parent) {
      this.parent._recursivePostUpdateTransform();
      this.transform.updateTransform(this.parent.transform);
    } else {
      this.transform.updateTransform(this._tempDisplayObjectParent.transform);
    }
  }
  
  calculateBounds() {
    console.log("please implement me")
  }

  getBounds(skipUpdate:boolean, rect?:Rectangle) {
    if (!skipUpdate) {
      if (!this.parent) {
        this.parent = this._tempDisplayObjectParent;
        this.updateTransform();
        this.parent = undefined;
      } else {
        this._recursivePostUpdateTransform();
        this.updateTransform();
      }
    }

    if (this._boundsID !== this._lastBoundsID) {
      this.calculateBounds();
      this._lastBoundsID = this._boundsID;
    }

    if (!rect) {
      if (!this._boundsRect) {
        this._boundsRect = new Rectangle();
      }

      rect = this._boundsRect;
    }

    return this._bounds.getRectangle(rect);
  }

  getLocalBounds(rect?:Rectangle) {
    const transformRef = this.transform;
    const parentRef = this.parent;

    this.parent = undefined;
    this.transform = this._tempDisplayObjectParent.transform;

    if (!rect) {
      if (!this._localBoundsRect) {
        this._localBoundsRect = new Rectangle();
      }

      rect = this._localBoundsRect;
    }

    const bounds = this.getBounds(false, rect);

    this.parent = parentRef;
    this.transform = transformRef;

    return bounds;
  }

  /**
   * Calculates the global position of the display object.
   */
  toGlobal(position:Point, point:Point, skipUpdate = false) {
    if (!skipUpdate) {
      this._recursivePostUpdateTransform();

      // this parent check is for just in case the item is a root object.
      // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly
      // this is mainly to avoid a parent check in the main loop. Every little helps for performance :)
      if (!this.parent) {
        this.parent = this._tempDisplayObjectParent;
        this.displayObjectUpdateTransform();
        this.parent = undefined;
      } else {
        this.displayObjectUpdateTransform();
      }
    }
    // don't need to update the lot
    return this.worldTransform.apply(position, point);
  }

  /**
   * Calculates the local position of the display object relative to another point.
   *
   * @param {PIXI.IPoint} position - The world origin to calculate from.
   * @param {PIXI.DisplayObject} [from] - The DisplayObject to calculate the global position from.
   * @param {PIXI.IPoint} [point] - A Point object in which to store the value, optional
   *  (otherwise will create a new Point).
   * @param {boolean} [skipUpdate=false] - Should we skip the update transform
   * @return {PIXI.IPoint} A point object representing the position of this object
   */
  toLocal(position:Point, from:DisplayObject, point:Point, skipUpdate:boolean) {
    if (from) {
      position = from.toGlobal(position, point, skipUpdate);
    }

    if (!skipUpdate) {
      this._recursivePostUpdateTransform();

      // this parent check is for just in case the item is a root object.
      // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly
      // this is mainly to avoid a parent check in the main loop. Every little helps for performance :)
      if (!this.parent) {
        this.parent = this._tempDisplayObjectParent;
        this.displayObjectUpdateTransform();
        this.parent = undefined;
      } else {
        this.displayObjectUpdateTransform();
      }
    }

    // simply apply the matrix..
    return this.worldTransform.applyInverse(position, point);
  }

  setParent(container:any) {
    if (!container || !container.addChild) {
      throw new Error("setParent: Argument must be a Container");
    }

    container.addChild(this);

    return container;
  }

  setTransform(
    x = 0,
    y = 0,
    scaleX = 1,
    scaleY = 1,
    rotation = 0,
    skewX = 0,
    skewY = 0,
    pivotX = 0,
    pivotY = 0
  ) {
    this.position.x = x;
    this.position.y = y;
    this.scale.x = !scaleX ? 1 : scaleX;
    this.scale.y = !scaleY ? 1 : scaleY;
    this.rotation = rotation;
    this.skew.x = skewX;
    this.skew.y = skewY;
    this.pivot.x = pivotX;
    this.pivot.y = pivotY;

    return this;
  }

  /**
   * Base destroy method for generic display objects. This will automatically
   * remove the display object from its parent Container as well as remove
   * all current event listeners and internal references. Do not use a DisplayObject
   * after calling `destroy()`.
   *
   */
  sortDirty = false;

  destroy() {

    if (this.parent) {
      this.parent.removeChild(this);
    }


    this.parent = undefined;
    this._destroyed = true;
  }

  /**
   * The position of the displayObject on the x axis relative to the local coordinates of the parent.
   * An alias to position.x
   */
  get x() {
    return this.position.x;
  }

  set x( value // eslint-disable-line require-jsdoc
  ) {
    this.transform.position.x = value;
  }

  /**
   * The position of the displayObject on the y axis relative to the local coordinates of the parent.
   * An alias to position.y
   *
   * @member {number}
   */
  get y() {
    return this.position.y;
  }

  set y(
    value // eslint-disable-line require-jsdoc
  ) {
    this.transform.position.y = value;
  }

  /**
   * Current transform of the object based on world (parent) factors.
   *
   * @member {PIXI.Matrix}
   * @readonly
   */
  get worldTransform() {
    return this.transform.worldTransform;
  }

  get localTransform() {
    return this.transform.localTransform;
  }

  /**
   * The coordinate of the object relative to the local coordinates of the parent.
   * Assignment by value since pixi-v4.
   *
   * @member {PIXI.IPoint}
   */
  get position() {
    return this.transform.position;
  }

  set position(
    value // eslint-disable-line require-jsdoc
  ) {
    this.transform.position.copyFrom(value);
  }

  /**
   * The scale factor of the object.
   * Assignment by value since pixi-v4.
   *
   * @member {PIXI.IPoint}
   */
  get scale() {
    return this.transform.scale;
  }

  set scale(
    value // eslint-disable-line require-jsdoc
  ) {
    this.transform.scale.copyFrom(value);
  }

  /**
   * The pivot point of the displayObject that it rotates around.
   * Assignment by value since pixi-v4.
   *
   * @member {PIXI.IPoint}
   */
  get pivot() {
    return this.transform.pivot;
  }

  set pivot( value ) {
    this.transform.pivot.copyFrom(value);
  }

  //保持当前对象位置不变，修改pivot
  setPivotWorldNoChange(x:number, y:number) {
    const targetPivot = { x, y };
    const point = { x: targetPivot.x, y: targetPivot.y };
    this.worldTransform.apply(point as any, point as any);

    this.pivot = targetPivot as any;
    this.position.x = point.x;
    this.position.y = point.y;

    this.updateTransform();
  }
  /**
   * The skew factor for the object in radians.
   * Assignment by value since pixi-v4.
   */
  get skew() {
    return this.transform.skew;
  }

  set skew(
    value // eslint-disable-line require-jsdoc
  ) {
    this.transform.skew.copyFrom(value);
  }

  /**
   * The rotation of the object in radians.
   * 'rotation' and 'angle' have the same effect on a display object; rotation is in radians, angle is in degrees.
   */
  get rotation() {
    return this.transform.rotation;
  }

  set rotation(
    value // eslint-disable-line require-jsdoc
  ) {
    this.transform.rotation = value;
  }

  /**
   * The angle of the object in degrees.
   * 'rotation' and 'angle' have the same effect on a display object; rotation is in radians, angle is in degrees.
   *
   * @member {number}
   */
  get angle() {
    return this.transform.rotation * RAD_TO_DEG;
  }

  set angle(
    value // eslint-disable-line require-jsdoc
  ) {
    this.transform.rotation = value * DEG_TO_RAD;
  }

  /**
   * The zIndex of the displayObject.
   * If a container has the sortableChildren property set to true, children will be automatically
   * sorted by zIndex value; a higher value will mean it will be moved towards the end of the array,
   * and thus rendered on top of other displayObjects within the same container.
   *
   * @member {number}
   */
  get zIndex() {
    return this._zIndex;
  }

  set zIndex(
    value // eslint-disable-line require-jsdoc
  ) {
    this._zIndex = value;
    if (this.parent) {
      this.parent.sortDirty = true;
    }
  }

  get worldVisible() {
    let item:any = this;
    do {
      if (!item.visible) {
        return false;
      }

      item = item.parent;
    } while (item);
    return true;
  }

  getGlobalPosition( point = new Point(),skipUpdate = false ) {
    if (this.parent) {
      this.parent.toGlobal(this.position, point, skipUpdate);
    } else {
      point.x = this.position.x;
      point.y = this.position.y;
    }
    return point;
  }

  displayObjectUpdateTransform = this.updateTransform;
}

