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

  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 "gdcmPDBHeader.h"
#include "gdcmPrivateTag.h"
#include "gdcmDataElement.h"
#include "gdcmDataSet.h"
#include "gdcmExplicitDataElement.h"
#include "gdcmSwapper.h"
#include "gdcmDeflateStream.h"

namespace gdcm
{

/*
 * In some GE MEDICAL SYSTEMS image one can find a Data Element: 0025,xx1b,GEMS_SERS_01
 * which is documented as Protocol Data Block (compressed).
 * in fact this is a simple text format compressed using the gzip algorithm
 *
 * Typically one could do:
 *
 *   $ gdcmraw -i input.dcm -o output.raw -t 0025,101b
 *
 * Skip the binary length (little endian encoding):
 *
 *   $ dd bs=4 skip=1 if=output.raw of=foo.gz
 *
 * Check file type:
 *
 *   $ file foo.gz
 *   foo: gzip compressed data, was "Ex421Ser8Scan1", from Unix
 *
 * Gunzip !
 *   $ gunzip foo.gz
 *   $ cat foo
 *
 * THANKS to: John Reiser (BitWagon.com) for hints
 *
 * For sample see:
 * GE_MR_0025xx1bProtocolDataBlock.dcm
 * ( <=> http://server.oersted.dtu.dk/personal/jw/jwpublic/courses/31540/mri/second_set/dicom/t2/b17.dcm)
 */

/* Typical output:
 *
 * ENTRY "Head First"
 * POSITION "Supine"
 * ANREF "NA"
 * COIL "HEAD"
 * PLANE "OBLIQUE"
 * SEDESCFLAG "1"
 * SEDESC "AX FSE T2"
 * IMODE "2D"
 * PSEQ "FSE-XL"
 * IOPT "FC, EDR, TRF, Fast"
 * PLUG "22"
 * FILTCHOICE "None"
 * BWRT "-1"
 * TRICKSIMG "1"
 * TAG_SPACE "7"
 * TAG_TYPE "None"
 * USERCV0 "0.00"
 * USERCV6 "0.00"
 * USERCV7 "0.00"
 * USERCV21 "0.00"
 * USERCV_MASK "2097344"
 * TE "102.0"
 * NECHO "1"
 * TR "5720.0"
 * NUMACQS "1"
 * ETL "17"
 * BPMMODE "0"
 * AUTOTRGTYPE "0"
 * PSDTRIG "0"
 * SLICEORDER "1"
 * VIEWORDER "1"
 * TRREST "0"
 * TRACTIVE "0"
 * SLICEASSET "1.00"
 * PHASEASSET "1.00"
 * SEPSERIES "0"
 * AUTOTRIGWIN "0"
 * FOV "24.0"
 * SLTHICK "2.0"
 * SPC "2.0"
 * GRXOPT "0"
 * SLOC1 "L11.8"
 * SLOC2 "P29.9"
 * SLOC3 "I50.0"
 * ELOC1 "L11.6"
 * ELOC2 "P29.4"
 * ELOC3 "S53.9"
 * NOSLC "27"
 * SL3PLANE "0"
 * SL3PLANE1 "0"
 * SL3PLANE2 "0"
 * SL3PLANE3 "0"
 * SPCPERPLANE1 "0.0"
 * SPCPERPLANE2 "0.0"
 * SPCPERPLANE3 "0.0"
 * MATRIXX "448"
 * MATRIXY "224"
 * SWAPPF "A/P"
 * NEX "4.00"
 * CONTRAST "No"
 * CONTAM "Yes   "
 * TBLDELTA "0.00"
 * PHASEFOV "0.75"
 * RBW "31.25"
 * AUTOSHIM "Auto"
 * PHASECORR "Yes"
 * FLDIR "Freq"
 * NUMACCELFACTOR "1.00"
 * PAUSEDELMASKACQ "1"
 * NOTES ".pn/_3"
 * GRIP_NUMSLGROUPS "1"
 * GRIP_SLGROUP1 "-11.703952 -29.677423 1.949659 0.002380 0.004775 0.999985 0.999997 0.000175 -0.002380 0.000186 -0.999988 0.004775 27 0.000000 1 0 0"
 * GRIP_SATGROUP1 "0"
 * GRIP_SATGROUP2 "0"
 * GRIP_SATGROUP3 "0"
 * GRIP_SATGROUP4 "0"
 * GRIP_SATGROUP5 "0"
 * GRIP_SATGROUP6 "0"
 * GRIP_TRACKER "0"
 * GRIP_SPECTRO "0"
 * GRIP_NUMPSCVOL "0"
 * GRIP_PSCVOL1 "0"
 * GRIP_PSCVOL2 "0"
 * GRIP_PSCVOLFOV "0.000000"
 * GRIP_PSCVOLTHICK "0.000000"
 * AUTOSUBOPTIONS "0"
 * AUTOSCIC "0"
 * AUTOVOICE "0"
 * PRESETDELAY "0.0"
 * MASKPHASE "0"
 * MASKPAUSE "0"
 * TOTALNOSTATION "0"
 * STATION "0"
 */

PDBElement PDBHeader::PDBEEnd = PDBElement( );

