/*=========================================================================
 *
 *  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
 *
 *         https://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 itkGeometricalQuadEdge_h
#define itkGeometricalQuadEdge_h

#include "itkQuadEdge.h"

namespace itk
{
/**
 * \class GeometricalQuadEdge
 * \brief This class extends the QuadEdge by adding a reference to the Origin.
 *
 * The class is implemented in such a way that it can generate its own Dual.
 * In a physical edge, there will be four GeometricalQuadEdge. Two of them will
 * be Primal and two will be Dual. The Primal ones are parallel to the physical
 * edge and their origins relate to the mesh points. The Dual ones are
 * orthogonal to the physical edge and their origins relate to the faces at
 * each side of the physical edge.
 *
 * The only purpose of the last parameter of the template is to guarantee that
 * the two types GeometricalQuadEdge and GeometricalQuadEdge::Dual
 * are always different (in the sense that their typeid() are different). If
 * we only had the four first parameters and assume that
 * GeometricalQuadEdge gets instantiated with types such that TVRef =
 * TFRef and TPrimalData = TDualData then this instantiation
 * GeometricalQuadEdge and GeometricalQuadEdge::Dual would be the
 * same types (this is simply due to the very definition of
 * GeometricalQuadEdge::Dual). This would in turn make the types QEType
 * and QEDual of \ref QuadEdgeMesh identical and would prevent any algorithm
 * requiring to distinguish those types (e.g. by relying on a
 * dynamic_cast<QEType*>) to be effective.  This justifies the existence of
 * last dummy template parameter and its default value.
 *
 * \author Alexandre Gouaillard, Leonardo Florez-Valencia, Eric Boix
 *
 * This implementation was contributed as a paper to the Insight Journal
 * https://doi.org/10.54294/4mx7kk
 *
 * \sa QuadEdge
 *
 * \ingroup MeshObjects
 * \ingroup ITKQuadEdgeMesh
 */
template <typename TVRef, typename TFRef, typename TPrimalData, typename TDualData, bool PrimalDual = true>
class ITK_TEMPLATE_EXPORT GeometricalQuadEdge : public QuadEdge
{
public:
  /** Hierarchy type alias. */
  using Self = GeometricalQuadEdge;
  using Superclass = QuadEdge;
  using RawPointer = Self *;

  /**
   * Dual type, basically the same type with swapped template
   * parameters.
   *
   */
  using DualType = GeometricalQuadEdge<TFRef, TVRef, TDualData, TPrimalData, !PrimalDual>;

  /** Input template parameters & values convenient renaming. */
  using OriginRefType = TVRef;
  using DualOriginRefType = TFRef;
  using PrimalDataType = TPrimalData;
  using DualDataType = TDualData;
  // Line Cell Id in Mesh Cell Container
  // used to go up to LineCell level
  using LineCellIdentifier = TFRef;

public:
  /** Iterator types. */
  using IteratorGeom = QuadEdgeMeshIteratorGeom<Self>;
  using ConstIteratorGeom = QuadEdgeMeshConstIteratorGeom<Self>;

  /** Basic iterators methods. */
  /** @ITKStartGrouping */
  inline itkQEDefineIteratorGeomMethodsMacro(Onext);
  inline itkQEDefineIteratorGeomMethodsMacro(Sym);
  inline itkQEDefineIteratorGeomMethodsMacro(Lnext);
  inline itkQEDefineIteratorGeomMethodsMacro(Rnext);
  inline itkQEDefineIteratorGeomMethodsMacro(Dnext);
  inline itkQEDefineIteratorGeomMethodsMacro(Oprev);
  inline itkQEDefineIteratorGeomMethodsMacro(Lprev);
  inline itkQEDefineIteratorGeomMethodsMacro(Rprev);
  inline itkQEDefineIteratorGeomMethodsMacro(Dprev);
  inline itkQEDefineIteratorGeomMethodsMacro(InvOnext);
  inline itkQEDefineIteratorGeomMethodsMacro(InvLnext);
  inline itkQEDefineIteratorGeomMethodsMacro(InvRnext);
  inline itkQEDefineIteratorGeomMethodsMacro(InvDnext);
  /** @ITKEndGrouping */
  /** QE macros. */
  itkQEAccessorsMacro(Superclass, Self, DualType);

public:
  GeometricalQuadEdge();
  GeometricalQuadEdge(const GeometricalQuadEdge &) = default;
  GeometricalQuadEdge(GeometricalQuadEdge &&) = default;
  GeometricalQuadEdge &
  operator=(const GeometricalQuadEdge &) = default;
  GeometricalQuadEdge &
  operator=(GeometricalQuadEdge &&) = default;
  ~GeometricalQuadEdge() override = default;

  /** Set methods. */
  inline void
  SetOrigin(const OriginRefType v)
  {
    m_Origin = v;
  }

  inline void
  SetDestination(const OriginRefType v)
  {
    this->GetSym()->SetOrigin(v);
  }

  inline void
  SetRight(const DualOriginRefType v)
  {
    this->GetRot()->SetOrigin(v);
  }

  inline void
  SetLeft(const DualOriginRefType v)
  {
    this->GetInvRot()->SetOrigin(v);
  }

  /**
   * Set the Left() of all the edges in the Lnext() ring of "this"
   * with the same given geometrical information.
   * @param  faceGeometry Looks at most maxSize edges in the Lnext() ring.
   * @param  maxSize Sets at most maxSize edges in the Lnext() ring.
   * @return Returns true on success. False otherwise.
   */
  bool
  SetLnextRingWithSameLeftFace(const DualOriginRefType faceGeometry, int maxSize = 100);

