/// DicomFile.cpp
/**
*/

#include "DicomFile.h"

using namespace jcs;

DicomFile::DicomFile(wxString filename)
: mVrSyntax(EXPLICIT_VR), mByteOrder(dLITTLE_ENDIAN),
  mFileName(filename), mBeginning(0)
{
  mErrors.reset();
  mDicom = Dicom_Dictionary::Instance();
  mInitializeMap();

  mInputFile.open((const char *) filename, std::ios::binary);
  // Don't need to check open condition here except to try 
  // opening files with differing Unicode encodings. Other code
  // below will handle not-open condition.
  //if (!mInputFile.is_open()) {
	  // was this but I can't get it to compile under windows.
	  // For now it's out, but we should figure out if we really need this extra lib
      // 20160120cdt - utf8proc library proves unnecessary,
      // build wxwidgets with intl and utf8 support instead
      //#ifndef _WIN64
		//filename = (unsigned char *) filename.ToUTF8().data(); 
		//mInputFile.open((const char *) filename, std::ios::binary); 
      //#endif
	//if (!mInputFile.is_open()) {
	//std::cout << "DicomFile ctor, unable to open " << filename << std::endl;
	//}
  //}

  if (!mSkipPreamble()) {
    if (mErrors.test(NO_DICM)) {
      // attempt to read as dataset
      DicomElement e1, e2;
      mReadNextElement(e1, mInputFile);
      mReadNextElement(e2, mInputFile);
      mInputFile.seekg(std::ios::beg);
      if ((e1.tag.group == 8) && (e2.tag.group == 8)) {
        mErrors.reset();
      } else {
        mSetTransferSyntax(IMPLICIT_VR, dLITTLE_ENDIAN);
        mReadNextElement(e1, mInputFile);
        mReadNextElement(e2, mInputFile);
        mInputFile.seekg(std::ios::beg);
        if ((e1.tag.group == 8) && (e2.tag.group == 8)) {
          mErrors.reset();
        }
      }
    }
  } else {
    mReadMetafile();
  }
  mCheckOk();

  std::string manufacturer;
  Find("Manufacturer", manufacturer);
  Find("SpecificCharacterSet", mCharacterSet);

  if (manufacturer == "SIEMENS") {
    std::string software;
    Find("SoftwareVersions", software);
    // Is Numaris used with other software versions? 20130409cdt
    if (software == ("VA13F")) {
      mSpecial = Numaris_Dictionary::Instance();
    } else {
      mSpecial = mDicom;
    }
  }
  else if (manufacturer == "GE MEDICAL SYSTEMS") {
    std::string product_id;
    Find(DicomTag(0x0009, 0x1004), product_id);
    if (product_id.find("SIGNA") != std::string::npos) {
      mSpecial = Excite_Dictionary::Instance();
    } else {
      mSpecial = mDicom;
    }
  } 
  else if (manufacturer.find("Philips") != std::string::npos) {
    std::string modality;
    Find("Modality", modality);
    if (modality == "MR") {
      mSpecial = PhilipsMr_Dictionary::Instance();
    } else {
      mSpecial = mDicom;
    }
  }
  else {
    mSpecial = mDicom;
  }

  foundPixelDataStart = false;
}


///
/** Wraps mErrors.none().
    \return True if no mErrors bits are set, false otherwise.
*/
bool
DicomFile::IsOk() 
{
  return mErrors.none(); 
}


///
/** If IsOk() returns true, checks for SOPInstanceUID, SeriesInstanceUID,
    InstanceNumber and PhotometricInterpretation elements. If these
    are not found, will set appropriate error code bit in mErrors.
*/
// This is where we put reasons to reject files.
void
DicomFile::mCheckOk()
{
  if (IsOk()) {

    std::string s;

    if (!Find("SOPInstanceUID", s)) {
      mErrors.set(NO_UID);
    }

    if (!Find("SeriesInstanceUID", s)) {
      mErrors.set(NO_SERIES_UID);
    }

    if (!Find("InstanceNumber", s)) {
      mErrors.set(NO_INSTANCE);
    }

    if (!Find("PhotometricInterpretation", s)) {
      mErrors.set(NO_IMAGE);
    }
  }
}


