/**
 * 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-2025 Orthanc Team SRL, Belgium
 * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
 *
 * This program is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Lesser 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program. If not, see
 * <http://www.gnu.org/licenses/>.
 **/


#pragma once

#include "Enumerations.h"
#include "OrthancFramework.h"

#include <stdint.h>
#include <vector>
#include <string>
#include <json/value.h>


#if !defined(ORTHANC_ENABLE_BASE64)
#  error The macro ORTHANC_ENABLE_BASE64 must be defined
#endif

#if !defined(ORTHANC_ENABLE_LOCALE)
#  error The macro ORTHANC_ENABLE_LOCALE must be defined
#endif

#if !defined(ORTHANC_ENABLE_MD5)
#  error The macro ORTHANC_ENABLE_MD5 must be defined
#endif

#if !defined(ORTHANC_ENABLE_PUGIXML)
#  error The macro ORTHANC_ENABLE_PUGIXML must be defined
#endif

#if !defined(ORTHANC_ENABLE_SSL)
#  error The macro ORTHANC_ENABLE_SSL must be defined
#endif


/**
 * NOTE: GUID vs. UUID
 * The simple answer is: no difference, they are the same thing. Treat
 * them as a 16 byte (128 bits) value that is used as a unique
 * value. In Microsoft-speak they are called GUIDs, but call them
 * UUIDs when not using Microsoft-speak.
 * http://stackoverflow.com/questions/246930/is-there-any-difference-between-a-guid-and-a-uuid
 **/


#if ORTHANC_ENABLE_PUGIXML == 1
#  include <pugixml.hpp>
#endif

#include <boost/date_time/posix_time/posix_time.hpp>


namespace Orthanc
{
  typedef std::vector<std::string> UriComponents;

  class NullType
  {
  };

  class ORTHANC_PUBLIC Toolbox
  {
  public:
#if ORTHANC_ENABLE_MD5 == 1
    class ORTHANC_PUBLIC MD5Context : public boost::noncopyable
    {
    private:
      class PImpl;
      boost::shared_ptr<PImpl> pimpl_;

    public:
      MD5Context();

      void Append(const void* data,
                  size_t size);

      void Append(const std::string& source);

      void Export(std::string& target);
    };
#endif

    class ORTHANC_PUBLIC LinesIterator : public boost::noncopyable
    {
    private:
      const std::string& content_;
      size_t             lineStart_;
      size_t             lineEnd_;

      void FindEndOfLine();
  
    public:
      explicit LinesIterator(const std::string& content);
  
      bool GetLine(std::string& target) const;

      void Next();
    };
    
    static void ToUpperCase(std::string& s);  // Inplace version

    static void ToLowerCase(std::string& s);  // Inplace version

    static void ToUpperCase(std::string& result,
                            const std::string& source);

    static void ToLowerCase(std::string& result,
                            const std::string& source);

    static void SplitUriComponents(UriComponents& components,
                                   const std::string& uri);
  
    static void TruncateUri(UriComponents& target,
                            const UriComponents& source,
                            size_t fromLevel);
  
    static bool IsChildUri(const UriComponents& baseUri,
                           const UriComponents& testedUri);

    static std::string FlattenUri(const UriComponents& components,
                                  size_t fromLevel = 0);

    static std::string JoinUri(const std::string& base, const std::string& uri);
    
#if ORTHANC_ENABLE_MD5 == 1
    static void ComputeMD5(std::string& result,
                           const std::string& data);

    static void ComputeMD5(std::string& result,
                           const void* data,
                           size_t size);

    static void ComputeMD5(std::string& result,
                           const std::set<std::string>& data);
#endif

    static void ComputeSHA1(std::string& result,
                            const std::string& data);

    static void ComputeSHA1(std::string& result,
                            const void* data,
                            size_t size);

    static bool IsSHA1(const void* str,
                       size_t size);

    static bool IsSHA1(const std::string& s);

#if ORTHANC_ENABLE_BASE64 == 1
    static void DecodeBase64(std::string& result, 
                             const std::string& data);

    static void EncodeBase64(std::string& result, 
                             const std::string& data);

    static bool DecodeDataUriScheme(std::string& mime,
                                    std::string& content,
                                    const std::string& source);

    static void EncodeDataUriScheme(std::string& result,
                                    const std::string& mime,
                                    const std::string& content);
#endif

#if ORTHANC_ENABLE_LOCALE == 1
    static std::string ConvertToUtf8(const std::string& source,
                                     Encoding sourceEncoding,
                                     bool hasCodeExtensions);

