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

#include "itkArray.h"
#include "itkMatrix.h"
#include "itkOffset.h"
#include "itkCovariantVector.h"
#include "itkVariableLengthVector.h"
#include "itkVector.h"
#include "itkPoint.h"

namespace itk
{

/**
 * \class MeshConvertPixelTraits
 *  \brief Traits class used to by ConvertPixels to convert blocks of pixels.
 *
 *  TOutputPixelType is the destination type. The input type is inferred
 *  by the templated static function Convert.
 *
 *  This implementation, does a simple assignment operator, so if you are
 *  going from from a higher bit representation to a lower bit one (int to
 *  char), you may want to specialize and add some sort of transfer function.
 *  \ingroup ITKIOMeshBase
 */
template <typename PixelType>
class MeshConvertPixelTraits
{
public:
  /** Determine the pixel data type. */
  using ComponentType = typename PixelType::ComponentType;

  /** Return the number of components per pixel. */
  static unsigned int
  GetNumberOfComponents()
  {
    return PixelType::GetNumberOfComponents();
  }

  static unsigned int
  GetNumberOfComponents(const PixelType &)
  {
    return PixelType::GetNumberOfComponents();
  }

  /** Return the nth component of the pixel. */
  static ComponentType
  GetNthComponent(int c, const PixelType & pixel)
  {
    return pixel.GetNthComponent(c);
  }

  /** Set the nth component of the pixel. */
  static void
  SetNthComponent(int c, PixelType & pixel, const ComponentType & v)
  {
    pixel.SetNthComponent(c, v);
  }

