/**
 * Orthanc - A Lightweight, RESTful DICOM Store
 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 * Department, University Hospital of Liege, Belgium
 * Copyright (C) 2017-2023 Osimis S.A., Belgium
 * Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
 * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
 *
 * This program is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 **/


#include "OrthancPluginCppWrapper.h"

#include <cassert>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/move/unique_ptr.hpp>
#include <boost/thread.hpp>
#include <boost/algorithm/string/join.hpp>
#include <limits>

#include <json/reader.h>
#include <json/version.h>
#include <json/writer.h>

#if !defined(JSONCPP_VERSION_MAJOR) || !defined(JSONCPP_VERSION_MINOR)
#  error Cannot access the version of JsonCpp
#endif


/**
 * We use deprecated "Json::Reader", "Json::StyledWriter" and
 * "Json::FastWriter" if JsonCpp < 1.7.0. This choice is rather
 * arbitrary, but if Json >= 1.9.0, gcc generates explicit deprecation
 * warnings (clang was warning in earlier versions). For reference,
 * these classes seem to have been deprecated since JsonCpp 1.4.0 (on
 * February 2015) by the following changeset:
 * https://github.com/open-source-parsers/jsoncpp/commit/8df98f6112890d6272734975dd6d70cf8999bb22
 **/
#if (JSONCPP_VERSION_MAJOR >= 2 ||                                      \
     (JSONCPP_VERSION_MAJOR == 1 && JSONCPP_VERSION_MINOR >= 8))
#  define JSONCPP_USE_DEPRECATED 0
#else
#  define JSONCPP_USE_DEPRECATED 1
#endif


#if !ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0)
static const OrthancPluginErrorCode OrthancPluginErrorCode_NullPointer = OrthancPluginErrorCode_Plugin;
#endif


namespace OrthancPlugins
{
  static OrthancPluginContext* globalContext_ = NULL;
  static std::string pluginName_;


  void SetGlobalContext(OrthancPluginContext* context)
  {
    if (context == NULL)
    {
      ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer);
    }
    else if (globalContext_ == NULL)
    {
      globalContext_ = context;
    }
    else
    {
      ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
    }
  }


  void SetGlobalContext(OrthancPluginContext* context,
                        const char* pluginName)
  {
    SetGlobalContext(context);
    pluginName_ = pluginName;
  }


  void ResetGlobalContext()
  {
    globalContext_ = NULL;
    pluginName_.clear();
  }

  bool HasGlobalContext()
  {
    return globalContext_ != NULL;
  }


  OrthancPluginContext* GetGlobalContext()
  {
    if (globalContext_ == NULL)
    {
      ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
    }
    else
    {
      return globalContext_;
    }
  }


#if HAS_ORTHANC_PLUGIN_LOG_MESSAGE == 1
  void LogMessage(OrthancPluginLogLevel level,
                  const char* file,
                  uint32_t line,
                  const std::string& message)
  {
    if (HasGlobalContext())
    {
#if HAS_ORTHANC_PLUGIN_LOG_MESSAGE == 1
      const char* pluginName = (pluginName_.empty() ? NULL : pluginName_.c_str());
      OrthancPluginLogMessage(GetGlobalContext(), message.c_str(), pluginName, file, line, OrthancPluginLogCategory_Generic, level);
#else
      switch (level)
      {
        case OrthancPluginLogLevel_Error:
          OrthancPluginLogError(GetGlobalContext(), message.c_str());
          break;

        case OrthancPluginLogLevel_Warning:
          OrthancPluginLogWarning(GetGlobalContext(), message.c_str());
          break;

        case OrthancPluginLogLevel_Info:
          OrthancPluginLogInfo(GetGlobalContext(), message.c_str());
          break;

        default:
          ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
      }
#endif
    }
  }
#endif


  void LogError(const std::string& message)
  {
    if (HasGlobalContext())
    {
      OrthancPluginLogError(GetGlobalContext(), message.c_str());
    }
  }

  void LogWarning(const std::string& message)
  {
    if (HasGlobalContext())
    {
      OrthancPluginLogWarning(GetGlobalContext(), message.c_str());
    }
  }

  void LogInfo(const std::string& message)
  {
    if (HasGlobalContext())
    {
      OrthancPluginLogInfo(GetGlobalContext(), message.c_str());
    }
  }


  void MemoryBuffer::Check(OrthancPluginErrorCode code)
  {
    if (code != OrthancPluginErrorCode_Success)
    {
      // Prevent using garbage information
      buffer_.data = NULL;
      buffer_.size = 0;
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
    }
  }


  bool MemoryBuffer::CheckHttp(OrthancPluginErrorCode code)
  {
    if (code != OrthancPluginErrorCode_Success)
    {
      // Prevent using garbage information
      buffer_.data = NULL;
      buffer_.size = 0;
    }

    if (code == OrthancPluginErrorCode_Success)
    {
      return true;
    }
    else if (code == OrthancPluginErrorCode_UnknownResource ||
             code == OrthancPluginErrorCode_InexistentItem)
    {
      return false;
    }
    else
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
    }
  }


  MemoryBuffer::MemoryBuffer()
  {
    buffer_.data = NULL;
    buffer_.size = 0;
  }


  MemoryBuffer::~MemoryBuffer()
  {
    try
    {
      Clear();
    }
    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS&) // NOLINT(bugprone-empty-catch)
    {
      // Don't throw exceptions in destructors
    }
  }


  void MemoryBuffer::Clear()
  {
    if (buffer_.data != NULL)
    {
      OrthancPluginFreeMemoryBuffer(GetGlobalContext(), &buffer_);
      buffer_.data = NULL;
      buffer_.size = 0;
    }
  }


#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
  void MemoryBuffer::Assign(const void* buffer,
                            size_t size)
  {
    uint32_t s = static_cast<uint32_t>(size);
    if (static_cast<size_t>(s) != size)
    {
      ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
    }

    Clear();

    if (OrthancPluginCreateMemoryBuffer(GetGlobalContext(), &buffer_, s) !=
        OrthancPluginErrorCode_Success)
    {
      ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
    }
    else
    {
      if (buffer != NULL && size > 0)
      {
        memcpy(buffer_.data, buffer, size);
      }
    }
  }
#endif

#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
  void MemoryBuffer::Assign(const std::string& s)
  {
    Assign(s.empty() ? NULL : s.c_str(), s.size());
  }
#endif


#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
  void MemoryBuffer::AssignJson(const Json::Value& value)
  {
    std::string s;
    WriteFastJson(s, value);
    Assign(s);
  }
#endif


  void MemoryBuffer::Assign(OrthancPluginMemoryBuffer& other)
  {
    Clear();

    buffer_.data = other.data;
    buffer_.size = other.size;

    other.data = NULL;
    other.size = 0;
  }


  void MemoryBuffer::Swap(MemoryBuffer& other)
  {
    std::swap(buffer_.data, other.buffer_.data);
    std::swap(buffer_.size, other.buffer_.size);
  }


  OrthancPluginMemoryBuffer MemoryBuffer::Release()
  {
    OrthancPluginMemoryBuffer result = buffer_;

    buffer_.data = NULL;
    buffer_.size = 0;

    return result;
  }


  void MemoryBuffer::ToString(std::string& target) const
  {
    if (buffer_.size == 0)
    {
      target.clear();
    }
    else
    {
      target.assign(reinterpret_cast<const char*>(buffer_.data), buffer_.size);
    }
  }


  void MemoryBuffer::ToJson(Json::Value& target) const
  {
    if (buffer_.data == NULL ||
        buffer_.size == 0)
    {
      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
    }

    if (!ReadJson(target, buffer_.data, buffer_.size))
    {
      ORTHANC_PLUGINS_LOG_ERROR("Cannot convert some memory buffer to JSON");
      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
    }
  }


  bool MemoryBuffer::RestApiGet(const std::string& uri,
                                bool applyPlugins)
  {
    Clear();

    if (applyPlugins)
    {
      return CheckHttp(OrthancPluginRestApiGetAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str()));
    }
    else
    {
      return CheckHttp(OrthancPluginRestApiGet(GetGlobalContext(), &buffer_, uri.c_str()));
    }
  }


#if (HAS_ORTHANC_PLUGIN_PEERS == 1) || (HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1) || (HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1)
  static void DecodeHttpHeaders(HttpHeaders& target,
                                const MemoryBuffer& source)
  {
    Json::Value v;
    source.ToJson(v);

    if (v.type() != Json::objectValue)
    {
      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
    }

    Json::Value::Members members = v.getMemberNames();
    target.clear();

    for (size_t i = 0; i < members.size(); i++)
    {
      const Json::Value& h = v[members[i]];
      if (h.type() != Json::stringValue)
      {
        ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
      }
      else
      {
        target[members[i]] = h.asString();
      }
    }
  }
