/*=========================================================================

  Program: GDCM (Grassroots DICOM). A DICOM library

  Copyright (c) 2006-2011 Mathieu Malaterre
  All rights reserved.
  See Copyright.txt or http://gdcm.sourceforge.net/Copyright.html for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
#include "gdcmPixelFormat.h"
#include "gdcmTrace.h"
#include "gdcmTransferSyntax.h"

#include <cstdlib>

namespace gdcm
{
static const char *ScalarTypeStrings[] = {
  "UINT8",
  "INT8",
  "UINT12",
  "INT12",
  "UINT16",
  "INT16",
  "UINT32",
  "INT32",
  "UINT64",
  "INT64",
  "FLOAT16",
  "FLOAT32",
  "FLOAT64",
  "SINGLEBIT",
  "UNKNOWN",
  nullptr,
};

PixelFormat::PixelFormat(ScalarType st)
{
  SamplesPerPixel = 1;
  SetScalarType( st );
}

unsigned short PixelFormat::GetSamplesPerPixel() const
{
  // \postcondition
  gdcm_assert( SamplesPerPixel == 1 || SamplesPerPixel == 3 || SamplesPerPixel == 4 );
  return SamplesPerPixel;
}

void PixelFormat::SetScalarType(ScalarType st)
{
  SamplesPerPixel = 1;
  switch(st)
    {
  case PixelFormat::UINT8:
    BitsAllocated = 8;
    PixelRepresentation = 0;
    break;
  case PixelFormat::INT8:
    BitsAllocated = 8;
    PixelRepresentation = 1;
    break;
  case PixelFormat::UINT12:
    BitsAllocated = 12;
    PixelRepresentation = 0;
    break;
  case PixelFormat::INT12:
    BitsAllocated = 12;
    PixelRepresentation = 1;
    break;
  case PixelFormat::UINT16:
    BitsAllocated = 16;
    PixelRepresentation = 0;
    break;
  case PixelFormat::INT16:
    BitsAllocated = 16;
    PixelRepresentation = 1;
    break;
  case PixelFormat::UINT32:
    BitsAllocated = 32;
    PixelRepresentation = 0;
    break;
  case PixelFormat::INT32:
    BitsAllocated = 32;
    PixelRepresentation = 1;
    break;
  case PixelFormat::UINT64:
    BitsAllocated = 64;
    PixelRepresentation = 0;
    break;
  case PixelFormat::INT64:
    BitsAllocated = 64;
    PixelRepresentation = 1;
    break;
  case PixelFormat::FLOAT16:
    BitsAllocated = 16;
    // secret code:
    PixelRepresentation = 2;
    break;
  case PixelFormat::FLOAT32:
    BitsAllocated = 32;
    // secret code:
    PixelRepresentation = 3;
    break;
  case PixelFormat::FLOAT64:
    BitsAllocated = 64;
    // secret code:
    PixelRepresentation = 4;
    break;
  case PixelFormat::SINGLEBIT:
    BitsAllocated = 1;
    PixelRepresentation = 0;
    break;
  case PixelFormat::UNKNOWN:
    BitsAllocated = 0;
    PixelRepresentation = 0;
    break;
  default:
    gdcm_assert(0);
    break;
    }
  BitsStored = BitsAllocated;
  HighBit = (uint16_t)(BitsStored - 1);
}

PixelFormat::ScalarType PixelFormat::GetScalarType() const
{
  ScalarType type = PixelFormat::UNKNOWN;
  gdcmAssertAlwaysMacro( BitsStored <= BitsAllocated );
  switch( BitsAllocated )
    {
  case 0:
    type = PixelFormat::UNKNOWN;
    break;
  case 1:
    type = PixelFormat::SINGLEBIT;
    break;
  case 8:
    type = PixelFormat::UINT8;
    break;
  case 12:
    type = PixelFormat::UINT12;
    break;
  case 16:
    type = PixelFormat::UINT16;
    break;
  case 32:
    type = PixelFormat::UINT32;
    break;
  case 64:
    type = PixelFormat::UINT64;
    break;
  case 24:
    gdcmDebugMacro( "This is illegal in DICOM, assuming a RGB image" );
    type = PixelFormat::UINT8;
    break;
  default:
    gdcmErrorMacro( "I have never seen this before BitsAllocated "
      << BitsAllocated );
    type = PixelFormat::UNKNOWN;
    }
  if( type != PixelFormat::UNKNOWN )
    {
    if( PixelRepresentation == 0 )
      {
      // all set !
      }
    else if( PixelRepresentation == 1 )
      {
      gdcm_assert( type <= INT64 );
      // That's why you need to order properly type in ScalarType
      type = ScalarType(int(type)+1);
      }
    else if( PixelRepresentation == 2 )
      {
      gdcm_assert( BitsAllocated == 16 );
      return FLOAT16;
      }
    else if( PixelRepresentation == 3 )
      {
      gdcm_assert( BitsAllocated == 32 );
      return FLOAT32;
      }
    else if( PixelRepresentation == 4 )
      {
      gdcm_assert( BitsAllocated == 64 );
      return FLOAT64;
      }
    else
      {
      gdcmAssertAlwaysMacro(0);
      }
    }
  return type;
}

const char *PixelFormat::GetScalarTypeAsString() const
{
  return ScalarTypeStrings[GetScalarType()];
}

uint8_t PixelFormat::GetPixelSize() const
{
  uint8_t pixelsize = (uint8_t)(BitsAllocated / 8);
  if( BitsAllocated == 12 )
    {
    pixelsize = 2; // fake a short value
    }
  else
    {
    gdcm_assert( !(BitsAllocated % 8) );
    }
  pixelsize *= SamplesPerPixel;

  return pixelsize;
}

int64_t PixelFormat::GetMin() const
{
  gdcm_assert( BitsAllocated ); // cannot be unknown
  if( BitsStored <= 32 )
    {
    if( PixelRepresentation == 1 )
      {
      return (int64_t)(~(((1ull << BitsStored) - 1) >> 1));
      }
    else if( PixelRepresentation == 0 )
      {
      return 0;
      }
    }
  // else
  gdcmAssertAlwaysMacro( 0 ); // throw "PixelFormat bad representation";
}

int64_t PixelFormat::GetMax() const
{
  gdcm_assert( BitsAllocated ); // cannot be unknown
  if( BitsStored <= 32 )
    {
    if( PixelRepresentation == 1 )
      {
      return (int64_t)(((1ull << BitsStored) - 1) >> 1);
      }
    else if( PixelRepresentation == 0 )
      {
      return (int64_t)((1ull << BitsStored) - 1);
      }
    }
  // else
  gdcmAssertAlwaysMacro( 0 ); // throw "PixelFormat bad representation";
}

bool PixelFormat::IsValid() const
{
  if( PixelRepresentation != 0 && PixelRepresentation != 1 )
    {
    return false;
    }
  if( BitsAllocated < BitsStored ) return false;
  if( BitsAllocated < HighBit ) return false;
  if( BitsStored > 32 ) return false;
  return true;
}

bool PixelFormat::Validate()
{
  if( !IsValid() ) return false;
  //gdcm_assert( BitsStored    >= HighBit ); // DigitexAlpha_no_7FE0.dcm
  gdcm_assert( PixelRepresentation == 0 || PixelRepresentation == 1 );
  gdcm_assert( SamplesPerPixel == 1 || SamplesPerPixel == 3 || SamplesPerPixel == 4 );
  if ( BitsStored == 0 )
    {
    gdcmDebugMacro( "Bits Stored is 0. Setting is to max value" );
    BitsStored = BitsAllocated;
    }
  if ( HighBit == 0 && BitsStored > 1 )
    {
    gdcmDebugMacro( "High Bit is 0. Setting is to BitStored - 1" );
    HighBit = BitsStored - 1;
    }
  if ( BitsAllocated == 24 )
    {
    gdcmDebugMacro( "ACR-NEMA way of storing RGB data. Updating" );
    if( BitsStored == 24 && HighBit == 23 && SamplesPerPixel == 1 )
      {
      BitsAllocated = 8;
      BitsStored = 8;
      HighBit = 7;
      SamplesPerPixel = 3;
      return true;
      }
    // all other case, simply give up
    return false;
    }
  return true;
}

void PixelFormat::Print(std::ostream &os) const
{
  os << "SamplesPerPixel    :" << SamplesPerPixel     << "\n";
  os << "BitsAllocated      :" << BitsAllocated       << "\n";
  os << "BitsStored         :" << BitsStored          << "\n";
  os << "HighBit            :" << HighBit             << "\n";
  os << "PixelRepresentation:" << PixelRepresentation << "\n";
  os << "ScalarType found   :" << GetScalarTypeAsString() << "\n";
}

bool PixelFormat::IsCompatible(const TransferSyntax & ts ) const
{
  if( ts == TransferSyntax::JPEGBaselineProcess1 && BitsAllocated != 8 ) return false;
  // FIXME are we missing any ?
  return true;
}

} // end namespace gdcm
