// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0

#include "gsvector.h"

#include <cmath>

GSVector4i GSVector4i::rfit(const GSVector4i& fit_rect, const GSVector2i& image_size)
{
  const GSVector2 ffit_size = GSVector2(fit_rect.rsize());
  const GSVector2 fimage_size = GSVector2(image_size);
  const float fit_ar = ffit_size.x / ffit_size.y;
  const float image_ar = fimage_size.x / fimage_size.y;

  GSVector4i ret;
  if (fit_ar > image_ar)
  {
    // center horizontally
    const float width = ffit_size.y * image_ar;
    const float offset = (ffit_size.x - width) / 2.0f;
    const float height = ffit_size.y;
    ret = GSVector4i(GSVector4(offset, 0.0f, offset + width, height));
  }
  else
  {
    // center vertically
    const float height = ffit_size.x / image_ar;
    const float offset = (ffit_size.y - height) / 2.0f;
    const float width = ffit_size.x;
    ret = GSVector4i(GSVector4(0.0f, offset, width, offset + height));
  }

  return ret;
}

GSMatrix2x2::GSMatrix2x2(float e00, float e01, float e10, float e11)
{
  E[0][0] = e00;
  E[0][1] = e01;
  E[1][0] = e10;
  E[1][1] = e11;
}

GSMatrix2x2 GSMatrix2x2::operator*(const GSMatrix2x2& m) const
{
  GSMatrix2x2 ret;
  ret.E[0][0] = E[0][0] * m.E[0][0] + E[0][1] * m.E[1][0];
  ret.E[0][1] = E[0][0] * m.E[0][1] + E[0][1] * m.E[1][1];
  ret.E[1][0] = E[1][0] * m.E[0][0] + E[1][1] * m.E[1][0];
  ret.E[1][1] = E[1][0] * m.E[0][1] + E[1][1] * m.E[1][1];
  return ret;
}

GSVector2 GSMatrix2x2::operator*(const GSVector2& v) const
{
  return GSVector2(row(0).dot(v), row(1).dot(v));
}

GSMatrix2x2 GSMatrix2x2::Identity()
{
  GSMatrix2x2 ret;
  ret.E[0][0] = 1.0f;
  ret.E[0][1] = 0.0f;
  ret.E[1][0] = 0.0f;
  ret.E[1][1] = 1.0f;
  return ret;
}

GSMatrix2x2 GSMatrix2x2::Rotation(float angle_in_radians)
{
  const float sin_angle = std::sin(angle_in_radians);
  const float cos_angle = std::cos(angle_in_radians);

  GSMatrix2x2 ret;
  ret.E[0][0] = cos_angle;
  ret.E[0][1] = -sin_angle;
  ret.E[1][0] = sin_angle;
  ret.E[1][1] = cos_angle;
  return ret;
}

GSVector2 GSMatrix2x2::row(size_t i) const
{
  return GSVector2::load<true>(&E[i][0]);
}

GSVector2 GSMatrix2x2::col(size_t i) const
{
  return GSVector2(E[0][i], E[1][i]);
}

void GSMatrix2x2::store(void* m)
{
  std::memcpy(m, E, sizeof(E));
}

ALWAYS_INLINE static void MatrixMult4x4(GSMatrix4x4& res, const GSMatrix4x4& lhs, const GSMatrix4x4& rhs)
{
  // This isn't speedy by any means, but it's not hot code either.
#define MultRC(rw, cl)                                                                                                 \
  lhs.E[rw][0] * rhs.E[0][cl] + lhs.E[rw][1] * rhs.E[1][cl] + lhs.E[rw][2] * rhs.E[2][cl] + lhs.E[rw][3] * rhs.E[3][cl]

  res.E[0][0] = MultRC(0, 0);
  res.E[0][1] = MultRC(0, 1);
  res.E[0][2] = MultRC(0, 2);
  res.E[0][3] = MultRC(0, 3);
  res.E[1][0] = MultRC(1, 0);
  res.E[1][1] = MultRC(1, 1);
  res.E[1][2] = MultRC(1, 2);
  res.E[1][3] = MultRC(1, 3);
  res.E[2][0] = MultRC(2, 0);
  res.E[2][1] = MultRC(2, 1);
  res.E[2][2] = MultRC(2, 2);
  res.E[2][3] = MultRC(2, 3);
  res.E[3][0] = MultRC(3, 0);
  res.E[3][1] = MultRC(3, 1);
  res.E[3][2] = MultRC(3, 2);
  res.E[3][3] = MultRC(3, 3);

#undef MultRC
}

GSMatrix4x4 GSMatrix4x4::operator*(const GSMatrix4x4& m) const
{
  GSMatrix4x4 res;
  MatrixMult4x4(res, *this, m);
  return res;
}

GSMatrix4x4& GSMatrix4x4::operator*=(const GSMatrix4x4& m)
{
  const GSMatrix4x4 copy(*this);
  MatrixMult4x4(*this, copy, m);
  return *this;
}

GSMatrix4x4 GSMatrix4x4::RotationX(float angle_in_radians)
{
  const float sin_angle = std::sin(angle_in_radians);
  const float cos_angle = std::cos(angle_in_radians);

  return GSMatrix4x4(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, cos_angle, -sin_angle, 0.0f, 0.0f, sin_angle, cos_angle, 0.0f, 0.0f,
                     0.0f, 0.0f, 1.0f);
}

GSMatrix4x4 GSMatrix4x4::RotationY(float angle_in_radians)
{
  const float sin_angle = std::sin(angle_in_radians);
  const float cos_angle = std::cos(angle_in_radians);

  return GSMatrix4x4(cos_angle, 0.0f, sin_angle, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, -sin_angle, 0.0f, cos_angle, 0.0f, 0.0f,
                     0.0f, 0.0f, 1.0f);
}