#endif


  // helper class to convert std::map of headers to the plugin SDK C structure
  class PluginHttpHeaders
  {
  private:
    std::vector<const char*> headersKeys_;
    std::vector<const char*> headersValues_;

  public:
    explicit PluginHttpHeaders(const HttpHeaders& httpHeaders)
    {
      for (HttpHeaders::const_iterator
             it = httpHeaders.begin(); it != httpHeaders.end(); ++it)
      {
        headersKeys_.push_back(it->first.c_str());
        headersValues_.push_back(it->second.c_str());
      }      
    }

    const char* const* GetKeys()
    {
      return (headersKeys_.empty() ? NULL : &headersKeys_[0]);
    }

    const char* const* GetValues()
    {
      return (headersValues_.empty() ? NULL : &headersValues_[0]);
    }

    uint32_t GetSize()
    {
      return static_cast<uint32_t>(headersKeys_.size());
    }
  };

  bool MemoryBuffer::RestApiGet(const std::string& uri,
                                const HttpHeaders& httpHeaders,
                                bool applyPlugins)
  {
    Clear();

    PluginHttpHeaders headers(httpHeaders);

    return CheckHttp(OrthancPluginRestApiGet2(
                       GetGlobalContext(), &buffer_, uri.c_str(), 
                       headers.GetSize(),
                       headers.GetKeys(),
                       headers.GetValues(), applyPlugins));
  }

  bool MemoryBuffer::RestApiPost(const std::string& uri,
                                 const void* body,
                                 size_t bodySize,
                                 bool applyPlugins)
  {
    Clear();
    
    // Cast for compatibility with Orthanc SDK <= 1.5.6
    const char* b = reinterpret_cast<const char*>(body);

    if (applyPlugins)
    {
      return CheckHttp(OrthancPluginRestApiPostAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
    }
    else
    {
      return CheckHttp(OrthancPluginRestApiPost(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
    }
  }

#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1

  bool MemoryBuffer::RestApiPost(const std::string& uri,
                                 const void* body,
                                 size_t bodySize,
                                 const HttpHeaders& httpHeaders,
                                 bool applyPlugins)
  {
    MemoryBuffer answerHeaders;
    uint16_t httpStatus;

    PluginHttpHeaders headers(httpHeaders);

    return CheckHttp(OrthancPluginCallRestApi(GetGlobalContext(), 
                                              &buffer_,
                                              *answerHeaders,
                                              &httpStatus,
                                              OrthancPluginHttpMethod_Post,
                                              uri.c_str(),
                                              headers.GetSize(), headers.GetKeys(), headers.GetValues(),
                                              body, bodySize,
                                              applyPlugins));
  }


  bool MemoryBuffer::RestApiPost(const std::string& uri,
                                 const Json::Value& body,
                                 const HttpHeaders& httpHeaders,
                                 bool applyPlugins)
  {
    std::string s;
    WriteFastJson(s, body);
    return RestApiPost(uri, s.c_str(), s.size(), httpHeaders, applyPlugins);
  }
#endif

  bool MemoryBuffer::RestApiPut(const std::string& uri,
                                const void* body,
                                size_t bodySize,
                                bool applyPlugins)
  {
    Clear();

    // Cast for compatibility with Orthanc SDK <= 1.5.6
    const char* b = reinterpret_cast<const char*>(body);

    if (applyPlugins)
    {
      return CheckHttp(OrthancPluginRestApiPutAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
    }
    else
    {
      return CheckHttp(OrthancPluginRestApiPut(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize));
    }
  }


  static bool ReadJsonInternal(Json::Value& target,
                               const void* buffer,
                               size_t size,
                               bool collectComments)
  {
#if JSONCPP_USE_DEPRECATED == 1
    Json::Reader reader;
    return reader.parse(reinterpret_cast<const char*>(buffer),
                        reinterpret_cast<const char*>(buffer) + size, target, collectComments);
#else
    Json::CharReaderBuilder builder;
    builder.settings_["collectComments"] = collectComments;
    
    const std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
    assert(reader.get() != NULL);
    
    JSONCPP_STRING err;
    if (reader->parse(reinterpret_cast<const char*>(buffer),
                      reinterpret_cast<const char*>(buffer) + size, &target, &err))
    {
      return true;
    }
    else
    {
      ORTHANC_PLUGINS_LOG_ERROR("Cannot parse JSON: " + std::string(err));
      return false;
    }
#endif
  }


  bool ReadJson(Json::Value& target,
                const std::string& source)
  {
    return ReadJson(target, source.empty() ? NULL : source.c_str(), source.size());
  }
  

  bool ReadJson(Json::Value& target,
                const void* buffer,
                size_t size)
  {
    return ReadJsonInternal(target, buffer, size, true);
  }
  

  bool ReadJsonWithoutComments(Json::Value& target,
                               const std::string& source)
  {
    return ReadJsonWithoutComments(target, source.empty() ? NULL : source.c_str(), source.size());
  }
  

  bool ReadJsonWithoutComments(Json::Value& target,
                               const void* buffer,
                               size_t size)
  {
    return ReadJsonInternal(target, buffer, size, false);
  }


  void WriteFastJson(std::string& target,
                     const Json::Value& source)
  {
#if JSONCPP_USE_DEPRECATED == 1
    Json::FastWriter writer;
    target = writer.write(source);
#else
    Json::StreamWriterBuilder builder;
    builder.settings_["indentation"] = "";
    target = Json::writeString(builder, source);
#endif
  }
  

  void WriteStyledJson(std::string& target,
                       const Json::Value& source)
  {
#if JSONCPP_USE_DEPRECATED == 1
    Json::StyledWriter writer;
    target = writer.write(source);
#else
    Json::StreamWriterBuilder builder;
    builder.settings_["indentation"] = "   ";
    target = Json::writeString(builder, source);
#endif
  }


  bool MemoryBuffer::RestApiPost(const std::string& uri,
                                 const Json::Value& body,
                                 bool applyPlugins)
  {
    std::string s;
    WriteFastJson(s, body);
    return RestApiPost(uri, s, applyPlugins);
  }


  bool MemoryBuffer::RestApiPut(const std::string& uri,
                                const Json::Value& body,
                                bool applyPlugins)
  {
    std::string s;
    WriteFastJson(s, body);
    return RestApiPut(uri, s, applyPlugins);
  }


  void MemoryBuffer::CreateDicom(const Json::Value& tags,
                                 OrthancPluginCreateDicomFlags flags)
  {
    Clear();

    std::string s;
    WriteFastJson(s, tags);

    Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), NULL, flags));
  }

  void MemoryBuffer::CreateDicom(const Json::Value& tags,
                                 const OrthancImage& pixelData,
                                 OrthancPluginCreateDicomFlags flags)
  {
    Clear();

    std::string s;
    WriteFastJson(s, tags);

    Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), pixelData.GetObject(), flags));
  }


  void MemoryBuffer::ReadFile(const std::string& path)
  {
    Clear();
    Check(OrthancPluginReadFile(GetGlobalContext(), &buffer_, path.c_str()));
  }


  void MemoryBuffer::GetDicomQuery(const OrthancPluginWorklistQuery* query)
  {
    Clear();
    Check(OrthancPluginWorklistGetDicomQuery(GetGlobalContext(), &buffer_, query));
  }


  OrthancString::~OrthancString()
  {
    try
    {
      Clear();
    }
    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS&) // NOLINT(bugprone-empty-catch)
    {
      // Don't throw exceptions in destructors
    }
  }


  void OrthancString::Assign(char* str)
  {
    Clear();

    if (str != NULL)
    {
      str_ = str;
    }
  }


  void OrthancString::Clear()
  {
    if (str_ != NULL)
    {
      OrthancPluginFreeString(GetGlobalContext(), str_);
      str_ = NULL;
    }
  }


  void OrthancString::ToString(std::string& target) const
  {
    if (str_ == NULL)
    {
      target.clear();
    }
    else
    {
      target.assign(str_);
    }
  }


  void OrthancString::ToJson(Json::Value& target) const
  {
    if (str_ == NULL)
    {
      ORTHANC_PLUGINS_LOG_ERROR("Cannot convert an empty memory buffer to JSON");
      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
    }

    if (!ReadJson(target, str_))
    {
      ORTHANC_PLUGINS_LOG_ERROR("Cannot convert some memory buffer to JSON");
      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
    }
  }


  void OrthancString::ToJsonWithoutComments(Json::Value& target) const
  {
    if (str_ == NULL)
    {
      ORTHANC_PLUGINS_LOG_ERROR("Cannot convert an empty memory buffer to JSON");
      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
    }

    if (!ReadJsonWithoutComments(target, str_))
    {
      ORTHANC_PLUGINS_LOG_ERROR("Cannot convert some memory buffer to JSON");
      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
    }
  }


  void MemoryBuffer::DicomToJson(Json::Value& target,
                                 OrthancPluginDicomToJsonFormat format,
                                 OrthancPluginDicomToJsonFlags flags,
                                 uint32_t maxStringLength)
  {
    OrthancString str;
    str.Assign(OrthancPluginDicomBufferToJson
               (GetGlobalContext(), reinterpret_cast<const char*>(GetData()), GetSize(), format, flags, maxStringLength));
    str.ToJson(target);
  }


  bool MemoryBuffer::HttpGet(const std::string& url,
                             const std::string& username,
                             const std::string& password)
  {
    Clear();
    return CheckHttp(OrthancPluginHttpGet(GetGlobalContext(), &buffer_, url.c_str(),
                                          username.empty() ? NULL : username.c_str(),
                                          password.empty() ? NULL : password.c_str()));
  }


  bool MemoryBuffer::HttpPost(const std::string& url,
                              const std::string& body,
                              const std::string& username,
                              const std::string& password)
  {
    Clear();

    if (body.size() > 0xffffffffu)
    {
      ORTHANC_PLUGINS_LOG_ERROR("Cannot handle body size > 4GB");
      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
    }

    return CheckHttp(OrthancPluginHttpPost(GetGlobalContext(), &buffer_, url.c_str(),
                                           body.c_str(), body.size(),
                                           username.empty() ? NULL : username.c_str(),
                                           password.empty() ? NULL : password.c_str()));
  }


  bool MemoryBuffer::HttpPut(const std::string& url,
                             const std::string& body,
                             const std::string& username,
                             const std::string& password)
  {
    Clear();

    if (body.size() > 0xffffffffu)
    {
      ORTHANC_PLUGINS_LOG_ERROR("Cannot handle body size > 4GB");
      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
    }

    return CheckHttp(OrthancPluginHttpPut(GetGlobalContext(), &buffer_, url.c_str(),
                                          body.empty() ? NULL : body.c_str(),
                                          body.size(),
                                          username.empty() ? NULL : username.c_str(),
                                          password.empty() ? NULL : password.c_str()));
  }


  void MemoryBuffer::GetDicomInstance(const std::string& instanceId)
  {
    Clear();
    Check(OrthancPluginGetDicomForInstance(GetGlobalContext(), &buffer_, instanceId.c_str()));
  }


  bool HttpDelete(const std::string& url,
                  const std::string& username,
                  const std::string& password)
  {
    OrthancPluginErrorCode error = OrthancPluginHttpDelete
      (GetGlobalContext(), url.c_str(),
       username.empty() ? NULL : username.c_str(),
       password.empty() ? NULL : password.c_str());

    if (error == OrthancPluginErrorCode_Success)
    {
      return true;
    }
    else if (error == OrthancPluginErrorCode_UnknownResource ||
             error == OrthancPluginErrorCode_InexistentItem)
    {
      return false;
    }
    else
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
    }
  }

  void OrthancConfiguration::LoadConfiguration()
  {
    OrthancString str;
    str.Assign(OrthancPluginGetConfiguration(GetGlobalContext()));

    if (str.GetContent() == NULL)
    {
      ORTHANC_PLUGINS_LOG_ERROR("Cannot access the Orthanc configuration");
      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
    }

    str.ToJsonWithoutComments(configuration_);

    if (configuration_.type() != Json::objectValue)
    {
      ORTHANC_PLUGINS_LOG_ERROR("Unable to read the Orthanc configuration");
      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
    }
  }
    

  OrthancConfiguration::OrthancConfiguration()
  {
    LoadConfiguration();
  }


  OrthancConfiguration::OrthancConfiguration(bool loadConfiguration)
  {
    if (loadConfiguration)
    {
      LoadConfiguration();
    }
    else
    {
      configuration_ = Json::objectValue;
    }
  }

  OrthancConfiguration::OrthancConfiguration(const Json::Value& configuration, const std::string& path) :
    configuration_(configuration),
    path_(path)
  {
  }


  std::string OrthancConfiguration::GetPath(const std::string& key) const
  {
    if (path_.empty())
    {
      return key;
    }
    else
    {
      return path_ + "." + key;
    }
  }


  bool OrthancConfiguration::IsSection(const std::string& key) const
  {
    assert(configuration_.type() == Json::objectValue);

    return (configuration_.isMember(key) &&
            configuration_[key].type() == Json::objectValue);
  }


  void OrthancConfiguration::GetSection(OrthancConfiguration& target,
                                        const std::string& key) const
  {
    assert(configuration_.type() == Json::objectValue);

    target.path_ = GetPath(key);

    if (!configuration_.isMember(key))
    {
      target.configuration_ = Json::objectValue;
    }
    else
    {
      if (configuration_[key].type() != Json::objectValue)
      {
        ORTHANC_PLUGINS_LOG_ERROR("The configuration section \"" + target.path_ +
                                  "\" is not an associative array as expected");

        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
      }

      target.configuration_ = configuration_[key];
    }
  }


  bool OrthancConfiguration::LookupStringValue(std::string& target,
                                               const std::string& key) const
  {
    assert(configuration_.type() == Json::objectValue);

    if (!configuration_.isMember(key))
    {
      return false;
    }

    if (configuration_[key].type() != Json::stringValue)
    {
      ORTHANC_PLUGINS_LOG_ERROR("The configuration option \"" + GetPath(key) +
                                "\" is not a string as expected");

      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
    }

    target = configuration_[key].asString();
    return true;
  }


  bool OrthancConfiguration::LookupIntegerValue(int& target,
                                                const std::string& key) const
  {
    assert(configuration_.type() == Json::objectValue);

    if (!configuration_.isMember(key))
    {
      return false;
    }

    switch (configuration_[key].type())
    {
      case Json::intValue:
        target = configuration_[key].asInt();
        return true;

      case Json::uintValue:
        if (configuration_[key].asUInt() > static_cast<unsigned int>(std::numeric_limits<int>::max()))
        {
          ORTHANC_PLUGINS_LOG_ERROR("The configuration option \"" + GetPath(key) +
                                    "\" is too large to fit in an integer");

          ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);          
        }

        target = static_cast<int>(configuration_[key].asUInt());
        return true;

      default:
        ORTHANC_PLUGINS_LOG_ERROR("The configuration option \"" + GetPath(key) +
                                  "\" is not an integer as expected");

        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
    }
  }


  bool OrthancConfiguration::LookupUnsignedIntegerValue(unsigned int& target,
                                                        const std::string& key) const
  {
    int tmp;
    if (!LookupIntegerValue(tmp, key))
    {
      return false;
    }

    if (tmp < 0)
    {
      ORTHANC_PLUGINS_LOG_ERROR("The configuration option \"" + GetPath(key) +
                                "\" is not a positive integer as expected");

      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
    }
    else
    {
      target = static_cast<unsigned int>(tmp);
      return true;
    }
  }


  bool OrthancConfiguration::LookupBooleanValue(bool& target,
                                                const std::string& key) const
  {
    assert(configuration_.type() == Json::objectValue);

    if (!configuration_.isMember(key))
    {
      return false;
    }

    if (configuration_[key].type() != Json::booleanValue)
    {
      ORTHANC_PLUGINS_LOG_ERROR("The configuration option \"" + GetPath(key) +
                                "\" is not a Boolean as expected");

      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
    }

    target = configuration_[key].asBool();
    return true;
  }


  bool OrthancConfiguration::LookupFloatValue(float& target,
                                              const std::string& key) const
  {
    assert(configuration_.type() == Json::objectValue);

    if (!configuration_.isMember(key))
    {
      return false;
    }

    switch (configuration_[key].type())
    {
      case Json::realValue:
        target = configuration_[key].asFloat();
        return true;

      case Json::intValue:
        target = static_cast<float>(configuration_[key].asInt());
        return true;

      case Json::uintValue:
        target = static_cast<float>(configuration_[key].asUInt());
        return true;

      default:
        ORTHANC_PLUGINS_LOG_ERROR("The configuration option \"" + GetPath(key) +
                                  "\" is not an integer as expected");

        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
    }
  }


  bool OrthancConfiguration::LookupListOfStrings(std::list<std::string>& target,
                                                 const std::string& key,
                                                 bool allowSingleString) const
  {
    assert(configuration_.type() == Json::objectValue);

    target.clear();

    if (!configuration_.isMember(key))
    {
      return false;
    }

    switch (configuration_[key].type())
    {
      case Json::arrayValue:
      {
        bool ok = true;

        for (Json::Value::ArrayIndex i = 0; ok && i < configuration_[key].size(); i++)
        {
          if (configuration_[key][i].type() == Json::stringValue)
          {
            target.push_back(configuration_[key][i].asString());
          }
          else
          {
            ok = false;
          }
        }

        if (ok)
        {
          return true;
        }

        break;
      }

      case Json::stringValue:
        if (allowSingleString)
        {
          target.push_back(configuration_[key].asString());
          return true;
        }

        break;

      default:
        break;
    }

    ORTHANC_PLUGINS_LOG_ERROR("The configuration option \"" + GetPath(key) +
                              "\" is not a list of strings as expected");

    ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
  }


  bool OrthancConfiguration::LookupSetOfStrings(std::set<std::string>& target,
                                                const std::string& key,
                                                bool allowSingleString) const
  {
    std::list<std::string> lst;

    if (LookupListOfStrings(lst, key, allowSingleString))
    {
      target.clear();

      for (std::list<std::string>::const_iterator
             it = lst.begin(); it != lst.end(); ++it)
      {
        target.insert(*it);
      }

      return true;
    }
    else
    {
      return false;
    }
  }


  std::string OrthancConfiguration::GetStringValue(const std::string& key,
                                                   const std::string& defaultValue) const
  {
    std::string tmp;
    if (LookupStringValue(tmp, key))
    {
      return tmp;
    }
    else
    {
      return defaultValue;
    }
  }


  int OrthancConfiguration::GetIntegerValue(const std::string& key,
                                            int defaultValue) const
  {
    int tmp;
    if (LookupIntegerValue(tmp, key))
    {
      return tmp;
    }
    else
    {
      return defaultValue;
    }
  }


  unsigned int OrthancConfiguration::GetUnsignedIntegerValue(const std::string& key,
                                                             unsigned int defaultValue) const
  {
    unsigned int tmp;
    if (LookupUnsignedIntegerValue(tmp, key))
    {
      return tmp;
    }
    else
    {
      return defaultValue;
    }
  }


  bool OrthancConfiguration::GetBooleanValue(const std::string& key,
                                             bool defaultValue) const
  {
    bool tmp;
    if (LookupBooleanValue(tmp, key))
    {
      return tmp;
    }
    else
    {
      return defaultValue;
    }
  }


  float OrthancConfiguration::GetFloatValue(const std::string& key,
                                            float defaultValue) const
  {
    float tmp;
    if (LookupFloatValue(tmp, key))
    {
      return tmp;
    }
    else
    {
      return defaultValue;
    }
  }


  void OrthancConfiguration::GetDictionary(std::map<std::string, std::string>& target,
                                           const std::string& key) const
  {
    assert(configuration_.type() == Json::objectValue);

    target.clear();

    if (!configuration_.isMember(key))
    {
      return;
    }

    if (configuration_[key].type() != Json::objectValue)
    {
      ORTHANC_PLUGINS_LOG_ERROR("The configuration option \"" + GetPath(key) +
                                "\" is not an object as expected");

      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
    }

    Json::Value::Members members = configuration_[key].getMemberNames();

    for (size_t i = 0; i < members.size(); i++)
    {
      const Json::Value& value = configuration_[key][members[i]];

      if (value.type() == Json::stringValue)
      {
        target[members[i]] = value.asString();
      }
      else
      {
        ORTHANC_PLUGINS_LOG_ERROR("The configuration option \"" + GetPath(key) +
                                  "\" is not a dictionary mapping strings to strings");

        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
      }
    }
  }


  void OrthancImage::Clear()
  {
    if (image_ != NULL)
    {
      OrthancPluginFreeImage(GetGlobalContext(), image_);
      image_ = NULL;
    }
  }


  void OrthancImage::CheckImageAvailable() const
  {
    if (image_ == NULL)
    {
      ORTHANC_PLUGINS_LOG_ERROR("Trying to access a NULL image");
      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
    }
  }


  OrthancImage::OrthancImage() :
    image_(NULL)
  {
  }


  OrthancImage::OrthancImage(OrthancPluginImage*  image) :
    image_(image)
  {
  }


  OrthancImage::OrthancImage(OrthancPluginPixelFormat  format,
                             uint32_t                  width,
                             uint32_t                  height)
  {
    image_ = OrthancPluginCreateImage(GetGlobalContext(), format, width, height);

    if (image_ == NULL)
    {
      ORTHANC_PLUGINS_LOG_ERROR("Cannot create an image");
      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
    }
  }


  OrthancImage::OrthancImage(OrthancPluginPixelFormat  format,
                             uint32_t                  width,
                             uint32_t                  height,
                             uint32_t                  pitch,
                             void*                     buffer)
  {
    image_ = OrthancPluginCreateImageAccessor
      (GetGlobalContext(), format, width, height, pitch, buffer);

    if (image_ == NULL)
    {
      ORTHANC_PLUGINS_LOG_ERROR("Cannot create an image accessor");
      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
    }
  }


  OrthancImage::~OrthancImage()
  {
    try
    {
      Clear();
    }
    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS&) // NOLINT(bugprone-empty-catch)
    {
      // Don't throw exceptions in destructors
    }
  }


  void OrthancImage::UncompressPngImage(const void* data,
                                        size_t size)
  {
    Clear();

    image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Png);

    if (image_ == NULL)
    {
      ORTHANC_PLUGINS_LOG_ERROR("Cannot uncompress a PNG image");
      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
    }
  }


  void OrthancImage::UncompressJpegImage(const void* data,
                                         size_t size)
  {
    Clear();
    image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Jpeg);
    if (image_ == NULL)
    {
      ORTHANC_PLUGINS_LOG_ERROR("Cannot uncompress a JPEG image");
      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
    }
  }


  void OrthancImage::DecodeDicomImage(const void* data,
                                      size_t size,
                                      unsigned int frame)
  {
    Clear();
    image_ = OrthancPluginDecodeDicomImage(GetGlobalContext(), data, size, frame);
    if (image_ == NULL)
    {
      ORTHANC_PLUGINS_LOG_ERROR("Cannot uncompress a DICOM image");
      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
    }
  }


  OrthancPluginPixelFormat OrthancImage::GetPixelFormat() const
  {
    CheckImageAvailable();
    return OrthancPluginGetImagePixelFormat(GetGlobalContext(), image_);
  }


  unsigned int OrthancImage::GetWidth() const
  {
    CheckImageAvailable();
    return OrthancPluginGetImageWidth(GetGlobalContext(), image_);
  }


  unsigned int OrthancImage::GetHeight() const
  {
    CheckImageAvailable();
    return OrthancPluginGetImageHeight(GetGlobalContext(), image_);
  }


  unsigned int OrthancImage::GetPitch() const
  {
    CheckImageAvailable();
    return OrthancPluginGetImagePitch(GetGlobalContext(), image_);
  }


  void* OrthancImage::GetBuffer() const
  {
    CheckImageAvailable();
    return OrthancPluginGetImageBuffer(GetGlobalContext(), image_);
  }


  void OrthancImage::CompressPngImage(MemoryBuffer& target) const
  {
    CheckImageAvailable();

    OrthancPlugins::MemoryBuffer answer;
    OrthancPluginCompressPngImage(GetGlobalContext(), *answer, GetPixelFormat(),
                                  GetWidth(), GetHeight(), GetPitch(), GetBuffer());

    target.Swap(answer);
  }


  void OrthancImage::CompressJpegImage(MemoryBuffer& target,
                                       uint8_t quality) const
  {
    CheckImageAvailable();

    OrthancPlugins::MemoryBuffer answer;
    OrthancPluginCompressJpegImage(GetGlobalContext(), *answer, GetPixelFormat(),
                                   GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality);

    target.Swap(answer);
  }


  void OrthancImage::AnswerPngImage(OrthancPluginRestOutput* output) const
  {
    CheckImageAvailable();
    OrthancPluginCompressAndAnswerPngImage(GetGlobalContext(), output, GetPixelFormat(),
                                           GetWidth(), GetHeight(), GetPitch(), GetBuffer());
  }


  void OrthancImage::AnswerJpegImage(OrthancPluginRestOutput* output,
                                     uint8_t quality) const
  {
    CheckImageAvailable();
    OrthancPluginCompressAndAnswerJpegImage(GetGlobalContext(), output, GetPixelFormat(),
                                            GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality);
  }


  OrthancPluginImage* OrthancImage::Release()
  {
    CheckImageAvailable();
    OrthancPluginImage* tmp = image_;
    image_ = NULL;
    return tmp;
  }


