import { Point } from "./objects/point";
const PI_2 = Math.PI * 2;

/**
 * | a | c | tx|
 * | b | d | ty|
 * | 0 | 0 | 1 |
 * ```
 */
export class Matrix {
  a = 1;
  b = 0;
  c = 0;
  d = 1;
  tx = 0;
  ty = 0;

  constructor(a = 1, b = 0, c = 0, d = 1, tx = 0, ty = 0) {
    this.a = a;

    this.b = b;
    this.c = c;

    this.d = d;

    this.tx = tx;
    this.ty = ty;
  }

  static createFromDiv(div: HTMLElement) {
    const out = new Matrix();
    out.setFormDiv(div);
    return out;
  }

  //matrix(1,0,0,1,0,0)
  static createFromMatrixStr(str: string) {
    const out = new Matrix();
    out.setMatrixStr(str);
    return out;
  }

  setMatrixStr(transformMatrix:string) {
    const values = transformMatrix.split("(")[1].split(")")[0].split(",");
    this.a = +values[0];
    this.b = +values[1];
    this.c = +values[2];
    this.d = +values[3];

    this.tx = +values[4]; 
    this.ty = +values[5];
  }

  setFormDiv(div: HTMLElement) {
    let transformMatrix = window
      .getComputedStyle(div)
      .getPropertyValue("transform");
    if (!transformMatrix || transformMatrix === "none") {
      transformMatrix = "matrix(1,0,0,1,0,0)";
    }
    this.setMatrixStr(transformMatrix);
  }

  applyDiv(div: HTMLElement) {
    div.style.transform = this.getMatrixStr();
    div.style.transformOrigin = "0 0";
    return this;
  }

  getMatrixStr() {
    return `matrix(${this.a},${this.b},${this.c},${this.d},${this.tx},${this.ty})`;
  }
  
  fromArray(array: number[]) {
    this.a = array[0];
    this.b = array[1];
    this.c = array[3];
    this.d = array[4];
    this.tx = array[2];
    this.ty = array[5];
  }

  set(a: number, b: number, c: number, d: number, tx: number, ty: number) {
    this.a = a;
    this.b = b;
    this.c = c;
    this.d = d;
    this.tx = tx;
    this.ty = ty;

    return this;
  }

  array = new Float32Array(9);
  toArray(transpose: boolean, out: any) {
    if (!this.array) {
      this.array = new Float32Array(9);
    }

    const array = out || this.array;

    if (transpose) {
      array[0] = this.a;
      array[1] = this.b;
      array[2] = 0;
      array[3] = this.c;
      array[4] = this.d;
      array[5] = 0;
      array[6] = this.tx;
      array[7] = this.ty;
      array[8] = 1;
    } else {
      array[0] = this.a;
      array[1] = this.c;
      array[2] = this.tx;
      array[3] = this.b;
      array[4] = this.d;
      array[5] = this.ty;
      array[6] = 0;
      array[7] = 0;
      array[8] = 1;
    }

    return array;
  }

  apply(pos: Point, newPos?: Point) {
    newPos = newPos || new Point();
    const x = pos.x;
    const y = pos.y;
    newPos.x = this.a * x + this.c * y + this.tx;
    newPos.y = this.b * x + this.d * y + this.ty;
    return newPos;
  }

  applyInverse(pos: Point, newPos?: Point) {
    newPos = newPos || new Point();

    const id = 1 / (this.a * this.d + this.c * -this.b);

    const x = pos.x;
    const y = pos.y;

    newPos.x =
      this.d * id * x +
      -this.c * id * y +
      (this.ty * this.c - this.tx * this.d) * id;
    newPos.y =
      this.a * id * y +
      -this.b * id * x +
      (-this.ty * this.a + this.tx * this.b) * id;

    return newPos;
  }

  translate(x: number, y: number) {
    this.tx += x;
    this.ty += y;

    return this;
  }
  scale(x: number, y: number) {
    this.a *= x;
    this.d *= y;
    this.c *= x;
    this.b *= y;
    this.tx *= x;
    this.ty *= y;

    return this;
  }

  getScale() {
    const a = this.a, b = this.b, c = this.c, d = this.d;
    const x = Math.sqrt((a * a) + (b * b));
    const y = Math.sqrt((c * c) + (d * d));
    return {x, y};
  }
  
