//============================================================================
//  Copyright (c) Kitware, Inc.
//  All rights reserved.
//  See LICENSE.txt for details.
//
//  This software is distributed WITHOUT ANY WARRANTY; without even
//  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
//  PURPOSE.  See the above copyright notice for more information.
//============================================================================
#ifndef vtk_m_VectorAnalysis_h
#define vtk_m_VectorAnalysis_h

// This header file defines math functions that deal with linear albegra functions

#include <vtkm/Math.h>
#include <vtkm/TypeTraits.h>
#include <vtkm/Types.h>
#include <vtkm/VecTraits.h>

namespace vtkm
{

// ----------------------------------------------------------------------------
/// @brief Returns the linear interpolation of two values based on weight
///
/// `Lerp` returns the linear interpolation of two values based on a weight. If
/// `weight` is outside [0,1] then `Lerp`
/// extrapolates. If `weight`=0 then `value0` is returned. If `weight`=1 then
/// `value1` is returned.
///
template <typename ValueType, typename WeightType>
inline VTKM_EXEC_CONT ValueType Lerp(const ValueType& value0,
                                     const ValueType& value1,
                                     const WeightType& weight)
{
  using ScalarType = typename detail::FloatingPointReturnType<ValueType>::Type;
  return static_cast<ValueType>((WeightType(1) - weight) * static_cast<ScalarType>(value0) +
                                weight * static_cast<ScalarType>(value1));
}
template <typename ValueType, vtkm::IdComponent N, typename WeightType>
VTKM_EXEC_CONT vtkm::Vec<ValueType, N> Lerp(const vtkm::Vec<ValueType, N>& value0,
                                            const vtkm::Vec<ValueType, N>& value1,
                                            const WeightType& weight)
{
  return (WeightType(1) - weight) * value0 + weight * value1;
}
template <typename ValueType, vtkm::IdComponent N>
VTKM_EXEC_CONT vtkm::Vec<ValueType, N> Lerp(const vtkm::Vec<ValueType, N>& value0,
                                            const vtkm::Vec<ValueType, N>& value1,
                                            const vtkm::Vec<ValueType, N>& weight)
{
  const vtkm::Vec<ValueType, N> One(ValueType(1));
  return (One - weight) * value0 + weight * value1;
}

// ----------------------------------------------------------------------------
/// @brief Returns the square of the magnitude of a vector.
///
/// It is usually much faster to compute the square of the magnitude than the
/// magnitude, so you should use this function in place of Magnitude or RMagnitude
/// when possible.
///
template <typename T>
VTKM_EXEC_CONT typename detail::FloatingPointReturnType<T>::Type MagnitudeSquared(const T& x)
{
  using U = typename detail::FloatingPointReturnType<T>::Type;
  return static_cast<U>(vtkm::Dot(x, x));
}

// ----------------------------------------------------------------------------
namespace detail
{
template <typename T>
VTKM_EXEC_CONT typename detail::FloatingPointReturnType<T>::Type MagnitudeTemplate(
  T x,
  vtkm::TypeTraitsScalarTag)
{
  return static_cast<typename detail::FloatingPointReturnType<T>::Type>(vtkm::Abs(x));
}

template <typename T>
VTKM_EXEC_CONT typename detail::FloatingPointReturnType<T>::Type MagnitudeTemplate(
  const T& x,
  vtkm::TypeTraitsVectorTag)
{
  return vtkm::Sqrt(vtkm::MagnitudeSquared(x));
}

} // namespace detail

/// @brief Returns the magnitude of a vector.
///
/// It is usually much faster to compute MagnitudeSquared, so that should be
/// substituted when possible (unless you are just going to take the square
/// root, which would be besides the point). On some hardware it is also faster
/// to find the reciprocal magnitude, so RMagnitude should be used if you
/// actually plan to divide by the magnitude.
///
template <typename T>
VTKM_EXEC_CONT typename detail::FloatingPointReturnType<T>::Type Magnitude(const T& x)
{
  return detail::MagnitudeTemplate(x, typename vtkm::TypeTraits<T>::DimensionalityTag());
}

// ----------------------------------------------------------------------------
namespace detail
{
template <typename T>
VTKM_EXEC_CONT typename detail::FloatingPointReturnType<T>::Type RMagnitudeTemplate(
  T x,
  vtkm::TypeTraitsScalarTag)
{
  return T(1) / vtkm::Abs(x);
}

template <typename T>
VTKM_EXEC_CONT typename detail::FloatingPointReturnType<T>::Type RMagnitudeTemplate(
  const T& x,
  vtkm::TypeTraitsVectorTag)
{
  return vtkm::RSqrt(vtkm::MagnitudeSquared(x));
}
} // namespace detail

/// @brief Returns the reciprocal magnitude of a vector.
///
/// On some hardware `RMagnitude` is faster than `Magnitude`, but neither is
/// as fast as `MagnitudeSquared`. This function works on scalars as well
/// as vectors, in which case it just returns the reciprocal of the scalar.
///
template <typename T>
VTKM_EXEC_CONT typename detail::FloatingPointReturnType<T>::Type RMagnitude(const T& x)
{
  return detail::RMagnitudeTemplate(x, typename vtkm::TypeTraits<T>::DimensionalityTag());
}

// ----------------------------------------------------------------------------
namespace detail
{
template <typename T>
VTKM_EXEC_CONT T NormalTemplate(T x, vtkm::TypeTraitsScalarTag)
{
  return vtkm::CopySign(T(1), x);
}

template <typename T>
VTKM_EXEC_CONT T NormalTemplate(const T& x, vtkm::TypeTraitsVectorTag)
{
  return vtkm::RMagnitude(x) * x;
}
} // namespace detail

/// @brief Returns a normalized version of the given vector.
///
/// The resulting vector points in the same direction but has unit length.
///
template <typename T>
VTKM_EXEC_CONT T Normal(const T& x)
{
  return detail::NormalTemplate(x, typename vtkm::TypeTraits<T>::DimensionalityTag());
}

// ----------------------------------------------------------------------------
/// @brief Changes a vector to be normal.
///
/// The given vector is scaled to be unit length.
///
template <typename T>
VTKM_EXEC_CONT void Normalize(T& x)
{
  x = vtkm::Normal(x);
}

// ----------------------------------------------------------------------------
/// @brief Find the cross product of two vectors.
///
/// If VTK-m is compiled with FMA support, it uses Kahan's difference of
/// products algorithm to achieve a maximum error of 1.5 ulps in each component.
template <typename T>
VTKM_EXEC_CONT vtkm::Vec<typename detail::FloatingPointReturnType<T>::Type, 3> Cross(
  const vtkm::Vec<T, 3>& x,
  const vtkm::Vec<T, 3>& y)
{
  return vtkm::Vec<typename detail::FloatingPointReturnType<T>::Type, 3>(
    DifferenceOfProducts(x[1], y[2], x[2], y[1]),
    DifferenceOfProducts(x[2], y[0], x[0], y[2]),
    DifferenceOfProducts(x[0], y[1], x[1], y[0]));
}

//-----------------------------------------------------------------------------
/// @brief Find the normal of a triangle.
///
/// Given three coordinates in space, which, unless degenerate, uniquely define
/// a triangle and the plane the triangle is on, returns a vector perpendicular
/// to that triangle/plane.
///
/// Note that the returned vector might not be a unit vector. In fact, the length
/// is equal to twice the area of the triangle. If you want a unit vector,
/// send the result through the `vtkm::Normal()` or `vtkm::Normalize()` function.
///
template <typename T>
VTKM_EXEC_CONT vtkm::Vec<typename detail::FloatingPointReturnType<T>::Type, 3>
TriangleNormal(const vtkm::Vec<T, 3>& a, const vtkm::Vec<T, 3>& b, const vtkm::Vec<T, 3>& c)
{
  return vtkm::Cross(b - a, c - a);
}

//-----------------------------------------------------------------------------
/// @brief Project a vector onto another vector.
///
/// This method computes the orthogonal projection of the vector v onto u;
/// that is, it projects its first argument onto its second.
///
/// Note that if the vector `u` has zero length, the output
/// vector will have all its entries equal to NaN.
template <typename T, int N>
VTKM_EXEC_CONT vtkm::Vec<T, N> Project(const vtkm::Vec<T, N>& v, const vtkm::Vec<T, N>& u)
{
  T uu = vtkm::Dot(u, u);
  T uv = vtkm::Dot(u, v);
  T factor = uv / uu;
  vtkm::Vec<T, N> result = factor * u;
  return result;
}

//-----------------------------------------------------------------------------
/// @brief Project a vector onto another vector, returning only the projected distance.
///
/// This method computes the orthogonal projection of the vector v onto u;
/// that is, it projects its first argument onto its second.
///
/// Note that if the vector `u` has zero length, the output will be NaN.
template <typename T, int N>
VTKM_EXEC_CONT T ProjectedDistance(const vtkm::Vec<T, N>& v, const vtkm::Vec<T, N>& u)
{
  T uu = vtkm::Dot(u, u);
  T uv = vtkm::Dot(u, v);
  T factor = uv / uu;
  return factor;
}

//-----------------------------------------------------------------------------
/// @brief Convert a set of vectors to an orthonormal basis.
///
/// This function performs Gram-Schmidt orthonormalization for 3-D vectors.
/// The first output vector will always be parallel to the first input vector.
/// The remaining output vectors will be orthogonal and unit length and have
/// the same handedness as their corresponding input vectors.
///
/// This method is geometric.
/// It does not require a matrix solver.
/// However, unlike the algebraic eigensolver techniques which do use matrix
/// inversion, this method may return zero-length output vectors if some input
/// vectors are collinear. The number of non-zero (to within the specified
/// tolerance, `tol`) output vectors is the return value.
///
/// See https://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process for details.
template <typename T, int N>
VTKM_EXEC_CONT int Orthonormalize(const vtkm::Vec<vtkm::Vec<T, N>, N>& inputs,
                                  vtkm::Vec<vtkm::Vec<T, N>, N>& outputs,
                                  T tol = static_cast<T>(1e-6))
{
  T tolsqr = tol * tol;
  int j = 0; // j is the number of non-zero-length, non-collinear inputs encountered.
  vtkm::Vec<vtkm::Vec<T, N>, N> u;
  for (int i = 0; i < N; ++i)
  {
    u[j] = inputs[i];
    for (int k = 0; k < j; ++k)
    {
      u[j] -= vtkm::Project(inputs[i], u[k]);
    }
    T magsqr = vtkm::MagnitudeSquared(u[j]);
    if (magsqr <= tolsqr)
    {
      // skip this vector, it is zero-length or collinear with others.
      continue;
    }
    outputs[j] = vtkm::RSqrt(magsqr) * u[j];
    ++j;
  }
  for (int i = j; i < N; ++i)
  {
    outputs[j] = Vec<T, N>{ 0. };
  }
  return j;
}

} // namespace vtkm

#endif //vtk_m_VectorAnalysis_h
