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

#include <algorithm>
#include "itkIntTypes.h"
#include "itkObjectFactory.h"
#include "itkImageRegion.h"

namespace itk
{
/** \class ImageIORegion
 * \brief An ImageIORegion represents a structured region of data.
 *
 * ImageIORegion is an class that represents some structured portion or
 * piece of an Image. The ImageIORegion is represented with an index and
 * a size in each of the n-dimensions of the image. (The index is the
 * corner of the image, the size is the lengths of the image in each of
 * the topological directions.)  ImageIORegion is not templated over
 * dimension, but uses dynamic arrays instead.
 *
 * The first pixel of an image always have a Zero index. Therefore the
 * index values of ImageIORegion may not directly correspond to those
 * of ImageRegion. When translation between the two is performed one
 * much consider the largest possible region who has a non-zero
 * starting index for the image.
 *
 * \sa Region
 * \sa ImageRegion
 * \sa Index
 * \sa Size
 * \sa MeshRegion
 * \ingroup ITKCommon
 */

class ITKCommon_EXPORT ImageIORegion : public Region
{
public:
  /** Standard class type aliases. */
  using Self = ImageIORegion;
  using Superclass = Region;

  /** these types correspond to those of itk::Size, itk::Offset and itk::Index
   */
  using SizeValueType = itk::SizeValueType;
  using IndexValueType = itk::IndexValueType;
  using OffsetValueType = itk::OffsetValueType;

  /** Index type alias support An index is used to access pixel values. */
  using IndexType = std::vector<IndexValueType>;

  /** Size type alias support A size is used to define region bounds. */
  using SizeType = std::vector<SizeValueType>;

  /** Region type taken from the superclass */
  using RegionType = Superclass::RegionEnum;

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

  /** Dimension of the image available at run time. */
  unsigned int
  GetImageDimension() const;

  /** Dimension of the region to be written. This differs from the
   * the image dimension and is calculated at run-time by examining
   * the size of the image in each coordinate direction. */
  unsigned int
  GetRegionDimension() const;

  /** Return the region type. Images are described with structured regions. */
  RegionType
  GetRegionType() const override;

  /** Constructor. ImageIORegion is a lightweight object that is not reference
   * counted, so the constructor is public. */
  ImageIORegion(unsigned int dimension);

  /** Constructor. ImageIORegion is a lightweight object that is not reference
   * counted, so the constructor is public.  Default dimension is 2. */
  ImageIORegion() = default;

  /** Destructor. ImageIORegion is a lightweight object that is not reference
   * counted, so the destructor is public. */
  ~ImageIORegion() override;

  /** Copy constructor. ImageIORegion is a lightweight object that is not
   * reference counted, so the copy constructor is public. */
  ImageIORegion(const Self &) = default;

  /** Move-constructor. Fast and non-throwing. */
  ImageIORegion(Self &&) = default;

  /** Copy-assignment operator. ImageIORegion is a lightweight object that is not reference
   * counted, so operator= is public. */
  Self &
  operator=(const Self & region);

  /** Move-assignment operator. Fast and non-throwing. */
  Self &
  operator=(Self &&) = default;

  /** Set the index defining the corner of the region. */
  void
  SetIndex(const IndexType & index);

  /** Get index defining the corner of the region. */
  const IndexType &
  GetIndex() const;
  IndexType &
  GetModifiableIndex();

  /** Set the size of the region. This plus the index determines the
   * rectangular shape, or extent, of the region. */
  void
  SetSize(const SizeType & size);

  /** Get the size of the region. */
  const SizeType &
  GetSize() const;
  SizeType &
  GetModifiableSize();

  /** Convenience methods to get the size of the image in a particular
   * coordinate direction i. Do not try to access image sizes beyond the
   * the ImageDimension. */
  SizeValueType
  GetSize(unsigned long i) const;

  IndexValueType
  GetIndex(unsigned long i) const;

  void
  SetSize(const unsigned long i, SizeValueType size);

  void
  SetIndex(const unsigned long i, IndexValueType idx);

  /** Compare two regions. */
  bool
  operator==(const Self & region) const;