///
/** Tests mErrors, returns a string describing any error conditions.
    \return A character string.
*/
const char*
DicomFile::ErrorMessage() const
{
  std::string retval;

  if (mErrors.none()) {
    return "No errors.";
  }

  if (mErrors.test(NO_BYTES)) {
    return "File is <128 bytes. Not a valid DICOM file.";
  }

  if (mErrors.test(NO_DICM)) {
    return "Unable to read file, possibly not DICOM format.";
  }
  
  if (mErrors.test(METAFILE_ERROR)) {
    return "Problem reading metafile header.";
  }

  if (mErrors.test(TRANSFER_SYNTAX)) {
    return "Unsupported transfer syntax.";
  }
  
  if (mErrors.test(NO_SERIES_UID)) {
    return "Unable to find series UID.";
  }

  if (mErrors.test(NO_UID)) {
    return "Unable to find instance UID.";
  }

  if (mErrors.test(NO_INSTANCE)) {
    return "Unable to find instance number.";
  }

  if (mErrors.test(NO_IMAGE)) {
    return "File does not contain image data.";
  }

  // should never get here, but...
  return "Error reading DICOM file.";
}


///
/** Map indices refer to VR types, defined in PS 3.5.
*/
void
DicomFile::mInitializeMap() {
  ReadMap["FL"] = ReadStream<float>::doit;
  ReadMap["FD"] = ReadStream<double>::doit;
  ReadMap["OW"] = ReadStream<wxUint16>::doit;
  //ReadMap["AT"] = ReadStream<std::tuple<wxUint16, wxUint16>>::doit;
  ReadMap["AT"] = ReadStream<wxUint16>::doit;
  ReadMap["SL"] = ReadStream<wxInt32>::doit;
  ReadMap["SS"] = ReadStream<wxInt16>::doit;
  ReadMap["UL"] = ReadStream<wxUint32>::doit;
  ReadMap["US"] = ReadStream<wxUint16>::doit;
  ReadMap["UN"] = ReadStream<std::string>::doit;  // Default
}


DicomFile::~DicomFile()
{
  mInputFile.close();
}


///
/** Wraps mFindElement.
    \param d Reference to a DicomElement to seek.
    \param input Input stream from which to read.
    \return True if element is found, false otherwise.
*/
bool
DicomFile::FindElement(DicomElement& d, std::istream& input) 
{
  bool retval = false;
  if (input.good()) {
    DicomElement e;
    if (mFindElement(d.tag, e, input)) {
      if (mVrSyntax == EXPLICIT_VR && e.vr != "OB" && e.vr != "UN") {
        d.vr = e.vr;
      }
      d.value_length = e.value_length;
      retval = true;
    }
  }
  return retval;
}


///
/** Determines whether the file is a Siemens syngo file based
    on SoftwareVersions tag and magic string.
    \return True if SoftwareVersions contains "syngo", false otherwise.
*/
bool
DicomFile::IsSyngoFile()
{
  bool retval = false;
  std::string software;
  Find("SoftwareVersions", software);

  if (software.find("syngo") != std::string::npos) {
    retval = true;
  }
  return retval;
}


///
/** Seeks magic string in private group 0x0029.
    \param or_val Value to OR with a matching element value. In
      practice, this will be 0x0010 or 0x0020.
    \param header A string reference to receive the data found
      in the matching element.
    \return True if element is found, false otherwise.
*/
bool
DicomFile::FindCSAHeader(int or_val, std::string& header)
{
  bool retval = false;
  wxUint16 group, element;
  group = 0x0029;

  if (IsSyngoFile()) {
    for (element = 0x0010; element < 0x0100; ++element) {
      DicomElement creator(DicomTag(group, element));
      std::string creator_name;
      if (Find(creator, creator_name)) {
        if (creator_name == "SIEMENS CSA HEADER") {
          element = (element << 8) | or_val;
          DicomTag csa_header_info_tag(group, element);
          if (Find(csa_header_info_tag, header)) {
            retval = true;
          }
          break;
        }
      }
    }
  }
  return retval;
}