#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1
  FindMatcher::FindMatcher(const OrthancPluginWorklistQuery* worklist) :
    matcher_(NULL),
    worklist_(worklist)
  {
    if (worklist_ == NULL)
    {
      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
    }
  }


  void FindMatcher::SetupDicom(const void*  query,
                               uint32_t     size)
  {
    worklist_ = NULL;

    matcher_ = OrthancPluginCreateFindMatcher(GetGlobalContext(), query, size);
    if (matcher_ == NULL)
    {
      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
    }
  }


  FindMatcher::~FindMatcher()
  {
    // The "worklist_" field

    if (matcher_ != NULL)
    {
      OrthancPluginFreeFindMatcher(GetGlobalContext(), matcher_);
    }
  }



  bool FindMatcher::IsMatch(const void*  dicom,
                            uint32_t     size) const
  {
    int32_t result;

    if (matcher_ != NULL)
    {
      result = OrthancPluginFindMatcherIsMatch(GetGlobalContext(), matcher_, dicom, size);
    }
    else if (worklist_ != NULL)
    {
      result = OrthancPluginWorklistIsMatch(GetGlobalContext(), worklist_, dicom, size);
    }
    else
    {
      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
    }

    if (result == 0)
    {
      return false;
    }
    else if (result == 1)
    {
      return true;
    }
    else
    {
      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
    }
  }

#endif /* HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 */

  static void CheckAnswerSizeIsLessThan4GB(const std::string& answer)
  {
    if (answer.size() > static_cast<size_t>(std::numeric_limits<uint32_t>::max()))
    {
  #if HAS_ORTHANC_EXCEPTION == 1
      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, "Cannot send HTTP response larger than 4GB");
  #else
      ORTHANC_PLUGINS_LOG_ERROR("Cannot send HTTP response larger than 4GB");
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);          
  #endif
    }
  }

  void AnswerJson(const Json::Value& value,
                  OrthancPluginRestOutput* output)
  {
    std::string bodyString;
    WriteStyledJson(bodyString, value);
    CheckAnswerSizeIsLessThan4GB(bodyString);    
    OrthancPluginAnswerBuffer(GetGlobalContext(), output, bodyString.c_str(), bodyString.size(), "application/json");
  }

  void AnswerString(const std::string& answer,
                    const char* mimeType,
                    OrthancPluginRestOutput* output)
  {
    CheckAnswerSizeIsLessThan4GB(answer);
    OrthancPluginAnswerBuffer(GetGlobalContext(), output, answer.c_str(), answer.size(), mimeType);
  }

  void AnswerHttpError(uint16_t httpError, OrthancPluginRestOutput *output)
  {
    OrthancPluginSendHttpStatusCode(GetGlobalContext(), output, httpError);
  }

  void AnswerHttpError(uint16_t httpError,
                       OrthancPluginRestOutput* output,
                       const std::string& answer,
                       const char* mimeType)
  {
    CheckAnswerSizeIsLessThan4GB(answer);

    OrthancPluginSetHttpHeader(GetGlobalContext(),
                               output,
                               "content-type",
                               mimeType);
                               
    OrthancPluginSendHttpStatus(GetGlobalContext(),
                                output,
                                httpError,
                                answer.c_str(),
                                static_cast<uint32_t>(answer.size()));
  }


  void AnswerMethodNotAllowed(OrthancPluginRestOutput *output, const char* allowedMethods)
  {
    OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowedMethods);
  }

  bool RestApiGetString(std::string& result,
                        const std::string& uri,
                        bool applyPlugins)
  {
    MemoryBuffer answer;
    if (!answer.RestApiGet(uri, applyPlugins))
    {
      return false;
    }
    else
    {
      answer.ToString(result);
      return true;
    }
  }

  bool RestApiGetString(std::string& result,
                        const std::string& uri,
                        const HttpHeaders& httpHeaders,
                        bool applyPlugins)
  {
    MemoryBuffer answer;
    if (!answer.RestApiGet(uri, httpHeaders, applyPlugins))
    {
      return false;
    }
    else
    {
      answer.ToString(result);
      return true;
    }
  }


  bool RestApiGet(Json::Value& result,
                  const std::string& uri,
                  const HttpHeaders& httpHeaders,
                  bool applyPlugins)
  {
    MemoryBuffer answer;

    if (!answer.RestApiGet(uri, httpHeaders, applyPlugins))
    {
      return false;
    }
    else
    {
      if (!answer.IsEmpty())
      {
        answer.ToJson(result);
      }
      return true;
    }
  }


  bool RestApiGet(Json::Value& result,
                  const std::string& uri,
                  bool applyPlugins)
  {
    MemoryBuffer answer;

    if (!answer.RestApiGet(uri, applyPlugins))
    {
      return false;
    }
    else
    {
      if (!answer.IsEmpty())
      {
        answer.ToJson(result);
      }
      return true;
    }
  }


  bool RestApiPost(std::string& result,
                   const std::string& uri,
                   const void* body,
                   size_t bodySize,
                   bool applyPlugins)
  {
    MemoryBuffer answer;

    if (!answer.RestApiPost(uri, body, bodySize, applyPlugins))
    {
      return false;
    }
    else
    {
      if (!answer.IsEmpty())
      {
        result.assign(reinterpret_cast<const char*>(answer.GetData()), answer.GetSize());
      }
      return true;
    }
  }


  bool RestApiPost(Json::Value& result,
                   const std::string& uri,
                   const void* body,
                   size_t bodySize,
                   bool applyPlugins)
  {
    MemoryBuffer answer;

    if (!answer.RestApiPost(uri, body, bodySize, applyPlugins))
    {
      return false;
    }
    else
    {
      if (!answer.IsEmpty())
      {
        answer.ToJson(result);
      }
      return true;
    }
  }

#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1
  bool RestApiPost(Json::Value& result,
                   const std::string& uri,
                   const Json::Value& body,
                   const HttpHeaders& httpHeaders,
                   bool applyPlugins)
  {
    MemoryBuffer answer;

    if (!answer.RestApiPost(uri, body, httpHeaders, applyPlugins))
    {
      return false;
    }
    else
    {
      if (!answer.IsEmpty())
      {
        answer.ToJson(result);
      }
      return true;
    }
  }
