/*=========================================================================
 *
 *  Copyright Insight Software Consortium
 *
 *  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 itkPoint_h
#define itkPoint_h


#include "itkNumericTraits.h"
#include "itkVector.h"

#include "vnl/vnl_vector_ref.h"
#include "itkMath.h"

namespace itk
{
/** \class Point
 * \brief A templated class holding a geometric point in n-Dimensional space.
 *
 * Point is a templated class that holds a set of coordinates (components).
 * Point 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 NPointDimension defines the number of
 * components in the point array.
 *
 * \ingroup Geometry
 * \ingroup DataRepresentation
 *
 * \sa Image \sa Mesh \sa Vector \sa CovariantVector \sa Matrix
 * \ingroup ITKCommon
 *
 * \wiki
 * \wikiexample{SimpleOperations/DistanceBetweenPoints,Distance between two points}
 * \wikiexample{SimpleOperations/DistanceBetweenIndices,Distance between two indices}
 * \endwiki
 */
template< typename TCoordRep, unsigned int NPointDimension = 3 >
class ITK_TEMPLATE_EXPORT Point:public FixedArray< TCoordRep, NPointDimension >
{
public:
  /** Standard class type aliases. */
  using Self = Point;
  using Superclass = FixedArray< TCoordRep, NPointDimension >;

  /** ValueType can be used to declare a variable that is the same type
   * as a data element held in an Point.   */
  using ValueType = TCoordRep;
  using CoordRepType = TCoordRep;

  using RealType = typename NumericTraits< ValueType >::RealType;

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

  /** The Array type from which this Vector is derived. */
  using BaseArray = FixedArray< TCoordRep, NPointDimension >;
  using Iterator = typename BaseArray::Iterator;
  using ConstIterator = typename BaseArray::ConstIterator;

  /** Get the dimension (size) of the point. */
  static unsigned int GetPointDimension()
  { return NPointDimension; }

  /** VectorType define the difference between two Points */
  using VectorType = Vector< ValueType, NPointDimension >;

  /** Default constructor, assignments */
  Point() = default;
  Point(const Point &) = default;
  Point(Point &&) = default;
  Point & operator=(const Point &) = default;
  Point & operator=(Point &&) = default;
  ~Point() = default;
  /** Pass-through constructors for different type points. */
  template< typename TPointValueType >
  Point(const Point< TPointValueType, NPointDimension > & r):BaseArray(r) {}
  /** Pass-through constructors for plain arrays. */
  template< typename TPointValueType >
  Point(const TPointValueType r[NPointDimension]):BaseArray(r) {}
  Point(const ValueType r[NPointDimension]):BaseArray(r) {}
  /** Pass-through constructors for single values */
  template< typename TPointValueType >
  Point(const TPointValueType & v):BaseArray(v) {}
  Point(const ValueType & v):BaseArray(v) {}

  /** Explicit constructor for std::array. */
  explicit Point(const std::array<ValueType, NPointDimension>& stdArray)
    :
    BaseArray(stdArray)
  {
  }

  /** Pass-through assignment operator for a plain array. */
  Point & operator=(const ValueType r[NPointDimension]);

  /** Compare two points for equality. */
  bool
  operator==(const Self & pt) const
  {
    bool same = true;

    for ( unsigned int i = 0; i < NPointDimension && same; ++i )
          { same = ( Math::ExactlyEquals(( *this )[i], pt[i]) ); }
    return same;
  }

  /** Compare two points for inequality. */
  bool
  operator!=(const Self & pt) const
  {
    bool same = true;

    for ( unsigned int i = 0; i < NPointDimension && same; ++i )
          { same = ( Math::ExactlyEquals(( *this )[i], pt[i]) ); }
    return !same;
  }

  /** Point operator+=.  Adds a vector to the current point. */
  const Self & operator+=(const VectorType & vec);

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

  /** Computes the Vector difference between two points */
  VectorType operator-(const Self & pnt) const;

  /** Add a vector to a point. Return a new point. */
  Self operator+(const VectorType & vec) const;

  /** Subtract a vector from a point. Return a new point. */
  Self operator-(const VectorType & vec) const;

  /** Access an element of a point. */
  VectorType GetVectorFromOrigin() const;

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

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

  /** Set to median point between the two points
   * given as arguments
   *
   * This method computes:
   *
   * \f[
   *   \overrightarrow{P}=\frac{(\overrightarrow{A}+\overrightarrow{B})}{2}
   * \f]
   *
   * using the two Points given as arguments, and store the result in
   * the Point on which the method is invoked. */
  void SetToMidPoint(const Self &, const Self &);