///
/** Calls FindCSAHeader with or_val of 0x0010.
    \param header A string reference that receives the 
    tag data.
    \return True or false.
*/
bool
DicomFile::GetCSAImageHeader(std::string& header)
{
  return FindCSAHeader(0x0010, header);
}


///
/** Calls FindCSAHeader with or_val of 0x0020.
    \param header A string reference that receives the 
    tag data.
    \return True or false.
*/
bool
DicomFile::GetCSASeriesHeader(std::string& header)
{
  return FindCSAHeader(0x0020, header);
}


///
/** Tests whether there is a private tag in group 0x0019
    that matches a magic string.
    \return True if there is such a tag, false if Siemens
    MR Header not found in private creator list.
*/
bool
DicomFile::HasSiemensMrHeader()
{
  bool retval = false;
  wxUint16 group, element;
  group = 0x0019;

  if (IsSyngoFile()) {
    for (element = 0x0010; element < 0x0100; ++element) {
      DicomElement creator(DicomTag(group, element));
      std::string creator_name;
      if (Find(creator, creator_name)) {
        if (creator_name == "SIEMENS MR HEADER") {
          retval = true;
          break;
        }
      }
    }
  }
  return retval;
}


///
/**
*/


int
DicomFile::ReadCSAImageHeader(const std::string& search, std::string& value)
{
  int retval = 0;
  value = std::string(); // set value to empty string
  std::string csa_header_info;
  if (GetCSAImageHeader(csa_header_info)) {
    retval = ReadCSAHeader(csa_header_info, search, value);
  }
  return retval;
}


///
/**
*/
int
DicomFile::ReadCSAImageHeader(const std::string& search, std::vector<std::string>& value)
{
  int retval = 0;
  value.clear(); 
  std::string csa_header_info;
  if (GetCSAImageHeader(csa_header_info)) {
    retval = ReadCSAHeader(csa_header_info, search, value);
  }
  return retval;
}


///
/**
*/
int
DicomFile::ReadCSASeriesHeader(const std::string& search, std::string& value)
{
  int retval = 0;
  value = std::string(); // set value to empty string
  std::string csa_header_info;
  if (GetCSASeriesHeader(csa_header_info)) {
    retval = ReadCSAHeader(csa_header_info, search, value);
  }
  return retval;
}


///
/**
*/
int
DicomFile::ReadCSASeriesHeader(const std::string& search, std::vector<std::string>& value)
{
  int retval = 0;
  value.clear();
  std::string csa_header_info;
  if (GetCSASeriesHeader(csa_header_info)) {
    retval = ReadCSAHeader(csa_header_info, search, value);
  }
  return retval;
}


///
/** Routine to read Siemens' "shadow" header.
    Searches data in 'header' for value given by 'search'.
    Expects to have a successful call to one of the GetCSA*Header
    methods to populate 'header'.
    \param header
    \param search
    \param value A string reference to hold value found.
    \return 0 if not found, -1 if found but no data, 1 if found and there exists data.
*/
int
DicomFile::ReadCSAHeader(const std::string& header, const std::string& search, std::string& value)
{
  std::string::size_type pos = 0;
  wxUint32 intval2;
  wxUint32 intval3;
  wxInt32 n_values;
  
  //*** Duplicate block.
  for (;;) {
    pos = header.find(search, pos);
    if (pos == std::string::npos) {
      return 0; // search string not found
    }

    if (search.compare(header.substr(pos, 64).c_str())) { // wrong hit
      pos += 1;
      continue;
    }

    pos += 64;

    n_values = *reinterpret_cast<wxInt32*> (&header.substr(pos)[0]);

    if (n_values > 10000) { // crude way to look for another wrong hit
      pos -= 63;
      continue;
    }

    pos += 3*4;
    intval2 = *reinterpret_cast<wxUint32*>(&header.substr(pos)[0]);

    pos += 4;
    intval3 = *reinterpret_cast<wxUint32*>(&header.substr(pos)[0]);

    if (intval3 == 205) {
      return -1; // search string found, but no value given
    }

    if ((intval2 % 6) == 0 && intval3 == 77) {
      break;
    }
  }
  //*** End duplicate block

  pos += 4*4;

  wxUint32 value_length = *reinterpret_cast<wxUint32*>(&header.substr(pos)[0]);
  pos += 4;
  value = header.substr(pos, value_length);

  return 1;
}


