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

#include "itkPathAndImageToPathFilter.h"
#include "itkOrthogonallyCorrected2DParametricPath.h"

#include <memory> // For unique_ptr.

namespace itk
{
/**
 * \class OrthogonalSwath2DPathFilter
 * \brief Filter that optimizes a 2D path relative to an image.
 *
 * OrthogonalSwath2DPathFilter produces an OrthogonallyCorrected2DParametricPath
 * representation of a path that is optimal with respect to an image and an
 * original Fourier series path (sometimes referred to as an "initial contour").
 * Usage is a little complex.  The input image must be preprocessed with
 * ExtractOrthogonalSwath2DImageFilter (the user may want to smooth the image
 * first).  The user should then use the resulting swath image to produce a new
 * "merit" swath image of the EXACT same size as the swath image produced in the
 * preceding step.  Each pixel value in the merit swath image indicates the
 * local merit of having the path pass through that swath index (taking the
 * absolute value of the vertical partial-derivative of the swath image is often
 * a good way to do this). Both the merit swath image and the path used to
 * extract the swath image should then be passed as inputs to this filter which
 * will search through the merit swath image using dynamic programming to find
 * the absolutely optimum (in terms of the swath image) path.
 *
 * The file itkOrthogonalSwath2DPathFilterTest.cxx provides a good usage example
 * of itk::OrthogonalSwath2DPathFilter
 *
 * \ingroup PathFilters
 * \ingroup ITKPath
 */
template <typename TFourierSeriesPath, typename TSwathMeritImage>
class ITK_TEMPLATE_EXPORT OrthogonalSwath2DPathFilter
  : public PathAndImageToPathFilter<TFourierSeriesPath, TSwathMeritImage, OrthogonallyCorrected2DParametricPath>
{
public:
  ITK_DISALLOW_COPY_AND_MOVE(OrthogonalSwath2DPathFilter);

  /** Standard class type aliases. */
  using Self = OrthogonalSwath2DPathFilter;
  using Superclass =
    PathAndImageToPathFilter<TFourierSeriesPath, TSwathMeritImage, OrthogonallyCorrected2DParametricPath>;
  using Pointer = SmartPointer<Self>;
  using ConstPointer = SmartPointer<const Self>;

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

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

  /** Some convenient type alias. */
  using InputPathType = TFourierSeriesPath;
  using InputPathPointer = typename InputPathType::Pointer;
  using InputPathInputType = typename InputPathType::InputType;

  using ImageType = TSwathMeritImage;
  using ImageConstPointer = typename ImageType::ConstPointer;

  using OutputPathType = OrthogonallyCorrected2DParametricPath;
  using OutputPathPointer = typename OutputPathType::Pointer;
  using OutputPathInputType = typename OutputPathType::InputType;
  using OrthogonalCorrectionTableType = typename OutputPathType::OrthogonalCorrectionTableType;
  using OrthogonalCorrectionTablePointer = typename OutputPathType::OrthogonalCorrectionTablePointer;

  using IndexType = typename InputPathType::IndexType;
  using OffsetType = typename InputPathType::OffsetType;
  using SizeType = typename ImageType::SizeType;

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

  void
  GenerateData() override;

private:
  // Find the "L" for the maximum merit over the range L-1 to L+1 at F & x.
  // This value is both returned and stored in m_StepValues.
  // The merits for F & x at L-1 to L+1 must have already been calculated.
  unsigned int
  FindAndStoreBestErrorStep(unsigned int x, unsigned int F, unsigned int L);

  // m_StepValues & m_MeritValues are stored as datatype[x][F][L] which requires
  // cols*rows*rows bytes of storage where rows and cols are the dimensions of
  // the processed image.
  //
  // This ordering of elements is most efficient when L is incremented in the
  // inner-most loop and x is incremented in the outer-most loop.
  //
  // m_StepValues & m_MeritValues should always be accessed using the
  // StepValue()
  // and MeritValue() access functions.  StepValue() and MeritValue() can each
  // be
  // used on both the left and right hand of assignments for reads & writes, ex:
  // StepValue(1,1,1) = 2+MeritValue(0,0,3);
  inline int &
  StepValue(int f, int l, int x)
  {
    int rows = m_SwathSize[1];

    return m_StepValues[(x * rows * rows) + (f * rows) + (l)];
  }

  inline double &
  MeritValue(int f, int l, int x)
  {
    int rows = m_SwathSize[1];

    return m_MeritValues[(x * rows * rows) + (f * rows) + (l)];
  }

  std::unique_ptr<int[]> m_StepValues{ nullptr }; // best y=error coordinate @ x of image for (0,F) ->
                                                  // (x+1,L)
  std::unique_ptr<double[]> m_MeritValues{ nullptr };

  std::unique_ptr<int[]> m_OptimumStepsValues{ nullptr }; // best step (e value)
                                                          // sequence for a
                                                          // closed path
  OrthogonalCorrectionTablePointer m_FinalOffsetValues{ OrthogonalCorrectionTableType::New() };

  SizeType m_SwathSize{ { 0 } };
};
} // end namespace itk

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

#endif