  /** Return a single scalar value from this pixel. */
  static ComponentType
  GetScalarValue(const PixelType & pixel)
  {
    return pixel.GetScalarValue();
  }
};

#define ITK_DEFAULTCONVERTTRAITS_NATIVE_SPECIAL(type)           \
  template <>                                                   \
  class MeshConvertPixelTraits<type>                            \
  {                                                             \
  public:                                                       \
    using ComponentType = type;                                 \
    static unsigned int                                         \
    GetNumberOfComponents()                                     \
    {                                                           \
      return 1;                                                 \
    }                                                           \
    static unsigned int                                         \
    GetNumberOfComponents(const type & itkNotUsed(pixel))       \
    {                                                           \
      return 1;                                                 \
    }                                                           \
    static ComponentType                                        \
    GetNthComponent(int itkNotUsed(c), const type & pixel)      \
    {                                                           \
      return pixel;                                             \
    }                                                           \
    static void                                                 \
    SetNthComponent(int, type & pixel, const ComponentType & v) \
    {                                                           \
      pixel = v;                                                \
    }                                                           \
    static type                                                 \
    GetScalarValue(const type & pixel)                          \
    {                                                           \
      return pixel;                                             \
    }                                                           \
  }

ITK_DEFAULTCONVERTTRAITS_NATIVE_SPECIAL(char);
ITK_DEFAULTCONVERTTRAITS_NATIVE_SPECIAL(signed char);
ITK_DEFAULTCONVERTTRAITS_NATIVE_SPECIAL(unsigned char);
ITK_DEFAULTCONVERTTRAITS_NATIVE_SPECIAL(short);
ITK_DEFAULTCONVERTTRAITS_NATIVE_SPECIAL(unsigned short);
ITK_DEFAULTCONVERTTRAITS_NATIVE_SPECIAL(int);
ITK_DEFAULTCONVERTTRAITS_NATIVE_SPECIAL(unsigned int);
ITK_DEFAULTCONVERTTRAITS_NATIVE_SPECIAL(long);
ITK_DEFAULTCONVERTTRAITS_NATIVE_SPECIAL(unsigned long);
ITK_DEFAULTCONVERTTRAITS_NATIVE_SPECIAL(long long);
ITK_DEFAULTCONVERTTRAITS_NATIVE_SPECIAL(unsigned long long);
ITK_DEFAULTCONVERTTRAITS_NATIVE_SPECIAL(bool);
ITK_DEFAULTCONVERTTRAITS_NATIVE_SPECIAL(float);
ITK_DEFAULTCONVERTTRAITS_NATIVE_SPECIAL(double);

#undef ITK_DEFAULTCONVERTTRAITS_NATIVE_SPECIAL

//
//  Default traits for the Offset<> pixel type
//

#define ITK_MESH_DEFAULTCONVERTTRAITS_OFFSET_TYPE(dimension)            \
  template <>                                                           \
  class MeshConvertPixelTraits<Offset<dimension>>                       \
  {                                                                     \
  public:                                                               \
    using TargetType = Offset<dimension>;                               \
    using ComponentType = TargetType::OffsetValueType;                  \
    static unsigned int                                                 \
    GetNumberOfComponents()                                             \
    {                                                                   \
      return dimension;                                                 \
    }                                                                   \
    static unsigned int                                                 \
    GetNumberOfComponents(const TargetType & itkNotUsed(pixel))         \
    {                                                                   \
      return dimension;                                                 \
    }                                                                   \
    static ComponentType                                                \
    GetNthComponent(int c, const TargetType & pixel)                    \
    {                                                                   \
      return pixel[c];                                                  \
    }                                                                   \
    static void                                                         \
    SetNthComponent(int i, TargetType & pixel, const ComponentType & v) \
    {                                                                   \
      pixel[i] = v;                                                     \
    }                                                                   \
    static ComponentType                                                \
    GetScalarValue(const TargetType & pixel)                            \
    {                                                                   \
      return pixel[0];                                                  \
    }                                                                   \
  };


// Define traits for Offset<> from dimensions 1 to 5
ITK_MESH_DEFAULTCONVERTTRAITS_OFFSET_TYPE(1)
ITK_MESH_DEFAULTCONVERTTRAITS_OFFSET_TYPE(2)
ITK_MESH_DEFAULTCONVERTTRAITS_OFFSET_TYPE(3)
ITK_MESH_DEFAULTCONVERTTRAITS_OFFSET_TYPE(4)
ITK_MESH_DEFAULTCONVERTTRAITS_OFFSET_TYPE(5)

//
//  Default traits for the pixel types deriving from FixedArray<>
//

#define ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE(type, componenttype, dimension) \
  template <>                                                                         \
  class MeshConvertPixelTraits<type<componenttype, dimension>>                        \
  {                                                                                   \
  public:                                                                             \
    using TargetType = type<componenttype, dimension>;                                \
    using ComponentType = componenttype;                                              \
    static unsigned int                                                               \
    GetNumberOfComponents()                                                           \
    {                                                                                 \
      return dimension;                                                               \
    }                                                                                 \
    static unsigned int                                                               \
    GetNumberOfComponents(const TargetType & itkNotUsed(pixel))                       \
    {                                                                                 \
      return dimension;                                                               \
    }                                                                                 \
    static ComponentType                                                              \
    GetNthComponent(int c, const TargetType & pixel)                                  \
    {                                                                                 \
      return pixel[c];                                                                \
    }                                                                                 \
    static void                                                                       \
    SetNthComponent(int i, TargetType & pixel, const ComponentType & v)               \
    {                                                                                 \
      pixel[i] = v;                                                                   \
    }                                                                                 \
    static ComponentType                                                              \
    GetScalarValue(const TargetType & pixel)                                          \
    {                                                                                 \
      return pixel[0];                                                                \
    }                                                                                 \
  };

//
//
// Define traits for Classed deriving from FixedArray from dimensions 1 to 6
// These classes include: Vector, CovariantVector and Point.
//
//
#define ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE_ALL_MACRO(ArrayType, Type) \
  ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE(ArrayType, Type, 1)              \
  ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE(ArrayType, Type, 2)              \
  ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE(ArrayType, Type, 3)              \
  ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE(ArrayType, Type, 4)              \
  ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE(ArrayType, Type, 5)              \
  ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE(ArrayType, Type, 6)

#define ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE_ALL_TYPES_MACRO(ArrayType)          \
  ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE_ALL_MACRO(ArrayType, char);               \
  ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE_ALL_MACRO(ArrayType, signed char);        \
  ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE_ALL_MACRO(ArrayType, unsigned char);      \
  ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE_ALL_MACRO(ArrayType, short);              \
  ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE_ALL_MACRO(ArrayType, unsigned short);     \
  ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE_ALL_MACRO(ArrayType, int);                \
  ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE_ALL_MACRO(ArrayType, unsigned int);       \
  ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE_ALL_MACRO(ArrayType, long);               \
  ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE_ALL_MACRO(ArrayType, unsigned long);      \
  ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE_ALL_MACRO(ArrayType, long long);          \
  ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE_ALL_MACRO(ArrayType, unsigned long long); \
  ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE_ALL_MACRO(ArrayType, float);              \
  ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE_ALL_MACRO(ArrayType, double);

ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE_ALL_TYPES_MACRO(Vector);
ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE_ALL_TYPES_MACRO(CovariantVector);
ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE_ALL_TYPES_MACRO(Point);
ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE_ALL_TYPES_MACRO(FixedArray);

//
//  End of Traits for the classes deriving from FixedArray.
//
//


//
//  Default traits for the pixel types deriving from Matrix<>
//

#define ITK_MESH_DEFAULTCONVERTTRAITS_MATRIX_TYPE(type, componenttype, rows, cols) \
  template <>                                                                      \
  class MeshConvertPixelTraits<type<componenttype, rows, cols>>                    \
  {                                                                                \
  public:                                                                          \
    using TargetType = type<componenttype, rows, cols>;                            \
    using ComponentType = componenttype;                                           \
    static unsigned int                                                            \
    GetNumberOfComponents()                                                        \
    {                                                                              \
      return rows * cols;                                                          \
    }                                                                              \
    static unsigned int                                                            \
    GetNumberOfComponents(const TargetType & itkNotUsed(pixel))                    \
    {                                                                              \
      return rows * cols;                                                          \
    }                                                                              \
    static ComponentType                                                           \
    GetNthComponent(int c, const TargetType & pixel)                               \
    {                                                                              \
      const unsigned int row = c / cols;                                           \
      const unsigned int col = c % cols;                                           \
      return pixel[row][col];                                                      \
    }                                                                              \
    static void                                                                    \
    SetNthComponent(int i, TargetType & pixel, const ComponentType & v)            \
    {                                                                              \
      const unsigned int row = i / cols;                                           \
      const unsigned int col = i % cols;                                           \
      pixel[row][col] = v;                                                         \
    }                                                                              \
    static ComponentType                                                           \
    GetScalarValue(const TargetType & pixel)                                       \
    {                                                                              \
      return pixel[0][0];                                                          \
    }                                                                              \
  };

//
//
// Define traits for Classed deriving from Matrix from dimensions 1 to 6
//
//
#define ITK_MESH_DEFAULTCONVERTTRAITS_MATRIX_TYPE_ALL_MACRO(ArrayType, Type) \
  ITK_MESH_DEFAULTCONVERTTRAITS_MATRIX_TYPE(ArrayType, Type, 1, 1)           \
  ITK_MESH_DEFAULTCONVERTTRAITS_MATRIX_TYPE(ArrayType, Type, 2, 2)           \
  ITK_MESH_DEFAULTCONVERTTRAITS_MATRIX_TYPE(ArrayType, Type, 3, 3)           \
  ITK_MESH_DEFAULTCONVERTTRAITS_MATRIX_TYPE(ArrayType, Type, 4, 4)           \
  ITK_MESH_DEFAULTCONVERTTRAITS_MATRIX_TYPE(ArrayType, Type, 5, 5)           \
  ITK_MESH_DEFAULTCONVERTTRAITS_MATRIX_TYPE(ArrayType, Type, 6, 6)

#define ITK_MESH_DEFAULTCONVERTTRAITS_MATRIX_TYPE_ALL_TYPES_MACRO(ArrayType)          \
  ITK_MESH_DEFAULTCONVERTTRAITS_MATRIX_TYPE_ALL_MACRO(ArrayType, char);               \
  ITK_MESH_DEFAULTCONVERTTRAITS_MATRIX_TYPE_ALL_MACRO(ArrayType, signed char);        \
  ITK_MESH_DEFAULTCONVERTTRAITS_MATRIX_TYPE_ALL_MACRO(ArrayType, unsigned char);      \
  ITK_MESH_DEFAULTCONVERTTRAITS_MATRIX_TYPE_ALL_MACRO(ArrayType, short);              \
  ITK_MESH_DEFAULTCONVERTTRAITS_MATRIX_TYPE_ALL_MACRO(ArrayType, unsigned short);     \
  ITK_MESH_DEFAULTCONVERTTRAITS_MATRIX_TYPE_ALL_MACRO(ArrayType, int);                \
  ITK_MESH_DEFAULTCONVERTTRAITS_MATRIX_TYPE_ALL_MACRO(ArrayType, unsigned int);       \
  ITK_MESH_DEFAULTCONVERTTRAITS_MATRIX_TYPE_ALL_MACRO(ArrayType, long);               \
  ITK_MESH_DEFAULTCONVERTTRAITS_MATRIX_TYPE_ALL_MACRO(ArrayType, unsigned long);      \
  ITK_MESH_DEFAULTCONVERTTRAITS_MATRIX_TYPE_ALL_MACRO(ArrayType, long long);          \
  ITK_MESH_DEFAULTCONVERTTRAITS_MATRIX_TYPE_ALL_MACRO(ArrayType, unsigned long long); \
  ITK_MESH_DEFAULTCONVERTTRAITS_MATRIX_TYPE_ALL_MACRO(ArrayType, float);              \
  ITK_MESH_DEFAULTCONVERTTRAITS_MATRIX_TYPE_ALL_MACRO(ArrayType, double);

//
// Add here other classes that derive from Matrix or that have the same API
//
ITK_MESH_DEFAULTCONVERTTRAITS_MATRIX_TYPE_ALL_TYPES_MACRO(Matrix);

//
//  End of Traits for the classes deriving from Matrix.
//
//


//
//  Default traits for the pixel types deriving from std::complex<>
//

#define ITK_MESH_DEFAULTCONVERTTRAITS_COMPLEX_TYPE(componenttype)       \
  template <>                                                           \
  class MeshConvertPixelTraits<std::complex<componenttype>>             \
  {                                                                     \
  public:                                                               \
    using TargetType = std::complex<componenttype>;                     \
    using ComponentType = componenttype;                                \
    static unsigned int                                                 \
    GetNumberOfComponents()                                             \
    {                                                                   \
      return 2;                                                         \
    }                                                                   \
    static unsigned int                                                 \
    GetNumberOfComponents(const TargetType & itkNotUsed(pixel))         \
    {                                                                   \
      return 2;                                                         \
    }                                                                   \
    static ComponentType                                                \
    GetNthComponent(int i, TargetType & pixel)                          \
    {                                                                   \
      if (i == 0)                                                       \
      {                                                                 \
        return pixel.imag();                                            \
      }                                                                 \
      else                                                              \
      {                                                                 \
        return pixel.real();                                            \
      }                                                                 \
    }                                                                   \
    static void                                                         \
    SetNthComponent(int i, TargetType & pixel, const ComponentType & v) \
    {                                                                   \
      if (i == 0)                                                       \
      {                                                                 \
        pixel = TargetType(v, pixel.imag());                            \
      }                                                                 \
      else                                                              \
      {                                                                 \
        pixel = TargetType(pixel.real(), v);                            \
      }                                                                 \
    }                                                                   \
    static ComponentType                                                \
    GetScalarValue(const TargetType & pixel)                            \
    {                                                                   \
      return std::norm(pixel);                                          \
    }                                                                   \
  };

ITK_MESH_DEFAULTCONVERTTRAITS_COMPLEX_TYPE(float);
ITK_MESH_DEFAULTCONVERTTRAITS_COMPLEX_TYPE(double);

#define ITK_MESH_DEFAULTCONVERTTRAITS_ARRAY_TYPE(type, componenttype)   \
  template <>                                                           \
  class MeshConvertPixelTraits<type<componenttype>>                     \
  {                                                                     \
  public:                                                               \
    using TargetType = type<componenttype>;                             \
    using ComponentType = componenttype;                                \
    static unsigned int                                                 \
    GetNumberOfComponents()                                             \
    {                                                                   \
      return 0;                                                         \
    }                                                                   \
    static unsigned int                                                 \
    GetNumberOfComponents(const TargetType & pixel)                     \
    {                                                                   \
      return pixel.Size();                                              \
    }                                                                   \
    static ComponentType                                                \
    GetNthComponent(int c, const TargetType & pixel)                    \
    {                                                                   \
      return pixel[c];                                                  \
    }                                                                   \
    static void                                                         \
    SetNthComponent(int i, TargetType & pixel, const ComponentType & v) \
    {                                                                   \
      pixel[i] = v;                                                     \
    }                                                                   \
    static ComponentType                                                \
    GetScalarValue(const TargetType & pixel)                            \
    {                                                                   \
      return pixel[0];                                                  \
    }                                                                   \
  };

#define ITK_MESH_DEFAULTCONVERTTRAITS_ARRAY_TYPE_ALL_TYPES_MACRO(ArrayType) \
  ITK_MESH_DEFAULTCONVERTTRAITS_ARRAY_TYPE(ArrayType, char);                \
  ITK_MESH_DEFAULTCONVERTTRAITS_ARRAY_TYPE(ArrayType, signed char);         \
  ITK_MESH_DEFAULTCONVERTTRAITS_ARRAY_TYPE(ArrayType, unsigned char);       \
  ITK_MESH_DEFAULTCONVERTTRAITS_ARRAY_TYPE(ArrayType, short);               \
  ITK_MESH_DEFAULTCONVERTTRAITS_ARRAY_TYPE(ArrayType, unsigned short);      \
  ITK_MESH_DEFAULTCONVERTTRAITS_ARRAY_TYPE(ArrayType, int);                 \
  ITK_MESH_DEFAULTCONVERTTRAITS_ARRAY_TYPE(ArrayType, unsigned int);        \
  ITK_MESH_DEFAULTCONVERTTRAITS_ARRAY_TYPE(ArrayType, long);                \
  ITK_MESH_DEFAULTCONVERTTRAITS_ARRAY_TYPE(ArrayType, unsigned long);       \
  ITK_MESH_DEFAULTCONVERTTRAITS_ARRAY_TYPE(ArrayType, long long);           \
  ITK_MESH_DEFAULTCONVERTTRAITS_ARRAY_TYPE(ArrayType, unsigned long long);  \
  ITK_MESH_DEFAULTCONVERTTRAITS_ARRAY_TYPE(ArrayType, float);               \
  ITK_MESH_DEFAULTCONVERTTRAITS_ARRAY_TYPE(ArrayType, double);

ITK_MESH_DEFAULTCONVERTTRAITS_ARRAY_TYPE_ALL_TYPES_MACRO(Array);
ITK_MESH_DEFAULTCONVERTTRAITS_ARRAY_TYPE_ALL_TYPES_MACRO(VariableLengthVector);
//
//  End of Traits for the classes deriving from std::complex.
//
//

} // end namespace itk
#endif