///
/** Routine to read Siemens' "shadow" header.
    Searches data in 'header' for value given by 'search'.
    Expects to have a successful call to one of the GetCSA*Header
    methods to populate 'header'.
    \param header
    \param search
    \param value A vector of strings reference to hold values found.
    \return 0 if not found, -1 if found but no data, 1 if found and there exists data.
*/
int
DicomFile::ReadCSAHeader(const std::string& header, const std::string& search, std::vector<std::string>& value)
{
  std::string::size_type pos = 0;
  wxUint32 intval2;
  wxUint32 intval3;
  wxInt32 n_values;
  
  //*** Duplicate block.
  for(;;) {
    pos = header.find(search, pos);
    if (pos == std::string::npos) {
      return 0; // search string not found
    }

    if (search.compare(header.substr(pos, 64).c_str())) { // wrong hit
      pos += 1;
      continue;
    }

    pos += 64;

    n_values = *reinterpret_cast<wxInt32*> (&header.substr(pos)[0]);

    if (n_values > 10000) { // crude way to look for another wrong hit
      pos -= 63;
      continue;
    }

    pos += 3*4;
    intval2 = *reinterpret_cast<wxUint32*>(&header.substr(pos)[0]);

    pos += 4;
    intval3 = *reinterpret_cast<wxUint32*>(&header.substr(pos)[0]);

    if (intval3 == 205) {
      return -1; // search string found, but no value given
    }

    if ((intval2 % 6) == 0 && intval3 == 77) {
      break;
    }
  }
  //*** End duplicate block

  pos += 4;
  for (;;) {
    pos += 4;
    intval2 = *reinterpret_cast<wxUint32*>(&header.substr(pos)[0]);
    pos += 4;
    intval3 = *reinterpret_cast<wxUint32*>(&header.substr(pos)[0]);

    if (intval3 != 77 || pos == std::string::npos) {
      break;
    }

    pos += 4;
    wxUint32 value_length = *reinterpret_cast<wxUint32*>(&header.substr(pos)[0]);
    pos += 4;
    value.push_back(header.substr(pos, value_length));
    while (value_length % 4) {
      ++value_length;
    }
    pos += value_length;
  }

  return 1;
}


///
/** Dumps elements in mInputFile to a vector of 
    DicomElements.
    \return A vector of DicomElements.
*/
std::vector<DicomElement>
DicomFile::DataDump()
{
  std::vector<DicomElement> dump;

  mInputFile.clear();
  mInputFile.seekg(mBeginning);

  std::vector<DicomElement> temp;
  temp = mReadNextElementAll();
  
  while(mInputFile.good()) {
    dump.insert(dump.end(), temp.begin(), temp.end());
    temp = mReadNextElementAll();
  };

  return dump;
}


///
/** Dumps all elements up to PixelData in mInputFile to
    file given as parameter.
    \param filename Target of the dump.
*/
void
DicomFile::DataDump(const char *filename)
{
  std::ofstream output;
  output.open(filename);
  DicomElement e;
  mReadNextElement(e, mInputFile);
  
  while(e.tag != Tag("PixelData")) {

    output << e.tag;

    if (e.value_length != UNDEFINED) {
      output << e.value_length << ' ';
      std::string s;
      mInputFile.seekg(-static_cast<std::streamoff>(e.value_length), std::ios::cur);
      for (unsigned int i = 0; i < e.value_length; ++i) {
        s += mInputFile.get();
      }      
      output << s << std::endl;
    }
    else {
      output << "Undefined length item" << std::endl;
    }
    mReadNextElement(e, mInputFile);
  }
  output.close();
}


///
/**
*/
//int
//DicomFile::GetHeaderSize()
//{
//  mInputFile.seekg(mBeginning);
//
//  DicomElement element;
//  mReadNextElement(element, mInputFile);
//  while (mInputFile.good() && (element.tag != Tag("PixelData"))) {
//    mReadNextElement(element, mInputFile);
//  }
//  return (static_cast<int>(mInputFile.tellg()) - element.value_length);
//}


