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

#include "itkSegmentationBorder.h"
#include "itkKLMSegmentationRegion.h"
#include "itkMacro.h"
#include "ITKKLMRegionGrowingExport.h"
#include "itkMath.h"
#include "vnl/vnl_vector.h"

namespace itk
{
/**
 * \class KLMDynamicBorderArray
 * \brief  Object maintaining a reference to a list of borders associated
 * with a region.
 *
 * This is a tiny class similar to smart pointers that maintains a reference
 * to a list of borders pointed by a region.
 *
 * \ingroup RegionGrowingSegmentation
 * \ingroup ITKKLMRegionGrowing
 */

template <typename TBorder>
class KLMDynamicBorderArray
{
public:
  /** Greater than operators defined to work with both static objects
   * or pointer to objects.  In the degenerate
   *  case of an image where all (or many) Lambda's are equal to some
   *  constant value, this operator will ensure that the future
   *  merged regions do not gain more borders than other regions,
   *  thus avoiding pathologically slow behavior.
   */
  bool
  operator>(const KLMDynamicBorderArray<TBorder> & rhs) const
  {
    if (Math::ExactlyEquals(m_Pointer->GetLambda(), rhs.m_Pointer->GetLambda()))
    {
      if (m_Pointer->GetLambda() < 0)
      {
        return (m_Pointer > rhs.m_Pointer);
      }
      else
      {
        // The purpose of this comparison is to not let any one region
        // get more borders than another region.  In the degenerate
        // case of an image where the Lambdas are always equal to some
        // constant C, allowing a single region to be repeatedly
        // merged so that it gains many borders will result in
        // pathologically slow behavior.
        double v1 = std::max(static_cast<double>(m_Pointer->GetRegion1()->GetRegionBorderSize()),
                             static_cast<double>(m_Pointer->GetRegion2()->GetRegionBorderSize()));

        double v2 = std::max(static_cast<double>(rhs.m_Pointer->GetRegion1()->GetRegionBorderSize()),
                             static_cast<double>(rhs.m_Pointer->GetRegion2()->GetRegionBorderSize()));

        return (v1 > v2);
      }
    }
    return (m_Pointer->GetLambda() > rhs.m_Pointer->GetLambda());
  }

  bool
  operator>(const KLMDynamicBorderArray<TBorder> * rhs) const
  {
    if (m_Pointer->GetLambda() == rhs->m_Pointer->GetLambda())
    {
      if (m_Pointer->GetLambda() < 0)
      {
        return (m_Pointer > rhs->m_Pointer);
      }
      else
      {
        // The purpose of this comparison is to not let any one region
        // get more borders than another region.  In the degenerate
        // case of an image where the Lambdas are always equal to some
        // constant C, allowing a single region to be repeatedly
        // merged so that it gains many borders will result in
        // pathologically slow behavior.
        double v1 = std::max(static_cast<double>(m_Pointer->GetRegion1()->GetRegionBorderSize()),
                             static_cast<double>(m_Pointer->GetRegion2()->GetRegionBorderSize()));

        double v2 = std::max(static_cast<double>(rhs->m_Pointer->GetRegion1()->GetRegionBorderSize()),
                             static_cast<double>(rhs->m_Pointer->GetRegion2()->GetRegionBorderSize()));

        return (v1 > v2);
      }
    }
    return (m_Pointer->GetLambda() > rhs->m_Pointer->GetLambda());
  }

  TBorder * m_Pointer;
};

/**
 * \class KLMSegmentationBorder
 * \brief Base class for KLMSegmentationBorder object
 *
 * itkKLMSegmentationBorder is the base class for the KLMSegmentationBorder
 * objects. It provides the basic function definitions that are inherent to a
 * KLMSegmentationBorder objects.
 *
 * This class implements the border object that is used in particular with
 * the KLM algorithm (see also KLMRegionGrowImageFilter). The border is
 * defined by the adjacency of two regions. The parameter Lambda ascertains
 * the importance of the border in defining the regions. The higher the
 * values of Lambda the more dominant is its presence in the a region. In
 * case of removal of a border during the region growing process the one with
 * least Lambda value is eliminated.
 *
 * \ingroup RegionGrowingSegmentation
 * \ingroup ITKKLMRegionGrowing
 */

// Forward reference because of circular dependencies
class ITK_FORWARD_EXPORT KLMSegmentationRegion;

class ITKKLMRegionGrowing_EXPORT KLMSegmentationBorder : public SegmentationBorder
{
public:
  ITK_DISALLOW_COPY_AND_MOVE(KLMSegmentationBorder);

  /** Standard class type aliases. */
  using Self = KLMSegmentationBorder;
  using Superclass = SegmentationBorder;
  using Pointer = SmartPointer<Self>;
  using ConstPointer = SmartPointer<const Self>;

  /** Method for creation through the object factory. */
  itkNewMacro(Self);

  /** \see LightObject::GetNameOfClass() */
  itkOverrideGetNameOfClassMacro(KLMSegmentationBorder);

  /** Set the region 1 associated with the border */
  void
  SetRegion1(KLMSegmentationRegion * Region1);

  /** Get the region 1 associated with the border. */
  KLMSegmentationRegion *
  GetRegion1();

  /** Set the region 2 associated with the border. */
  void
  SetRegion2(KLMSegmentationRegion * Region2);

  /** Get the region 2 associated with the border. */
  KLMSegmentationRegion *
  GetRegion2();

  /** Set/Get the Lambda parameter associate with the borders
   * in the KLM algorithm */
  itkSetMacro(Lambda, double);
  itkGetConstReferenceMacro(Lambda, double);

  /** Evaluate the Lambda for a given border. */
  void
  EvaluateLambda();

  /** Print the data associated with each border. */
  void
  PrintBorderInfo();

protected:
  /** Constructor. */
  KLMSegmentationBorder();

  /** Destructor. */
  ~KLMSegmentationBorder() override;

  /** Print self identity */
  void
  PrintSelf(std::ostream & os, Indent indent) const override;

private:
  double                  m_Lambda{};
  KLMSegmentationRegion * m_Region1{};
  KLMSegmentationRegion * m_Region2{};
};
} // end namespace itk

#endif
