// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "flutter/flow/matrix_decomposition.h"

namespace flutter {

static inline SkVector3 SkVector3Combine(const SkVector3& a,
                                         float a_scale,
                                         const SkVector3& b,
                                         float b_scale) {
  return {
      a_scale * a.fX + b_scale * b.fX,  //
      a_scale * a.fY + b_scale * b.fY,  //
      a_scale * a.fZ + b_scale * b.fZ,  //
  };
}

static inline SkVector3 SkVector3Cross(const SkVector3& a, const SkVector3& b) {
  return {
      (a.fY * b.fZ) - (a.fZ * b.fY),  //
      (a.fZ * b.fX) - (a.fX * b.fZ),  //
      (a.fX * b.fY) - (a.fY * b.fX)   //
  };
}

MatrixDecomposition::MatrixDecomposition(const SkMatrix& matrix)
    : MatrixDecomposition(SkMatrix44{matrix}) {}

// Use custom normalize to avoid skia precision loss/normalize() privatization.
static inline void SkVector3Normalize(SkVector3& v) {
  double mag = sqrt(v.fX * v.fX + v.fY * v.fY + v.fZ * v.fZ);
  double scale = 1.0 / mag;
  v.fX *= scale;
  v.fY *= scale;
  v.fZ *= scale;
}

MatrixDecomposition::MatrixDecomposition(SkMatrix44 matrix) : valid_(false) {
  if (matrix.get(3, 3) == 0) {
    return;
  }

  for (int i = 0; i < 4; i++) {
    for (int j = 0; j < 4; j++) {
      matrix.set(j, i, matrix.get(j, i) / matrix.get(3, 3));
    }
  }

  SkMatrix44 perpective_matrix = matrix;
  for (int i = 0; i < 3; i++) {
    perpective_matrix.set(3, i, 0.0);
  }

  perpective_matrix.set(3, 3, 1.0);

  if (perpective_matrix.determinant() == 0.0) {
    return;
  }

  if (matrix.get(3, 0) != 0.0 || matrix.get(3, 1) != 0.0 ||
      matrix.get(3, 2) != 0.0) {
    const SkVector4 right_hand_side(matrix.get(3, 0), matrix.get(3, 1),
                                    matrix.get(3, 2), matrix.get(3, 3));

    SkMatrix44 inverted_transposed(
        SkMatrix44::Uninitialized_Constructor::kUninitialized_Constructor);
    if (!perpective_matrix.invert(&inverted_transposed)) {
      return;
    }
    inverted_transposed.transpose();

    perspective_ = inverted_transposed * right_hand_side;

    matrix.set(3, 0, 0);
    matrix.set(3, 1, 0);
    matrix.set(3, 2, 0);
    matrix.set(3, 3, 1);
  }

  translation_ = {matrix.get(0, 3), matrix.get(1, 3), matrix.get(2, 3)};

  matrix.set(0, 3, 0.0);
  matrix.set(1, 3, 0.0);
  matrix.set(2, 3, 0.0);

  SkVector3 row[3];
  for (int i = 0; i < 3; i++) {
    row[i].set(matrix.get(0, i), matrix.get(1, i), matrix.get(2, i));
  }

  scale_.fX = row[0].length();

  SkVector3Normalize(row[0]);

  shear_.fX = row[0].dot(row[1]);
  row[1] = SkVector3Combine(row[1], 1.0, row[0], -shear_.fX);

  scale_.fY = row[1].length();

  SkVector3Normalize(row[1]);

  shear_.fX /= scale_.fY;

  shear_.fY = row[0].dot(row[2]);
  row[2] = SkVector3Combine(row[2], 1.0, row[0], -shear_.fY);
  shear_.fZ = row[1].dot(row[2]);
  row[2] = SkVector3Combine(row[2], 1.0, row[1], -shear_.fZ);

  scale_.fZ = row[2].length();

  SkVector3Normalize(row[2]);

  shear_.fY /= scale_.fZ;
  shear_.fZ /= scale_.fZ;

  if (row[0].dot(SkVector3Cross(row[1], row[2])) < 0) {
    scale_.fX *= -1;
    scale_.fY *= -1;
    scale_.fZ *= -1;

    for (int i = 0; i < 3; i++) {
      row[i].fX *= -1;
      row[i].fY *= -1;
      row[i].fZ *= -1;
    }
  }

  rotation_.set(0.5 * sqrt(fmax(1.0 + row[0].fX - row[1].fY - row[2].fZ, 0.0)),
                0.5 * sqrt(fmax(1.0 - row[0].fX + row[1].fY - row[2].fZ, 0.0)),
                0.5 * sqrt(fmax(1.0 - row[0].fX - row[1].fY + row[2].fZ, 0.0)),
                0.5 * sqrt(fmax(1.0 + row[0].fX + row[1].fY + row[2].fZ, 0.0)));

  if (row[2].fY > row[1].fZ) {
    rotation_.fData[0] = -rotation_.fData[0];
  }
  if (row[0].fZ > row[2].fX) {
    rotation_.fData[1] = -rotation_.fData[1];
  }
  if (row[1].fX > row[0].fY) {
    rotation_.fData[2] = -rotation_.fData[2];
  }

  valid_ = true;
}

MatrixDecomposition::~MatrixDecomposition() = default;

bool MatrixDecomposition::IsValid() const {
  return valid_;
}

}  // namespace flutter