#endif


  bool RestApiPost(Json::Value& result,
                   const std::string& uri,
                   const Json::Value& body,
                   bool applyPlugins)
  {
    std::string s;
    WriteFastJson(s, body);
    return RestApiPost(result, uri, s, applyPlugins);
  }


  bool RestApiPut(Json::Value& result,
                  const std::string& uri,
                  const void* body,
                  size_t bodySize,
                  bool applyPlugins)
  {
    MemoryBuffer answer;

    if (!answer.RestApiPut(uri, body, bodySize, applyPlugins))
    {
      return false;
    }
    else
    {
      if (!answer.IsEmpty()) // i.e, on a PUT to metadata/..., orthanc returns an empty response
      {
        answer.ToJson(result);
      }
      return true;
    }
  }


  bool RestApiPut(Json::Value& result,
                  const std::string& uri,
                  const Json::Value& body,
                  bool applyPlugins)
  {
    std::string s;
    WriteFastJson(s, body);
    return RestApiPut(result, uri, s, applyPlugins);
  }


  bool RestApiDelete(const std::string& uri,
                     bool applyPlugins)
  {
    OrthancPluginErrorCode error;

    if (applyPlugins)
    {
      error = OrthancPluginRestApiDeleteAfterPlugins(GetGlobalContext(), uri.c_str());
    }
    else
    {
      error = OrthancPluginRestApiDelete(GetGlobalContext(), uri.c_str());
    }

    if (error == OrthancPluginErrorCode_Success)
    {
      return true;
    }
    else if (error == OrthancPluginErrorCode_UnknownResource ||
             error == OrthancPluginErrorCode_InexistentItem)
    {
      return false;
    }
    else
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
    }
  }


  void ReportMinimalOrthancVersion(unsigned int major,
                                   unsigned int minor,
                                   unsigned int revision)
  {
    ORTHANC_PLUGINS_LOG_ERROR("Your version of the Orthanc core (" +
                              std::string(GetGlobalContext()->orthancVersion) +
                              ") is too old to run this plugin (version " +
                              boost::lexical_cast<std::string>(major) + "." +
                              boost::lexical_cast<std::string>(minor) + "." +
                              boost::lexical_cast<std::string>(revision) +
                              " is required)");
  }

  bool CheckMinimalVersion(const char* version,
                           unsigned int major,
                           unsigned int minor,
                           unsigned int revision)
  {
    if (!strcmp(version, "mainline"))
    {
      // Assume compatibility with the mainline
      return true;
    }

#ifdef _MSC_VER
#define ORTHANC_SCANF sscanf_s
#else
#define ORTHANC_SCANF sscanf
#endif

    // Parse the version
    int aa, bb, cc = 0;
    if ((ORTHANC_SCANF(version, "%4d.%4d.%4d", &aa, &bb, &cc) != 3 &&
         ORTHANC_SCANF(version, "%4d.%4d", &aa, &bb) != 2) ||
        aa < 0 ||
        bb < 0 ||
        cc < 0)
    {
      return false;
    }

    unsigned int a = static_cast<unsigned int>(aa);
    unsigned int b = static_cast<unsigned int>(bb);
    unsigned int c = static_cast<unsigned int>(cc);

    // Check the major version number

    if (a > major)
    {
      return true;
    }

    if (a < major)
    {
      return false;
    }

    // Check the minor version number
    assert(a == major);

    if (b > minor)
    {
      return true;
    }

    if (b < minor)
    {
      return false;
    }

    // Check the patch level version number
    assert(a == major && b == minor);

    if (c >= revision)
    {
      return true;
    }
    else
    {
      return false;
    }
  }


  bool CheckMinimalOrthancVersion(unsigned int major,
                                  unsigned int minor,
                                  unsigned int revision)
  {
    if (!HasGlobalContext())
    {
      ORTHANC_PLUGINS_LOG_ERROR("Bad Orthanc context in the plugin");
      return false;
    }

    return CheckMinimalVersion(GetGlobalContext()->orthancVersion,
                               major, minor, revision);
  }


#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0)
  const char* AutodetectMimeType(const std::string& path)
  {
    const char* mime = OrthancPluginAutodetectMimeType(GetGlobalContext(), path.c_str());

    if (mime == NULL)
    {
      // Should never happen, just for safety
      return "application/octet-stream";
    }
    else
    {
      return mime;
    }
  }
#endif


#if HAS_ORTHANC_PLUGIN_PEERS == 1
  size_t OrthancPeers::GetPeerIndex(const std::string& name) const
  {
    size_t index;
    if (LookupName(index, name))
    {
      return index;
    }
    else
    {
      ORTHANC_PLUGINS_LOG_ERROR("Inexistent peer: " + name);
      ORTHANC_PLUGINS_THROW_EXCEPTION(UnknownResource);
    }
  }


  OrthancPeers::OrthancPeers() :
    peers_(NULL),
    timeout_(0)
  {
    peers_ = OrthancPluginGetPeers(GetGlobalContext());

    if (peers_ == NULL)
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
    }

    uint32_t count = OrthancPluginGetPeersCount(GetGlobalContext(), peers_);

    for (uint32_t i = 0; i < count; i++)
    {
      const char* name = OrthancPluginGetPeerName(GetGlobalContext(), peers_, i);
      if (name == NULL)
      {
        OrthancPluginFreePeers(GetGlobalContext(), peers_);
        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
      }

      index_[name] = i;
    }
  }


  OrthancPeers::~OrthancPeers()
  {
    if (peers_ != NULL)
    {
      OrthancPluginFreePeers(GetGlobalContext(), peers_);
    }
  }


  bool OrthancPeers::LookupName(size_t& target,
                                const std::string& name) const
  {
    Index::const_iterator found = index_.find(name);

    if (found == index_.end())
    {
      return false;
    }
    else
    {
      target = found->second;
      return true;
    }
  }


  std::string OrthancPeers::GetPeerName(size_t index) const
  {
    if (index >= index_.size())
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
    }
    else
    {
      const char* s = OrthancPluginGetPeerName(GetGlobalContext(), peers_, static_cast<uint32_t>(index));
      if (s == NULL)
      {
        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
      }
      else
      {
        return s;
      }
    }
  }


  std::string OrthancPeers::GetPeerUrl(size_t index) const
  {
    if (index >= index_.size())
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
    }
    else
    {
      const char* s = OrthancPluginGetPeerUrl(GetGlobalContext(), peers_, static_cast<uint32_t>(index));
      if (s == NULL)
      {
        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
      }
      else
      {
        return s;
      }
    }
  }


  std::string OrthancPeers::GetPeerUrl(const std::string& name) const
  {
    return GetPeerUrl(GetPeerIndex(name));
  }


  bool OrthancPeers::LookupUserProperty(std::string& value,
                                        size_t index,
                                        const std::string& key) const
  {
    if (index >= index_.size())
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
    }
    else
    {
      const char* s = OrthancPluginGetPeerUserProperty(GetGlobalContext(), peers_, static_cast<uint32_t>(index), key.c_str());
      if (s == NULL)
      {
        return false;
      }
      else
      {
        value.assign(s);
        return true;
      }
    }
  }


  bool OrthancPeers::LookupUserProperty(std::string& value,
                                        const std::string& peer,
                                        const std::string& key) const
  {
    return LookupUserProperty(value, GetPeerIndex(peer), key);
  }


  bool OrthancPeers::DoGet(MemoryBuffer& target,
                           size_t index,
                           const std::string& uri,
                           const HttpHeaders& headers) const
  {
    if (index >= index_.size())
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
    }

    OrthancPlugins::MemoryBuffer answer;
    uint16_t status;
    PluginHttpHeaders pluginHeaders(headers);

    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
      (GetGlobalContext(), *answer, NULL, &status, peers_,
       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Get, uri.c_str(),
       pluginHeaders.GetSize(), pluginHeaders.GetKeys(), pluginHeaders.GetValues(), NULL, 0, timeout_);

    if (code == OrthancPluginErrorCode_Success)
    {
      target.Swap(answer);
      return (status == 200);
    }
    else
    {
      return false;
    }
  }


  bool OrthancPeers::DoGet(MemoryBuffer& target,
                           const std::string& name,
                           const std::string& uri,
                           const HttpHeaders& headers) const
  {
    size_t index;
    return (LookupName(index, name) &&
            DoGet(target, index, uri, headers));
  }


  bool OrthancPeers::DoGet(Json::Value& target,
                           size_t index,
                           const std::string& uri,
                           const HttpHeaders& headers) const
  {
    MemoryBuffer buffer;

    if (DoGet(buffer, index, uri, headers))
    {
      buffer.ToJson(target);
      return true;
    }
    else
    {
      return false;
    }
  }


  bool OrthancPeers::DoGet(Json::Value& target,
                           const std::string& name,
                           const std::string& uri,
                           const HttpHeaders& headers) const
  {
    MemoryBuffer buffer;

    if (DoGet(buffer, name, uri, headers))
    {
      buffer.ToJson(target);
      return true;
    }
    else
    {
      return false;
    }
  }


  bool OrthancPeers::DoPost(MemoryBuffer& target,
                            const std::string& name,
                            const std::string& uri,
                            const std::string& body,
                            const HttpHeaders& headers) const
  {
    size_t index;
    return (LookupName(index, name) &&
            DoPost(target, index, uri, body, headers));
  }

  bool OrthancPeers::DoPost(Json::Value& target,
                            size_t index,
                            const std::string& uri,
                            const std::string& body,
                            const HttpHeaders& headers, 
                            unsigned int timeout) const
  {
    MemoryBuffer buffer;
    HttpHeaders answerHeaders;

    if (DoPost(buffer, answerHeaders, index, uri, body, headers, timeout))
    {
      buffer.ToJson(target);
      return true;
    }
    else
    {
      return false;
    }
  }

  bool OrthancPeers::DoPost(Json::Value& target,
                            HttpHeaders& answerHeaders,
                            size_t index,
                            const std::string& uri,
                            const std::string& body,
                            const HttpHeaders& headers, 
                            unsigned int timeout) const
  {
    MemoryBuffer buffer;

    if (DoPost(buffer, answerHeaders, index, uri, body, headers, timeout))
    {
      buffer.ToJson(target);
      return true;
    }
    else
    {
      return false;
    }
  }


  bool OrthancPeers::DoPost(Json::Value& target,
                            size_t index,
                            const std::string& uri,
                            const std::string& body,
                            const HttpHeaders& headers) const
  {
    MemoryBuffer buffer;

    if (DoPost(buffer, index, uri, body, headers))
    {
      buffer.ToJson(target);
      return true;
    }
    else
    {
      return false;
    }
  }


  bool OrthancPeers::DoPost(Json::Value& target,
                            const std::string& name,
                            const std::string& uri,
                            const std::string& body,
                            const HttpHeaders& headers) const
  {
    MemoryBuffer buffer;

    if (DoPost(buffer, name, uri, body, headers))
    {
      buffer.ToJson(target);
      return true;
    }
    else
    {
      return false;
    }
  }


  bool OrthancPeers::DoPost(MemoryBuffer& target,
                            size_t index,
                            const std::string& uri,
                            const std::string& body,
                            const HttpHeaders& headers) const
  {
    HttpHeaders answerHeaders;
    return DoPost(target, answerHeaders, index, uri, body, headers, timeout_);
  }

  bool OrthancPeers::DoPost(MemoryBuffer& target,
                            size_t index,
                            const std::string& uri,
                            const std::string& body,
                            const HttpHeaders& headers,
                            unsigned int timeout) const
  {
    HttpHeaders answerHeaders;
    return DoPost(target, answerHeaders, index, uri, body, headers, timeout);
  }

  bool OrthancPeers::DoPost(MemoryBuffer& target,
                            HttpHeaders& answerHeaders,
                            size_t index,
                            const std::string& uri,
                            const std::string& body,
                            const HttpHeaders& headers,
                            unsigned int timeout) const
  {
    if (index >= index_.size())
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
    }

    if (body.size() > 0xffffffffu)
    {
      ORTHANC_PLUGINS_LOG_ERROR("Cannot handle body size > 4GB");
      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
    }

    OrthancPlugins::MemoryBuffer answer;
    OrthancPlugins::MemoryBuffer answerHeadersBuffer;
    uint16_t status;
    PluginHttpHeaders pluginHeaders(headers);

    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
      (GetGlobalContext(), *answer, *answerHeadersBuffer, &status, peers_,
       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Post, uri.c_str(),
       pluginHeaders.GetSize(), pluginHeaders.GetKeys(), pluginHeaders.GetValues(), body.empty() ? NULL : body.c_str(), body.size(), timeout);

    if (code == OrthancPluginErrorCode_Success)
    {
      target.Swap(answer);
      DecodeHttpHeaders(answerHeaders, answerHeadersBuffer);
      
      return (status == 200);
    }
    else
    {
      return false;
    }
  }


  bool OrthancPeers::DoPut(size_t index,
                           const std::string& uri,
                           const std::string& body,
                           const HttpHeaders& headers) const
  {
    if (index >= index_.size())
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
    }

    if (body.size() > 0xffffffffu)
    {
      ORTHANC_PLUGINS_LOG_ERROR("Cannot handle body size > 4GB");
      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
    }

    OrthancPlugins::MemoryBuffer answer;
    uint16_t status;
    PluginHttpHeaders pluginHeaders(headers);

    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
      (GetGlobalContext(), *answer, NULL, &status, peers_,
       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Put, uri.c_str(),
       pluginHeaders.GetSize(), pluginHeaders.GetKeys(), pluginHeaders.GetValues(), body.empty() ? NULL : body.c_str(), body.size(), timeout_);

    if (code == OrthancPluginErrorCode_Success)
    {
      return (status == 200);
    }
    else
    {
      return false;
    }
  }


  bool OrthancPeers::DoPut(const std::string& name,
                           const std::string& uri,
                           const std::string& body,
                           const HttpHeaders& headers) const
  {
    size_t index;
    return (LookupName(index, name) &&
            DoPut(index, uri, body, headers));
  }


  bool OrthancPeers::DoDelete(size_t index,
                              const std::string& uri,
                              const HttpHeaders& headers) const
  {
    if (index >= index_.size())
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
    }

    OrthancPlugins::MemoryBuffer answer;
    uint16_t status;
    PluginHttpHeaders pluginHeaders(headers);

    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
      (GetGlobalContext(), *answer, NULL, &status, peers_,
       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Delete, uri.c_str(),
       pluginHeaders.GetSize(), pluginHeaders.GetKeys(), pluginHeaders.GetValues(), NULL, 0, timeout_);

    if (code == OrthancPluginErrorCode_Success)
    {
      return (status == 200);
    }
    else
    {
      return false;
    }
  }


  bool OrthancPeers::DoDelete(const std::string& name,
                              const std::string& uri,
                              const HttpHeaders& headers) const
  {
    size_t index;
    return (LookupName(index, name) &&
            DoDelete(index, uri, headers));
  }