///
/**
*/
//int
//DicomFile::GetFileSize()
//{
//  mInputFile.seekg(0, std::ios::end);
//  return static_cast<int>(mInputFile.tellg());
//}


///
/** Reads file, skips the magic number of bytes, then seeks
    the magic DICOM string.  Leaves file pointer ready to read
    DICOM elements on success, or at beginning of file on failure.
    Sets error code in some cases.
    \return false on failure, true if magic DICOM string is found.
*/
bool 
DicomFile::mSkipPreamble() 
{  
  //****
  // This bit of code illustrates a way of reading DICOM files
  // using stream operators. Relies on developing istream>> operators
  // for each data bit to be read. Such data bits provide the means
  // to read themselves.  Generally, a DICOM group,element comprise
  // a "data bit".  201304cdt
  //  
  // Preamble is the unused 128 bytes at the start of DICOM files.
  /**
  Preamble p;
  mInputFile >> p;

  // MetaHeader is the byte-swap-agnostic portion of DICOM files.
  mInputFile >> mh;
  
  // The rest of the header must consider byte-ordering, hence the 
  // setting of transferSyntaxCode.
  main.transferSyntaxCode = mh.GetMainTransferSyntaxCode();
  mInputFile >> main;
  **/
  
  // Display fluff, proving we know what we are about.
  /**
  std::vector<DicomElementInstance>::iterator it;
  std::vector<DicomElementInstance>::iterator it_end;
  it = mh.mhElements.begin();
  it_end = mh.mhElements.end();
  for (; it < it_end; ++it) {
    std::cout << *it << std::endl;
  }
  std::cout << "transfer code: " << main.transferSyntaxCode << std::endl;
  it = main.mhElements.begin();
  it_end = main.mhElements.end();
  for (; it < it_end; ++it) {
//    if (it->tag.group % 2 == 0) {
      std::cout << *it << std::endl;
//    }
  }
  **/

  // Reset input pointer, as if nothing had happened...
  //mInputFile.seekg(0);

  //
  //****
 
  bool retval = false;
  
  //std::cout << "mSkipPreamble" << std::endl;
  
  if (mInputFile.is_open()) {
    if (IsOk()) {
      // First 128 bytes of DICOM files are not used.
      mInputFile.ignore(128);
     
      if (mInputFile.good())  {
        std::string s;
        for (int i = 0; i < 4; ++i) {
          s += mInputFile.get();
        }
        if (s == "DICM") {
          retval = true;
        }
        else {
          mErrors.set(NO_DICM);
          mInputFile.seekg(std::ios::beg);
        }
      }
      else {
        mErrors.set(NO_BYTES);
      }
    }
  }
  return retval;
}

