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

#include "ui/gfx/geometry/matrix3_f.h"

#include <string.h>

#include <algorithm>
#include <cmath>
#include <limits>

#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/strings/stringprintf.h"

namespace {

// This is only to make accessing indices self-explanatory.
enum MatrixCoordinates {
  M00,
  M01,
  M02,
  M10,
  M11,
  M12,
  M20,
  M21,
  M22,
  M_END
};

template <typename T>
double Determinant3x3(base::span<T, M_END> data) {
  // This routine is separated from the Matrix3F::Determinant because in
  // computing inverse we do want higher precision afforded by the explicit
  // use of 'double'.
  return static_cast<double>(data[M00]) *
             (static_cast<double>(data[M11]) * data[M22] -
              static_cast<double>(data[M12]) * data[M21]) +
         static_cast<double>(data[M01]) *
             (static_cast<double>(data[M12]) * data[M20] -
              static_cast<double>(data[M10]) * data[M22]) +
         static_cast<double>(data[M02]) *
             (static_cast<double>(data[M10]) * data[M21] -
              static_cast<double>(data[M11]) * data[M20]);
}

}  // namespace

namespace gfx {

Matrix3F::Matrix3F() {
}

Matrix3F::~Matrix3F() {
}

// static
Matrix3F Matrix3F::Zeros() {
  Matrix3F matrix;
  matrix.set(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
  return matrix;
}

// static
Matrix3F Matrix3F::Ones() {
  Matrix3F matrix;
  matrix.set(1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f);
  return matrix;
}

// static
Matrix3F Matrix3F::Identity() {
  Matrix3F matrix;
  matrix.set(1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
  return matrix;
}

// static
Matrix3F Matrix3F::FromOuterProduct(const Vector3dF& a, const Vector3dF& bt) {
  Matrix3F matrix;
  matrix.set(a.x() * bt.x(), a.x() * bt.y(), a.x() * bt.z(),
             a.y() * bt.x(), a.y() * bt.y(), a.y() * bt.z(),
             a.z() * bt.x(), a.z() * bt.y(), a.z() * bt.z());
  return matrix;
}

bool Matrix3F::IsEqual(const Matrix3F& rhs) const {
  return data_ == rhs.data_;
}

bool Matrix3F::IsNear(const Matrix3F& rhs, float precision) const {
  DCHECK(precision >= 0);
  for (int i = 0; i < M_END; ++i) {
    if (std::abs(data_[i] - rhs.data_[i]) > precision)
      return false;
  }
  return true;
}

Matrix3F Matrix3F::Add(const Matrix3F& rhs) const {
  Matrix3F result;
  for (int i = 0; i < M_END; ++i)
    result.data_[i] = data_[i] + rhs.data_[i];
  return result;
}

Matrix3F Matrix3F::Subtract(const Matrix3F& rhs) const {
  Matrix3F result;
  for (int i = 0; i < M_END; ++i)
    result.data_[i] = data_[i] - rhs.data_[i];
  return result;
}

Matrix3F Matrix3F::Inverse() const {
  Matrix3F inverse = Matrix3F::Zeros();
  double determinant = Determinant3x3(base::span(data_));
  if (std::numeric_limits<float>::epsilon() > std::abs(determinant))
    return inverse;  // Singular matrix. Return Zeros().

  inverse.set(
      static_cast<float>((data_[M11] * data_[M22] - data_[M12] * data_[M21]) /
          determinant),
      static_cast<float>((data_[M02] * data_[M21] - data_[M01] * data_[M22]) /
          determinant),
      static_cast<float>((data_[M01] * data_[M12] - data_[M02] * data_[M11]) /
          determinant),
      static_cast<float>((data_[M12] * data_[M20] - data_[M10] * data_[M22]) /
          determinant),
      static_cast<float>((data_[M00] * data_[M22] - data_[M02] * data_[M20]) /
          determinant),
      static_cast<float>((data_[M02] * data_[M10] - data_[M00] * data_[M12]) /
          determinant),
      static_cast<float>((data_[M10] * data_[M21] - data_[M11] * data_[M20]) /
          determinant),
      static_cast<float>((data_[M01] * data_[M20] - data_[M00] * data_[M21]) /
          determinant),
      static_cast<float>((data_[M00] * data_[M11] - data_[M01] * data_[M10]) /
          determinant));
  return inverse;
}

Matrix3F Matrix3F::Transpose() const {
  Matrix3F transpose;
  transpose.set(data_[M00], data_[M10], data_[M20], data_[M01], data_[M11],
                data_[M21], data_[M02], data_[M12], data_[M22]);
  return transpose;
}

float Matrix3F::Determinant() const {
  return static_cast<float>(Determinant3x3(base::span(data_)));
}

Matrix3F MatrixProduct(const Matrix3F& lhs, const Matrix3F& rhs) {
  Matrix3F result = Matrix3F::Zeros();
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
      result.set(i, j, DotProduct(lhs.get_row(i), rhs.get_column(j)));
    }
  }
  return result;
}

Vector3dF MatrixProduct(const Matrix3F& lhs, const Vector3dF& rhs) {
  return Vector3dF(DotProduct(lhs.get_row(0), rhs),
                   DotProduct(lhs.get_row(1), rhs),
                   DotProduct(lhs.get_row(2), rhs));
}

std::string Matrix3F::ToString() const {
  return base::StringPrintf(
      "[[%+0.4f, %+0.4f, %+0.4f],"
      " [%+0.4f, %+0.4f, %+0.4f],"
      " [%+0.4f, %+0.4f, %+0.4f]]",
      data_[M00], data_[M01], data_[M02], data_[M10], data_[M11], data_[M12],
      data_[M20], data_[M21], data_[M22]);
}

}  // namespace gfx
