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

  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.

=========================================================================*/
#ifndef GDCMFRAGMENT_H
#define GDCMFRAGMENT_H

#include "gdcmDataElement.h"
#include "gdcmByteValue.h"
#include "gdcmSmartPointer.h"
#include "gdcmParseException.h"

namespace gdcm_ns
{

// Implementation detail:
// I think Fragment should be a protected subclass of DataElement:
// looking somewhat like this:
/*
class GDCM_EXPORT Fragment : protected DataElement
{
public:
  using DataElement::GetTag;
  using DataElement::GetVL;
  using DataElement::SetByteValue;
  using DataElement::GetByteValue;
  using DataElement::GetValue;
*/
// Instead I am only hiding the SetTag member...

/**
 * \brief Class to represent a Fragment
 */
class GDCM_EXPORT Fragment : public DataElement
{
//protected:
//  void SetTag(const Tag &t);
public:
  Fragment() : DataElement(Tag(0xfffe, 0xe000), 0) {}
  friend std::ostream &operator<<(std::ostream &os, const Fragment &val);

  VL GetLength() const;

  VL ComputeLength() const;

  template <typename TSwap>
  std::istream &Read(std::istream &is)
    {
    ReadPreValue<TSwap>(is);
    return ReadValue<TSwap>(is);
    }

  template <typename TSwap>
  std::istream &ReadPreValue(std::istream &is)
    {
    TagField.Read<TSwap>(is);
    if( !is )
      {
      //  BogusItemStartItemEnd.dcm
      throw Exception( "Problem #1" );
      }
    if( !ValueLengthField.Read<TSwap>(is) )
      {
      // GENESIS_SIGNA-JPEG-CorruptFrag.dcm
      // JPEG fragment is declared to have 61902, but in fact really is only 61901
      // so we end up reading 0xddff,0x00e0, and VL = 0x0 (1 byte)
      throw Exception( "Problem #2" );
      }
#ifdef GDCM_SUPPORT_BROKEN_IMPLEMENTATION
    const Tag itemStart(0xfffe, 0xe000);
    const Tag seqDelItem(0xfffe,0xe0dd);
    if( TagField != itemStart && TagField != seqDelItem )
      {
      throw Exception( "Problem #3" );
      }
#endif
    return is;
    }

  template <typename TSwap>
  std::istream &ReadValue(std::istream &is)
    {
    // Self
    SmartPointer<ByteValue> bv = new ByteValue;
    const std::streampos cur = is.tellg();
    if( cur != std::streampos(-1) )
      {
      is.seekg(0, std::ios::end);
      const std::streampos end = is.tellg();
      is.seekg(cur);
      if( end != std::streampos(-1) && is.good()
        && static_cast<uint64_t>(end - cur) < static_cast<uint32_t>(ValueLengthField) )
        {
        throw Exception( "Fragment Value Length exceeds remaining stream size" );
        }
      }
    bv->SetLength(ValueLengthField);
    if( !bv->Read<TSwap>(is) )
      {
      // Fragment is incomplete, but is a itemStart, let's try to push it anyway...
      gdcmWarningMacro( "Fragment could not be read" );
      //bv->SetLength(is.gcount());
      ValueField = bv;
      ParseException pe;
      pe.SetLastElement( *this );
      throw pe;
      }
    ValueField = bv;
    return is;
    }

  template <typename TSwap>
  std::istream &ReadBacktrack(std::istream &is)
    {
    const Tag itemStart(0xfffe, 0xe000);
    const Tag seqDelItem(0xfffe,0xe0dd);

    bool cont = true;
    const std::streampos start = is.tellg();
    const int max = 10;
    int offset = 0;
    while( cont )
      {
      TagField.Read<TSwap>(is);
      gdcm_assert( is );
      if( TagField != itemStart && TagField != seqDelItem )
        {
        ++offset;
        is.seekg( (std::streampos)((size_t)start - offset) );
        gdcmWarningMacro( "Fuzzy Search, backtrack: " << (start - is.tellg()) << " Offset: " << is.tellg() );
        if( offset > max )
          {
          gdcmErrorMacro( "Giving up" );
          throw "Impossible to backtrack";
          }
        }
      else
        {
        cont = false;
        }
      }
    gdcm_assert( TagField == itemStart || TagField == seqDelItem );
    if( !ValueLengthField.Read<TSwap>(is) )
      {
      return is;
      }

    // Self
    SmartPointer<ByteValue> bv = new ByteValue;
    const std::streampos cur2 = is.tellg();
    if( cur2 != std::streampos(-1) )
      {
      is.seekg(0, std::ios::end);
      const std::streampos end2 = is.tellg();
      is.seekg(cur2);
      if( end2 != std::streampos(-1) && is.good()
        && static_cast<uint64_t>(end2 - cur2) < static_cast<uint32_t>(ValueLengthField) )
        {
        throw Exception( "Fragment Value Length exceeds remaining stream size" );
        }
      }
    bv->SetLength(ValueLengthField);
    if( !bv->Read<TSwap>(is) )
      {
      // Fragment is incomplete, but is a itemStart, let's try to push it anyway...
      gdcmWarningMacro( "Fragment could not be read" );
      //bv->SetLength(is.gcount());
      ValueField = bv;
      ParseException pe;
      pe.SetLastElement( *this );
      throw pe;
      }
    ValueField = bv;
    return is;
    }


  template <typename TSwap>
  std::ostream &Write(std::ostream &os) const {
    const Tag itemStart(0xfffe, 0xe000);
    const Tag seqDelItem(0xfffe,0xe0dd);
    if( !TagField.Write<TSwap>(os) )
      {
      gdcm_assert(0 && "Should not happen");
      return os;
      }
    gdcm_assert( TagField == itemStart
         || TagField == seqDelItem );
    const ByteValue *bv = GetByteValue();
    // VL
    // The following piece of code is hard to read in order to support such broken file as:
    // CompressedLossy.dcm
    if( IsEmpty() )
      {
      //gdcm_assert( bv );
      VL zero = 0;
      if( !zero.Write<TSwap>(os) )
        {
        gdcm_assert(0 && "Should not happen");
        return os;
        }
      }
    else
      {
      gdcm_assert( ValueLengthField );
      gdcm_assert( !ValueLengthField.IsUndefined() );
      const VL actuallen = bv->ComputeLength();
      gdcm_assert( actuallen == ValueLengthField || actuallen == ValueLengthField + 1 );
      if( !actuallen.Write<TSwap>(os) )
        {
        gdcm_assert(0 && "Should not happen");
        return os;
        }
      }
    // Value
    if( ValueLengthField && bv )
      {
      // Self
      gdcm_assert( bv );
      gdcm_assert( bv->GetLength() == ValueLengthField );
      if( !bv->Write<TSwap>(os) )
        {
        gdcm_assert(0 && "Should not happen");
        return os;
        }
      }
    return os;
    }
};
//-----------------------------------------------------------------------------
inline std::ostream &operator<<(std::ostream &os, const Fragment &val)
{
  os << "Tag: " << val.TagField;
  os << "\tVL: " << val.ValueLengthField;
  if( val.ValueField )
    {
    os << "\t" << *(val.ValueField);
    }

  return os;
}

} // end namespace gdcm_ns

#endif //GDCMFRAGMENT_H
