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

#include "itkMacro.h"

#include "vxl_version.h"
#include "vnl/vnl_vector.h"

namespace itk
{
/**
 * \class Array
 *  \brief Array class with size defined at construction time.
 *
 * This class derives from the vnl_vector<> class.
 * Its size is assigned at construction time (run time) and can
 * not be changed afterwards except by using assignment to another
 * Array.
 *
 * The class is templated over the type of the elements.
 *
 * Template parameters for class Array:
 *
 * - TValue = Element type stored at each location in the array.
 *
 * \ingroup DataRepresentation
 * \ingroup ITKCommon
 */
template <typename TValue>
class ITK_TEMPLATE_EXPORT Array : public vnl_vector<TValue>
{
public:
  /** The element type stored at each location in the Array. */
  using ValueType = TValue;
  using Self = Array;
  using VnlVectorType = vnl_vector<TValue>;
  using SizeValueType = typename vnl_vector<TValue>::size_type;

public:
  /** Default constructor. It is created with an empty array
   *  it has to be allocated later by assignment */
  Array() = default;

  /** Copy constructor.  Uses VNL copy constructor with correct
   *  setting for memory management. */
  Array(const Array &);

  /** Construct from a VnlVectorType */
  explicit Array(const VnlVectorType &);

  /** Constructor with size. Size can only be changed by assignment.
   * \note This constructor may not initialize its elements.
   */
  explicit Array(SizeValueType dimension);

  /** Constructor with size and initial value for each element. */
  explicit Array(SizeValueType dimension, const ValueType & value);

  /** Constructor that initializes array with contents from a user supplied
   * buffer. The pointer to the buffer and the length is specified. By default,
   * the array does not manage the memory of the buffer. It merely points to
   * that location and it is the user's responsibility to delete it.
   * If "LetArrayManageMemory" is true, then this class will free the
   * memory when this object is destroyed.
   * \note This constructor supports class template argument deduction (CTAD). */
  Array(TValue * datain, SizeValueType sz, bool LetArrayManageMemory = false);

#if defined(ITK_LEGACY_REMOVE)
  /** Constructor that initializes array with contents from a user supplied
   * const buffer. The pointer to the buffer and the length is specified. By default,
   * the array does a deep copy of the const pointer data, so the array class also
   * manages memory.
   * \note This constructor supports class template argument deduction (CTAD). */
  Array(const TValue * datain, SizeValueType sz);

#else // defined ( ITK_LEGACY_REMOVE )
  /** Constructor that initializes array with contents from a user supplied
   * buffer. The pointer to the buffer and the length is specified. The array
   * does a deep copy of the const pointer data, so the array class also
   * manages memory. The 3rd argument is only for backward compatibility.
   * \note This constructor supports class template argument deduction (CTAD). */
  Array(const TValue * datain, SizeValueType sz, bool LetArrayManageMemory = false);
#endif

  /** Constructor to initialize an array from another of any data type */
  template <typename TArrayValue>
  Array(const Array<TArrayValue> & r)
  {
    this->SetSize(r.GetSize());
    for (SizeValueType i = 0; i < r.GetSize(); ++i)
    {
      this->operator[](i) = static_cast<TValue>(r[i]);
    }
  }

  /** Set all the elements of the array to the specified value */
  void
  Fill(const TValue & v)
  {
    this->fill(v);
  }

  /** Copy operator */
  Self &
  operator=(const Self & rhs);

  Self &
  operator=(const VnlVectorType & rhs);

  /** Return the number of elements in the Array  */
  /** @ITKStartGrouping */
  [[nodiscard]] SizeValueType
  Size() const
  {
    return static_cast<SizeValueType>(this->size());
  }
  [[nodiscard]] unsigned int
  GetNumberOfElements() const
  {
    return static_cast<SizeValueType>(this->size());
  }
  /** @ITKEndGrouping */
  /** Get one element */
  [[nodiscard]] const TValue &
  GetElement(SizeValueType i) const
  {
    return this->operator[](i);
  }

  /** Set one element */
  void
  SetElement(SizeValueType i, const TValue & value)
  {
    this->operator[](i) = value;
  }

  /** Destructively set the size to that given.  Will lose data.  */
  void
  SetSize(SizeValueType sz);

  [[nodiscard]] SizeValueType
  GetSize() const
  {
    return static_cast<SizeValueType>(this->size());
  }

  /** Set the pointer from which the data is imported.
   * If "LetArrayManageMemory" is false, then the application retains
   * the responsibility of freeing the memory for this data.  If
   * "LetArrayManageMemory" is true, then this class will free the
   * memory when this object is destroyed.
   * NOTE: This signature requires that internal array is being
   *       replaced by data array of exactly the same size
   */
  void
  SetDataSameSize(TValue * datain, bool LetArrayManageMemory = false);

  /** Similar to the previous method. In the above method, the size must be
   * separately set prior to using user-supplied data. This introduces an
   * unnecessary allocation step to be performed. This method avoids it
   * and should be used to import data wherever possible to avoid this.
   * Set the pointer from which the data is imported.
   * If "LetArrayManageMemory" is false, then the application retains
   * the responsibility of freeing the memory for this data.  If
   * "LetArrayManageMemory" is true, then this class will free the
   * memory when this object is destroyed. */
  void
  SetData(TValue * datain, SizeValueType sz, bool LetArrayManageMemory = false);

#ifdef __INTEL_COMPILER
#  pragma warning disable 444 // destructor for base class "itk::Array<>" is not virtual
#endif
  /** This destructor is not virtual for performance reasons. However, this
   * means that subclasses cannot allocate memory. */
  ~Array() override;

#if !defined(ITK_LEGACY_REMOVE)
  void
  swap(Array & other)
  {
    this->Swap(other);
  }
#endif

  void
  Swap(Array & other) noexcept
  {
    using std::swap;
    this->VnlVectorType::swap(other);
    swap(this->m_LetArrayManageMemory, other.m_LetArrayManageMemory);
  }

private:
  /** Indicates whether this array manages the memory of its data. */
  bool m_LetArrayManageMemory{ true };
};

// Deduction guide to avoid compiler warnings (-wctad-maybe-unsupported) when using class template argument deduction.
template <typename TValue>
Array(TValue *, typename vnl_vector<TValue>::size_type, bool) -> Array<TValue>;

template <typename TValue>
std::ostream &
operator<<(std::ostream & os, const Array<TValue> & arr)
{
  os << '[';
  const unsigned int length = arr.size();
  if (length >= 1)
  {
    const unsigned int last = length - 1;
    for (unsigned int i = 0; i < last; ++i)
    {
      os << arr[i] << ", ";
    }
    os << arr[last];
  }
  os << ']';
  return os;
}

// declaration of specialization
template <>
ITKCommon_EXPORT std::ostream & operator<< <double>(std::ostream & os, const Array<double> & arr);
template <>
ITKCommon_EXPORT std::ostream & operator<< <float>(std::ostream & os, const Array<float> & arr);


template <typename T>
inline void
swap(Array<T> & a, Array<T> & b) noexcept
{
  a.Swap(b);
}

} // namespace itk

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

#endif