    static std::string ConvertFromUtf8(const std::string& source,
                                       Encoding targetEncoding);
#endif

    static bool IsAsciiString(const void* data,
                              size_t size);

    static bool IsAsciiString(const std::string& s);

    static std::string ConvertToAscii(const std::string& source);

    static std::string StripSpaces(const std::string& source);

    // In-place percent-decoding for URL
    static void UrlDecode(std::string& s);

    static Endianness DetectEndianness();

    static std::string WildcardToRegularExpression(const std::string& s);

    // TokenizeString result might contain empty strings (not SplitString)
    static void TokenizeString(std::vector<std::string>& result,
                               const std::string& source,
                               char separator);

    // SplitString result won't contain empty strings (compared to TokenizeString)
    static void SplitString(std::vector<std::string>& result,
                            const std::string& source,
                            char separator);

    // SplitString result won't contain empty strings (compared to TokenizeString)
    static void SplitString(std::set<std::string>& result,
                            const std::string& source,
                            char separator);

    static void JoinStrings(std::string& result,
                            const std::set<std::string>& source,
                            const char* separator);

    static void JoinStrings(std::string& result,
                            const std::vector<std::string>& source,
                            const char* separator);

    // returns true if all element of 'needles' are found in 'haystack'
    template <typename T> static bool IsSetInSet(const std::set<T>& needles, const std::set<T>& haystack)
    {
      for (typename std::set<T>::const_iterator it = needles.begin();
            it != needles.end(); ++it)
      {
        if (haystack.count(*it) == 0)
        {
          return false;
        }
      }

      return true;
    }

    // returns the set of elements from 'needles' that are not in 'haystack'
    template <typename T> static size_t GetMissingsFromSet(std::set<T>& missings, const std::set<T>& needles, const std::set<T>& haystack)
    {
      missings.clear();

      for (typename std::set<T>::const_iterator it = needles.begin();
            it != needles.end(); ++it)
      {
        if (haystack.count(*it) == 0)
        {
          missings.insert(*it);
        }
      }

      return missings.size();
    }

    template <typename T> static void AppendSets(std::set<T>& target, const std::set<T>& toAppend)
    {
      for (typename std::set<T>::const_iterator it = toAppend.begin();
            it != toAppend.end(); ++it)
      {
        target.insert(*it);
      }
    }

    template <typename T> static void RemoveSets(std::set<T>& target, const std::set<T>& toRemove)
    {
      for (typename std::set<T>::const_iterator it = toRemove.begin();
            it != toRemove.end(); ++it)
      {
        target.erase(*it);
      }
    }

    // returns the elements that are both in a and b
    template <typename T> static void GetIntersection(std::set<T>& target, const std::set<T>& a, const std::set<T>& b)
    {
      target.clear();

      for (typename std::set<T>::const_iterator it = a.begin();
            it != a.end(); ++it)
      {
        if (b.count(*it) > 0)
        {
          target.insert(*it);
        }
      }
    }


#if ORTHANC_ENABLE_PUGIXML == 1
    static void JsonToXml(std::string& target,
                          const Json::Value& source,
                          const std::string& rootElement = "root",
                          const std::string& arrayElement = "item");
#endif

#if ORTHANC_ENABLE_PUGIXML == 1
    static void XmlToString(std::string& target,
                            const pugi::xml_document& source);
#endif

    static bool IsInteger(const std::string& str);

    static void CopyJsonWithoutComments(Json::Value& target,
                                        const Json::Value& source);

    static bool StartsWith(const std::string& str,
                           const std::string& prefix);

    static void UriEncode(std::string& target,
                          const std::string& source);

    static std::string GetJsonStringField(const ::Json::Value& json,
                                          const std::string& key,
                                          const std::string& defaultValue);

    static bool GetJsonBooleanField(const ::Json::Value& json,
                                    const std::string& key,
                                    bool defaultValue);

    static int GetJsonIntegerField(const ::Json::Value& json,
                                   const std::string& key,
                                   int defaultValue);

    static unsigned int GetJsonUnsignedIntegerField(const ::Json::Value& json,
                                                    const std::string& key,
                                                    unsigned int defaultValue);

    static bool IsUuid(const std::string& str);