#endif





  /******************************************************************
   ** JOBS
   ******************************************************************/

#if HAS_ORTHANC_PLUGIN_JOB == 1
  void OrthancJob::CallbackFinalize(void* job)
  {
    if (job != NULL)
    {
      delete reinterpret_cast<OrthancJob*>(job);
    }
  }


  float OrthancJob::CallbackGetProgress(void* job)
  {
    assert(job != NULL);

    try
    {
      return reinterpret_cast<OrthancJob*>(job)->progress_;
    }
    catch (...)
    {
      return 0;
    }
  }


#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3)
  static OrthancPluginErrorCode CopyStringToMemoryBuffer(OrthancPluginMemoryBuffer* target,
                                                         const std::string& source)
  {
    if (OrthancPluginCreateMemoryBuffer(globalContext_, target, source.size()) != OrthancPluginErrorCode_Success)
    {
      return OrthancPluginErrorCode_NotEnoughMemory;
    }
    else
    {
      if (!source.empty())
      {
        memcpy(target->data, source.c_str(), source.size());
      }
      
      return OrthancPluginErrorCode_Success;
    }
  }
#endif


#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3)
  OrthancPluginErrorCode OrthancJob::CallbackGetContent(OrthancPluginMemoryBuffer* target,
                                                        void* job)
  {
    assert(job != NULL);
    OrthancJob& that = *reinterpret_cast<OrthancJob*>(job);

    boost::mutex::scoped_lock lock(that.contentMutex_);
    return CopyStringToMemoryBuffer(target, that.content_);
  }
#else
  const char* OrthancJob::CallbackGetContent(void* job)
  {
    assert(job != NULL);

    try
    {
      OrthancJob& that = *reinterpret_cast<OrthancJob*>(job);
      boost::mutex::scoped_lock lock(that.contentMutex_);

      return that.content_.c_str();
    }
    catch (...)
    {
      return 0;
    }
  }
#endif


#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3)
  int32_t OrthancJob::CallbackGetSerialized(OrthancPluginMemoryBuffer* target,
                                            void* job)
  {
    assert(job != NULL);
    OrthancJob& that = *reinterpret_cast<OrthancJob*>(job);
    
    if (that.hasSerialized_)
    {
      if (CopyStringToMemoryBuffer(target, that.serialized_) == OrthancPluginErrorCode_Success)
      {
        return 1;
      }
      else
      {
        return -1;
      }
    }
    else
    {
      return 0;
    }
  }
#else
  const char* OrthancJob::CallbackGetSerialized(void* job)
  {
    assert(job != NULL);

    try
    {
      const OrthancJob& tmp = *reinterpret_cast<OrthancJob*>(job);

      if (tmp.hasSerialized_)
      {
        return tmp.serialized_.c_str();
      }
      else
      {
        return NULL;
      }
    }
    catch (...)
    {
      return 0;
    }
  }
#endif


  OrthancPluginJobStepStatus OrthancJob::CallbackStep(void* job)
  {
    assert(job != NULL);

    try
    {
      return reinterpret_cast<OrthancJob*>(job)->Step();
    }
    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS&)
    {
      return OrthancPluginJobStepStatus_Failure;
    }
    catch (...)
    {
      return OrthancPluginJobStepStatus_Failure;
    }
  }


  OrthancPluginErrorCode OrthancJob::CallbackStop(void* job,
                                                  OrthancPluginJobStopReason reason)
  {
    assert(job != NULL);

    try
    {
      reinterpret_cast<OrthancJob*>(job)->Stop(reason);
      return OrthancPluginErrorCode_Success;
    }
    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
    {
      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
    }
    catch (...)
    {
      return OrthancPluginErrorCode_Plugin;
    }
  }


  OrthancPluginErrorCode OrthancJob::CallbackReset(void* job)
  {
    assert(job != NULL);

    try
    {
      reinterpret_cast<OrthancJob*>(job)->Reset();
      return OrthancPluginErrorCode_Success;
    }
    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
    {
      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
    }
    catch (...)
    {
      return OrthancPluginErrorCode_Plugin;
    }
  }


  void OrthancJob::ClearContent()
  {
    Json::Value empty = Json::objectValue;
    UpdateContent(empty);
  }


  void OrthancJob::UpdateContent(const Json::Value& content)
  {
    boost::mutex::scoped_lock lock(contentMutex_);

    if (content.type() != Json::objectValue)
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat);
    }
    else
    {
      WriteFastJson(content_, content);
    }
  }


  void OrthancJob::ClearSerialized()
  {
    hasSerialized_ = false;
    serialized_.clear();
  }


  void OrthancJob::UpdateSerialized(const Json::Value& serialized)
  {
    if (serialized.type() != Json::objectValue)
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat);
    }
    else
    {
      WriteFastJson(serialized_, serialized);
      hasSerialized_ = true;
    }
  }


  void OrthancJob::UpdateProgress(float progress)
  {
    if (progress < 0 ||
        progress > 1)
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
    }

    progress_ = progress;
  }


  OrthancJob::OrthancJob(const std::string& jobType) :
    jobType_(jobType),
    progress_(0)
  {
    ClearContent();
    ClearSerialized();
  }


  OrthancPluginJob* OrthancJob::Create(OrthancJob* job)
  {
    if (job == NULL)
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer);
    }

    OrthancPluginJob* orthanc =
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3)
      OrthancPluginCreateJob2
#else
      OrthancPluginCreateJob
#endif
      (GetGlobalContext(), job, CallbackFinalize, job->jobType_.c_str(),
       CallbackGetProgress, CallbackGetContent, CallbackGetSerialized,
       CallbackStep, CallbackStop, CallbackReset);

    if (orthanc == NULL)
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
    }
    else
    {
      return orthanc;
    }
  }


  std::string OrthancJob::Submit(OrthancJob* job,
                                 int priority)
  {
    if (job == NULL)
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer);
    }

    OrthancPluginJob* orthanc = Create(job);

    char* id = OrthancPluginSubmitJob(GetGlobalContext(), orthanc, priority);

    if (id == NULL)
    {
      ORTHANC_PLUGINS_LOG_ERROR("Plugin cannot submit job");
      OrthancPluginFreeJob(GetGlobalContext(), orthanc);
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
    }
    else
    {
      std::string tmp(id);
      tmp.assign(id);
      OrthancPluginFreeString(GetGlobalContext(), id);

      return tmp;
    }
  }


  void OrthancJob::SubmitAndWait(Json::Value& result,
                                 OrthancJob* job /* takes ownership */,
                                 int priority)
  {
    std::string id = Submit(job, priority);

    for (;;)
    {
      boost::this_thread::sleep(boost::posix_time::milliseconds(100));

      Json::Value status;
      if (!RestApiGet(status, "/jobs/" + id, false) ||
          !status.isMember("State") ||
          status["State"].type() != Json::stringValue)
      {
        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InexistentItem);        
      }

      const std::string state = status["State"].asString();
      if (state == "Success")
      {
        if (status.isMember("Content"))
        {
          result = status["Content"];
        }
        else
        {
          result = Json::objectValue;
        }

        return;
      }
      else if (state == "Running" ||
               state == "Pending" ||
               state == "Paused" ||
               state == "Retry")
      {
        continue;
      }
      else if (state == "Failure")
      {
        if (!status.isMember("ErrorCode") ||
            status["ErrorCode"].type() != Json::intValue)
        {
          ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InternalError);
        }
        else
        {
          if (!status.isMember("ErrorDescription") ||
              status["ErrorDescription"].type() != Json::stringValue)
          {
            ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt());
          }
          else
          {
  #if HAS_ORTHANC_EXCEPTION == 1
            throw Orthanc::OrthancException(static_cast<Orthanc::ErrorCode>(status["ErrorCode"].asInt()),
                                            status["ErrorDescription"].asString());
  #else
            ORTHANC_PLUGINS_LOG_ERROR("Exception while executing the job: " + status["ErrorDescription"].asString());
            ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt());          
  #endif
          }
        }
      }
      else
      {
        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InternalError);
      }
    }
  }


  void OrthancJob::SubmitFromRestApiPost(OrthancPluginRestOutput* output,
                                         const Json::Value& body,
                                         OrthancJob* job)
  {
    static const char* KEY_SYNCHRONOUS = "Synchronous";
    static const char* KEY_ASYNCHRONOUS = "Asynchronous";
    static const char* KEY_PRIORITY = "Priority";

    boost::movelib::unique_ptr<OrthancJob> protection(job);
  
    if (body.type() != Json::objectValue)
    {
#if HAS_ORTHANC_EXCEPTION == 1
      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
                                      "Expected a JSON object in the body");
#else
      ORTHANC_PLUGINS_LOG_ERROR("Expected a JSON object in the body");
      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
#endif
    }

    bool synchronous = true;
  
    if (body.isMember(KEY_SYNCHRONOUS))
    {
      if (body[KEY_SYNCHRONOUS].type() != Json::booleanValue)
      {
#if HAS_ORTHANC_EXCEPTION == 1
        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
                                        "Option \"" + std::string(KEY_SYNCHRONOUS) +
                                        "\" must be Boolean");
#else
        ORTHANC_PLUGINS_LOG_ERROR("Option \"" + std::string(KEY_SYNCHRONOUS) + "\" must be Boolean");
        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
#endif
      }
      else
      {
        synchronous = body[KEY_SYNCHRONOUS].asBool();
      }
    }

    if (body.isMember(KEY_ASYNCHRONOUS))
    {
      if (body[KEY_ASYNCHRONOUS].type() != Json::booleanValue)
      {
#if HAS_ORTHANC_EXCEPTION == 1
        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
                                        "Option \"" + std::string(KEY_ASYNCHRONOUS) +
                                        "\" must be Boolean");
#else
        ORTHANC_PLUGINS_LOG_ERROR("Option \"" + std::string(KEY_ASYNCHRONOUS) + "\" must be Boolean");
        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
#endif
      }
      else
      {
        synchronous = !body[KEY_ASYNCHRONOUS].asBool();
      }
    }

    int priority = 0;

    if (body.isMember(KEY_PRIORITY))
    {
      if (body[KEY_PRIORITY].type() != Json::intValue)
      {
#if HAS_ORTHANC_EXCEPTION == 1
        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
                                        "Option \"" + std::string(KEY_PRIORITY) +
                                        "\" must be an integer");
#else
        ORTHANC_PLUGINS_LOG_ERROR("Option \"" + std::string(KEY_PRIORITY) + "\" must be an integer");
        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
#endif
      }
      else
      {
        priority = !body[KEY_PRIORITY].asInt();
      }
    }
  
    Json::Value result;

    if (synchronous)
    {
      OrthancPlugins::OrthancJob::SubmitAndWait(result, protection.release(), priority);
    }
    else
    {
      std::string id = OrthancPlugins::OrthancJob::Submit(protection.release(), priority);

      result = Json::objectValue;
      result["ID"] = id;
      result["Path"] = "/jobs/" + id;
    }

    std::string s = result.toStyledString();
    OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(),
                              s.size(), "application/json");
  }

#endif




  /******************************************************************
   ** METRICS
   ******************************************************************/

#if HAS_ORTHANC_PLUGIN_METRICS == 1
  MetricsTimer::MetricsTimer(const char* name) :
    name_(name)
  {
    start_ = boost::posix_time::microsec_clock::universal_time();
  }
  
  MetricsTimer::~MetricsTimer()
  {
    const boost::posix_time::ptime stop = boost::posix_time::microsec_clock::universal_time();
    const boost::posix_time::time_duration diff = stop - start_;
    OrthancPluginSetMetricsValue(GetGlobalContext(), name_.c_str(), static_cast<float>(diff.total_milliseconds()),
                                 OrthancPluginMetricsType_Timer);
  }
#endif




  /******************************************************************
   ** HTTP CLIENT
   ******************************************************************/

#if HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1
  class HttpClient::RequestBodyWrapper : public boost::noncopyable
  {
  private:
    static RequestBodyWrapper& GetObject(void* body)
    {
      assert(body != NULL);
      return *reinterpret_cast<RequestBodyWrapper*>(body);
    }

    IRequestBody&  body_;
    bool           done_;
    std::string    chunk_;

  public:
    RequestBodyWrapper(IRequestBody& body) :
      body_(body),
      done_(false)
    {
    }      

    static uint8_t IsDone(void* body)
    {
      return GetObject(body).done_;
    }
    
    static const void* GetChunkData(void* body)
    {
      return GetObject(body).chunk_.c_str();
    }
    
    static uint32_t GetChunkSize(void* body)
    {
      return static_cast<uint32_t>(GetObject(body).chunk_.size());
    }

    static OrthancPluginErrorCode Next(void* body)
    {
      RequestBodyWrapper& that = GetObject(body);
        
      if (that.done_)
      {
        return OrthancPluginErrorCode_BadSequenceOfCalls;
      }
      else
      {
        try
        {
          that.done_ = !that.body_.ReadNextChunk(that.chunk_);
          return OrthancPluginErrorCode_Success;
        }
        catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
        {
          return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
        }
        catch (...)
        {
          return OrthancPluginErrorCode_Plugin;
        }
      }
    }    
  };


#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
  static OrthancPluginErrorCode AnswerAddHeaderCallback(void* answer,
                                                        const char* key,
                                                        const char* value)
  {
    assert(answer != NULL && key != NULL && value != NULL);

    try
    {
      reinterpret_cast<HttpClient::IAnswer*>(answer)->AddHeader(key, value);
      return OrthancPluginErrorCode_Success;
    }
    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
    {
      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
    }
    catch (...)
    {
      return OrthancPluginErrorCode_Plugin;
    }
  }
#endif


