/*=========================================================================
 *
 *  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 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"
#include "itkMath.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 definitons 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_ASSIGN(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);

  /** Run-time type information (and related methods). */
  itkTypeMacro(KLMSegmentationBorder, SegmentationBorder);

  /** 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