///////
//int
//DicomFile::mReadMetafile() 
//{
//  if (!mInputFile.is_open() || !IsOk()) {
//    return 0;
//  }
//
//  mBeginning = mInputFile.tellg();
//  std::streamsize group_length;
//  //int Findresult = Find("MfGroupLength", group_length);
//  int Findresult = Find("FileMetaInformationGroupLength", group_length);
//  if (!Findresult) {
//    mErrors.set(METAFILE_ERROR);
//    return 0;
//  }
//
//  std::streampos meta_file_end = mInputFile.tellg() + 
//  static_cast<std::streampos>(group_length);
//
//  std::string s;
//  //if (!Find("MfTransferSyntax", s)) {
//  if (!Find("TransferSyntaxUID", s)) {
//    mErrors.set(TRANSFER_SYNTAX);
//    return 0;
//  }
//
//  // Can only read little endian
//  if (strcmp(s.c_str(), LEI_UID) == 0) {
//    mSetTransferSyntax(IMPLICIT_VR, dLITTLE_ENDIAN);
//  }
//  else if (strcmp(s.c_str(), LEE_UID) == 0) {
//    mSetTransferSyntax(EXPLICIT_VR, dLITTLE_ENDIAN);
//  } 
//  else {
//    mErrors.set(TRANSFER_SYNTAX);
//    return 0;
//  }
//
//  mInputFile.seekg(meta_file_end);
//  mBeginning = mInputFile.tellg();
//  return 1;
//}
/////////
///
/** Careful here, substantial rewrite. Compare to 
    original code above. 20131111cdt
    \return True if the TransferSyntax is little-endian
            implicit or little-endian explicit, false
            otherwise.
*/
bool
DicomFile::mReadMetafile() 
{
  bool retval = false;
  if (mInputFile.is_open() && IsOk()) {

    mBeginning = mInputFile.tellg();
    std::streamsize group_length;
    //if (Find("MfGroupLength", group_length)) {
    if (Find("FileMetaInformationGroupLength", group_length)) {
      std::streampos meta_file_end = mInputFile.tellg() + 
      static_cast<std::streampos>(group_length);
      std::string s;
      //if (Find("MfTransferSyntax", s)) {
      if (Find("TransferSyntaxUID", s)) {
        // Can only read little endian
        if (strcmp(s.c_str(), LEI_UID) == 0) {
          mSetTransferSyntax(IMPLICIT_VR, dLITTLE_ENDIAN);
          mInputFile.seekg(meta_file_end);
          mBeginning = mInputFile.tellg();
          retval = true;
        }
        else if (strcmp(s.c_str(), LEE_UID) == 0) {
          mSetTransferSyntax(EXPLICIT_VR, dLITTLE_ENDIAN);
          mInputFile.seekg(meta_file_end);
          mBeginning = mInputFile.tellg();
          retval = true;
        } 
        else {
          mErrors.set(TRANSFER_SYNTAX);
        }
      } else {
        mErrors.set(TRANSFER_SYNTAX);
      }
    } else {
      mErrors.set(METAFILE_ERROR);
    }
  }
  return retval;
}


///
/**
*/
void 
DicomFile::mSetTransferSyntax(VrSyntaxType v, dicomEndianType b) 
{
  mByteOrder = b;
  mVrSyntax = v;
}


///
/**
*/
// 20070511cdt - byte-swap on big-endian machines
void
DicomFile::mReadElementTag(DicomElement& e, std::istream& input) 
{
  input.read(reinterpret_cast<char*> (&e.tag.group), 2);
  input.read(reinterpret_cast<char*> (&e.tag.element), 2);

  wxUint16 group_raw = e.tag.group;
  wxUint16 element_raw = e.tag.element;
}


///
/**
*/
void
DicomFile::mReadValueRepresentation(DicomElement& e, std::istream& input) 
{
  // Check VR Syntax, but groups < 0x0008 are always EXPLICIT_VR.
  if ((mVrSyntax == IMPLICIT_VR) && (e.tag.group >= 0x0008)) {
    return;
  }
  e.vr = input.get();
  e.vr += input.get();
}


///
/**
*/
void
DicomFile::mReadValueLength(DicomElement& e, std::istream& input) 
{

  wxUint32 value_length_raw = e.value_length;

  // Check VR Syntax, but groups < 0x0008 are always EXPLICIT_VR.
  if ((mVrSyntax == IMPLICIT_VR) && (e.tag.group >= 0x0008)) {

    input.read(reinterpret_cast<char*> (&e.value_length), 
    sizeof(e.value_length));
    //    e.value_length = ByteSwap (e.value_length);
  }
  // These VR types have a 32-bit value length, see Table 7.1-1
  //  of PS 3.5-2011.
  else if ( e.vr == "OB" || 
      e.vr == "OW" || 
      e.vr == "OF" ||   // 20070516cdt
      e.vr == "UT" ||   // 20070516cdt
      e.vr == "SQ" ||
      e.vr == "UN" ) {

    input.ignore(2);  // 2 bytes are reserved
    
    input.read(reinterpret_cast<char*> (&e.value_length), 
    sizeof(e.value_length));
    //      e.value_length = ByteSwap (e.value_length);
  } else {
    
    wxUint16 vl_16;
    input.read(reinterpret_cast<char*> (&vl_16), 
    sizeof(vl_16));
    //      vl_16 = ByteSwap (vl_16);
    e.value_length = static_cast<wxUint32> (vl_16);
  }

}