#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
  static OrthancPluginErrorCode AnswerAddChunkCallback(void* answer,
                                                       const void* data,
                                                       uint32_t size)
  {
    assert(answer != NULL);

    try
    {
      reinterpret_cast<HttpClient::IAnswer*>(answer)->AddChunk(data, size);
      return OrthancPluginErrorCode_Success;
    }
    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
    {
      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
    }
    catch (...)
    {
      return OrthancPluginErrorCode_Plugin;
    }
  }
#endif


  HttpClient::HttpClient() :
    httpStatus_(0),
    method_(OrthancPluginHttpMethod_Get),
    timeout_(0),
    pkcs11_(false),
    chunkedBody_(NULL),
    allowChunkedTransfers_(true)
  {
  }


  void HttpClient::AddHeaders(const HttpHeaders& headers)
  {
    for (HttpHeaders::const_iterator it = headers.begin();
         it != headers.end(); ++it)
    {
      headers_[it->first] = it->second;
    }
  }

  
  void HttpClient::SetCredentials(const std::string& username,
                                  const std::string& password)
  {
    username_ = username;
    password_ = password;
  }

  
  void HttpClient::ClearCredentials()
  {
    username_.clear();
    password_.clear();
  }


  void HttpClient::SetCertificate(const std::string& certificateFile,
                                  const std::string& keyFile,
                                  const std::string& keyPassword)
  {
    certificateFile_ = certificateFile;
    certificateKeyFile_ = keyFile;
    certificateKeyPassword_ = keyPassword;
  }

  
  void HttpClient::ClearCertificate()
  {
    certificateFile_.clear();
    certificateKeyFile_.clear();
    certificateKeyPassword_.clear();
  }


  void HttpClient::ClearBody()
  {
    fullBody_.clear();
    chunkedBody_ = NULL;
  }

  
  void HttpClient::SwapBody(std::string& body)
  {
    fullBody_.swap(body);
    chunkedBody_ = NULL;
  }

  
  void HttpClient::SetBody(const std::string& body)
  {
    fullBody_ = body;
    chunkedBody_ = NULL;
  }

  
  void HttpClient::SetBody(IRequestBody& body)
  {
    fullBody_.clear();
    chunkedBody_ = &body;
  }


  namespace
  {
    class HeadersWrapper : public boost::noncopyable
    {
    private:
      std::vector<const char*>  headersKeys_;
      std::vector<const char*>  headersValues_;

    public:
      HeadersWrapper(const HttpHeaders& headers)
      {
        headersKeys_.reserve(headers.size());
        headersValues_.reserve(headers.size());

        for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it)
        {
          headersKeys_.push_back(it->first.c_str());
          headersValues_.push_back(it->second.c_str());
        }
      }

      void AddStaticString(const char* key,
                           const char* value)
      {
        headersKeys_.push_back(key);
        headersValues_.push_back(value);
      }

      uint32_t GetCount() const
      {
        return headersKeys_.size();
      }

      const char* const* GetKeys() const
      {
        return headersKeys_.empty() ? NULL : &headersKeys_[0];
      }

      const char* const* GetValues() const
      {
        return headersValues_.empty() ? NULL : &headersValues_[0];
      }
    };


    class MemoryRequestBody : public HttpClient::IRequestBody
    {
    private:
      std::string  body_;
      bool         done_;

    public:
      MemoryRequestBody(const std::string& body) :
        body_(body),
        done_(false)
      {
        if (body_.empty())
        {
          done_ = true;
        }
      }

      virtual bool ReadNextChunk(std::string& chunk)
      {
        if (done_)
        {
          return false;
        }
        else
        {
          chunk.swap(body_);
          done_ = true;
          return true;
        }
      }
    };


    // This class mimics Orthanc::ChunkedBuffer
    class ChunkedBuffer : public boost::noncopyable
    {
    private:
      typedef std::list<std::string*>  Content;

      Content  content_;
      size_t   size_;

    public:
      ChunkedBuffer() :
        size_(0)
      {
      }

      ~ChunkedBuffer()
      {
        Clear();
      }

      void Clear()
      {
        for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
        {
          assert(*it != NULL);
          delete *it;
        }

        size_ = 0;
        content_.clear();
      }

      /**
       * Since Orthanc 1.9.3, this function also clears the content of
       * the ChunkedBuffer in order to mimic the behavior of the
       * original class "Orthanc::ChunkedBuffer". This prevents the
       * forgetting of calling "Clear()" in order to reduce memory
       * consumption.
       **/
      void Flatten(std::string& target)
      {
        target.resize(size_);

        size_t pos = 0;

        for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it)
        {
          assert(*it != NULL);
          size_t s = (*it)->size();

          if (s != 0)
          {
            memcpy(&target[pos], (*it)->c_str(), s);
            pos += s;
          }

          delete *it;
        }

        assert(pos == target.size());

        size_ = 0;
        content_.clear();
      }

      void AddChunk(const void* data,
                    size_t size)
      {
        content_.push_back(new std::string(reinterpret_cast<const char*>(data), size));
        size_ += size;
      }

      void AddChunk(const std::string& chunk)
      {
        content_.push_back(new std::string(chunk));
        size_ += chunk.size();
      }
    };


#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
    class MemoryAnswer : public HttpClient::IAnswer
    {
    private:
      HttpHeaders    headers_;
      ChunkedBuffer  body_;

    public:
      const HttpHeaders& GetHeaders() const
      {
        return headers_;
      }

      ChunkedBuffer& GetBody()
      {
        return body_;
      }

      virtual void AddHeader(const std::string& key,
                             const std::string& value)
      {
        headers_[key] = value;
      }

      virtual void AddChunk(const void* data,
                            size_t size)
      {
        body_.AddChunk(data, size);
      }
    };
#endif
  }


#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
  void HttpClient::ExecuteWithStream(uint16_t& httpStatus,
                                     IAnswer& answer,
                                     IRequestBody& body) const
  {
    HeadersWrapper h(headers_);

    if (method_ == OrthancPluginHttpMethod_Post ||
        method_ == OrthancPluginHttpMethod_Put)
    {
      // Automatically set the "Transfer-Encoding" header if absent
      bool found = false;

      for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it)
      {
        if (boost::iequals(it->first, "Transfer-Encoding"))
        {
          found = true;
          break;
        }
      }

      if (!found)
      {
        h.AddStaticString("Transfer-Encoding", "chunked");
      }
    }

    RequestBodyWrapper request(body);
        
    OrthancPluginErrorCode error = OrthancPluginChunkedHttpClient(
      GetGlobalContext(),
      &answer,
      AnswerAddChunkCallback,
      AnswerAddHeaderCallback,
      &httpStatus,
      method_,
      url_.c_str(),
      h.GetCount(),
      h.GetKeys(),
      h.GetValues(),
      &request,
      RequestBodyWrapper::IsDone,
      RequestBodyWrapper::GetChunkData,
      RequestBodyWrapper::GetChunkSize,
      RequestBodyWrapper::Next,
      username_.empty() ? NULL : username_.c_str(),
      password_.empty() ? NULL : password_.c_str(),
      timeout_,
      certificateFile_.empty() ? NULL : certificateFile_.c_str(),
      certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(),
      certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(),
      pkcs11_ ? 1 : 0);

    if (error != OrthancPluginErrorCode_Success)
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
    }
  }
#endif    

  void HttpClient::ExecuteWithoutStream(uint16_t& httpStatus,
                                        HttpHeaders& answerHeaders,
                                        std::string& answerBody,
                                        const std::string& body) const
  {
    HeadersWrapper headers(headers_);

    MemoryBuffer answerBodyBuffer, answerHeadersBuffer;

    if (body.size() > 0xffffffffu)
    {
      ORTHANC_PLUGINS_LOG_ERROR("Cannot handle body size > 4GB");
      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
    }

    OrthancPluginErrorCode error = OrthancPluginHttpClient(
      GetGlobalContext(),
      *answerBodyBuffer,
      *answerHeadersBuffer,
      &httpStatus,
      method_,
      url_.c_str(),
      headers.GetCount(),
      headers.GetKeys(),
      headers.GetValues(),
      body.empty() ? NULL : body.c_str(),
      body.size(),
      username_.empty() ? NULL : username_.c_str(),
      password_.empty() ? NULL : password_.c_str(),
      timeout_,
      certificateFile_.empty() ? NULL : certificateFile_.c_str(),
      certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(),
      certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(),
      pkcs11_ ? 1 : 0);

    if (error != OrthancPluginErrorCode_Success)
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
    }

    DecodeHttpHeaders(answerHeaders, answerHeadersBuffer);
    answerBodyBuffer.ToString(answerBody);
  }


  void HttpClient::Execute(IAnswer& answer)
  {
#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
    if (allowChunkedTransfers_)
    {
      if (chunkedBody_ != NULL)
      {
        ExecuteWithStream(httpStatus_, answer, *chunkedBody_);
      }
      else
      {
        MemoryRequestBody wrapper(fullBody_);
        ExecuteWithStream(httpStatus_, answer, wrapper);
      }

      return;
    }
#endif
    
    // Compatibility mode for Orthanc SDK <= 1.5.6 or if chunked
    // transfers are disabled. This results in higher memory usage
    // (all chunks from the answer body are sent at once)

    HttpHeaders answerHeaders;
    std::string answerBody;
    Execute(answerHeaders, answerBody);

    for (HttpHeaders::const_iterator it = answerHeaders.begin(); 
         it != answerHeaders.end(); ++it)
    {
      answer.AddHeader(it->first, it->second);      
    }

    if (!answerBody.empty())
    {
      answer.AddChunk(answerBody.c_str(), answerBody.size());
    }
  }


  void HttpClient::Execute(HttpHeaders& answerHeaders /* out */,
                           std::string& answerBody /* out */)
  {
#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
    if (allowChunkedTransfers_)
    {
      MemoryAnswer answer;
      Execute(answer);
      answerHeaders = answer.GetHeaders();
      answer.GetBody().Flatten(answerBody);
      return;
    }
#endif
    
    // Compatibility mode for Orthanc SDK <= 1.5.6 or if chunked
    // transfers are disabled. This results in higher memory usage
    // (all chunks from the request body are sent at once)

    if (chunkedBody_ != NULL)
    {
      ChunkedBuffer buffer;
      
      std::string chunk;
      while (chunkedBody_->ReadNextChunk(chunk))
      {
        buffer.AddChunk(chunk);
      }

      std::string body;
      buffer.Flatten(body);

      ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, body);
    }
    else
    {
      ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, fullBody_);
    }
  }


  void HttpClient::Execute(HttpHeaders& answerHeaders /* out */,
                           Json::Value& answerBody /* out */)
  {
    std::string body;
    Execute(answerHeaders, body);
    
    if (!ReadJson(answerBody, body))
    {
      ORTHANC_PLUGINS_LOG_ERROR("Cannot convert HTTP answer body to JSON");
      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
    }
  }


  void HttpClient::Execute()
  {
    HttpHeaders answerHeaders;
    std::string body;
    Execute(answerHeaders, body);
  }

#endif  /* HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 */





  /******************************************************************
   ** CHUNKED HTTP SERVER
   ******************************************************************/

  namespace Internals
  {
    void NullRestCallback(OrthancPluginRestOutput* output,
                          const char* url,
                          const OrthancPluginHttpRequest* request)
    {
    }
  
    IChunkedRequestReader *NullChunkedRestCallback(const char* url,
                                                   const OrthancPluginHttpRequest* request)
    {
      return NULL;
    }


#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1

    OrthancPluginErrorCode ChunkedRequestReaderAddChunk(
      OrthancPluginServerChunkedRequestReader* reader,
      const void*                              data,
      uint32_t                                 size)
    {
      try
      {
        if (reader == NULL)
        {
          return OrthancPluginErrorCode_InternalError;
        }

        reinterpret_cast<IChunkedRequestReader*>(reader)->AddChunk(data, size);
        return OrthancPluginErrorCode_Success;
      }
      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
      {
        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
      }
      catch (boost::bad_lexical_cast&)
      {
        return OrthancPluginErrorCode_BadFileFormat;
      }
      catch (...)
      {
        return OrthancPluginErrorCode_Plugin;
      }
    }

    
    OrthancPluginErrorCode ChunkedRequestReaderExecute(
      OrthancPluginServerChunkedRequestReader* reader,
      OrthancPluginRestOutput*                 output)
    {
      try
      {
        if (reader == NULL)
        {
          return OrthancPluginErrorCode_InternalError;
        }

        reinterpret_cast<IChunkedRequestReader*>(reader)->Execute(output);
        return OrthancPluginErrorCode_Success;
      }
      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
      {
        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
      }
      catch (boost::bad_lexical_cast&)
      {
        return OrthancPluginErrorCode_BadFileFormat;
      }
      catch (...)
      {
        return OrthancPluginErrorCode_Plugin;
      }
    }

    
    void ChunkedRequestReaderFinalize(
      OrthancPluginServerChunkedRequestReader* reader)
    {
      if (reader != NULL)
      {
        delete reinterpret_cast<IChunkedRequestReader*>(reader);
      }
    }

#else
    
    OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output,
                                                    const char* url,
                                                    const OrthancPluginHttpRequest* request,
                                                    RestCallback         GetHandler,
                                                    ChunkedRestCallback  PostHandler,
                                                    RestCallback         DeleteHandler,
                                                    ChunkedRestCallback  PutHandler)
    {
      try
      {
        std::string allowed;

        if (GetHandler != Internals::NullRestCallback)
        {
          allowed += "GET";
        }

        if (PostHandler != Internals::NullChunkedRestCallback)
        {
          if (!allowed.empty())
          {
            allowed += ",";
          }
        
          allowed += "POST";
        }

        if (DeleteHandler != Internals::NullRestCallback)
        {
          if (!allowed.empty())
          {
            allowed += ",";
          }
        
          allowed += "DELETE";
        }

        if (PutHandler != Internals::NullChunkedRestCallback)
        {
          if (!allowed.empty())
          {
            allowed += ",";
          }
        
          allowed += "PUT";
        }
      
        switch (request->method)
        {
          case OrthancPluginHttpMethod_Get:
            if (GetHandler == Internals::NullRestCallback)
            {
              OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
            }
            else
            {
              GetHandler(output, url, request);
            }

            break;

          case OrthancPluginHttpMethod_Post:
            if (PostHandler == Internals::NullChunkedRestCallback)
            {
              OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
            }
            else
            {
              boost::movelib::unique_ptr<IChunkedRequestReader> reader(PostHandler(url, request));
              if (reader.get() == NULL)
              {
                ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
              }
              else
              {
                reader->AddChunk(request->body, request->bodySize);
                reader->Execute(output);
              }
            }

            break;

          case OrthancPluginHttpMethod_Delete:
            if (DeleteHandler == Internals::NullRestCallback)
            {
              OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
            }
            else
            {
              DeleteHandler(output, url, request);
            }

            break;

          case OrthancPluginHttpMethod_Put:
            if (PutHandler == Internals::NullChunkedRestCallback)
            {
              OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str());
            }
            else
            {
              boost::movelib::unique_ptr<IChunkedRequestReader> reader(PutHandler(url, request));
              if (reader.get() == NULL)
              {
                ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
              }
              else
              {
                reader->AddChunk(request->body, request->bodySize);
                reader->Execute(output);
              }
            }

            break;

          default:
            ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
        }

        return OrthancPluginErrorCode_Success;
      }
      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
      {
#if HAS_ORTHANC_EXCEPTION == 1 && HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS == 1
        if (HasGlobalContext() &&
            e.HasDetails())
        {
          // The "false" instructs Orthanc not to log the detailed
          // error message. This is to avoid duplicating the details,
          // because "OrthancException" already does it on construction.
          OrthancPluginSetHttpErrorDetails
            (GetGlobalContext(), output, e.GetDetails(), false);
        }
#endif

        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
      }
      catch (boost::bad_lexical_cast&)
      {
        return OrthancPluginErrorCode_BadFileFormat;
      }
      catch (...)
      {
        return OrthancPluginErrorCode_Plugin;
      }
    }