  rotate(angle: number) {
    const cos = Math.cos(angle);
    const sin = Math.sin(angle);

    const a1 = this.a;
    const c1 = this.c;
    const tx1 = this.tx;

    this.a = a1 * cos - this.b * sin;
    this.b = a1 * sin + this.b * cos;
    this.c = c1 * cos - this.d * sin;
    this.d = c1 * sin + this.d * cos;
    this.tx = tx1 * cos - this.ty * sin;
    this.ty = tx1 * sin + this.ty * cos;

    return this;
  }
  getRotate() {
    const b = this.b;
    const a = this.a;
    return Math.atan2(b, a);
  }
  

  append(matrix: Matrix) {
    const a1 = this.a;
    const b1 = this.b;
    const c1 = this.c;
    const d1 = this.d;

    this.a = matrix.a * a1 + matrix.b * c1;
    this.b = matrix.a * b1 + matrix.b * d1;
    this.c = matrix.c * a1 + matrix.d * c1;
    this.d = matrix.c * b1 + matrix.d * d1;

    this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx;
    this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty;

    return this;
  }

  setTransform(
    x: number,
    y: number,
    pivotX: number,
    pivotY: number,
    scaleX: number,
    scaleY: number,
    rotation: number,
    skewX: number,
    skewY: number
  ) {
    this.a = Math.cos(rotation + skewY) * scaleX;
    this.b = Math.sin(rotation + skewY) * scaleX;
    this.c = -Math.sin(rotation - skewX) * scaleY;
    this.d = Math.cos(rotation - skewX) * scaleY;

    this.tx = x - (pivotX * this.a + pivotY * this.c);
    this.ty = y - (pivotX * this.b + pivotY * this.d);

    return this;
  }

  prepend(matrix: Matrix) {
    const tx1 = this.tx;

    if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) {
      const a1 = this.a;
      const c1 = this.c;

      this.a = a1 * matrix.a + this.b * matrix.c;
      this.b = a1 * matrix.b + this.b * matrix.d;
      this.c = c1 * matrix.a + this.d * matrix.c;
      this.d = c1 * matrix.b + this.d * matrix.d;
    }

    this.tx = tx1 * matrix.a + this.ty * matrix.c + matrix.tx;
    this.ty = tx1 * matrix.b + this.ty * matrix.d + matrix.ty;

    return this;
  }

  decompose(transform:any)
  {
      // sort out rotation / skew..
      const a = this.a;
      const b = this.b;
      const c = this.c;
      const d = this.d;

      const skewX = -Math.atan2(-c, d);
      const skewY = Math.atan2(b, a);

      const delta = Math.abs(skewX + skewY);

      transform.rotation = skewY;
      transform.skew.x = transform.skew.y = 0;

      if (delta < 0.00001 || Math.abs(PI_2 - delta) < 0.00001)
      {
          transform.rotation = skewY;
          transform.skew.x = transform.skew.y = 0;
      }
      else
      {
          transform.rotation = 0;
          transform.skew.x = skewX;
          transform.skew.y = skewY;
      }

      // next set scale
      transform.scale.x = Math.sqrt((a * a) + (b * b));
      transform.scale.y = Math.sqrt((c * c) + (d * d));

      // next set position
      transform.position.x = this.tx;
      transform.position.y = this.ty;

      return transform;
  }

  invert() {
    const a1 = this.a;
    const b1 = this.b;
    const c1 = this.c;
    const d1 = this.d;
    const tx1 = this.tx;
    const n = a1 * d1 - b1 * c1;

    this.a = d1 / n;
    this.b = -b1 / n;
    this.c = -c1 / n;
    this.d = a1 / n;
    this.tx = (c1 * this.ty - d1 * tx1) / n;
    this.ty = -(a1 * this.ty - b1 * tx1) / n;

    return this;
  }

  identity() {
    this.a = 1;
    this.b = 0;
    this.c = 0;
    this.d = 1;
    this.tx = 0;
    this.ty = 0;

    return this;
  }

  clone() {
    const matrix = new Matrix();

    matrix.a = this.a;
    matrix.b = this.b;
    matrix.c = this.c;
    matrix.d = this.d;
    matrix.tx = this.tx;
    matrix.ty = this.ty;

    return matrix;
  }

  copyTo(matrix: Matrix) {
    matrix.a = this.a;
    matrix.b = this.b;
    matrix.c = this.c;
    matrix.d = this.d;
    matrix.tx = this.tx;
    matrix.ty = this.ty;

    return matrix;
  }

  copyFrom(matrix: Matrix) {
    this.a = matrix.a;
    this.b = matrix.b;
    this.c = matrix.c;
    this.d = matrix.d;
    this.tx = matrix.tx;
    this.ty = matrix.ty;

    return this;
  }

  static get IDENTITY() {
    return new Matrix();
  }

  static get TEMP_MATRIX() {
    return new Matrix();
  }
}
