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

  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 "gdcmStringFilter.h"
#include "gdcmGlobal.h"
#include "gdcmElement.h"
#include "gdcmByteValue.h"
#include "gdcmAttribute.h"
#include "gdcmDataSetHelper.h"

#include <cstring> // strtok

namespace gdcm
{

//-----------------------------------------------------------------------------
StringFilter::StringFilter():F(new File)
{
}
//-----------------------------------------------------------------------------
StringFilter::~StringFilter()
= default;

void StringFilter::SetDicts(const Dicts &dicts)
{
  (void)dicts;
  gdcm_assert(0); // FIXME
}

std::string StringFilter::ToString(const Tag& t) const
{
  return ToStringPair(t).second;
}

std::string StringFilter::ToString(const PrivateTag& privTag) const
{
  const DataSet &ds = GetFile().GetDataSet();
  const DataElement &de = ds.GetDataElement(privTag);
  return ToStringPair(de).second;
}

std::string StringFilter::ToString(const DataElement& de) const
{
  return ToStringPair(de).second;
}

/*
std::string StringFilter::ToMIME64(const Tag& t) const
{
  return ToStringPair(t).second;
          // base64 streams have to be a multiple of 4 bytes long
          int encodedLengthEstimate = 2 * bv->GetLength();
          encodedLengthEstimate = ((encodedLengthEstimate / 4) + 1) * 4;

          char *bin = new char[encodedLengthEstimate];
          unsigned int encodedLengthActual = static_cast<unsigned int>(
            itksysBase64_Encode(
              (const unsigned char *) bv->GetPointer(),
              static_cast< unsigned long>( bv->GetLength() ),
              (unsigned char *) bin,
              static_cast< int >( 0 ) ));
          std::string encodedValue(bin, encodedLengthActual);

}
*/

#define StringFilterCase(type) \
  case VR::type: \
    { \
      Element<VR::type,VM::VM1_n> el; \
      if( !de.IsEmpty() ) { \
      el.Set( de.GetValue() ); \
      if( el.GetLength() ) { \
      os << el.GetValue(); \
      for(unsigned int i = 1; i < el.GetLength(); ++i) os << "\\" << el.GetValue(i); \
      retvalue = os.str(); } } \
    } break

std::pair<std::string, std::string> StringFilter::ToStringPair(const Tag& t) const
{
  if( t.GetGroup() == 0x2 )
    {
    const FileMetaInformation &header = GetFile().GetHeader();
    return ToStringPair(t, header);
    }
  else
    {
    const DataSet &ds = GetFile().GetDataSet();
    return ToStringPair(t, ds);
    }
}

std::pair<std::string, std::string> StringFilter::ToStringPair(const DataElement& de) const
{
  const Tag & t = de.GetTag();
  if( t.GetGroup() == 0x2 )
    {
    const FileMetaInformation &header = GetFile().GetHeader();
    return ToStringPairInternal(de, header);
    }
  else
    {
    const DataSet &ds = GetFile().GetDataSet();
    return ToStringPairInternal(de, ds);
    }
}

bool StringFilter::ExecuteQuery(std::string const & query_const, std::string &value ) const
{
//  if( t.GetGroup() == 0x2 )
//    {
//    const FileMetaInformation &header = GetFile().GetHeader();
//    return ToStringPair(query_const, header);
//    }
//  else
    {
    const DataSet &ds = GetFile().GetDataSet();
    return ExecuteQuery(query_const, ds, value);
    }
}

bool StringFilter::ExecuteQuery(std::string const & query_const,
  DataSet const &ds, std::string &retvalue ) const
{
  //std::pair<std::string, std::string> ret;
  static Global &g = Global::GetInstance();
  static const Dicts &dicts = g.GetDicts();
  static const Dict &pubdict = dicts.GetPublicDict();

  char *query = strdup( query_const.c_str() );
  const char delim[] = "/";
  const char subdelim[] = "[]@='";

  char *str1, *str2, *token, *subtoken;
  char *saveptr1= nullptr, *saveptr2;

  //bool dicomnativemodel = false;//unused
  const DataSet *curds = nullptr;
  const DataElement *curde = nullptr;
  Tag t;
  int state = 0;
  SmartPointer<SequenceOfItems> sqi;
  for (str1 = query; state >= 0 ; str1 = nullptr)
    {
    token = System::StrTokR(str1, delim, &saveptr1);

    if (token == nullptr)
      break;

    std::vector< std::string > subtokens;
    for (str2 = token; ; str2 = nullptr)
      {
      subtoken = System::StrTokR(str2, subdelim, &saveptr2);
      if (subtoken == nullptr)
        break;
      //printf(" --> %s\n", subtoken);
      subtokens.emplace_back(subtoken );
      }
    if( subtokens[0] == "DicomNativeModel" )
      {
      // move to next state
      gdcm_assert( state == 0 );
      state = 1;
      curds = &ds;
      }
    else if( subtokens[0] == "DicomAttribute" )
      {
      if( state != 1 )
        {
        state = -1;
        break;
        }
      gdcm_assert( subtokens[1] == "keyword" );
      const char *k = subtokens[2].c_str();
      /*const DictEntry &dictentry = */pubdict.GetDictEntryByKeyword(k, t);
      if( !curds->FindDataElement( t ) )
        {
        state = -1;
        break;
        }
      curde = &curds->GetDataElement( t );
      }
    else if( subtokens[0] == "Item" )
      {
      gdcm_assert( state == 1 );
      gdcm_assert( curde );
      gdcm_assert( subtokens[1] == "number" );
      sqi = curde->GetValueAsSQ();
      if( !sqi )
        {
        state = -1;
        break;
        }
      Item const &item = sqi->GetItem( atoi( subtokens[2].c_str() ) );
      curds = &item.GetNestedDataSet();
      }
    else if( subtokens[0] == "Value" )
      {
      gdcm_assert( state == 1 );
      // move to next state
      state = 2;
      gdcm_assert( subtokens[1] == "number" );
#if !defined(NDEBUG)
      const ByteValue * const bv = curde->GetByteValue(); (void)bv;
      gdcm_assert( bv );
      //bv->Print( std::cout << std::endl );
#endif
      }
    else
      {
      gdcm_assert( !subtokens.empty() );
      gdcmDebugMacro( "Unhandled token: " << subtokens[0] );
      state = -1;
      }
    }
  if( state != 2 )
    {
    free( query );
    return false;
    }
  free( query );

  const DataElement &de = *curde;

  const DictEntry &entry = pubdict.GetDictEntry(de.GetTag());

  const VR &vr_read = de.GetVR();
  const VR &vr_dict = entry.GetVR();

  if( vr_dict == VR::INVALID )
    {
    // FIXME This is a public element we do not support...
    return false;
    }

  VR vr;
  // always prefer the vr from the file:
  if( vr_read == VR::INVALID )
    {
    vr = vr_dict;
    }
  else if ( vr_read == VR::UN && vr_dict != VR::INVALID ) // File is explicit, but still prefer vr from dict when UN
    {
    vr = vr_dict;
    }
  else // cool the file is Explicit !
    {
    vr = vr_read;
    }
  if( vr.IsDual() ) // This mean vr was read from a dict entry:
    {
    vr = DataSetHelper::ComputeVR(*F,ds, t);
    }

  if( vr == VR::UN )
    {
    // this element is not known...
    return false;
    }

  gdcm_assert( vr != VR::UN && vr != VR::INVALID );
  //ret.first = entry.GetName();
  if( VR::IsASCII( vr ) )
    {
    gdcm_assert( vr & VR::VRASCII );
    const ByteValue *bv = de.GetByteValue();
    if( de.GetVL() )
      {
      gdcm_assert( bv /*|| bv->IsEmpty()*/ );
      retvalue = std::string( bv->GetPointer(), bv->GetLength() );
      // Let's remove any trailing \0 :
      retvalue.resize( std::min( retvalue.size(), strlen( retvalue.c_str() ) ) ); // strlen is guarantee to be lower or equal to ::size()
      }
    else
      {
      //gdcm_assert( bv == NULL );
      retvalue = ""; // ??
      }
    }
  else
    {
    gdcm_assert( vr & VR::VRBINARY );
    const ByteValue *bv = de.GetByteValue();
    if( bv )
      {
      //VM::VMType vm = entry.GetVM();//!!mmr-- can I remove this, or will it mess with the stream?
      //gdcm_assert( vm == VM::VM1 );
      if( vr.IsDual() ) // This mean vr was read from a dict entry:
        {
        vr = DataSetHelper::ComputeVR(GetFile(),ds, t);
        }
      std::ostringstream os;
      switch(vr)
        {
        StringFilterCase(AT);
        StringFilterCase(FL);
        StringFilterCase(FD);
        //StringFilterCase(OB);
        StringFilterCase(OF);
        //StringFilterCase(OW);
        StringFilterCase(SL);
        //StringFilterCase(SQ);
        StringFilterCase(SS);
        StringFilterCase(UL);
        //StringFilterCase(UN);
        StringFilterCase(US);
        StringFilterCase(UT);
      case VR::UN:
      case VR::US_SS:
        gdcm_assert(0);
        break;
      case VR::OB:
      case VR::OW:
      case VR::OB_OW:
      case VR::SQ:
        gdcmWarningMacro( "Unhandled: " << vr << " for tag " << de.GetTag() );
        retvalue = "";
        break;
      default:
        gdcm_assert(0);
        break;
        }
      }
    }
  return true;
}

std::pair<std::string, std::string> StringFilter::ToStringPair(const Tag& t, DataSet const &ds) const
{
  std::pair<std::string, std::string> ret;
  if( !ds.FindDataElement(t) )
    {
    gdcmDebugMacro( "DataSet does not contains tag:" );
    return ret;
    }
  const DataElement &de = ds.GetDataElement( t );
  ret = ToStringPairInternal( de, ds );
  return ret;
}

std::pair<std::string, std::string> StringFilter::ToStringPairInternal(const DataElement& de, DataSet const &ds) const
{
  std::pair<std::string, std::string> ret;
  const Global &g = GlobalInstance;
  const Dicts &dicts = g.GetDicts();
  if( ds.IsEmpty() )
    {
    gdcmDebugMacro( "DataSet is empty or does not contains tag:" );
    return ret;
    }
  //gdcm_assert( de.GetTag().IsPublic() );
  std::string strowner;
  const char *owner = nullptr;
  const Tag &t = de.GetTag();
  if( t.IsPrivate() && !t.IsPrivateCreator() )
    {
    strowner = ds.GetPrivateCreator(t);
    owner = strowner.c_str();
    }

  const DictEntry &entry = dicts.GetDictEntry(de.GetTag(), owner);

  const VR &vr_read = de.GetVR();
  const VR &vr_dict = entry.GetVR();

  VR vr;
  // always prefer the vr from the file:
  if( vr_read == VR::INVALID && vr_dict != VR::INVALID )
    {
    vr = vr_dict;
    }
  else if ( vr_read == VR::UN && vr_dict != VR::INVALID ) // File is explicit, but still prefer vr from dict when UN
    {
    vr = vr_dict;
    }
  else // cool the file is Explicit !
    {
    vr = vr_read;
    }
  if( vr == VR::INVALID )
    {
    // FIXME This is a public element we do not support...
    gdcmDebugMacro( "DataElement does not specify the VR." );
    return ret;
    }

   if( vr.IsDual() ) // This mean vr was read from a dict entry:
    {
    vr = DataSetHelper::ComputeVR(*F,ds, t);
    }

  if( vr == VR::UN )
    {
    // this element is not known...
    return ret;
    }

  gdcm_assert( vr != VR::UN && vr != VR::INVALID );
  //std::cerr << "Found " << vr << " for " << de.GetTag() << std::endl;
  ret.first = entry.GetName();
  if( VR::IsASCII( vr ) )
    {
    gdcm_assert( vr & VR::VRASCII );
    const ByteValue *bv = de.GetByteValue();
    if( de.GetVL() )
      {
      gdcm_assert( bv /*|| bv->IsEmpty()*/ );
      ret.second = std::string( bv->GetPointer(), bv->GetLength() );
      // Let's remove any trailing \0 :
      ret.second.resize( std::min( ret.second.size(), strlen( ret.second.c_str() ) ) ); // strlen is guarantee to be lower or equal to ::size()
      }
    else
      {
      //gdcm_assert( bv == NULL );
      ret.second = ""; // ??
      }
    }
  else
    {
    gdcm_assert( vr & VR::VRBINARY );
    const ByteValue *bv = de.GetByteValue();
    if( bv )
      {
      //VM::VMType vm = entry.GetVM();//!!mmr-- can I remove this, or will it mess with the stream?
      //gdcm_assert( vm == VM::VM1 );
      if( vr.IsDual() ) // This mean vr was read from a dict entry:
        {
        vr = DataSetHelper::ComputeVR(GetFile(),ds, t);
        }
      std::ostringstream os;
      std::string retvalue;
      switch(vr)
        {
        StringFilterCase(AT);
        StringFilterCase(FL);
        StringFilterCase(FD);
        //StringFilterCase(OB);
        StringFilterCase(OF);
        //StringFilterCase(OW);
        StringFilterCase(SL);
        //StringFilterCase(SQ);
        StringFilterCase(SS);
        StringFilterCase(UL);
        //StringFilterCase(UN);
        StringFilterCase(US);
        StringFilterCase(UT);
      case VR::UN:
      case VR::US_SS:
        gdcm_assert(0);
        break;
      case VR::OB:
      case VR::OW:
      case VR::OB_OW:
      case VR::SQ:
        gdcmWarningMacro( "Unhandled: " << vr << " for tag " << de.GetTag() );
        ret.second = "";
        break;
      default:
        gdcm_assert(0);
        break;
        }
        ret.second = retvalue;
      }
    }
  return ret;
}

#define FromStringFilterCase(type) \
  case VR::type: \
      { \
      Element<VR::type,VM::VM1_n> el; \
      /* el.ReadComputeLength( is ); */ \
      el.SetLength( vl );  \
       for(unsigned int i = 0; i < vm.GetLength(); ++i)  \
        { \
        if(i) is.get(); \
        is >> el.GetValue(i);  \
        } \
      el.Write(os); \
      } \
    break

#if 0
static inline size_t count_backslash(const char *s, size_t len)
{
  gdcm_assert( s );
  size_t c = 0;
  for(size_t i = 0; i < len; ++i, ++s)
    {
    if( *s == '\\' )
      {
      ++c;
      }
    }
  return c;
}
#endif

std::string StringFilter::FromString(const Tag&t, const char * value, size_t len)
{
  if( !value || !len ) return "";
  const Global &g = GlobalInstance;
  const Dicts &dicts = g.GetDicts();
  std::string strowner;
  const char *owner = nullptr;
  const DataSet &ds = GetFile().GetDataSet();
  if( t.IsPrivate() && !t.IsPrivateCreator() )
    {
    strowner = ds.GetPrivateCreator(t);
    owner = strowner.c_str();
    }

  const DictEntry &entry = dicts.GetDictEntry(t, owner);
  const VM &vm = entry.GetVM();
  //const VR &vr = entry.GetVR();
  const DataElement &de = ds.GetDataElement( t );
  const VR &vr_read = de.GetVR();
  const VR &vr_dict = entry.GetVR();

  VR vr;
  // always prefer the vr from the file:
  if( vr_read == VR::INVALID )
    {
    vr = vr_dict;
    }
  else if ( vr_read == VR::UN && vr_dict != VR::INVALID ) // File is explicit, but still prefer vr from dict when UN
    {
    vr = vr_dict;
    }
  else // cool the file is Explicit !
    {
    vr = vr_read;
    }
  if( vr.IsDual() ) // This mean vr was read from a dict entry:
    {
    vr = DataSetHelper::ComputeVR(*F,ds, t);
    }

  if( vr == VR::UN )
    {
    // this element is not known...
    //return ret;
    }

  std::string s(value,value+len);
  if( VR::IsASCII( vr ) )
    {
    return s;
    }
  VL::Type castLen = (VL::Type)len;
  size_t count = VM::GetNumberOfElementsFromArray(value, castLen);
  VL vl = vm.GetLength() * vr.GetSizeof();
  if( vm.GetLength() == 0 )
    {
    // VM1_n
    vl = (VL)( (uint32_t)count * vr.GetSizeof());
#if !defined(NDEBUG)
    VM check = VM::GetVMTypeFromLength(count, 1);
    if( vm != VM::VM0 )
    gdcm_assert( vm.Compatible( check ) );
#endif
    }

  std::istringstream is;
  is.str( s );
  std::ostringstream os;
  switch(vr)
    {
    FromStringFilterCase(AT);
    FromStringFilterCase(FL);
    FromStringFilterCase(FD);
    //FromStringFilterCase(OB);
    FromStringFilterCase(OF);
    //FromStringFilterCase(OW);
    FromStringFilterCase(SL);
    //FromStringFilterCase(SQ);
    FromStringFilterCase(SS);
    FromStringFilterCase(UL);
    //FromStringFilterCase(UN);
    FromStringFilterCase(US);
  default:
    gdcmErrorMacro( "Not implemented" );
    gdcm_assert(0);
    }
  return os.str();
}

} // end namespace gdcm
