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

  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 "gdcmPNMCodec.h"
#include "gdcmTransferSyntax.h"
#include "gdcmSystem.h"
#include "gdcmDataElement.h"
#include "gdcmSequenceOfFragments.h"
#include "gdcmSwapper.h"

namespace gdcm
{

PNMCodec::PNMCodec():BufferLength(0)
{
}

PNMCodec::~PNMCodec()
= default;

bool PNMCodec::CanDecode(TransferSyntax const &) const
{
  return false;
}

bool PNMCodec::CanCode(TransferSyntax const &) const
{
  return false;
}


static uint8_t reverseBitsByte(uint8_t x)
{
  uint8_t b = 0;
  for (int i = 0; i < 8; ++i) {
    b <<= 1;
    b |= (x & 1);
    x >>= 1;
  }
  return b;
}

bool PNMCodec::Write(const char *filename, const DataElement &out) const
{
  std::ofstream os(filename, std::ios::binary);
  const unsigned int *dims = this->GetDimensions();
  const PhotometricInterpretation &pi = this->GetPhotometricInterpretation();
  const PixelFormat& pf = GetPixelFormat();
  if( pi == PhotometricInterpretation::MONOCHROME2
    || pi == PhotometricInterpretation::MONOCHROME1 ) // warning viz will be surprising
    {
    // FIXME possible mismatch pi vs pf (eg. pbm with mono2)
    if( pf == PixelFormat::SINGLEBIT)
      os << "P4\n";
    else
      os << "P5\n";
    }
  else if( pi == PhotometricInterpretation::RGB
    || pi == PhotometricInterpretation::PALETTE_COLOR )
    {
    os << "P6\n";
    }
  else
    {
    gdcmErrorMacro( "PhotometricInterpretation unhandled: " << pi );
    return false;
    }
  os << dims[0] << " " << dims[1] << "\n";

  const unsigned int pc = this->GetPlanarConfiguration();
  if( pc )
    {
    gdcmErrorMacro( "PlanarConfiguration unhandled: " << pc );
    return false;
    }

  switch(pf)
    {
  case PixelFormat::SINGLEBIT:
    break;
  case PixelFormat::UINT8:
  //case PixelFormat::INT8:
    os << "255\n";
    break;
  case PixelFormat::UINT16:
  //case PixelFormat::INT16:
    os << "65535\n";
    break;
  default:
    gdcmErrorMacro( "Unhandled PF: " << pf );
    return false;
    }

  const ByteValue *bv = out.GetByteValue();
  // FIXME: PNM Codec cannot handle encapsulated syntax... sigh
  if(!bv)
    {
    gdcmErrorMacro( "PNM Codec does not handle compress syntax. You need to decompress first." );
    return false;
    }
  gdcm_assert(bv);

  if( pi == PhotometricInterpretation::PALETTE_COLOR )
    {
    std::stringstream is;
    is.write( bv->GetPointer(), bv->GetLength() );

    const LookupTable &lut = this->GetLUT();
    lut.Decode(is, os);
    }
  else
    {
    if( pf.GetBitsAllocated() == 16 )
      {
      bv->Write<SwapperDoOp, uint16_t>( os );
      }
    else if( pf.GetBitsAllocated() == 1 )
      {
      const uint8_t *x = (const uint8_t*) bv->GetPointer();
      for( size_t i = 0; i < bv->GetLength(); i++ )
        {
        uint8_t b = reverseBitsByte(x[i]);
        os.write((char*)&b, 1);
        }
      }
    else
      {
      //bv->Write<SwapperDoOp, uint8_t>( os );
      bv->WriteBuffer( os );
      }
    }

  os.close();


  return true;
}

bool PNMCodec::Read(const char *filename, DataElement &out) const
{
  size_t len = System::FileSize(filename);
  std::ifstream is(filename, std::ios::binary);
  std::string type, str;
  std::getline(is,type);
  PhotometricInterpretation pi;
  if( type == "P5" )
    pi = PhotometricInterpretation::MONOCHROME2;
  else if( type == "P6" )
    pi = PhotometricInterpretation::RGB;
  else
    {
    std::cerr << "Unhandled PGM type: " << type << std::endl;
    return false;
    }

  // skip comments:
  while( is.peek() == '#' )
    {
    std::getline(is, str);
    //std::cout << str << std::endl;
    }
  unsigned int dims[3] = {};
  is >> dims[0]; is >> dims[1];
  unsigned int maxval;
  is >> maxval;
  // some kind of empty line...
  if( is.peek() == '\n' )
    {
    is.get();
    }
  std::streampos pos = is.tellg();
  //gdcm_assert(pos < INT_MAX);
  size_t m = (len - (size_t)pos ) / ( dims[0]*dims[1] );
  if( m * dims[0] * dims[1] != len - pos )
    {
    std::cerr << "Problem computing length" << std::endl;
    return false;
    }
  PixelFormat pf;
  switch(maxval)
    {
  case 255:
    pf = PixelFormat::UINT8;
    break;
  case 1023:
    pf = PixelFormat::UINT16;
    pf.SetBitsStored( 10 );
    break;
  case 4095:
    pf = PixelFormat::UINT16;
    pf.SetBitsStored( 12 );
    break;
  case 32767:
    pf = PixelFormat::UINT16;
    pf.SetBitsStored( 15 );
    break;
  case 65535:
    pf = PixelFormat::UINT16;
    break;
  default:
    std::cerr << "Unhandled max val: " << maxval << std::endl;
    return false;
    }
  if( pi == PhotometricInterpretation::RGB )
    {
    pf.SetSamplesPerPixel( 3 );
    }
  //if ( maxval * 8 != bpp ) return 1;

  size_t pdlen = GetBufferLength();
  gdcm_assert( pdlen );
  char * buf = new char[pdlen];
  // is should be at right offset, just read!
  is.read(buf, len);
  if( !is.eof() )
  {
    delete[] buf;
    return false;
  }

  out.SetTag( Tag(0x7fe0,0x0010) );
  VL::Type pdLenSize = (VL::Type)pdlen;
  out.SetByteValue( buf, pdLenSize );
  delete[] buf;

  is.close();

  return true;
}

static inline int log2( int n )
{
  int bits = 0;
  while (n > 0)
    {
    bits++;
    n >>= 1;
    }
  return bits;
}

bool PNMCodec::GetHeaderInfo(std::istream &is, TransferSyntax &ts)
{
  is.seekg( 0, std::ios::end );
  std::streampos len = is.tellg();
  //gdcm_assert(len < INT_MAX);
  is.seekg( 0, std::ios::beg );

  std::string type, str;
  std::getline(is,type);
  PhotometricInterpretation pi;
  if( type == "P4" ) // P4 => mono W/B !
    pi = PhotometricInterpretation::MONOCHROME1;
  else if( type == "P5" )
    pi = PhotometricInterpretation::MONOCHROME2;
  else if( type == "P6" ) // P3 => ASCII
    pi = PhotometricInterpretation::RGB;
  else
    {
    std::cerr << "Unhandled PGM type: " << type << std::endl;
    return false;
    }

  // skip comments:
  while( is.peek() == '#' )
    {
    std::getline(is, str);
    //std::cout << str << std::endl;
    }
  unsigned int dims[3] = {};
  is >> dims[0]; is >> dims[1];
  unsigned int maxval;
  if( type == "P4" )
    maxval = 1;
  else
    is >> maxval;
  // http://netpbm.sourceforge.net/doc/pgm.html
  // some kind of empty line...
  if( is.peek() == '\n' )
    {
    is.get();
    }
  std::streamoff pos = is.tellg();
  //gdcm_assert(len < INT_MAX);
  //gdcm_assert(pos < INT_MAX);
  size_t m = ((size_t)len - (size_t)pos ) / ( dims[0]*dims[1] );
  bool cond;
  if( type == "P4" ) {
    const size_t bytesPerRow = dims[0] / 8 + (dims[0] % 8 != 0 ? 1 : 0);
    cond = bytesPerRow * dims[1] != ((size_t)len - (size_t)pos);
  }
  else
    cond = m * dims[0] * dims[1] != (size_t)len - (size_t)pos;
  if( cond )
    {
    std::cerr << "Problem computing length" << std::endl;
    std::cerr << "Pos: " << len - pos << std::endl;
    std::cerr << "expected: " << m * dims[0] * dims[1] << std::endl;
    return false;
    }
  PixelFormat pf = GetPixelFormat();
#if 0
  switch(maxval)
    {
  case 255:
    pf = PixelFormat::UINT8;
    break;
  case 1023:
    pf = PixelFormat::UINT16;
    pf.SetBitsStored( 10 );
    break;
  case 4095:
    pf = PixelFormat::UINT16;
    pf.SetBitsStored( 12 );
    break;
  case 32767:
    pf = PixelFormat::UINT16;
    pf.SetBitsStored( 15 );
    break;
  case 65535:
    pf = PixelFormat::UINT16;
    break;
  default:
    std::cerr << "Unhandled max val: " << maxval << std::endl;
    return false;
    }
#else
  const int nbits = log2( maxval );
  // handle case where nbits = 0 also:
  if( nbits > 0 && nbits <= 1 )
    {
    pf.SetBitsAllocated( 1 );
    }
  else if( nbits > 1 && nbits <= 8 )
    {
    pf.SetBitsAllocated( 8 );
    pf.SetBitsStored( (unsigned short)nbits );
    }
  else if( nbits > 8 && nbits <= 16 )
    {
    pf.SetBitsAllocated( 16 );
    pf.SetBitsStored( (unsigned short)nbits );
    }
  else if( nbits > 16 && nbits <= 32 )
    {
    pf.SetBitsAllocated( 32 );
    pf.SetBitsStored( (unsigned short)nbits );
    }
  else
    {
    std::cerr << "Unhandled max val: " << maxval << std::endl;
    return false;
    }
#endif
  if( pi == PhotometricInterpretation::RGB )
    {
    pf.SetSamplesPerPixel( 3 );
    }
  //if ( maxval * 8 != bpp ) return 1;

  //image.SetTransferSyntax( TransferSyntax::ExplicitVRBigEndian ); // PGM are big endian
  //image.SetTransferSyntax( TransferSyntax::ExplicitVRLittleEndian ); // PGM are big endian
  //image.SetTransferSyntax( TransferSyntax::ImplicitVRBigEndianPrivateGE ); // PGM are big endian
  if( pf.GetBitsAllocated() > 8 )
    ts = TransferSyntax::ImplicitVRBigEndianPrivateGE;
  else
    //ts = TransferSyntax::ImplicitVRLittleEndian; // nicer to handle than private GE
    ts = TransferSyntax::ExplicitVRLittleEndian; // nicer to handle than private GE

  SetPhotometricInterpretation( pi );
  SetPixelFormat( pf );
  SetDimensions( dims );

  return true;
}

ImageCodec * PNMCodec::Clone() const
{
  return nullptr;
}

} // end namespace gdcm
