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


#include <list>
#include <vector>
#include "itkImage.h"
#include "itkProcessObject.h"
#include <unordered_map>

namespace itk
{
namespace watershed
{
/** \class Boundary
 * \par
 * A data object for used by watershed segmentation process objects in
 * streaming applications. A "boundary" represents the single-pixel wide
 * surface of an image chunk. This class is used to store information needed to
 * resolve processing at chunk  boundaries after data streaming of the
 * watershed segmentation algorithm.
 *
 * \par
 * This is an itkDataObject.  It contains a matrix of "faces" of an
 * N-dimensional hypercube. A chunk of a volume with \f$ N \f$ dimensions
 * has \f$ 2N \f$ faces of dimension \f$ N-1 \f$. Some examples: A
 * 2-dimensional image has 4 faces that are lines.  A 3-dimensional image has 6
 * faces that are planes.  A 4-dimensional image has 8 faces which are
 * cubes. Faces are indexed as \f$ N \f$ sets of pairs \f$ (low, high) \f$.
 *
 * \sa WatershedSegmenter
 * \sa WatershedBoundaryResolver
 * \ingroup WatershedSegmentation
 * \ingroup ITKWatersheds
 */
template <typename TScalar, unsigned int TDimension>
class ITK_TEMPLATE_EXPORT Boundary : public DataObject
{
public:
  ITK_DISALLOW_COPY_AND_MOVE(Boundary);

  /** The dimensionality of this boundary.  For example, if the boundary
   * of a set of planes, it has dimensionality 2.  If the boundary is
   * a set of lines, it has dimensionality 1.  Dimensionality is one less
   * than the image chunks from which the boundary is derived.   */
  static constexpr unsigned int Dimension = TDimension;

  /** A pair of values used to index into the boundary data structure.
   * The IndexType.first is the dimension of the face and IndexType.second is a
   * binary value 0 or 1 indicating the LOW face or the HIGH face,
   * respectively.    */
  using IndexType = std::pair<unsigned int, unsigned int>;
  using ImageType = Image<IdentifierType, TDimension>;
  using ImageIndexType = typename ImageType::IndexType;
  using ScalarType = TScalar;

  /** Data type stored at each pixel in a face.   */
  struct face_pixel_t
  {
    /**Index of the direction of watershed flow through this pixel.
     * A negative value indicates that the flow does not move out
     * of the region.  A positive value is the index into the
     * pixel neighborhood of the facing chunk boundary into which
     * flow moves.
     *
     * Note that the range of values of the index depends on the
     * the connectivity used by the watershed segmentation algorithm.
     * If the WS algorithm uses city-block style connectivity (4-connectivity
     * in 2D, 6-connectivity in 3D, etc) this could actually be a boolean
     * value indicating inward or outward flow since there is only one
     * valid neighbor to reference.  For extensibility to other
     * connectivities, this flow value can be used to index a number of
     * different neighbors.     */
    short flow;

    /** The label associated with this pixel.     */
    IdentifierType label;
  };

  /**    */
  struct flat_region_t
  {
    /** Indices into the associated Face containing boundary pixels.  These
     * give access to spatial information, label and flow associated with
     * this boundary pixel connection.     */
    std::list<IdentifierType> offset_list;

    /** The value of the lowest point (indicating the steepest descent) along
     * the boundary of the flat region of which this pixel is a member.     */
    ScalarType bounds_min;

    /** The label associated with the lowest point
     * point along this flat region boundary.     */
    IdentifierType min_label;

    /** The value of this flat region     */
    ScalarType value;
  };

  /** The face data structure.  This is just an Image of face pixel
      types. */
  using face_t = Image<face_pixel_t, TDimension>;

  /** A hash table holding flat region data structures.   */
  using flat_hash_t = std::unordered_map<IdentifierType, flat_region_t>;
  using FlatHashValueType = typename flat_hash_t::value_type;

