/*=========================================================================
 *
 *  Copyright NumFOCUS
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0.txt
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *=========================================================================*/
#ifndef itkCovariantVector_h
#define itkCovariantVector_h

#include "itkIndent.h"
#include "itkVector.h"
#include "vnl/vnl_vector_ref.h"

namespace itk
{
/**
 *\class CovariantVector
 * \brief A templated class holding a n-Dimensional covariant vector.
 *
 * CovariantVector is a templated class that holds a single vector
 * (i.e., an array of values).  CovariantVector can be used as the data
 * type held at each pixel in an Image or at each vertex of an Mesh.
 * The template parameter T can be any data type that behaves like a
 * primitive (or atomic) data type (int, short, float, complex).
 * The NVectorDimension defines the number of components in the vector array.
 *
 * CovariantVector is not a dynamically extendible array like std::vector. It is
 * intended to be used like a mathematical vector.
 *
 * If you wish a simpler pixel types, you can use Scalar, which represents
 * a single data value at a pixel. There is also the more complex type
 * ScalarCovariantVector, which supports (for a given pixel)
 * a single scalar value plus an array of vector values.
 * (The scalar and vectors can be of different data type.)
 *
 * CovariantVector is the type that should be used for representing normals
 * to surfaces and gradients of functions. AffineTransform transform
 * covariant vectors different than vectors.
 *
 * \ingroup Geometry
 * \ingroup DataRepresentation
 *
 * \sa Image
 * \sa Mesh
 * \sa Point
 * \sa Vector
 * \sa Matrix
 * \ingroup ITKCommon
 *
 * \sphinx
 * \sphinxexample{Core/Common/CreateACovariantVector,Create a CovariantVector}
 * \sphinxexample{Core/Common/CovariantVectorNorm,Covariant Vector Norm}
 * \sphinxexample{Core/Common/CovariantVectorDotProduct, Covariant Vector Dot Product}
 * \endsphinx
 */

template <typename T, unsigned int NVectorDimension = 3>
class ITK_TEMPLATE_EXPORT CovariantVector : public FixedArray<T, NVectorDimension>
{
public:
  /** Standard class type aliases. */
  using Self = CovariantVector;
  using Superclass = FixedArray<T, NVectorDimension>;

  /** ValueType can be used to declare a variable that is the same type
   * as a data element held in an CovariantVector.   */
  using ValueType = T;
  using RealValueType = typename NumericTraits<ValueType>::RealType;

  /** Component value type */
  using ComponentType = T;

  /** Dimension of the Space */
  static constexpr unsigned int Dimension = NVectorDimension;

  /** I am a covariant vector. */
  using CovariantVectorType = Self;

  /** The Array type from which this CovariantVector is derived. */
  using BaseArray = FixedArray<T, NVectorDimension>;

  /** Get the dimension (size) of the vector. */
  static unsigned int
  GetCovariantVectorDimension()
  {
    return NVectorDimension;
  }

  /** Set a vnl_vector_ref referencing the same memory block. */
  void
  SetVnlVector(const vnl_vector<T> &);

  /** Get a vnl_vector_ref referencing the same memory block. */
  vnl_vector_ref<T>
  GetVnlVector();

  /** Get a vnl_vector with a copy of the internal memory block. */
  vnl_vector<T>
  GetVnlVector() const;

  /** Default constructor. */
  CovariantVector() = default;
  CovariantVector(const CovariantVector &) = default;
  CovariantVector(CovariantVector &&) = default;
  CovariantVector &
  operator=(const CovariantVector &) = default;
  CovariantVector &
  operator=(CovariantVector &&) = default;
  ~CovariantVector() = default;

  /**
   * Constructor to initialize entire vector to one value.
   */
  explicit CovariantVector(const ValueType & r);

  /** Pass-through constructor for the Array base class. Implicit casting is
   * performed to initialize constructor from any another one of datatype. */
  template <typename TVectorValueType>
  CovariantVector(const CovariantVector<TVectorValueType, NVectorDimension> & r)
    : BaseArray(r)
  {}
  CovariantVector(const ValueType r[Dimension])
    : BaseArray(r)
  {}

  /** Assignment operator with implicit casting from another data type */
  template <typename TCovariantVectorValueType>
  Self &
  operator=(const CovariantVector<TCovariantVectorValueType, NVectorDimension> & r)
  {
    BaseArray::operator=(r);
    return *this;
  }

  /** Pass-through assignment operator for the Array base class. */
  CovariantVector &
  operator=(const ValueType r[NVectorDimension]);

  /** Scalar operator*=.  Scales elements by a scalar. */
  template <typename Tt>
  inline const Self &
  operator*=(const Tt & value)
  {
    for (unsigned int i = 0; i < NVectorDimension; i++)
    {
      (*this)[i] = static_cast<ValueType>((*this)[i] * value);
    }
    return *this;
  }