  ITK_UNEQUAL_OPERATOR_MEMBER_FUNCTION(Self);

  /** Test if an index is inside */
  bool
  IsInside(const IndexType & index) const;

  /** Test if a region (the argument) is completely inside of this region */
  bool
  IsInside(const Self & region) const;

  /** Get the number of pixels contained in this region. This just
   * multiplies the size components. */
  SizeValueType
  GetNumberOfPixels() const;

protected:
  /** Methods invoked by Print() to print information about the object
   * including superclasses. Typically not called by the user (use Print()
   * instead) but used in the hierarchical print process to combine the
   * output of several classes.  */
  void
  PrintSelf(std::ostream & os, Indent indent) const override;

private:
  unsigned int m_ImageDimension{ 2 };
  IndexType    m_Index{ IndexType(2) };
  SizeType     m_Size{ SizeType(2) };
};


// Declare operator<<
extern ITKCommon_EXPORT std::ostream &
                        operator<<(std::ostream & os, const ImageIORegion & region);


/** \class ImageIORegionAdaptor
 * \brief Helper class for converting ImageRegions into ImageIORegions and back.
 *
 * \ingroup ITKCommon
 */
template <unsigned int VDimension>
class ImageIORegionAdaptor
{
public:
  using ImageRegionType = ImageRegion<VDimension>;
  using ImageIORegionType = ImageIORegion;

  using ImageSizeType = typename ImageRegionType::SizeType;
  using ImageIndexType = typename ImageRegionType::IndexType;

  static void
  Convert(const ImageRegionType & inImageRegion,
          ImageIORegionType &     outIORegion,
          const ImageIndexType &  largestRegionIndex)
  {
    //
    // The ImageRegion and ImageIORegion objects may have different dimensions.
    // Here we only copy the common dimensions between the two. If the
    // ImageRegion
    // has more dimensions than the ImageIORegion, then the defaults of the
    // ImageRegion
    // will take care of the remaining codimension. If the ImageRegion has less
    // dimensions
    // than the ImageIORegion, then the remaining IO dimensions are simply
    // ignored.
    //
    const unsigned int ioDimension = outIORegion.GetImageDimension();
    const unsigned int imageDimension = VDimension;

    const unsigned int minDimension = std::min(ioDimension, imageDimension);

    const ImageSizeType &  size = inImageRegion.GetSize();
    const ImageIndexType & index = inImageRegion.GetIndex();

    for (unsigned int i = 0; i < minDimension; ++i)
    {
      outIORegion.SetSize(i, size[i]);
      outIORegion.SetIndex(i, index[i] - largestRegionIndex[i]);
    }

    //
    // Fill in the remaining codimension (if any) with default values
    //
    for (unsigned int k = minDimension; k < ioDimension; ++k)
    {
      outIORegion.SetSize(k, 1); // Note that default size in IO is 1 not 0
      outIORegion.SetIndex(k, 0);
    }
  }

  static void
  Convert(const ImageIORegionType & inIORegion,
          ImageRegionType &         outImageRegion,
          const ImageIndexType &    largestRegionIndex)
  {
    ImageSizeType  size;
    ImageIndexType index;

    size.Fill(1); // initialize with default values
    index.Fill(0);

    //
    // The ImageRegion and ImageIORegion objects may have different dimensions.
    // Here we only copy the common dimensions between the two. If the
    // ImageRegion
    // has more dimensions than the ImageIORegion, then the defaults of the
    // ImageRegion
    // will take care of the remaining codimension. If the ImageRegion has less
    // dimensions
    // than the ImageIORegion, then the remaining IO dimensions are simply
    // ignored.
    //
    const unsigned int ioDimension = inIORegion.GetImageDimension();
    const unsigned int imageDimension = VDimension;

    const unsigned int minDimension = std::min(ioDimension, imageDimension);

    for (unsigned int i = 0; i < minDimension; ++i)
    {
      size[i] = inIORegion.GetSize(i);
      index[i] = inIORegion.GetIndex(i) + largestRegionIndex[i];
    }

    outImageRegion.SetSize(size);
    outImageRegion.SetIndex(index);
  }
};
} // end namespace itk

#endif