#endif
  }


#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1
  OrthancPluginErrorCode IStorageCommitmentScpHandler::Lookup(
    OrthancPluginStorageCommitmentFailureReason* target,
    void* rawHandler,
    const char* sopClassUid,
    const char* sopInstanceUid)
  {
    assert(target != NULL &&
           rawHandler != NULL);
      
    try
    {
      IStorageCommitmentScpHandler& handler = *reinterpret_cast<IStorageCommitmentScpHandler*>(rawHandler);
      *target = handler.Lookup(sopClassUid, sopInstanceUid);
      return OrthancPluginErrorCode_Success;
    }
    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
    {
      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
    }
    catch (...)
    {
      return OrthancPluginErrorCode_Plugin;
    }
  }
#endif


#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1
  void IStorageCommitmentScpHandler::Destructor(void* rawHandler)
  {
    assert(rawHandler != NULL);
    delete reinterpret_cast<IStorageCommitmentScpHandler*>(rawHandler);
  }
#endif


#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)    
  DicomInstance::DicomInstance(const OrthancPluginDicomInstance* instance) :
    toFree_(false),
    instance_(instance)
  {
  }
#else
  DicomInstance::DicomInstance(OrthancPluginDicomInstance* instance) :
    toFree_(false),
    instance_(instance)
  {
  }
#endif


#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
  DicomInstance::DicomInstance(const void* buffer,
                               size_t size) :
    toFree_(true),
    instance_(OrthancPluginCreateDicomInstance(GetGlobalContext(), buffer, size))
  {
    if (instance_ == NULL)
    {
      ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer);
    }
  }
#endif


  DicomInstance::~DicomInstance()
  {
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
    if (toFree_ &&
        instance_ != NULL)
    {
      OrthancPluginFreeDicomInstance(
        GetGlobalContext(), const_cast<OrthancPluginDicomInstance*>(instance_));
    }
#endif
  }

  
  std::string DicomInstance::GetRemoteAet() const
  {
    const char* s = OrthancPluginGetInstanceRemoteAet(GetGlobalContext(), instance_);
    if (s == NULL)
    {
      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
    }
    else
    {
      return std::string(s);
    }
  }


  void DicomInstance::GetJson(Json::Value& target) const
  {
    OrthancString s;
    s.Assign(OrthancPluginGetInstanceJson(GetGlobalContext(), instance_));
    s.ToJson(target);
  }
  

  void DicomInstance::GetSimplifiedJson(Json::Value& target) const
  {
    OrthancString s;
    s.Assign(OrthancPluginGetInstanceSimplifiedJson(GetGlobalContext(), instance_));
    s.ToJson(target);
  }


#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
  std::string DicomInstance::GetTransferSyntaxUid() const
  {
    OrthancString s;
    s.Assign(OrthancPluginGetInstanceTransferSyntaxUid(GetGlobalContext(), instance_));

    std::string result;
    s.ToString(result);
    return result;
  }
#endif

  
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
  bool DicomInstance::HasPixelData() const
  {
    int32_t result = OrthancPluginHasInstancePixelData(GetGlobalContext(), instance_);
    if (result < 0)
    {
      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
    }
    else
    {
      return (result != 0);
    }
  }
#endif


#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)  
  void DicomInstance::GetRawFrame(std::string& target,
                                  unsigned int frameIndex) const
  {
    MemoryBuffer buffer;
    OrthancPluginErrorCode code = OrthancPluginGetInstanceRawFrame(
      GetGlobalContext(), *buffer, instance_, frameIndex);

    if (code == OrthancPluginErrorCode_Success)
    {
      buffer.ToString(target);
    }
    else
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
    }
  }
#endif


#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)  
  OrthancImage* DicomInstance::GetDecodedFrame(unsigned int frameIndex) const
  {
    OrthancPluginImage* image = OrthancPluginGetInstanceDecodedFrame(
      GetGlobalContext(), instance_, frameIndex);

    if (image == NULL)
    {
      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
    }
    else
    {
      return new OrthancImage(image);
    }
  }
#endif  


#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
  void DicomInstance::Serialize(std::string& target) const
  {
    MemoryBuffer buffer;
    OrthancPluginErrorCode code = OrthancPluginSerializeDicomInstance(
      GetGlobalContext(), *buffer, instance_);

    if (code == OrthancPluginErrorCode_Success)
    {
      buffer.ToString(target);
    }
    else
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
    }
  }
#endif
  

#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
  DicomInstance* DicomInstance::Transcode(const void* buffer,
                                          size_t size,
                                          const std::string& transferSyntax)
  {
    OrthancPluginDicomInstance* instance = OrthancPluginTranscodeDicomInstance(
      GetGlobalContext(), buffer, size, transferSyntax.c_str());

    if (instance == NULL)
    {
      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
    }
    else
    {
      boost::movelib::unique_ptr<DicomInstance> result(new DicomInstance(instance));
      result->toFree_ = true;
      return result.release();
    }
  }
#endif


#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 1)
  DicomInstance* DicomInstance::Load(const std::string& instanceId,
                                     OrthancPluginLoadDicomInstanceMode mode)
  {
    OrthancPluginDicomInstance* instance = OrthancPluginLoadDicomInstance(
      GetGlobalContext(), instanceId.c_str(), mode);

    if (instance == NULL)
    {
      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
    }
    else
    {
      boost::movelib::unique_ptr<DicomInstance> result(new DicomInstance(instance));
      result->toFree_ = true;
      return result.release();
    }
  }
#endif


#if HAS_ORTHANC_PLUGIN_WEBDAV == 1
  static std::vector<std::string> WebDavConvertPath(uint32_t pathSize,
                                                    const char* const*  pathItems)
  {
    std::vector<std::string> result(pathSize);

    for (uint32_t i = 0; i < pathSize; i++)
    {
      result[i] = pathItems[i];
    }

    return result;
  }
#endif
  
    
#if HAS_ORTHANC_PLUGIN_WEBDAV == 1
  static OrthancPluginErrorCode WebDavIsExistingFolder(uint8_t*            isExisting,
                                                       uint32_t            pathSize,
                                                       const char* const*  pathItems,
                                                       void*               payload)
  {
    IWebDavCollection& that = *reinterpret_cast<IWebDavCollection*>(payload);

    try
    {
      *isExisting = (that.IsExistingFolder(WebDavConvertPath(pathSize, pathItems)) ? 1 : 0);
      return OrthancPluginErrorCode_Success;
    }
    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
    {
      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
    }
    catch (...)
    {
      return OrthancPluginErrorCode_Plugin;
    }
  }
#endif

  
#if HAS_ORTHANC_PLUGIN_WEBDAV == 1
  static OrthancPluginErrorCode WebDavListFolder(uint8_t*                        isExisting,
                                                 OrthancPluginWebDavCollection*  collection,
                                                 OrthancPluginWebDavAddFile      addFile,
                                                 OrthancPluginWebDavAddFolder    addFolder,
                                                 uint32_t                        pathSize,
                                                 const char* const*              pathItems,
                                                 void*                           payload)
  {
    IWebDavCollection& that = *reinterpret_cast<IWebDavCollection*>(payload);
      
    try
    {
      std::list<IWebDavCollection::FileInfo> files;
      std::list<IWebDavCollection::FolderInfo> subfolders;
      
      if (!that.ListFolder(files, subfolders, WebDavConvertPath(pathSize, pathItems)))
      {
        *isExisting = 0;
      }
      else
      {
        *isExisting = 1;
      
        for (std::list<IWebDavCollection::FileInfo>::const_iterator
               it = files.begin(); it != files.end(); ++it)
        {
          OrthancPluginErrorCode code = addFile(
            collection, it->GetName().c_str(), it->GetContentSize(),
            it->GetMimeType().c_str(), it->GetDateTime().c_str());
        
          if (code != OrthancPluginErrorCode_Success)
          {
            return code;
          }
        }
      
        for (std::list<IWebDavCollection::FolderInfo>::const_iterator it =
               subfolders.begin(); it != subfolders.end(); ++it)
        {
          OrthancPluginErrorCode code = addFolder(
            collection, it->GetName().c_str(), it->GetDateTime().c_str());
        
          if (code != OrthancPluginErrorCode_Success)
          {
            return code;
          }
        }
      }
      
      return OrthancPluginErrorCode_Success;
    }
    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
    {
      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
    }
    catch (...)
    {
      return OrthancPluginErrorCode_Plugin;
    }
  }
#endif    


#if HAS_ORTHANC_PLUGIN_WEBDAV == 1
  static OrthancPluginErrorCode WebDavRetrieveFile(OrthancPluginWebDavCollection*   collection,
                                                   OrthancPluginWebDavRetrieveFile  retrieveFile,
                                                   uint32_t                         pathSize,
                                                   const char* const*               pathItems,
                                                   void*                            payload)
  {
    IWebDavCollection& that = *reinterpret_cast<IWebDavCollection*>(payload);

    try
    {
      std::string content, mime, dateTime;
        
      if (that.GetFile(content, mime, dateTime, WebDavConvertPath(pathSize, pathItems)))
      {
        return retrieveFile(collection, content.empty() ? NULL : content.c_str(),
                            content.size(), mime.c_str(), dateTime.c_str());
      }
      else
      {
        // Inexisting file
        return OrthancPluginErrorCode_Success;
      }
    }
    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
    {
      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
    }
    catch (...)
    {
      return OrthancPluginErrorCode_InternalError;
    }
  }  
#endif


#if HAS_ORTHANC_PLUGIN_WEBDAV == 1
  static OrthancPluginErrorCode WebDavStoreFileCallback(uint8_t*            isReadOnly, /* out */
                                                        uint32_t            pathSize,
                                                        const char* const*  pathItems,
                                                        const void*         data,
                                                        uint64_t            size,
                                                        void*               payload)
  {
    IWebDavCollection& that = *reinterpret_cast<IWebDavCollection*>(payload);

    try
    {
      if (static_cast<uint64_t>(static_cast<size_t>(size)) != size)
      {
        ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
      }
      
      *isReadOnly = (that.StoreFile(WebDavConvertPath(pathSize, pathItems), data,
                                    static_cast<size_t>(size)) ? 1 : 0);
      return OrthancPluginErrorCode_Success;
    }
    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
    {
      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
    }
    catch (...)
    {
      return OrthancPluginErrorCode_InternalError;
    }
  }
#endif

  
#if HAS_ORTHANC_PLUGIN_WEBDAV == 1
  static OrthancPluginErrorCode WebDavCreateFolderCallback(uint8_t*            isReadOnly, /* out */
                                                           uint32_t            pathSize,
                                                           const char* const*  pathItems,
                                                           void*               payload)
  {
    IWebDavCollection& that = *reinterpret_cast<IWebDavCollection*>(payload);

    try
    {
      *isReadOnly = (that.CreateFolder(WebDavConvertPath(pathSize, pathItems)) ? 1 : 0);
      return OrthancPluginErrorCode_Success;
    }
    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
    {
      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
    }
    catch (...)
    {
      return OrthancPluginErrorCode_InternalError;
    }
  }
#endif
  
  
#if HAS_ORTHANC_PLUGIN_WEBDAV == 1
  static OrthancPluginErrorCode WebDavDeleteItemCallback(uint8_t*            isReadOnly, /* out */
                                                         uint32_t            pathSize,
                                                         const char* const*  pathItems,
                                                         void*               payload)
  {
    IWebDavCollection& that = *reinterpret_cast<IWebDavCollection*>(payload);

    try
    {
      *isReadOnly = (that.DeleteItem(WebDavConvertPath(pathSize, pathItems)) ? 1 : 0);
      return OrthancPluginErrorCode_Success;
    }
    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
    {
      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
    }
    catch (...)
    {
      return OrthancPluginErrorCode_InternalError;
    }
  }