  /** Itk type alias and macros defining smart pointer and type identification.
   */
  using Self = Boundary;
  using Superclass = DataObject;
  using Pointer = SmartPointer<Self>;
  using ConstPointer = SmartPointer<const Self>;
  itkNewMacro(Self);
  itkOverrideGetNameOfClassMacro(Boundary);

  /** The following averts an internal compiler error on microsoft compilers */
  using FacePointer = typename face_t::Pointer;

  /** Returns the face at the specified index  */
  FacePointer
  GetFace(const IndexType & idx)
  {
    return this->GetFace(idx.first, idx.second);
  }

  /** Returns the face at the specified index, where dimension is
   * the number of the axial dimension and highlow is 0 for the LOW
   * face and 1 for the HIGH face.   */
  FacePointer
  GetFace(unsigned int dimension, unsigned int highlow)
  {
    if (highlow == 0)
    {
      return m_Faces[dimension].first;
    }
    else
    {
      return m_Faces[dimension].second;
    }
  }

  void
  SetFace(FacePointer f, const IndexType & idx)
  {
    this->SetFace(f, idx.first, idx.second);
  }

  void
  SetFace(FacePointer f, unsigned int dimension, unsigned int highlow)
  {
    if (highlow == 0)
    {
      m_Faces[dimension].first = f;
    }
    else
    {
      m_Faces[dimension].second = f;
    }
    this->Modified();
  }

  /** Get/Set the table of flat region connections specified by the index. */
  flat_hash_t *
  GetFlatHash(const IndexType & idx)
  {
    return this->GetFlatHash(idx.first, idx.second);
  }
  flat_hash_t *
  GetFlatHash(unsigned int dimension, unsigned int highlow)
  {
    if (highlow == 0)
    {
      return &(m_FlatHashes[dimension].first);
    }
    else
    {
      return &(m_FlatHashes[dimension].second);
    }
  }

  void
  SetFlatHash(flat_hash_t & l, const IndexType & idx)
  {
    this->SetFlatHash(l, idx.first, idx.second);
  }
  void
  SetFlatHash(flat_hash_t & l, unsigned int dimension, unsigned int highlow)
  {
    if (highlow == 0)
    {
      m_FlatHashes[dimension].first = l;
    }
    else
    {
      m_FlatHashes[dimension].second = l;
    }
    this->Modified();
  }

  /** Marks a face in the boundary object as either valid (true) or
   * invalid (false).  A valid face is assumed to be initialized
   * and contain information.  No assumptions are made about an
   * invalid face.   */
  void
  SetValid(bool & l, const IndexType & idx)
  {
    this->SetValid(l, idx.first, idx.second);
  }
  void
  SetValid(bool b, unsigned int dimension, unsigned int highlow)
  {
    if (highlow == 0)
    {
      m_Valid[dimension].first = b;
    }
    else
    {
      m_Valid[dimension].second = b;
    }
    this->Modified();
  }

  bool
  GetValid(const IndexType & idx) const
  {
    return this->GetValid(idx.first, idx.second);
  }
  bool
  GetValid(unsigned int dimension, unsigned int highlow) const
  {
    if (highlow == 0)
    {
      return m_Valid[dimension].first;
    }
    else
    {
      return m_Valid[dimension].second;
    }
  }

protected:
  Boundary();
  ~Boundary() override = default;

  void
  PrintSelf(std::ostream & os, Indent indent) const override;

  /** The Nx2 matrix of faces of this boundary.   */
  std::vector<std::pair<FacePointer, FacePointer>> m_Faces{};

  /** The Nx2 matrix flat region connections associated with
   * this boundary.   */
  std::vector<std::pair<flat_hash_t, flat_hash_t>> m_FlatHashes{};

  /** The Nx2 matrix of boolean flags indicating which faces contain
   * information.    */
  std::vector<std::pair<bool, bool>> m_Valid{};
};
} // end namespace watershed
} // end namespace itk

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

#endif