  /** Scalar operator/=.  Scales (divides) elements by a scalar. */
  template <typename Tt>
  const Self &
  operator/=(const Tt & value)
  {
    for (unsigned int i = 0; i < NVectorDimension; i++)
    {
      (*this)[i] = static_cast<ValueType>((*this)[i] / value);
    }
    return *this;
  }

  /** CovariantVector operator+=.  Adds a vectors to the current vector. */
  const Self &
  operator+=(const Self & vec);

  /** CovariantVector operator-=.  Subtracts a vector from a current vector. */
  const Self &
  operator-=(const Self & vec);

  /** CovariantVector negation.  Negate all the elements of a vector.
   *  Return a new vector */
  Self
  operator-() const;

  /** CovariantVector addition. Add two vectors. Return a new vector. */
  Self
  operator+(const Self & vec) const;

  /** CovariantVector subtraction. Subtract two vectors. Return a new vector. */
  Self
  operator-(const Self & vec) const;

  /** CovariantVector operator*.
   * Performs the inner product of two covariant vectors.
   * \warning This is equivalent to the scalar product only if the reference
   * system has orthogonal axis and equal scales.  */
  ValueType operator*(const Self & vec) const;

  /** operator*.  Performs the scalar product with a vector (contravariant).
   * This scalar product is invariant under affine transformations */
  ValueType operator*(const Vector<T, NVectorDimension> & vec) const;

  /** Scalar operator*. Scale the elements of a vector by a scalar.
   * Return a new vector. */
  inline Self operator*(const ValueType & val) const
  {
    Self result;

    for (unsigned int i = 0; i < NVectorDimension; i++)
    {
      result[i] = static_cast<ValueType>((*this)[i] * val);
    }
    return result;
  }

  /** Scalar operator/. Scale (divide) the elements of a vector by a scalar.
   * Return a new vector. */
  template <typename Tt>
  inline Self
  operator/(const Tt & val) const
  {
    Self result;

    for (unsigned int i = 0; i < NVectorDimension; i++)
    {
      result[i] = static_cast<ValueType>((*this)[i] / val);
    }
    return result;
  }

  /** Returns the Euclidean Norm of the vector  */
  RealValueType
  GetNorm() const;

  /** Returns the number of components in this vector type */
  static unsigned int
  GetNumberOfComponents()
  {
    return NVectorDimension;
  }

  /** Divides the covariant vector components by the norm and return the norm */
  RealValueType
  Normalize();

  /** Returns vector's Squared Euclidean Norm  */
  RealValueType
  GetSquaredNorm() const;

  /** Copy from another CovariantVector with a different representation type.
   *  Casting is done with C-Like rules  */
  template <typename TCoordRepB>
  void
  CastFrom(const CovariantVector<TCoordRepB, NVectorDimension> & pa)
  {
    for (unsigned int i = 0; i < NVectorDimension; i++)
    {
      (*this)[i] = static_cast<T>(pa[i]);
    }
  }
};

/** Premultiply Operator for product of a vector and a scalar.
 *  CovariantVector< T, N >  =  T * CovariantVector< T,N > */
template <typename T, unsigned int NVectorDimension>
inline CovariantVector<T, NVectorDimension> operator*(const T & scalar, const CovariantVector<T, NVectorDimension> & v)
{
  return v.operator*(scalar);
}

/** Performs the scalar product of a covariant with a contravariant.
 * This scalar product is invariant under affine transformations */
template <typename T, unsigned int NVectorDimension>
inline T operator*(const Vector<T, NVectorDimension> &          contravariant,
                   const CovariantVector<T, NVectorDimension> & covariant)
{
  return covariant.operator*(contravariant);
}

ITKCommon_EXPORT void
CrossProduct(CovariantVector<double, 3> &, const Vector<double, 3> &, const Vector<double, 3> &);

ITKCommon_EXPORT void
CrossProduct(CovariantVector<float, 3> &, const Vector<float, 3> &, const Vector<float, 3> &);

ITKCommon_EXPORT void
CrossProduct(CovariantVector<int, 3>, const Vector<int, 3> &, const Vector<int, 3> &);


template <typename T, unsigned int NVectorDimension>
inline void
swap(CovariantVector<T, NVectorDimension> & a, CovariantVector<T, NVectorDimension> & b)
{
  a.swap(b);
}

} // end namespace itk

//
// Numeric traits must be included after (optionally) including the explicit
// instantiations control of this class, in case the implicit instantiation
// needs to be disabled.
//
// NumericTraits must be included before (optionally) including the .hxx file,
// in case the .hxx requires to use NumericTraits.
//
#include "itkNumericTraitsCovariantVectorPixel.h"

#ifndef ITK_MANUAL_INSTANTIATION
#  include "itkCovariantVector.hxx"
#endif

#endif