#endif

  
#if HAS_ORTHANC_PLUGIN_WEBDAV == 1
  void IWebDavCollection::Register(const std::string& uri,
                                   IWebDavCollection& collection)
  {
    OrthancPluginErrorCode code = OrthancPluginRegisterWebDavCollection(
      GetGlobalContext(), uri.c_str(), WebDavIsExistingFolder, WebDavListFolder, WebDavRetrieveFile,
      WebDavStoreFileCallback, WebDavCreateFolderCallback, WebDavDeleteItemCallback, &collection);

    if (code != OrthancPluginErrorCode_Success)
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
    }
  }
#endif

  void GetGetArguments(GetArguments& result, const OrthancPluginHttpRequest* request)
  {
    result.clear();

    for (uint32_t i = 0; i < request->getCount; ++i)
    {
      result[request->getKeys[i]] = request->getValues[i];
    }    
  }

  void GetHttpHeaders(HttpHeaders& result, const OrthancPluginHttpRequest* request)
  {
    result.clear();

    for (uint32_t i = 0; i < request->headersCount; ++i)
    {
      result[request->headersKeys[i]] = request->headersValues[i];
    }    
  }

  void SerializeGetArguments(std::string& output, const OrthancPluginHttpRequest* request)
  {
    output.clear();
    std::vector<std::string> arguments;
    for (uint32_t i = 0; i < request->getCount; ++i)
    {
      if (request->getValues[i] && strlen(request->getValues[i]) > 0)
      {
        arguments.push_back(std::string(request->getKeys[i]) + "=" + std::string(request->getValues[i]));
      }
      else
      {
        arguments.push_back(std::string(request->getKeys[i]));
      }
    }

    output = boost::algorithm::join(arguments, "&");
  }


#if !ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 4)
  static void SetPluginProperty(const std::string& pluginIdentifier,
                                _OrthancPluginProperty property,
                                const std::string& value)
  {
    _OrthancPluginSetPluginProperty params;
    params.plugin = pluginIdentifier.c_str();
    params.property = property;
    params.value = value.c_str();

    GetGlobalContext()->InvokeService(GetGlobalContext(), _OrthancPluginService_SetPluginProperty, &params);
  }
#endif

  void SetRootUri(const std::string& pluginIdentifier,
                  const std::string& uri)
  {
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 4)
    OrthancPluginSetRootUri2(GetGlobalContext(), pluginIdentifier.c_str(), uri.c_str());
#else
    SetPluginProperty(pluginIdentifier, _OrthancPluginProperty_RootUri, uri);
#endif
  }

  void SetDescription(const std::string& pluginIdentifier,
                      const std::string& description)
  {
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 4)
    OrthancPluginSetDescription2(GetGlobalContext(), pluginIdentifier.c_str(), description.c_str());
#else
    SetPluginProperty(pluginIdentifier, _OrthancPluginProperty_Description, description);
#endif
  }

  void ExtendOrthancExplorer(const std::string& pluginIdentifier,
                             const std::string& javascript)
  {
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 4)
    OrthancPluginExtendOrthancExplorer2(GetGlobalContext(), pluginIdentifier.c_str(), javascript.c_str());
#else
    SetPluginProperty(pluginIdentifier, _OrthancPluginProperty_OrthancExplorer, javascript);
#endif
  }


#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1
  RestApiClient::RestApiClient() :
    method_(OrthancPluginHttpMethod_Get),
    path_("/"),
    afterPlugins_(false),
    httpStatus_(0)
  {
  }

  RestApiClient::RestApiClient(const char* url,
                               const OrthancPluginHttpRequest* request) :
    method_(request->method),
    path_(url),
    afterPlugins_(false),
    httpStatus_(0)
  {
    OrthancPlugins::GetHttpHeaders(requestHeaders_, request);

    std::string getArguments;
    OrthancPlugins::SerializeGetArguments(getArguments, request);

    if (!getArguments.empty())
    {
      path_ += "?" + getArguments;
    }

    if (request->bodySize > 0 && request->body != NULL)
    {
      requestBody_.assign(reinterpret_cast<const char*>(request->body), request->bodySize);
    }
  }
#endif


#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1
  void RestApiClient::AddRequestHeader(const std::string& key,
                                       const std::string& value)
  {
    if (requestHeaders_.find(key) == requestHeaders_.end())
    {
      requestHeaders_[key] = value;
    }
    else
    {
      ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
    }
  }
#endif


#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1
  void RestApiClient::SetRequestHeader(const std::string& key,
                                       const std::string& value)
  {
    requestHeaders_[key] = value;
  }
#endif


#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1
  bool RestApiClient::Execute()
  {
    if (requestBody_.size() > 0xffffffffu)
    {
      ORTHANC_PLUGINS_LOG_ERROR("Cannot handle body size > 4GB");
      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
    }

    PluginHttpHeaders converted(requestHeaders_);

    MemoryBuffer body;
    MemoryBuffer headers;

    OrthancPluginErrorCode code = OrthancPluginCallRestApi(GetGlobalContext(), *body, *headers, &httpStatus_, method_, path_.c_str(),
                                                           requestHeaders_.size(), converted.GetKeys(), converted.GetValues(),
                                                           requestBody_.c_str(), requestBody_.size(), afterPlugins_ ? 1 : 0);

    answerHeaders_.clear();
    answerBody_.clear();

    if (code == OrthancPluginErrorCode_Success)
    {
      if (httpStatus_ == 0)
      {
        ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
      }

      DecodeHttpHeaders(answerHeaders_, headers);
      body.ToString(answerBody_);
      return true;
    }
    else
    {
      if (code == OrthancPluginErrorCode_UnknownResource ||
          code == OrthancPluginErrorCode_InexistentItem)
      {
        httpStatus_ = 404;
        return false;
      }
      else
      {
        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
      }
    }
  }

  void RestApiClient::ExecuteAndForwardAnswer(OrthancPluginContext* context, OrthancPluginRestOutput* output)
  {
    if (Execute())
    {
      ForwardAnswer(context, output);
    }
  }

  void RestApiClient::ForwardAnswer(OrthancPluginContext* context, OrthancPluginRestOutput* output)
  {
    if (httpStatus_ == 200)
    {
      const char* mimeType = NULL;
      for (HttpHeaders::const_iterator h = answerHeaders_.begin(); h != answerHeaders_.end(); ++h)
      {
        if (h->first == "content-type")
        {
          mimeType = h->second.c_str();
        }
      }
      
      AnswerString(answerBody_, mimeType, output);
    }
    else
    {
      AnswerHttpError(httpStatus_, output);
    }
  }

  bool RestApiClient::GetAnswerJson(Json::Value& output) const
  {
    return ReadJson(output, answerBody_);
  }
#endif


#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1
  uint16_t RestApiClient::GetHttpStatus() const
  {
    if (httpStatus_ == 0)
    {
      ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
    }
    else
    {
      return httpStatus_;
    }
  }
#endif


#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1
  bool RestApiClient::LookupAnswerHeader(std::string& value,
                                         const std::string& key) const
  {
    if (httpStatus_ == 0)
    {
      ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
    }
    else
    {
      HttpHeaders::const_iterator found = answerHeaders_.find(key);
      if (found == answerHeaders_.end())
      {
        return false;
      }
      else
      {
        value = found->second;
        return true;
      }
    }
  }
#endif


#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1
  const std::string& RestApiClient::GetAnswerBody() const
  {
    if (httpStatus_ == 0)
    {
      ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
    }
    else
    {
      return answerBody_;
    }
  }
#endif


#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1
  KeyValueStore::Iterator::Iterator(OrthancPluginKeysValuesIterator  *iterator) :
    iterator_(iterator)
  {
    if (iterator_ == NULL)
    {
      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
    }
  }
#endif


#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1
  KeyValueStore::Iterator::~Iterator()
  {
    OrthancPluginFreeKeysValuesIterator(OrthancPlugins::GetGlobalContext(), iterator_);
  }
#endif


#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1
  bool KeyValueStore::Iterator::Next()
  {
    uint8_t done;
    OrthancPluginErrorCode code = OrthancPluginKeysValuesIteratorNext(OrthancPlugins::GetGlobalContext(), &done, iterator_);

    if (code != OrthancPluginErrorCode_Success)
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
    }
    else
    {
      return (done != 0);
    }
  }
#endif


#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1
  std::string KeyValueStore::Iterator::GetKey() const
  {
    const char* s = OrthancPluginKeysValuesIteratorGetKey(OrthancPlugins::GetGlobalContext(), iterator_);
    if (s == NULL)
    {
      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
    }
    else
    {
      return s;
    }
  }
#endif


#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1
  void KeyValueStore::Iterator::GetValue(std::string& value) const
  {
    OrthancPlugins::MemoryBuffer valueBuffer;
    OrthancPluginErrorCode code = OrthancPluginKeysValuesIteratorGetValue(OrthancPlugins::GetGlobalContext(), *valueBuffer, iterator_);

    if (code != OrthancPluginErrorCode_Success)
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
    }
    else
    {
      valueBuffer.ToString(value);
    }
  }
#endif


#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1
  void KeyValueStore::Store(const std::string& key,
                            const void* value,
                            size_t valueSize)
  {
    if (static_cast<size_t>(static_cast<uint32_t>(valueSize)) != valueSize)
    {
      ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
    }

    OrthancPluginErrorCode code = OrthancPluginStoreKeyValue(OrthancPlugins::GetGlobalContext(), storeId_.c_str(),
                                                             key.c_str(), value, static_cast<uint32_t>(valueSize));
    if (code != OrthancPluginErrorCode_Success)
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
    }
  }
#endif


#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1
  bool KeyValueStore::GetValue(std::string& value,
                               const std::string& key)
  {
    uint8_t found = false;
    OrthancPlugins::MemoryBuffer valueBuffer;
    OrthancPluginErrorCode code = OrthancPluginGetKeyValue(OrthancPlugins::GetGlobalContext(), &found,
                                                           *valueBuffer, storeId_.c_str(), key.c_str());

    if (code != OrthancPluginErrorCode_Success)
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
    }
    else if (found)
    {
      valueBuffer.ToString(value);
      return true;
    }
    else
    {
      return false;
    }
  }
#endif


#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1
  void KeyValueStore::DeleteKey(const std::string& key)
  {
    OrthancPluginErrorCode code = OrthancPluginDeleteKeyValue(OrthancPlugins::GetGlobalContext(),
                                                              storeId_.c_str(), key.c_str());

    if (code != OrthancPluginErrorCode_Success)
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
    }
  }
#endif


#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1
  KeyValueStore::Iterator* KeyValueStore::CreateIterator()
  {
    return new Iterator(OrthancPluginCreateKeysValuesIterator(OrthancPlugins::GetGlobalContext(), storeId_.c_str()));
  }
#endif


#if HAS_ORTHANC_PLUGIN_QUEUES == 1
  void Queue::Enqueue(const void* value,
                      size_t valueSize)
  {
    if (static_cast<size_t>(static_cast<uint32_t>(valueSize)) != valueSize)
    {
      ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
    }

    OrthancPluginErrorCode code = OrthancPluginEnqueueValue(OrthancPlugins::GetGlobalContext(),
                                                            queueId_.c_str(), value, static_cast<uint32_t>(valueSize));

    if (code != OrthancPluginErrorCode_Success)
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
    }
  }
#endif


#if HAS_ORTHANC_PLUGIN_QUEUES == 1
  bool Queue::DequeueInternal(std::string& value,
                              OrthancPluginQueueOrigin origin)
  {
    uint8_t found = false;
    OrthancPlugins::MemoryBuffer valueBuffer;

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
    OrthancPluginErrorCode code = OrthancPluginDequeueValue(OrthancPlugins::GetGlobalContext(), &found,
                                                            *valueBuffer, queueId_.c_str(), origin);
#pragma GCC diagnostic pop

    if (code != OrthancPluginErrorCode_Success)
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
    }
    else if (found)
    {
      valueBuffer.ToString(value);
      return true;
    }
    else
    {
      return false;
    }
  }
#endif


#if HAS_ORTHANC_PLUGIN_QUEUES == 1
  uint64_t Queue::GetSize()
  {
    uint64_t size = 0;
    OrthancPluginErrorCode code = OrthancPluginGetQueueSize(OrthancPlugins::GetGlobalContext(), queueId_.c_str(), &size);

    if (code != OrthancPluginErrorCode_Success)
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
    }
    else
    {
      return size;
    }
  }
#endif


#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1
  bool Queue::ReserveInternal(std::string& value, uint64_t& valueId, OrthancPluginQueueOrigin origin, uint32_t releaseTimeout)
  {
    uint8_t found = false;
    OrthancPlugins::MemoryBuffer valueBuffer;

    OrthancPluginErrorCode code = OrthancPluginReserveQueueValue(OrthancPlugins::GetGlobalContext(), &found,
                                                                 *valueBuffer, &valueId, queueId_.c_str(), origin, releaseTimeout);

    if (code != OrthancPluginErrorCode_Success)
    {
      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
    }
    else if (found)
    {
      valueBuffer.ToString(value);
      return true;
    }
    else
    {
      return false;
    }
  }
#endif


#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1
  bool Queue::ReserveBack(std::string& value, uint64_t& valueId, uint32_t releaseTimeout)
  {
    return ReserveInternal(value, valueId, OrthancPluginQueueOrigin_Back, releaseTimeout);
  }
#endif


#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1
  bool Queue::ReserveFront(std::string& value, uint64_t& valueId, uint32_t releaseTimeout)
  {
    return ReserveInternal(value, valueId, OrthancPluginQueueOrigin_Front, releaseTimeout);
  }
#endif


#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1
  void Queue::Acknowledge(uint64_t valueId)
  {
    OrthancPluginAcknowledgeQueueValue(OrthancPlugins::GetGlobalContext(), queueId_.c_str(), valueId);
  }
#endif
}
