/*=========================================================================
 *
 *  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
 *
 *         http://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 int);
ITK_DEFAULTCONVERTTRAITS_NATIVE_SPECIAL(unsigned short int);
ITK_DEFAULTCONVERTTRAITS_NATIVE_SPECIAL(int);
ITK_DEFAULTCONVERTTRAITS_NATIVE_SPECIAL(unsigned int);
ITK_DEFAULTCONVERTTRAITS_NATIVE_SPECIAL(long int);
ITK_DEFAULTCONVERTTRAITS_NATIVE_SPECIAL(unsigned long int);
ITK_DEFAULTCONVERTTRAITS_NATIVE_SPECIAL(long long int);
ITK_DEFAULTCONVERTTRAITS_NATIVE_SPECIAL(unsigned long long int);
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 int);                                       \
  ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE_ALL_MACRO(ArrayType, unsigned short int);                              \
  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 int);                                        \
  ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE_ALL_MACRO(ArrayType, unsigned long int);                               \
  ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE_ALL_MACRO(ArrayType, long long int);                                   \
  ITK_MESH_DEFAULTCONVERTTRAITS_FIXEDARRAY_TYPE_ALL_MACRO(ArrayType, unsigned long long int);                          \
  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 int);                                           \
  ITK_MESH_DEFAULTCONVERTTRAITS_MATRIX_TYPE_ALL_MACRO(ArrayType, unsigned short int);                                  \
  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 int);                                            \
  ITK_MESH_DEFAULTCONVERTTRAITS_MATRIX_TYPE_ALL_MACRO(ArrayType, unsigned long int);                                   \
  ITK_MESH_DEFAULTCONVERTTRAITS_MATRIX_TYPE_ALL_MACRO(ArrayType, long long int);                                       \
  ITK_MESH_DEFAULTCONVERTTRAITS_MATRIX_TYPE_ALL_MACRO(ArrayType, unsigned long long int);                              \
  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 int);                                                      \
  ITK_MESH_DEFAULTCONVERTTRAITS_ARRAY_TYPE(ArrayType, unsigned short int);                                             \
  ITK_MESH_DEFAULTCONVERTTRAITS_ARRAY_TYPE(ArrayType, int);                                                            \
  ITK_MESH_DEFAULTCONVERTTRAITS_ARRAY_TYPE(ArrayType, unsigned int);                                                   \
  ITK_MESH_DEFAULTCONVERTTRAITS_ARRAY_TYPE(ArrayType, long int);                                                       \
  ITK_MESH_DEFAULTCONVERTTRAITS_ARRAY_TYPE(ArrayType, unsigned long int);                                              \
  ITK_MESH_DEFAULTCONVERTTRAITS_ARRAY_TYPE(ArrayType, long long int);                                                  \
  ITK_MESH_DEFAULTCONVERTTRAITS_ARRAY_TYPE(ArrayType, unsigned long long int);                                         \
  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
