#ifndef TRIANGLE_TRANSFORMATION_HPP
#define TRIANGLE_TRANSFORMATION_HPP

#include <algebra/TinyMatrix.hpp>
#include <algebra/TinyVector.hpp>

template <size_t GivenDimension>
class TriangleTransformation;

template <>
class TriangleTransformation<2>
{
 public:
  constexpr static size_t Dimension = 2;

 private:
  TinyMatrix<Dimension> m_jacobian;
  TinyVector<Dimension> m_shift;
  double m_jacobian_determinant;

 public:
  PUGS_INLINE
  TinyVector<Dimension>
  operator()(const TinyVector<2>& x) const
  {
    return m_jacobian * x + m_shift;
  }

  double
  jacobianDeterminant() const
  {
    return m_jacobian_determinant;
  }

  PUGS_INLINE
  TriangleTransformation(const TinyVector<Dimension>& a, const TinyVector<Dimension>& b, const TinyVector<Dimension>& c)
  {
    for (size_t i = 0; i < Dimension; ++i) {
      m_jacobian(i, 0) = b[i] - a[i];
      m_jacobian(i, 1) = c[i] - a[i];
    }

    m_shift = a;

    m_jacobian_determinant = det(m_jacobian);
  }

  ~TriangleTransformation() = default;
};

template <size_t GivenDimension>
class TriangleTransformation
{
 public:
  constexpr static size_t Dimension = GivenDimension;
  static_assert(Dimension == 3, "Triangle transformation is defined in dimension 2 or 3");

 private:
  TinyMatrix<Dimension, 2> m_jacobian;
  TinyVector<Dimension> m_shift;
  double m_area_variation_norm;

 public:
  PUGS_INLINE
  TinyVector<Dimension>
  operator()(const TinyVector<2>& x) const
  {
    return m_jacobian * x + m_shift;
  }

  double
  areaVariationNorm() const
  {
    return m_area_variation_norm;
  }

  PUGS_INLINE
  TriangleTransformation(const TinyVector<Dimension>& a, const TinyVector<Dimension>& b, const TinyVector<Dimension>& c)
  {
    for (size_t i = 0; i < Dimension; ++i) {
      m_jacobian(i, 0) = b[i] - a[i];
      m_jacobian(i, 1) = c[i] - a[i];
    }

    m_shift = a;

    m_area_variation_norm = l2Norm(crossProduct(b - a, c - a));
  }

  ~TriangleTransformation() = default;
};

#endif   // TRIANGLE_TRANSFORMATION_HPP