  inline void
  UnsetOrigin()
  {
    m_Origin = m_NoPoint;
  }
  inline void
  UnsetDestination()
  {
    this->GetSym()->UnsetOrigin();
  }
  inline void
  UnsetRight()
  {
    this->GetRot()->UnsetOrigin();
  }
  inline void
  UnsetLeft()
  {
    this->GetInvRot()->UnsetOrigin();
  }

  /** Get methods. */
  /** @ITKStartGrouping */
  // ORIENTATION_NOTE: this definition of GetLeft (or GetRight)
  // implicitly assumes that the Onext order is counter-clockwise !
  [[nodiscard]] inline const OriginRefType
  GetOrigin() const
  {
    return m_Origin;
  }
  [[nodiscard]] inline const OriginRefType
  GetDestination() const
  {
    return GetSym()->GetOrigin();
  }
  [[nodiscard]] inline const DualOriginRefType
  GetRight() const
  {
    return GetRot()->GetOrigin();
  }
  [[nodiscard]] inline const DualOriginRefType
  GetLeft() const
  {
    return GetInvRot()->GetOrigin();
  }
  /** @ITKEndGrouping */
  /** Boolean accessors. */
  [[nodiscard]] bool
  IsOriginSet() const;

  [[nodiscard]] bool
  IsDestinationSet() const;

  [[nodiscard]] bool
  IsRightSet() const;

  [[nodiscard]] bool
  IsLeftSet() const;

  /** Extra data set methods. */
  /** @ITKStartGrouping */
  inline void
  SetPrimalData(const PrimalDataType data)
  {
    m_Data = data;
    this->SetPrimalData();
  }
  inline void
  SetDualData(const DualDataType data)
  {
    this->GetRot()->SetPrimalData(data);
  }
  /** @ITKEndGrouping */
  inline void
  SetPrimalData()
  {
    m_DataSet = true;
  }
  inline void
  SetDualData()
  {
    this->GetRot()->SetPrimalData();
  }

  inline void
  UnsetPrimalData()
  {
    m_Data = false;
  }
  inline void
  UnsetDualData()
  {
    this->GetRot()->UnsetPrimalData();
  }

  /** Extra data get methods. */
  /** @ITKStartGrouping */
  inline PrimalDataType
  GetPrimalData()
  {
    return m_Data;
  }
  inline DualDataType
  GetDualData()
  {
    return this->GetRot()->GetPrimalData();
  }
  /** @ITKEndGrouping */
  /** Boolean accessors. */
  /** @ITKStartGrouping */
  inline bool
  IsPrimalDataSet()
  {
    return m_DataSet;
  }
  inline bool
  IsDualDataSet()
  {
    return this->GetRot()->IsPrimalDataSet();
  }
  /** @ITKEndGrouping */
  /**
   * @return Returns true when "this" has no faces set on both sides.
   *         Return false otherwise.
   */
  inline bool
  IsWire()
  {
    return (!(this->IsLeftSet()) && !(this->IsRightSet()));
  }

  /**
   * @return Returns true when "this" is on the boundary i.e.
   *         one and only one of the faces is set. Return false
   *         otherwise.
   */
  inline bool
  IsAtBorder()
  {
    return ((this->IsLeftSet() && !this->IsRightSet()) || (!this->IsLeftSet() && this->IsRightSet()));
  }

  /**
   * @return Returns true when "this" has faces set on both sides.
   *         Return false otherwise.
   */
  [[nodiscard]] inline bool
  IsInternal() const
  {
    return this->IsLeftSet() && this->IsRightSet();
  }

  [[nodiscard]] bool
  IsOriginInternal() const;

  bool
  IsLnextSharingSameFace(int maxSize = 100);

  bool
  IsLnextOfTriangle();

  bool
  IsInOnextRing(Self *);

  bool
  IsInLnextRing(Self *);

  Self *
  GetNextBorderEdgeWithUnsetLeft(Self * edgeTest = nullptr);

  bool
  InsertAfterNextBorderEdgeWithUnsetLeft(Self * isol, Self * hint = nullptr);

  bool
  ReorderOnextRingBeforeAddFace(Self * second);

  /** Disconnection methods. */
  /** @ITKStartGrouping */
  inline bool
  IsOriginDisconnected()
  {
    return this == this->GetOnext();
  }
  inline bool
  IsDestinationDisconnected()
  {
    return this->GetSym()->IsOriginDisconnected();
  }
  inline bool
  IsDisconnected()
  {
    return this->IsOriginDisconnected() && this->IsDestinationDisconnected();
  }
  /** @ITKEndGrouping */
  void
  Disconnect();

  inline void
  SetIdent(const LineCellIdentifier & User_Value)
  {
    this->m_LineCellIdent = User_Value;
  }
  inline LineCellIdentifier
  GetIdent()
  {
    return this->m_LineCellIdent;
  }

public:
  // Reserved OriginRefType designated to represent the absence of Origin
  static const OriginRefType m_NoPoint;

protected:
  OriginRefType      m_Origin{};         // Geometrical information
  PrimalDataType     m_Data{};           // User data associated to this edge.
  bool               m_DataSet{ false }; // Indicates if the data is set.
  LineCellIdentifier m_LineCellIdent{};
};
} // namespace itk

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

#endif