  const PDBElement& PDBHeader::GetPDBEEnd() const
  {
    return PDBEEnd;
  }

int PDBHeader::readprotocoldatablock(const char *input, size_t inputlen, bool verbose)
{
  (void)verbose;
  // First 4 bytes are the length (again)
  uint32_t len; // = *(const uint32_t*)input;
  memcpy(&len, input, sizeof len);
  SwapperNoOp::SwapArray(&len,1);
  uint32_t offset = 4;
  //if( verbose )
  //  std::cout << len << "," << inputlen << std::endl;
  if( len + 4 + 1 == inputlen )
    {
    //if( verbose )
    //  std::cout << "gzip stream was padded with an extra 0 \n";
    }
  else if( len + 4 == inputlen )
    {
    //if( verbose )
    //  std::cout << "gzip stream was not padded with an extra 0 \n";
    }
  else
    {
    //std::cerr << "Found the Protocol Data Block but could not read length..." << std::endl;
    if( len == 0x8088b1f )
      {
      offset = 0;
      len = (uint32_t)inputlen;
      }
    else
      {
      gdcmWarningMacro("Unhandled Protocol Data Block magic value: " << len );
      return 1;
      }
    }
  // Alright we need to check if the binary blob was padded, if padded we need to
  // discard the trailing \0 to please gzip:
  std::string str( input + offset, input + len );
  std::istringstream is( str );

  zlib_stream::zip_istream gzis( is );

//  if (gzis.is_gzip())
//    {
//    std::cout<<"crc check: "<<( gzis.check_crc() ? "ok" : "failed");
//    std::cout << std::endl;
//    }

  std::string out;
  // We need to handle the old format and the new XML format here:
  // test:
  // <?xml version="1.0" encoding="UTF-8"?>
  static const char xmlstr[] = R"(<?xml version="1.0" encoding="UTF-8"?>)";
  bool isxml = false;
  while( std::getline(gzis, out) )
    {
    if( !isxml && strncmp(out.c_str(), xmlstr, strlen(xmlstr)) == 0 )
      {
      isxml = true;
      }
    if( isxml )
      {
      xmltxt.append( out );
      xmltxt.append( "\n" );
      }
    else
      {
      PDBElement pdbel;
      std::istringstream is2( out );
      std::string name, value;
      is2 >> name;
      std::getline(is2, value);
      pdbel.SetName( name.c_str() );
      // remove the first space character and the first & last " character
      std::string value2( value.begin()+2, value.end()-1);
      pdbel.SetValue( value2.c_str() );
      InternalPDBDataSet.push_back( pdbel );
      }
    }
  //std::cout << out.size();
  IsXML = isxml;

  return 0;
}

/*
int DumpProtocolDataBlock(const std::string & filename, bool verbose)
{
  gdcm::Reader reader;
  reader.SetFileName( filename.c_str() );
  if( !reader.Read() )
    {
    std::cerr << "Failed to read: " << filename << std::endl;
    return 1;
    }
  const gdcm::DataSet& ds = reader.GetFile().GetDataSet();

  const gdcm::PrivateTag tprotocoldatablock(0x0025,0x1b,"GEMS_SERS_01");
  if( !ds.FindDataElement( tprotocoldatablock) )
    {
    std::cerr << "Could not find tag: " << tprotocoldatablock << std::endl;
    return 1;
    }
  const gdcm::DataElement& protocoldatablock= ds.GetDataElement( tprotocoldatablock);
  if ( protocoldatablock.IsEmpty() ) return 1;
  const gdcm::ByteValue * bv = protocoldatablock.GetByteValue();

  std::cout << "Dumping: " << tprotocoldatablock << std::endl;
  int ret = readprotocoldatablock( bv->GetPointer(), bv->GetLength(), verbose );

  return ret;
}
*/

bool PDBHeader::LoadFromDataElement(DataElement const &protocoldatablock)
{
  InternalPDBDataSet.clear();
  if ( protocoldatablock.IsEmpty() ) return false;
  const gdcm::ByteValue * bv = protocoldatablock.GetByteValue();

  //std::cout << "Dumping: " << tprotocoldatablock << std::endl;
  int ret = readprotocoldatablock( bv->GetPointer(), bv->GetLength(), false);

  if(ret) return false;
  return true;
}

void PDBHeader::Print(std::ostream &os) const
{
  if( IsXML )
    {
    os << xmltxt << std::endl;
    }
  else
    {
    std::vector<PDBElement>::const_iterator it = InternalPDBDataSet.begin();

    for(; it != InternalPDBDataSet.end(); ++it)
      {
      os << *it << std::endl;
      }
    }
}

const PDBElement &PDBHeader::GetPDBElementByName(const char *name)
{
  if( !IsXML )
    {
    std::vector<PDBElement>::const_iterator it = InternalPDBDataSet.begin();
    for(; it != InternalPDBDataSet.end(); ++it)
      {
      const char *itname = it->GetName();
      if( strcmp(name, itname) == 0 )
        {
        return *it;
        }
      }
    }
  return GetPDBEEnd();
}

bool PDBHeader::FindPDBElementByName(const char *name)
{
  if( !IsXML )
    {
    std::vector<PDBElement>::const_iterator it = InternalPDBDataSet.begin();
    for(; it != InternalPDBDataSet.end(); ++it)
      {
      const char *itname = it->GetName();
      if( strcmp(name, itname) == 0 )
        {
        return true;
        }
      }
    }
  return false;
}

static const char pdbheader[] = "GEMS_SERS_01";
static const gdcm::PrivateTag t1(0x0025,0x001b,pdbheader);

const PrivateTag & PDBHeader::GetPDBInfoTag()
{
  return t1;
}

} // end namespace gdcm
