/*=========================================================================
 *
 *  Copyright Insight Software Consortium
 *
 *  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.
 *
 *=========================================================================*/
/*=========================================================================
 *
 *  Portions of this file are subject to the VTK Toolkit Version 3 copyright.
 *
 *  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
 *
 *  For complete copyright, license and disclaimer of warranty information
 *  please refer to the NOTICE file at the top of the ITK source tree.
 *
 *=========================================================================*/
#ifndef itkByteSwapper_hxx
#define itkByteSwapper_hxx
#include "itkByteSwapper.h"
#include <memory>
#include <cstring>

namespace itk
{
// The following are the public methods --------------------------------
//
// Machine definitions
#ifdef CMAKE_WORDS_BIGENDIAN
template< typename T >
bool ByteSwapper< T >::SystemIsBigEndian() { return true; }
template< typename T >
bool ByteSwapper< T >::SystemIsLittleEndian() { return false; }
#else
template< typename T >
bool ByteSwapper< T >::SystemIsBigEndian() { return false; }
template< typename T >
bool ByteSwapper< T >::SystemIsLittleEndian() { return true; }
#endif

//------Big Endian methods----------------------------------------------

// Use different swap methods based on type
#ifdef CMAKE_WORDS_BIGENDIAN
template< typename T >
void
ByteSwapper< T >
::SwapFromSystemToBigEndian(T *){}
#else
template< typename T >
void
ByteSwapper< T >
::SwapFromSystemToBigEndian(T *p)
{
  switch ( sizeof( T ) )
    {
    case 1:
      return;
    case 2:
      ByteSwapper< T >::Swap2( (void *)p );
      return;
    case 4:
      ByteSwapper< T >::Swap4( (void *)p );
      return;
    case 8:
      ByteSwapper< T >::Swap8( (void *)p );
      return;
    default:
      itkGenericExceptionMacro (<< "Cannot swap number of bytes requested");
    }
}

#endif

#ifdef CMAKE_WORDS_BIGENDIAN
template< typename T >
void
ByteSwapper< T >
::SwapRangeFromSystemToBigEndian(T *, BufferSizeType)
{
  // nothing needs to be done here...
}

#else

#ifdef __INTEL_COMPILER
#pragma warning disable 280 //remark #280: selector expression is constant
#endif
template< typename T >
void
ByteSwapper< T >
::SwapRangeFromSystemToBigEndian(T *p, BufferSizeType num)
{
  switch ( sizeof( T ) )
    {
    case 1:
      return;
    case 2:
      ByteSwapper< T >::Swap2Range( (void *)p, num );
      return;
    case 4:
      ByteSwapper< T >::Swap4Range( (void *)p, num );
      return;
    case 8:
      ByteSwapper< T >::Swap8Range( (void *)p, num );
      return;
    default:
      itkGenericExceptionMacro (<< "Cannot swap number of bytes requested");
      return;
    }
}

#endif

#ifdef CMAKE_WORDS_BIGENDIAN
template< typename T >
void
ByteSwapper< T >
::SwapWriteRangeFromSystemToBigEndian(T *p, int num, OStreamType *fp)
{
  num *= sizeof( T );
  fp->write( (char *)p, num );
}

#else
template< typename T >
void
ByteSwapper< T >
::SwapWriteRangeFromSystemToBigEndian(T *p, int num, OStreamType *fp)
{
  switch ( sizeof( T ) )
    {
    case 1:
      return;
    case 2:
      ByteSwapper< T >::SwapWrite2Range( (void *)p, num, fp );
      return;
    case 4:
      ByteSwapper< T >::SwapWrite4Range( (void *)p, num, fp );
      return;
    case 8:
      ByteSwapper< T >::SwapWrite8Range( (void *)p, num, fp );
      return;
    default:
      itkGenericExceptionMacro (<< "Cannot swap number of bytes requested");
    }
}

#endif

//------Little Endian methods----------------------------------------------

#ifdef CMAKE_WORDS_BIGENDIAN
template< typename T >
void
ByteSwapper< T >
::SwapFromSystemToLittleEndian(T *p)
{
  switch ( sizeof( T ) )
    {
    case 1:
      return;
    case 2:
      ByteSwapper< T >::Swap2( (void *)p );
      return;
    case 4:
      ByteSwapper< T >::Swap4( (void *)p );
      return;
    case 8:
      ByteSwapper< T >::Swap8( (void *)p );
      return;
    default:
      itkGenericExceptionMacro (<< "Cannot swap number of bytes requested");
    }
}

#else
template< typename T >
void
ByteSwapper< T >
::SwapFromSystemToLittleEndian(T *){}
#endif

#ifdef CMAKE_WORDS_BIGENDIAN
template< typename T >
void
ByteSwapper< T >
::SwapRangeFromSystemToLittleEndian(T *p, BufferSizeType num)
{
  switch ( sizeof( T ) )
    {
    case 1:
      return;
    case 2:
      ByteSwapper< T >::Swap2Range( (void *)p, num );
      return;
    case 4:
      ByteSwapper< T >::Swap4Range( (void *)p, num );
      return;
    case 8:
      ByteSwapper< T >::Swap8Range( (void *)p, num );
      return;
    default:
      itkGenericExceptionMacro (<< "Cannot swap number of bytes requested");
    }
}

#else
template< typename T >
void
ByteSwapper< T >
::SwapRangeFromSystemToLittleEndian(T *, BufferSizeType) {}
#endif

#ifdef CMAKE_WORDS_BIGENDIAN
template< typename T >
void
ByteSwapper< T >
::SwapWriteRangeFromSystemToLittleEndian(T *p, int num, OStreamType *fp)
{
  switch ( sizeof( T ) )
    {
    case 1:
      return;
    case 2:
      ByteSwapper< T >::SwapWrite2Range( (void *)p, num, fp );
      return;
    case 4:
      ByteSwapper< T >::SwapWrite4Range( (void *)p, num, fp );
      return;
    case 8:
      ByteSwapper< T >::SwapWrite8Range( (void *)p, num, fp );
      return;
    default:
      itkGenericExceptionMacro (<< "Cannot swap number of bytes requested");
    }
}

#else
template< typename T >
void
ByteSwapper< T >
::SwapWriteRangeFromSystemToLittleEndian(T *p, int num, OStreamType *fp)
{
  num *= sizeof( T );
  fp->write( (char *)p, num );
}

#endif

// The following are the protected methods -------------------------
//

//------2-byte methods----------------------------------------------

// Swap 2 byte word.
template< typename T >
void
ByteSwapper< T >
::Swap2(void *pin)
{
  auto * p = reinterpret_cast< unsigned short * >( pin );
  const unsigned short h1 = (*p) << static_cast<short unsigned int>(8);
  const unsigned short h2 = (*p) >> static_cast<short unsigned int>(8);
  *p = h1 | h2;
}

// Swap bunch of bytes. Num is the number of two byte words to swap.
template< typename T >
void
ByteSwapper< T >
::Swap2Range(void *ptr, BufferSizeType num)
{
  auto * pos = reinterpret_cast< char * >( ptr );
  for ( BufferSizeType i = 0; i < num; i++ )
    {
    const char one_byte = pos[0];
    pos[0] = pos[1];
    pos[1] = one_byte;
    pos = pos + 2;
    }
}

// Swap bunch of bytes. Num is the number of four byte words to swap.
template< typename T >
void
ByteSwapper< T >
::SwapWrite2Range(void *ptr, BufferSizeType num, OStreamType *fp)
{
  BufferSizeType chunkSize = 1000000;
  if ( num < chunkSize )
    {
    chunkSize = num;
    }
  auto * cpy = new char[chunkSize * 2];
  while ( num )
    {
    memcpy(cpy, ptr, chunkSize * 2);

    char * pos = cpy;
    for ( BufferSizeType i = 0; i < chunkSize; i++ )
      {
      const char one_byte = pos[0];
      pos[0] = pos[1];
      pos[1] = one_byte;
      pos = pos + 2;
      }
    fp->write( (char *)cpy, static_cast<std::streamsize>(2 * chunkSize) );
    ptr = (char *)ptr + chunkSize * 2;
    num -= chunkSize;
    if ( num < chunkSize )
      {
      chunkSize = num;
      }
    }
  delete[] cpy;
}

//------4-byte methods----------------------------------------------

// Swap four byte word.
template< typename T >
void
ByteSwapper< T >
::Swap4(void *ptr)
{
  char  one_byte;
  auto * p = reinterpret_cast< char * >( ptr );

  one_byte    = p[0];
  p[0] = p[3];
  p[3] = one_byte;

  one_byte    = p[1];
  p[1] = p[2];
  p[2] = one_byte;
}

// Swap bunch of bytes. Num is the number of four byte words to swap.
template< typename T >
void
ByteSwapper< T >
::Swap4Range(void *ptr, BufferSizeType num)
{
  auto * pos = reinterpret_cast< char * >( ptr );

  for ( BufferSizeType i = 0; i < num; i++ )
    {
    char one_byte = pos[0];
    pos[0] = pos[3];
    pos[3] = one_byte;

    one_byte = pos[1];
    pos[1] = pos[2];
    pos[2] = one_byte;
    pos = pos + 4;
    }
}

// Swap bunch of bytes. Num is the number of four byte words to swap.
template< typename T >
void
ByteSwapper< T >
::SwapWrite4Range(void *ptr, BufferSizeType num, OStreamType *fp)
{
  BufferSizeType chunkSize = 1000000;

  if ( num < chunkSize )
    {
    chunkSize = num;
    }
  auto * cpy = new char[chunkSize * 4];

  while ( num )
    {
    memcpy(cpy, ptr, chunkSize * 4);

    char * pos = cpy;
    for ( BufferSizeType i = 0; i < chunkSize; i++ )
      {
      char one_byte = pos[0];
      pos[0] = pos[3];
      pos[3] = one_byte;

      one_byte = pos[1];
      pos[1] = pos[2];
      pos[2] = one_byte;
      pos = pos + 4;
      }
    fp->write( (char *)cpy, static_cast<std::streamsize>(4 * chunkSize) );
    ptr  = (char *)ptr + chunkSize * 4;
    num -= chunkSize;
    if ( num < chunkSize )
      {
      chunkSize = num;
      }
    }
  delete[] cpy;
}

//------8-byte methods----------------------------------------------

// Swap 8 byte double precision
template< typename T >
void
ByteSwapper< T >
::Swap8(void *ptr)
{
  char  one_byte;
  auto * p = reinterpret_cast< char * >( ptr );

  one_byte    = p[0];
  p[0] = p[7];
  p[7] = one_byte;

  one_byte    = p[1];
  p[1] = p[6];
  p[6] = one_byte;

  one_byte    = p[2];
  p[2] = p[5];
  p[5] = one_byte;

  one_byte    = p[3];
  p[3] = p[4];
  p[4] = one_byte;
}

// Swap bunch of bytes. Num is the number of eight byte words to swap.
template< typename T >
void
ByteSwapper< T >
::Swap8Range(void *ptr, BufferSizeType num)
{
  auto * pos = reinterpret_cast< char * >( ptr );

  for ( BufferSizeType i = 0; i < num; i++ )
    {
    char one_byte    = pos[0];
    pos[0] = pos[7];
    pos[7] = one_byte;

    one_byte    = pos[1];
    pos[1] = pos[6];
    pos[6] = one_byte;

    one_byte    = pos[2];
    pos[2] = pos[5];
    pos[5] = one_byte;

    one_byte    = pos[3];
    pos[3] = pos[4];
    pos[4] = one_byte;
    pos = pos + 8;
    }
}

// Swap bunch of bytes. Num is the number of four byte words to swap.
template< typename T >
void
ByteSwapper< T >
::SwapWrite8Range(void *ptr, BufferSizeType num, OStreamType *fp)
{
  BufferSizeType chunkSize = 1000000;
  if ( num < chunkSize )
    {
    chunkSize = num;
    }
  auto * cpy = new char[chunkSize * 8];

  while ( num )
    {
    memcpy(cpy, ptr, chunkSize * 8);

    ByteSwapper< T >::Swap8Range( (void *)cpy, chunkSize );

    fp->write( (char *)cpy, static_cast<std::streamsize>(8 * chunkSize) );
    ptr  = (char *)ptr + chunkSize * 8;
    num -= chunkSize;
    if ( num < chunkSize )
      {
      chunkSize = num;
      }
    }
  delete[] cpy;
}
} // end namespace itk

#endif
