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

  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 "gdcmMrProtocol.h"

#include <map>
#include <string>

#if defined(_MSC_VER) && (_MSC_VER < 1900)
#define snprintf _snprintf
#endif


namespace gdcm
{

struct MrProtocol::Element {
};

using MyMapType = std::map<std::string, std::string>;
struct MrProtocol::Internals {
  MyMapType mymap;
  std::string csastr;
  int version;
};

static inline bool starts_with(const std::string& s1, const std::string& s2)
{
  return s2.size() <= s1.size() && s1.compare(0, s2.size(), s2) == 0;
}

MrProtocol::MrProtocol()
{
  Pimpl = new MrProtocol::Internals;
}

bool MrProtocol::Load( const ByteValue * bv, const char * csastr, int version )
{
  if( bv )
  {
    std::string str(bv->GetPointer(), bv->GetLength());
    std::istringstream is(str);
    std::string s;
    Pimpl->version = version;
    if( csastr ) Pimpl->csastr = csastr;
    else Pimpl->csastr = "";
    MyMapType &mymap = Pimpl->mymap;
    mymap.clear();
    // Need to handle both:
    // ### ASCCONV BEGIN ###
    // as well as:
    // ### ASCCONV BEGIN object=MrProtDataImpl@MrProtocolData version=41310008 converter=%MEASCONST%/ConverterList/Prot_Converter.txt ###
    // and
    //       "### ASCCONV BEGIN object=MrProtDataImpl@MrProtocolData version=51130001 converter=%MEASCONST%/ConverterList/Prot_Converter.txt ###
    static const char begin[] = "### ASCCONV BEGIN ";
    static const char begin2[] = "\"### ASCCONV BEGIN ";
    static const char end[] = "### ASCCONV END ###";
    bool hasstarted = false;
    while( std::getline(is, s ) )
    {
      if( !hasstarted )
      {
        // syngo E11C does not write on begin of line anymore
        s.erase(0, s.find_first_not_of(' '));
        hasstarted = starts_with(s, begin) || starts_with(s, begin2);
        if( hasstarted ) {
          if( version == -1 ) {
            // find version if not specified:
            static const char vers[] = "version=";
            std::string::size_type p = s.find(vers);
            if ( p != std::string::npos) {
              const char *v = s.c_str() + p + sizeof(vers) - 1;
              Pimpl->version = atoi(v);
            }
          }
          continue; // do not insert ASCCONV begin
        }
      }
      if( !hasstarted ) continue;
      if( starts_with(s, end) ) break;
      std::string::size_type pos = s.find( '=' );
      if( pos != std::string::npos )
      {
        std::string sub1 = s.substr(0, pos);
        sub1.erase( sub1.find_last_not_of(" \t") + 1);
        std::string sub2 = s.substr(pos+1); // skip the '=' char
        sub2.erase( 0, sub2.find_first_not_of(" \t"));
        mymap.insert( MyMapType::value_type(sub1, sub2) );
      }
      else
      {
        // ### ASCCONV BEGIN ###
        // ### ASCCONV END ###
      }
    }
  }
  else
  {
    Pimpl->version = 0;
    Pimpl->csastr = "";
    Pimpl->mymap.clear();
    return false;
  }
  return true;
}

MrProtocol::~MrProtocol()
{
  delete Pimpl;
}

int MrProtocol::GetVersion() const
{
  return Pimpl->version;
}

static inline std::string trim(std::string str)
{
  str.erase(0, str.find_first_not_of('"'));
  str.erase(str.find_last_not_of('"')+1);
  return str;
}

void MrProtocol::Print(std::ostream &os) const
{
  {
    os << Pimpl->csastr << " / Version: " << Pimpl->version << std::endl;
    os << std::endl;
    MyMapType &mymap = Pimpl->mymap;
    for( MyMapType::const_iterator it = mymap.begin();
        it != mymap.end(); ++it)
    {
      os << it->first << " : " << trim(std::string(it->second)) << std::endl;
    }
  }
}

const char * MrProtocol::GetMrProtocolByName(const char *name) const
{
  if( name )
  {
    MyMapType &mymap = Pimpl->mymap;
    MyMapType::const_iterator it = mymap.find ( name );
    if( it == mymap.end() ) return nullptr;
    return it->second.c_str();
  }
  return nullptr;
}

bool MrProtocol::FindMrProtocolByName(const char *name) const
{
  if( name )
  {
    MyMapType &mymap = Pimpl->mymap;
    MyMapType::const_iterator it = mymap.find ( name );
    if( it != mymap.end() ) return true;
  }
  return false;
}

bool MrProtocol::GetSliceArray( MrProtocol::SliceArray & sa ) const
{
  // Technically this is all boilerplate code, since we could infer the type
  // from the first few leters, eg:
  // us -> unsigned char
  // d -> double
  // ...
  sa.Slices.clear();
  static const char saSize[] = "sSliceArray.lSize";
  const char * sizestr = GetMrProtocolByName(saSize);
  if( sizestr == nullptr ) return false;
  const int size = atoi( sizestr );
  sa.Slices.resize( size );

  // sSliceArray.asSlice[*].sPosition.dSag
  // sSliceArray.asSlice[*].sPosition.dCor
  // sSliceArray.asSlice[*].sPosition.dTra
  char buf[512];
  static const char templ1[] = "sSliceArray.asSlice[%d].sPosition.%s";
  static const char templ2[] = "sSliceArray.asSlice[%d].sNormal.%s";
  static const char *dir[3] = { "dSag", "dCor", "dTra"};
  for( int i = 0; i < size; ++i )
  {
    Slice & slice = sa.Slices[i];
    {
      double v[3];
      for( int j = 0; j < 3; ++j )
      {
        snprintf( buf, sizeof(buf), templ1, i, dir[j] );
        const char * valstr = GetMrProtocolByName(buf);
        // when not present this means 0.0
        double val = 0.0;
        if( valstr ) val = atof( valstr );
        v[j] = val;
      }
      Vector3 & pos = slice.Position;
      pos.dSag = v[0];
      pos.dCor = v[1];
      pos.dTra = v[2];
    }
    {
      double v[3];
      for( int j = 0; j < 3; ++j )
      {
        snprintf( buf, sizeof(buf), templ2, i, dir[j] );
        const char * valstr = GetMrProtocolByName(buf);
        // when not present this means 0.0
        double val = 0.0;
        if( valstr ) val = atof( valstr );
        v[j] = val;
      }
      Vector3 & pos = slice.Normal;
      pos.dSag = v[0];
      pos.dCor = v[1];
      pos.dTra = v[2];
    }
  }

  return true;
}

} // end namespace gdcm