///
/**
*/
void
DicomFile::mReadNextElement(DicomElement &rElement, std::istream& input) 
{
  mReadElementTag(rElement, input);
  mReadValueRepresentation(rElement, input);
  mReadValueLength(rElement, input);

  if (rElement.value_length != UNDEFINED) {
    input.ignore(rElement.value_length);
  }

  // Undefined length item
  else {
    std::istream::pos_type start_pos = input.tellg();
    DicomElement item;
    mReadElementTag(item, input);
    input.read(reinterpret_cast<char*> (&item.value_length), 
    sizeof(item.value_length));

    //while (item.tag != Tag("SqDelimiter")) { 
    while (item.tag != Tag("SequenceDelimitationItem")) { 
      if (item.value_length != UNDEFINED) {
        input.ignore(item.value_length);
      }
      else {
        DicomElement e;
        mReadElementTag(e, input);
        //while (e.tag != Tag("ItemDelimiter")) {
        while (e.tag != Tag("ItemDelimitationItem")) {
          input.seekg(-4, std::ios::cur);
          mReadNextElement(e, input);
          mReadElementTag(e, input);
        }
        input.ignore(4);
      }

      mReadElementTag(item, input);
      input.read(reinterpret_cast<char*> (&item.value_length), 
      sizeof(item.value_length));
    }
    rElement.value_length = input.tellg() - start_pos;
  }
}


///
/**
*/
std::vector<DicomElement> 
DicomFile::mReadNextElementAll() 
{
  DicomElement e;
  std::vector<DicomElement> elementVector;

  mReadElementTag(e, mInputFile);
  mReadValueRepresentation(e, mInputFile);
  mReadValueLength(e, mInputFile);

  if (e.tag == Tag("PixelData")) {
    mInputFile.ignore(e.value_length);
    elementVector.push_back(e);

  }
  else if (e.value_length != UNDEFINED) {
    
    DicomElement dict_entry;
    if (e.tag.group & 1) { // private tags
      dict_entry = mSpecial->Lookup(e.tag);
    }
    else {
      dict_entry = mDicom->Lookup(e.tag);
    }
    
    if ((mVrSyntax != EXPLICIT_VR) || (dict_entry.vr != "UN")) {
      e.vr = dict_entry.vr;
    }

    e.description = dict_entry.description;

    mReadValue(e, e.value, mInputFile);
    elementVector.push_back(e);

  }
  else {  // Undefined length item
    elementVector.push_back(e);
    
    DicomElement item;
    mReadElementTag(item, mInputFile);
    mInputFile.read(reinterpret_cast<char*> (&item.value_length), 
    sizeof(item.value_length));

    //while (item.tag != Tag("SqDelimiter")) {
    while (item.tag != Tag("SequenceDelimitationItem")) {
      if (item.value_length != UNDEFINED) {
        mReadValue(item, item.value, mInputFile);
        elementVector.push_back(item);
      }
      else {
        DicomElement ee;
        mReadElementTag(ee, mInputFile);
        //while (ee.tag != Tag("ItemDelimiter")) {
        while (ee.tag != Tag("ItemDelimitationItem")) {
          mInputFile.seekg(-4, std::ios::cur);
          std::vector<DicomElement> nv = mReadNextElementAll();
          elementVector.insert(elementVector.end(), nv.begin(), nv.end());
          mReadElementTag(ee, mInputFile);
        }
        mInputFile.ignore(4);
      }
      mReadElementTag(item, mInputFile);
      mInputFile.read(reinterpret_cast<char*> (&item.value_length), 
      sizeof(item.value_length));
    }
  }
  return elementVector;
}


///
/**
    \param tag Constant reference to a DicomTag instance.
    \param element A DicomElement reference, receives data from 'input'.
    \param input An input stream.
    \return 
*/
bool
DicomFile::mFindElement(const DicomTag& tag, DicomElement& element, std::istream& input) 
{
  bool retval = false;
  
  mReadNextElement(element, input);

  if (element.tag == tag) {
    retval = true;
  } else {

    if ((element.tag > tag) || !input.good() || element.tag == DicomTag(0,0)) {
      input.clear();
	  if (&input == &mInputFile) {
        input.seekg(mBeginning);
      }
      else {
        input.seekg(0);
      }
    }   

    while (input.good()) {
      mReadNextElement(element, input);
      if (element.tag == tag) {
        retval = true;
        break;
      }
      if (element.tag > tag) {
        break;
      } 
    }
  }
  return retval;
}