    static bool StartsWithUuid(const std::string& str);

#if ORTHANC_ENABLE_LOCALE == 1
    static void InitializeGlobalLocale(const char* locale);

    static void FinalizeGlobalLocale();

    static std::string ToUpperCaseWithAccents(const std::string& source);
#endif

    static void InitializeOpenSsl();

    static void FinalizeOpenSsl();

    static std::string GenerateUuid();

    static std::string SubstituteVariables(const std::string& source,
                                           const std::map<std::string, std::string>& dictionary);

    static void RemoveIso2022EscapeSequences(std::string& dest,
                                             const std::string& src);

    static void Utf8ToUnicodeCharacter(uint32_t& unicode,
                                       size_t& utf8Length,
                                       const std::string& utf8,
                                       size_t position);

    static std::string LargeHexadecimalToDecimal(const std::string& hex);

    // http://dicom.nema.org/medical/dicom/2019a/output/chtml/part05/sect_B.2.html
    static std::string GenerateDicomPrivateUniqueIdentifier();

    static void SimplifyDicomAsJson(Json::Value& target,
                                    const Json::Value& source,
                                    DicomToJsonFormat format);

    static bool ReadJson(Json::Value& target,
                         const std::string& source);

    static bool ReadJson(Json::Value& target,
                         const void* buffer,
                         size_t size);

    static bool ReadJsonWithoutComments(Json::Value& target,
                                        const std::string& source);

    static bool ReadJsonWithoutComments(Json::Value& target,
                                        const void* buffer,
                                        size_t size);

    static void WriteFastJson(std::string& target,
                              const Json::Value& source);

    static void WriteStyledJson(std::string& target,
                                const Json::Value& source);

    static void RemoveSurroundingQuotes(std::string& value);

    class ORTHANC_PUBLIC ElapsedTimer : public boost::noncopyable
    {
    private:
      boost::posix_time::ptime  start_;

    public:
      ElapsedTimer();

      uint64_t GetElapsedMilliseconds();
      uint64_t GetElapsedMicroseconds();
      uint64_t GetElapsedNanoseconds();
      
      std::string GetHumanElapsedDuration();
      std::string GetHumanTransferSpeed(bool full, uint64_t sizeInBytes);
      
      void Restart();
    };

    // This is a helper class to measure and log time spend e.g in a method.
    // This should be used only during debugging and should likely not ever be used in a release.
    // By default, you should use it as a RAII but you may force Restart/StopAndLog manually if needed.
    class ORTHANC_PUBLIC DebugElapsedTimeLogger : public boost::noncopyable
    {
    private:
      ElapsedTimer      timer_;
      const std::string message_;
      bool              logged_;

    public:
      explicit DebugElapsedTimeLogger(const std::string& message);

      ~DebugElapsedTimeLogger();  

      void Restart();
      void StopAndLog();
    };

    // This variant logs the same message when entering the method and when exiting (with the elapsed time).
    // Logs goes to verbose-http.
    class ORTHANC_PUBLIC ApiElapsedTimeLogger : public boost::noncopyable
    {
    private:
      ElapsedTimer      timer_;
      const std::string message_;

    public:
      explicit ApiElapsedTimeLogger(const std::string& message);

      ~ApiElapsedTimeLogger();
    };

    static std::string GetHumanFileSize(uint64_t sizeInBytes);

    static std::string GetHumanDuration(uint64_t durationInNanoseconds);

    static std::string GetHumanTransferSpeed(bool full, uint64_t sizeInBytes, uint64_t durationInNanoseconds);

    static bool ParseVersion(unsigned int& major,
                             unsigned int& minor,
                             unsigned int& revision,
                             const char* version);

    static bool IsVersionAbove(const char* version,
                               unsigned int major,
                               unsigned int minor,
                               unsigned int revision);
  };
}




/**
 * The plain C, opaque data structure "OrthancLinesIterator" is a thin
 * wrapper around Orthanc::Toolbox::LinesIterator, and is only used by
 * "../Resources/Patches/dcmtk-dcdict_orthanc.cc", in order to avoid
 * code duplication
 **/

struct OrthancLinesIterator;

OrthancLinesIterator* OrthancLinesIterator_Create(const std::string& content);

bool OrthancLinesIterator_GetLine(std::string& target,
                                  const OrthancLinesIterator* iterator);

void OrthancLinesIterator_Next(OrthancLinesIterator* iterator);

void OrthancLinesIterator_Free(OrthancLinesIterator* iterator);
