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

#include "itkLabelMapFilter.h"
#include "itkLabelImageToLabelMapFilter.h"
#include "itkBinaryImageToLabelMapFilter.h"
#include "itkLabelMapToBinaryImageFilter.h"
#include "itkLabelSelectionLabelMapFilter.h"
#include "itkAutoCropLabelMapFilter.h"
#include "itkPadLabelMapFilter.h"


namespace itk {

/** \class ObjectByObjectLabelMapFilter
 * \brief ObjectByObjectLabelMapFilter applies an image pipeline to all the objects of a label map and produce a new label map
 *
 * The image pipeline can simply produce a modified object or produce several objects
 * from the single input object. Several options are provided to handle the different
 * cases.
 *
 * KeepLabel, which defaults to true, makes the filter try to keep as much as possible
 * the labels of the original objects. If an image pipeline produce several objects
 * the label of the input object is assigned to the first output object. The other
 * output objects get another label not present in the input image.
 * When KeepLabel is set to false, all the objects are relabeled in the order of
 * apparition during the filter process.
 *
 * BinaryInternalOutput can be set to true if the image pipeline produce binary
 * output image. In that case, the objects produced are identified with a connected
 * component algorithm before being reinserted in the output label map.
 * InternalForegroundValue can be set to a specific value which represent the foreground
 * value in the binary image.
 *
 * PadSize and ConstrainPaddingToImage can be used to extend the size of the image to
 * process passed to the image pipeline. This is useful if the image pipeline is known
 * to be able to enlarge the object. The padding can be constrained to the input label map
 * region by setting ConstrainPaddingToImage to true - this parameter can make a difference
 * for the algorithm with a different behavior on the border of the image.
 * By default, the image is padded by 1 pixel and constrained to the image region.
 *
 * \note: When applying a single filter, input and output filters are the same; while applying a pipeline,
 * input and output filters are different, may not even be of the same type. It is the responsibility of the
 * user to connect the pipeline properly outside of this filter.
 *
 *
 * \author Gaetan Lehmann. Biologie du Developpement et de la Reproduction, INRA de Jouy-en-Josas, France.
 *
 * This implementation was taken from the Insight Journal paper:
 * https://hdl.handle.net/1926/584  or
 * http://www.insight-journal.org/browse/publication/176
 *
 * \ingroup ImageEnhancement  MathematicalMorphologyImageFilters
 * \ingroup ITKLabelMap
 *
 * \wiki
 * \wikiexample{ImageProcessing/ObjectByObjectLabelMapFilter,Apply an operation to every label object in a label map}
 * \endwiki
 */
template<typename TInputImage, typename TOutputImage=TInputImage,
  typename TInputFilter=ImageToImageFilter<
    Image< unsigned char, TInputImage::ImageDimension >,
    Image< unsigned char, TOutputImage::ImageDimension > >,
  class TOutputFilter=typename TInputFilter::Superclass,
  class TInternalInputImage=typename TInputFilter::InputImageType,
  class TInternalOutputImage=typename TOutputFilter::OutputImageType >
class ITK_TEMPLATE_EXPORT ObjectByObjectLabelMapFilter :
    public LabelMapFilter<TInputImage, TOutputImage>
{
public:
  ITK_DISALLOW_COPY_AND_ASSIGN(ObjectByObjectLabelMapFilter);

  /** Standard class type aliases. */
  using Self = ObjectByObjectLabelMapFilter;
  using Superclass = LabelMapFilter<TInputImage, TOutputImage>;
  using Pointer = SmartPointer<Self>;
  using ConstPointer = SmartPointer<const Self>;

  /** Some convenient type alias. */
  using InputImageType = TInputImage;
  using OutputImageType = TOutputImage;
  using InputImagePointer = typename InputImageType::Pointer;
  using InputImageConstPointer = typename InputImageType::ConstPointer;
  using InputImageRegionType = typename InputImageType::RegionType;
  using InputImagePixelType = typename InputImageType::PixelType;
  using OutputImagePointer = typename OutputImageType::Pointer;
  using OutputImageConstPointer = typename OutputImageType::ConstPointer;
  using OutputImageRegionType = typename OutputImageType::RegionType;
  using OutputImagePixelType = typename OutputImageType::PixelType;
  using SizeType = typename OutputImageType::SizeType;
  using LabelMapType = OutputImageType;
  using LabelObjectType = typename LabelMapType::LabelObjectType;

  using InputFilterType = TInputFilter;
  using OutputFilterType = TOutputFilter;

  using InternalInputImageType = TInternalInputImage;
  using InternalRegionType = typename InternalInputImageType::RegionType;
  using InternalSizeType = typename InternalInputImageType::SizeType;
  using InternalIndexType = typename InternalInputImageType::IndexType;
  using InternalOffsetType = typename InternalInputImageType::OffsetType;
  using InternalInputPixelType = typename InternalInputImageType::PixelType;

  using InternalOutputImageType = TInternalOutputImage;
  using InternalOutputPixelType = typename InternalOutputImageType::PixelType;

  /** ImageDimension constants */
  static constexpr unsigned int InputImageDimension = TInputImage::ImageDimension;
  static constexpr unsigned int OutputImageDimension = TOutputImage::ImageDimension;
  static constexpr unsigned int ImageDimension = TOutputImage::ImageDimension;

  /** Standard New method. */
  itkNewMacro(Self);

  /** Runtime information support. */
  itkTypeMacro(ObjectByObjectLabelMapFilter,
               LabelMapFilter);

  void SetFilter(InputFilterType * filter);
  InputFilterType * GetFilter()
    {
    return this->m_InputFilter;
    }

  const InputFilterType * GetFilter() const
    {
    return this->m_InputFilter;
    }

  void SetInputFilter( InputFilterType * filter );
  itkGetModifiableObjectMacro(InputFilter, InputFilterType );

  void SetOutputFilter( OutputFilterType * filter );
  itkGetModifiableObjectMacro(OutputFilter, OutputFilterType );

  /** If KeepLabels is set to true, the filter will do its best to reuse the labels
   * of the input objects in the output ones. However, this is possible only if the
   * internal pipeline produce a single object - the other labels will be taken as
   * they come.
   * If KeepLabels is false, no care is made of the input labels, and a new label is produced
   * for all the objects using LabelMap::PushLabelObject().
   */
  itkSetMacro(KeepLabels, bool);
  itkGetMacro(KeepLabels, bool);
  itkBooleanMacro(KeepLabels);

  /** If PadSize is not zero, the image produce for each object will be padded.
    * The default value is 1 on all the dimensions.
    */
  itkSetMacro(PadSize, SizeType);
  itkGetMacro(PadSize, SizeType);

  /** Padding by PadSize will be constrained to the input image region if
   * ConstrainPaddingToImage is true, and won't be constrained if it is set to false.
   * Default value is true.
   */
  itkSetMacro(ConstrainPaddingToImage, bool);
  itkGetMacro(ConstrainPaddingToImage, bool);
  itkBooleanMacro(ConstrainPaddingToImage);

  /** Set/Get whether the internal image produced by OutputFilter should be interpreted
   * as a binary image in which the filter have to search for connected components. If
   * set to false, the filter consider the image as a label image.
   * Default is false.
   */
  itkSetMacro(BinaryInternalOutput, bool);
  itkGetMacro(BinaryInternalOutput, bool);
  itkBooleanMacro(BinaryInternalOutput);

  /** The foreground value used internally to represent the object in the image passed to
   * InputFilter, and to read the data produced by OutputFilter, if BinaryInternalOutput
   * is true
   */
  itkSetMacro(InternalForegroundValue, InternalOutputPixelType);
  itkGetMacro(InternalForegroundValue, InternalOutputPixelType);

  /** The label of the object currently processed by the filter. This is intended to be
   * used with the IterationEvent sent before the processing of each object. It contains
   * a relevant value only during the filter update.
   */
  itkGetMacro(Label, InputImagePixelType);

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

  void GenerateData() override;

private:
  bool     m_ConstrainPaddingToImage;
  SizeType m_PadSize;
  bool     m_BinaryInternalOutput;

  bool     m_KeepLabels;

  InternalOutputPixelType m_InternalForegroundValue;

  using SelectType = itk::LabelSelectionLabelMapFilter< LabelMapType >;
  typename SelectType::Pointer m_Select;

  using CropType = itk::AutoCropLabelMapFilter< LabelMapType >;
  typename CropType::Pointer   m_Crop;

  using PadType = itk::PadLabelMapFilter< LabelMapType >;
  typename PadType::Pointer    m_Pad;

  using LM2BIType = itk::LabelMapToBinaryImageFilter< LabelMapType, InternalInputImageType>;
  typename LM2BIType::Pointer  m_LM2BI;

  using LI2LMType = itk::LabelImageToLabelMapFilter< InternalOutputImageType, LabelMapType>;
  typename LI2LMType::Pointer  m_LI2LM;

  using BI2LMType = itk::BinaryImageToLabelMapFilter< InternalOutputImageType, LabelMapType>;
  typename BI2LMType::Pointer  m_BI2LM;

  typename InputFilterType::Pointer       m_InputFilter;
  typename OutputFilterType::Pointer      m_OutputFilter;


  InputImagePixelType          m_Label;

}; // end of class

} // end namespace itk

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

#endif
