/*=========================================================================
 *
 *  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 itkNormalQuadEdgeMeshFilter_h
#define itkNormalQuadEdgeMeshFilter_h

#include "itkQuadEdgeMeshToQuadEdgeMeshFilter.h"
#include "itkQuadEdgeMeshPolygonCell.h"
#include "itkTriangleHelper.h"

namespace itk
{
/** \class NormalQuadEdgeMeshFilter
 *
 * \brief Filter which computes normals to faces and vertices and store it in
 * the output mesh.
 * Normals to face are first computed, then normals to vertices are computed
 * as linear combination of neighbor face normals, i.e.
 * \f[
 * n_v = \frac{\sum_{i=0}^{N_f} \omega_i \cdot n_i}{\| \sum_{k=0}^{N_f} \omega_x \cdot n_k\|}
 * \f]
 *
 * The difference between each method relies in the definition of the weight
 * \f$ \omega_i \f$ that you can specify by the method SetWeight.
 *
 * <ul>
 *    <li> GOURAUD \f$ \omega_i = 1\f$ [1] </li>
 *    <li> THURMER \f$ \omega_i = \f$ Angle of the considered triangle at the given vertex} [2] </li>
 *    <li> AREA \f$ \omega_i = Area(t_i)\f$ [3] </li>
 * </ul>
 *
 * These weights are defined in the literature:
 * <ul>
 *    <li> [1] Henri Gouraud. Continuous shading of curved surfaces.
 *    IEEE Transaction on Computers, 20(6):623-629, 1971 </li>
 *    <li> [2] Shuangshuang Jin, Robert R. Lewis, and David West.
 *    A comparison of algorithms for vertex normal computation.
 *    The Visual Computer, 21(1-2):71-82, 2005. </li>
 *    <li> [3] Grit Thurmer and Charles A. Wuthrich.
 *    Computing vertex normals from polygonal facets.
 *    Journal of Graphic Tools, 3(1):43-46, 1998. </li>
 * </ul>
 *
 * \note By default the weight is set to the TURMER weight.
 *
 * \todo Fix run-time issues regarding the difference between the Traits of
 * TInputMesh and the one of TOutputMesh. Right now, it only works if
 * TInputMesh::MeshTraits == TOutputMesh::MeshTraits
 * (and of course it requires that the output have some itk::Vector for point
 * data and cell data.
 * \ingroup ITKQuadEdgeMeshFiltering
 */
template< typename TInputMesh, typename TOutputMesh >
class ITK_TEMPLATE_EXPORT NormalQuadEdgeMeshFilter:
  public QuadEdgeMeshToQuadEdgeMeshFilter< TInputMesh, TOutputMesh >
{
public:
  using Self = NormalQuadEdgeMeshFilter;
  using Superclass = QuadEdgeMeshToQuadEdgeMeshFilter< TInputMesh, TOutputMesh >;
  using Pointer = SmartPointer< Self >;
  using ConstPointer = SmartPointer< const Self >;

  itkNewMacro (Self);

  itkTypeMacro (NormalQuadEdgeMeshFilter,
                QuadEdgeMeshToQuadEdgeMeshFilter);

  using InputMeshType = TInputMesh;
  using InputMeshPointer = typename InputMeshType::Pointer;
  using InputPointIdentifier = typename InputMeshType::PointIdentifier;
  using InputPointType = typename InputMeshType::PointType;
  using InputVectorType = typename InputMeshType::VectorType;
  using InputQEType = typename InputMeshType::QEType;

  using OutputMeshType = TOutputMesh;
  using OutputMeshPointer = typename OutputMeshType::Pointer;
  using OutputPointType = typename OutputMeshType::PointType;
  using OutputVectorType = typename OutputPointType::VectorType;
  using OutputQEType = typename OutputMeshType::QEType;
  using OutputPointIdentifier = typename OutputMeshType::PointIdentifier;
  using OutputPointIdIterator = typename OutputMeshType::PointIdIterator;
  using OutputPointsContainerPointer = typename OutputMeshType::PointsContainerPointer;
  using OutputPointsContainerIterator = typename OutputMeshType::PointsContainerIterator;
  using OutputCellType = typename OutputMeshType::CellType;
  using OutputCellIdentifier = typename OutputMeshType::CellIdentifier;
  using OutputCellAutoPointer = typename OutputMeshType::CellAutoPointer;
  using OutputCellsContainerPointer = typename OutputMeshType::CellsContainerConstIterator;
  using OutputCellsContainerConstIterator = typename OutputMeshType::CellsContainerConstIterator;

  using TriangleType = TriangleHelper< OutputPointType >;

  using OutputPolygonType = QuadEdgeMeshPolygonCell< OutputCellType >;
  using OutputPolygonAutoPointer = typename OutputPolygonType::SelfAutoPointer;

  using OutputCellDataContainer = typename OutputMeshType::CellDataContainer;
  using OutputPointDataContainer = typename OutputMeshType::PointDataContainer;

  using OutputMeshTraits = typename OutputMeshType::MeshTraits;
  using OutputVertexNormalType = typename OutputMeshTraits::PixelType;
  using OutputVertexNormalComponentType = typename OutputVertexNormalType::ValueType;

  using OutputFaceNormalType = typename OutputMeshTraits::CellPixelType;
  using OutputFaceNormalComponentType = typename OutputFaceNormalType::ValueType;

  enum WeightType {
    GOURAUD = 0, // Uniform weights
    THURMER,     // Angle on a triangle at the given vertex
    AREA
    };

  itkSetMacro (Weight, WeightType);
  itkGetConstMacro (Weight, WeightType);

protected:
  NormalQuadEdgeMeshFilter();
  ~NormalQuadEdgeMeshFilter() override = default;
  void PrintSelf(std::ostream & os, Indent indent) const override;

  WeightType m_Weight;

  /** \brief Compute the normal to a face iPoly. It assumes that iPoly != 0
  * and
  * iPoly is a Triangle, i.e. 3 points only.
  * \note The normal computation itself can be further improved by making
  * possible to cast a CellType into a TriangleType.
  */
  OutputFaceNormalType ComputeFaceNormal(OutputPolygonType *iPoly);

  /** \brief Compute the normal to all faces on the mesh.
  * \note This method should be implemented in a multi-thread way in order
  * to reduce the processing time.
  */
  void ComputeAllFaceNormals();

  /** \brief Compute the normal to all vertices on the mesh.
  * \note This method should be implemented in a multi-thread way in order
  * to reduce the processing time.
  */
  void ComputeAllVertexNormals();

  /** \brief Compute the normal to one vertex by a weighted sum of the faces
  * normal in the 0-ring.
  * \note The weight is chosen by the member m_Weight.
  */
  OutputVertexNormalType ComputeVertexNormal(const OutputPointIdentifier & iId, OutputMeshType *outputMesh);

  /** \brief Definition of the weight in the 0-ring used for the vertex
  * normal computation. By default m_Weight = THURMER;
  */
  OutputVertexNormalComponentType Weight(const OutputPointIdentifier & iPId,
                                         const OutputCellIdentifier & iCId,
                                         OutputMeshType *outputMesh);

  /** \note Calling Superclass::GenerateData( ) is the longest part in the
  * filter! Something must be done in the class
  * itkQuadEdgeMeshToQuadEdgeMeshFilter.
  */
  void GenerateData() override;

private:
  NormalQuadEdgeMeshFilter (const Self &) = delete;
  void operator=(const Self &) = delete;
};
}

#include "itkNormalQuadEdgeMeshFilter.hxx"
#endif