///
/**
*/
// specialization of readvalue for strings
int
DicomFile::mReadValue(DicomElement d, std::string& sValue, std::istream& input)
{
  sValue.clear();
  if (!mInputFile.good()) {
    return 0;
  }
  
  std::vector<std::string> v;
  if (ReadMap.count(d.vr)) {
    v = ReadMap[d.vr](input, d.value_length);
  }
  else {
    v = ReadMap["UN"](input, d.value_length);
  }

  if (!v.empty()) {
    sValue = v.front();
    std::vector<std::string>::iterator it = v.begin();
    while(++it != v.end()) {
      sValue += "/";
      sValue += *it;
    }
  }

  //  remove leading/trailing spaces
  sValue.erase(0, sValue.find_first_not_of(' '));
  sValue.resize(sValue.find_last_not_of(' ') + 1);

  return 1;
}


///
/** Handles MoCo and DTI series.
    \param seq
    \param str
    \param v
    \return
*/
int
DicomFile::FindInSequence(const std::string& seq, const char* str, std::vector<std::string>& v)
{ 
  DicomElement de = DicomElement(Tag(str));
  return FindInSequence(seq, de, v);
}


///
/** Handles MoCo and DTI series.
    \param seq
    \param d
    \param v
    \return
*/
int 
DicomFile::FindInSequence(const std::string& seq, DicomElement& d, std::vector<std::string>& v)
{
  int found = 0;
  std::stringstream ss(seq);

  DicomElement item;

  mReadElementTag(item, ss);
  ss.read(reinterpret_cast<char*> (&item.value_length), sizeof(item.value_length));

  while (ss.good()) { 
    if (item.value_length != UNDEFINED) {
      std::istream::pos_type pos = ss.tellg();
      std::istream::pos_type end_pos = pos + static_cast<std::streamoff>(item.value_length);
      DicomElement e;
      while (ss.good() && (pos < end_pos)) {
        mReadNextElement(e, ss);
        if (ss.good() && (e.tag == d.tag)) {
          std::string str;
          ss.seekg(- static_cast<std::streamoff>(e.value_length), 
          std::ios::cur);

          mReadValue(e, str, ss);
          v.push_back(str);
          found = 1;
        }
        pos = ss.tellg();
      }
    }
    else {
      DicomElement e;
      mReadElementTag(e, ss);
      //while (e.tag != Tag("ItemDelimiter")) {
      while (e.tag != Tag("ItemDelimitationItem")) {
        ss.seekg(-4, std::ios::cur);
        mReadNextElement(e, ss);
        if (e.tag == d.tag) {
          std::string str;
          ss.seekg(- static_cast<std::streamoff>(e.value_length), std::ios::cur);
          mReadValue(e, str, ss);
          v.push_back(str);
          found = 1;
        }
        mReadElementTag(e, ss);
      }
      ss.ignore(4);
    }

    mReadElementTag(item, ss);
    ss.read(reinterpret_cast<char*> (&item.value_length), 
    sizeof(item.value_length));
  }
  return found;
}


///
/**
*/
std::iostream::pos_type
DicomFile::GetPixelDataStart()
{
  if (!foundPixelDataStart) {
    if (!mInputFile.is_open()) { return 0; }
    mInputFile.clear();

    mInputFile.seekg(mBeginning);

    DicomElement element;
    mReadNextElement(element, mInputFile);
    while ((mInputFile.good()) && (element.tag != Tag("PixelData"))) {
      mReadNextElement(element, mInputFile);
    }
    
    std::streamoff offset = static_cast<std::streamoff>(element.value_length);
    
    // assume if input file not good, data is at end
    if (!mInputFile.good()) {
      mInputFile.clear();
      mInputFile.seekg(-offset, std::ios::end);
    } else {
      mInputFile.seekg(-offset, std::ios::cur);
    }
    pixelDataStart = mInputFile.tellg();
    foundPixelDataStart = true;
  }
  return pixelDataStart;
}
