/*=========================================================================
 *
 *  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 itkMetaArrayReader_h
#define itkMetaArrayReader_h
#include "ITKIOMetaExport.h"

#include "itkLightProcessObject.h"
#include "itkArray.h"
#include "itkCovariantVector.h"
#include "itkVariableLengthVector.h"
#include "metaArray.h"

namespace itk
{
class ITKIOMeta_EXPORT MetaArrayReader : public LightProcessObject
{
public:
  /** SmartPointer type alias support */
  using Self = MetaArrayReader;
  using Superclass = LightProcessObject;
  using Pointer = SmartPointer<Self>;
  using ConstPointer = SmartPointer<const Self>;

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

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

  /** Set the filename. */
  itkSetStringMacro(FileName);

  /** Get the filename. */
  itkGetStringMacro(FileName);

  /** Get a pointer to the MetaArray so that its options, such as adding
   *  comments and user-defined fields, can be changed. */
  MetaArray *
  GetMetaArrayPointer();

  /** Specify the buffer (already allocated) into which data should be read. */
  void
  SetBuffer(void * _buffer);

  /** Read the Array in MetaIO format. */
  void
  Update();

  /** Return the MetaIO type that corresponds to the element type. */
  MET_ValueEnumType
  GetDataType() const
  {
    return m_MetaArray.ElementType();
  }

  /** Size is used by itkArray and VariableLengthVector. */
  int
  Size() const
  {
    return m_MetaArray.Length();
  }

  /** GetNumberOfElements is used by itkArray and VariableLengthVector. */
  int
  GetNumberOfElements() const
  {
    return m_MetaArray.Length();
  }

  /** GetVectorDimension is used by itkVector. */
  int
  GetVectorDimension() const
  {
    return m_MetaArray.Length();
  }

  /** GetNumberOfComponents is used by itkVector and itkCovariantVector. */
  int
  GetNumberOfComponents() const
  {
    return m_MetaArray.Length();
  }

  /** GetCovariantVectorDimension is used by itkVector. */
  int
  GetCovariantVectorDimension() const
  {
    return m_MetaArray.Length();
  }

  /** GetElement is used by itkArray, itkFixedArray, and VariableLengthVector. */
  template <typename TValue>
  inline void
  GetElement(TValue & value, unsigned int i, unsigned int channel = 0) const
  {
    value = static_cast<TValue>(m_MetaArray.ElementData(i * m_MetaArray.ElementNumberOfChannels() + channel));
  }

  /** Get an itkArray.
   *  Specify the MetaType of the elements of the Array and provide a
   *  pointer to the Array. The buffer of the Array is replaced by the
   *  buffer into which the data was directly read (a copy does not occur).
   *  If _letVectorManageData is true, the buffer persists even after this
   *  MetaArrayReader is destroyed. Otherwise, the buffer (and therefore
   *  the validity of the data in the Array) is destroyed when the
   *  MetaArrayReader is destroyed. */
  template <typename TValue>
  void
  GetOutput(MET_ValueEnumType _metaElementType, Array<TValue> * _array, bool _letArrayManageData = true)
  {
    if (m_MetaArray.ElementType() != _metaElementType)
    {
      m_MetaArray.ConvertElementDataTo(_metaElementType);
    }
    _array->SetData((TValue *)(m_MetaArray.ElementData()), m_MetaArray.Length(), _letArrayManageData);
    if (_letArrayManageData)
    {
      m_MetaArray.AutoFreeElementData(false);
    }
  }

  /** Get an itkFixedArray. */
  template <typename TValue, unsigned int VLength>
  bool
  GetOutput(MET_ValueEnumType itkNotUsed(_metaElementType), FixedArray<TValue, VLength> * _array)
  {
    if (static_cast<int>(VLength) <= m_MetaArray.Length())
    {
      for (unsigned int i = 0; i < VLength; ++i)
      {
        this->GetElement((*_array)[i], i);
      }
      return true;
    }
    return false;
  }

  /** Get an itkVector.
   *  Specify the MetaType of the elements of the itkVector and provide
   *  a pointer to the itkVector into which the data should be copied. */
  template <typename TValue, unsigned int VLength>
  bool
  GetOutput(MET_ValueEnumType itkNotUsed(_metaElementType), Vector<TValue, VLength> * _vector)
  {
    if (static_cast<int>(VLength) <= m_MetaArray.Length())
    {
      for (unsigned int i = 0; i < VLength; ++i)
      {
        this->GetElement((*_vector)[i], i);
      }
      return true;
    }
    return false;
  }

  /** Get an itkCovariantVector.
   *  Specify the MetaType of the elements of the itkCovariantVector and
   *  provide a pointer to the itkCovariantVector into which the data
   *  should be copied. */
  template <typename TValue, unsigned int VLength>
  bool
  GetOutput(MET_ValueEnumType itkNotUsed(_metaElementType), CovariantVector<TValue, VLength> * _vector)
  {
    if (static_cast<int>(VLength) <= m_MetaArray.Length())
    {
      for (unsigned int i = 0; i < VLength; ++i)
      {
        this->GetElement((*_vector)[i], i);
      }
      return true;
    }
    return false;
  }

  /** Get it VariableLengthVector.
   *  Specify the MetaType of the elements of the VariableLengthVector and
   *  provide a pointer to the VariableLengthVector. The buffer of the
   *  VariableLengthVector is replaced by the buffer into which the data
   *  was directly read (a copy does not occur).
   *  If _letVectorManageData is true, the buffer persists even after this
   *  MetaArrayReader is destroyed. Otherwise, the buffer (and therefore
   *  the validity of the data in the VariableLengthVector) is destroyed
   *  when the MetaArrayReader is destroyed. */
  template <typename TValue>
  void
  GetOutput(MET_ValueEnumType              _metaElementType,
            VariableLengthVector<TValue> * _vector,
            bool                           _letVectorManageData = true)
  {
    if (m_MetaArray.ElementType() != _metaElementType)
    {
      m_MetaArray.ConvertElementDataTo(_metaElementType);
    }
    _vector->SetData((TValue *)(m_MetaArray.ElementData()), m_MetaArray.Length(), _letVectorManageData);
    if (_letVectorManageData)
    {
      m_MetaArray.AutoFreeElementData(false);
    }
  }

  /** Get an itkArray of Arrays, itk::Array< itk::Array<short> >.
   *  Assumes all sub-arrays have the same length.
   *  Specify the MetaType of the elements of the Array< Array< * > > and
   *  provide a pointer to the Array of arrays. Elements are copied
   *  into the array of arrays. */
  template <typename TValue>
  void
  GetMultiChannelOutput(MET_ValueEnumType _metaElementType, Array<TValue> * _array)
  {
    if (m_MetaArray.ElementType() != _metaElementType)
    {
      m_MetaArray.ConvertElementDataTo(_metaElementType);
    }
    int rows = m_MetaArray.Length();
    int cols = m_MetaArray.ElementNumberOfChannels();
    _array->SetSize(rows);
    for (int i = 0; i < rows; ++i)
    {
      (*_array)[i].SetSize(cols);
      for (int j = 0; j < cols; ++j)
      {
        (*_array)[i][j] = static_cast<typename TValue::ValueType>(m_MetaArray.ElementData(i * cols + j));
      }
    }
  }

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

private:
  MetaArray m_MetaArray{};

  std::string m_FileName{};

  void * m_Buffer{ nullptr };
};
} // namespace itk

#endif // itkMetaArrayReader_h