  /** Set the current point to a barycentric combination of the two points
   * given as arguments.
   *
   * \param A First point
   * \param B Second point
   * \param alpha Weight for the first point
   *
   * The first point is multiplied by \f$ \alpha \f$, the second is multiplied
   * by * \f$ (1-\alpha) \f$, and the sum is stored in the Point on which the
   * method is invoked.
   *
   * \f[
   *   \overrightarrow{P}=\alpha * \overrightarrow{A}+ (1-\alpha)*\overrightarrow{B}
   * \f]
   *
   * If the value of \f$ \alpha \in [0,1] \f$, the resulting point will be placed
   * in the line segment \f$ \overline{AB} \f$ joining  \f$ \overrightarrow{A} \f$
   * and \f$  \overrightarrow{A} \f$
   *
   * If the value of \f$ \alpha < 0 \f$ the resulting point will be placed outside
   * the line segment   \f$ \overline{AB} \f$ on the side of \f$ \overrightarrow{A} \f$.
   *
   * If the value of \f$ \alpha > 1 \f$ the resulting point will be placed outside
   * the line segment   \f$ \overline{AB} \f$ on the side of \f$ \overrightarrow{B} \f$.
   *
   * \sa SetToMedian */
  void SetToBarycentricCombination(const Self & A, const Self & B, double alpha);

  /** Set the current point to a barycentric combination of three points
   * Two values are expected to weight the contribution of the first two points,
   * the weight of for the third point is computed to ensure that the three weights
   * sum 1.
   *
   * This method computes:
   *
   * \f[
   *   \overrightarrow{P}=     w_1        * \overrightarrow{P}_1
                          +    w_2        * \overrightarrow{P}_2
                          +  (1-w_1-w_2 ) * \overrightarrow{P}_3
   * \f]
   *
   * If the two weight are \f$ \in [0,1] \f$ , The resulting point will alway be placed
   * inside the triangle formed by the three points given as arguments. */
  void SetToBarycentricCombination(const Self & A, const Self & B, const Self & C,
                                   double weightA,  double weightB);

  /** Set the current point to a barycentric combination of an array of N points
   * An array of (N-1) values is expected to weight the contribution of the
   * first (N-1) points, the weight of the Nth point is computed to ensure that
   * the N weights sum 1.
   *
   * This method computes:
   *
   * \f[
   *   \overrightarrow{P}=    \sum_{i=1}^{N-1} w_i * \overrightarrow{P}_i
          +   \left(1- \sum_{i=1}^{N-1} w_i\right) * \overrightarrow{P}_N
   * \f]
   */
  void SetToBarycentricCombination(const Self *P, const double *weights, unsigned int N);

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

  /** Compute the Squared Euclidean Distance from this point to another point
    * with a different representation type.  Casting is done with
    * C-Like rules */

  template< typename TCoordRepB >
  RealType SquaredEuclideanDistanceTo(const Point< TCoordRepB, NPointDimension > & pa) const
  {
    RealType sum = NumericTraits< RealType >::ZeroValue();

    for ( unsigned int i = 0; i < NPointDimension; i++ )
      {
      const auto component =  static_cast< RealType >( pa[i] );
      const RealType difference = static_cast< RealType >( ( *this )[i] ) - component;
      sum += difference * difference;
      }
    return sum;
  }

  /** Compute the Euclidean Distance from this point to another point
    * with a different representation type.  Casting is done with
    * C-Like rules */
  template< typename TCoordRepB >
  RealType EuclideanDistanceTo(const Point< TCoordRepB, NPointDimension > & pa) const
  {
    const double distance = std::sqrt(
      static_cast< double >( this->SquaredEuclideanDistanceTo(pa) ) );

    return static_cast< RealType >( distance );
  }
};

template< typename T, unsigned int NPointDimension >
std::ostream & operator<<(std::ostream & os,
                                     const Point< T, NPointDimension > & v);

template< typename T, unsigned int NPointDimension >
std::istream & operator>>(std::istream & is,
                                     Point< T, NPointDimension > & v);

/** \class BarycentricCombination
 *  \brief Computes the barycentric combination of an array of N points.
 *
 * This class computes the barycentric combination of an array of N points.
 *
 * An array of (N-1) values is expected to weight the contribution of the
 * first (N-1) points, the weight of the Nth point is computed to ensure that
 * the N weights sum 1.
 *
 * This method computes:
 *
 * \f[
 *   \overrightarrow{P}=    \sum_{i=1}^{N-1} w_i * \overrightarrow{P}_i
 *      +   \left(1- \sum_{i=1}^{N-1} w_i\right) * \overrightarrow{P}_N
 * \f]
 *
 * The points are expected to be stored in an itkContainer class like
 * itk::VectorContainer, responding to the Begin(), End(), Value() API.
 *
 * The weights are expected to be stored in any array-like container
 * having a operator[i].
 *
 * \ingroup Geometry
 * \ingroup ITKCommon
 */
template< typename TPointContainer, typename TWeightContainer >
class ITK_TEMPLATE_EXPORT BarycentricCombination
{
public:
  /** Convenient type alias. */
  using PointContainerType = TPointContainer;
  using PointContainerPointer = typename PointContainerType::Pointer;
  using PointType = typename PointContainerType::Element;
  using WeightContainerType = TWeightContainer;

  BarycentricCombination() = default;
  ~BarycentricCombination() = default;

  static PointType Evaluate(
    const PointContainerPointer & points,
    const WeightContainerType & weights);
};


template<typename TCoordRep, unsigned int NPointDimension>
inline void swap( Point<TCoordRep, NPointDimension> &a, Point<TCoordRep, NPointDimension> &b )
{
  a.swap(b);
}

}  // end namespace itk

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

#endif