GSMatrix4x4 GSMatrix4x4::RotationZ(float angle_in_radians)
{
  const float sin_angle = std::sin(angle_in_radians);
  const float cos_angle = std::cos(angle_in_radians);

  return GSMatrix4x4(cos_angle, -sin_angle, 0.0f, 0.0f, sin_angle, cos_angle, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
                     0.0f, 0.0f, 1.0f);
}

GSMatrix4x4 GSMatrix4x4::Translation(float x, float y, float z)
{
  return GSMatrix4x4(1.0f, 0.0f, 0.0f, x, 0.0f, 1.0f, 0.0f, y, 0.0f, 0.0f, 1.0f, z, 0.0f, 0.0f, 0.0f, 1.0f);
}

GSMatrix4x4 GSMatrix4x4::OffCenterOrthographicProjection(float left, float top, float right, float bottom, float zNear,
                                                         float zFar)
{
  return GSMatrix4x4(2.0f / (right - left), 0.0f, 0.0f, (left + right) / (left - right), 0.0f, 2.0f / (top - bottom),
                     0.0f, (top + bottom) / (bottom - top), 0.0f, 0.0f, 1.0f / (zNear - zFar), zNear / (zNear - zFar),
                     0.0f, 0.0f, 0.0f, 1.0f);
}

GSMatrix4x4 GSMatrix4x4::OffCenterOrthographicProjection(float width, float height, float zNear, float zFar)
{
  return OffCenterOrthographicProjection(0.0f, 0.0f, width, height, zNear, zFar);
}

GSMatrix4x4 GSMatrix4x4::invert() const
{
  GSMatrix4x4 ret;

  float v0 = E[2][0] * E[3][1] - E[2][1] * E[3][0];
  float v1 = E[2][0] * E[3][2] - E[2][2] * E[3][0];
  float v2 = E[2][0] * E[3][3] - E[2][3] * E[3][0];
  float v3 = E[2][1] * E[3][2] - E[2][2] * E[3][1];
  float v4 = E[2][1] * E[3][3] - E[2][3] * E[3][1];
  float v5 = E[2][2] * E[3][3] - E[2][3] * E[3][2];

  const float t00 = +(v5 * E[1][1] - v4 * E[1][2] + v3 * E[1][3]);
  const float t10 = -(v5 * E[1][0] - v2 * E[1][2] + v1 * E[1][3]);
  const float t20 = +(v4 * E[1][0] - v2 * E[1][1] + v0 * E[1][3]);
  const float t30 = -(v3 * E[1][0] - v1 * E[1][1] + v0 * E[1][2]);

  const float inv_det = 1.0f / (t00 * E[0][0] + t10 * E[0][1] + t20 * E[0][2] + t30 * E[0][3]);

  ret.E[0][0] = t00 * inv_det;
  ret.E[1][0] = t10 * inv_det;
  ret.E[2][0] = t20 * inv_det;
  ret.E[3][0] = t30 * inv_det;

  ret.E[0][1] = -(v5 * E[0][1] - v4 * E[0][2] + v3 * E[0][3]) * inv_det;
  ret.E[1][1] = +(v5 * E[0][0] - v2 * E[0][2] + v1 * E[0][3]) * inv_det;
  ret.E[2][1] = -(v4 * E[0][0] - v2 * E[0][1] + v0 * E[0][3]) * inv_det;
  ret.E[3][1] = +(v3 * E[0][0] - v1 * E[0][1] + v0 * E[0][2]) * inv_det;

  v0 = E[1][0] * E[3][1] - E[1][1] * E[3][0];
  v1 = E[1][0] * E[3][2] - E[1][2] * E[3][0];
  v2 = E[1][0] * E[3][3] - E[1][3] * E[3][0];
  v3 = E[1][1] * E[3][2] - E[1][2] * E[3][1];
  v4 = E[1][1] * E[3][3] - E[1][3] * E[3][1];
  v5 = E[1][2] * E[3][3] - E[1][3] * E[3][2];

  ret.E[0][2] = +(v5 * E[0][1] - v4 * E[0][2] + v3 * E[0][3]) * inv_det;
  ret.E[1][2] = -(v5 * E[0][0] - v2 * E[0][2] + v1 * E[0][3]) * inv_det;
  ret.E[2][2] = +(v4 * E[0][0] - v2 * E[0][1] + v0 * E[0][3]) * inv_det;
  ret.E[3][2] = -(v3 * E[0][0] - v1 * E[0][1] + v0 * E[0][2]) * inv_det;

  v0 = E[2][1] * E[1][0] - E[2][0] * E[1][1];
  v1 = E[2][2] * E[1][0] - E[2][0] * E[1][2];
  v2 = E[2][3] * E[1][0] - E[2][0] * E[1][3];
  v3 = E[2][2] * E[1][1] - E[2][1] * E[1][2];
  v4 = E[2][3] * E[1][1] - E[2][1] * E[1][3];
  v5 = E[2][3] * E[1][2] - E[2][2] * E[1][3];

  ret.E[0][3] = -(v5 * E[0][1] - v4 * E[0][2] + v3 * E[0][3]) * inv_det;
  ret.E[1][3] = +(v5 * E[0][0] - v2 * E[0][2] + v1 * E[0][3]) * inv_det;
  ret.E[2][3] = -(v4 * E[0][0] - v2 * E[0][1] + v0 * E[0][3]) * inv_det;
  ret.E[3][3] = +(v3 * E[0][0] - v1 * E[0][1] + v0 * E[0][2]) * inv_det;

  return ret;
}

void GSMatrix4x4::store(void* m)
{
  std::memcpy(m, &E[0][0], sizeof(E));
}
