#pragma once
#include <drogon/exports.h>
#include <trantor/utils/Date.h>
#include <trantor/utils/Funcs.h>
#include <trantor/utils/Utilities.h>
#include <trantor/utils/LogStream.h>
#include <memory>
#include <string>
#include <vector>
#include <set>
#include <limits>
#include <sstream>
#include <algorithm>
#include <filesystem>
#include <string_view>
#include <unordered_map>
#include <type_traits>
#ifdef _WIN32
#include <time.h>
DROGON_EXPORT char *strptime(const char *s, const char *f, struct tm *tm);
DROGON_EXPORT time_t timegm(struct tm *tm);
#endif
namespace drogon
{
namespace internal
{
template <typename T, typename = void>
struct CanConvertFromStringStream : std::false_type
{
};
template <typename T>
struct CanConvertFromStringStream<
    T,
    std::void_t<decltype(std::declval<std::stringstream &>() >>
                         std::declval<T &>())>> : std::true_type
{
};
template <typename T>
struct CanConstructFromString : std::is_constructible<T, std::string>
{
};
template <typename T>
struct CanConvertFromString : std::is_assignable<T &, std::string>
{
};
}  
DROGON_EXPORT const std::string_view &statusCodeToString(int code);
namespace utils
{
DROGON_EXPORT bool isInteger(std::string_view str);
DROGON_EXPORT bool isBase64(std::string_view str);
DROGON_EXPORT std::string genRandomString(int length);
DROGON_EXPORT std::string binaryStringToHex(const unsigned char *ptr,
                                            size_t length,
                                            bool lowerCase = false);
DROGON_EXPORT std::string hexToBinaryString(const char *ptr, size_t length);
DROGON_EXPORT std::vector<char> hexToBinaryVector(const char *ptr,
                                                  size_t length);
DROGON_EXPORT void binaryStringToHex(const char *ptr,
                                     size_t length,
                                     char *out,
                                     bool lowerCase = false);
inline std::vector<std::string> splitString(const std::string &str,
                                            const std::string &separator,
                                            bool acceptEmptyString = false)
{
    return trantor::splitString(str, separator, acceptEmptyString);
}
DROGON_EXPORT std::set<std::string> splitStringToSet(
    const std::string &str,
    const std::string &separator);
DROGON_EXPORT std::string getUuid(bool lowercase = true);
constexpr size_t base64EncodedLength(size_t in_len, bool padded = true)
{
    return padded ? ((in_len + 3 - 1) / 3) * 4 : (in_len * 8 + 6 - 1) / 6;
}
DROGON_EXPORT void base64Encode(const unsigned char *bytesToEncode,
                                size_t inLen,
                                unsigned char *outputBuffer,
                                bool urlSafe = false,
                                bool padded = true);
inline std::string base64Encode(const unsigned char *bytesToEncode,
                                size_t inLen,
                                bool urlSafe = false,
                                bool padded = true)
{
    std::string ret;
    ret.resize(base64EncodedLength(inLen, padded));
    base64Encode(
        bytesToEncode, inLen, (unsigned char *)ret.data(), urlSafe, padded);
    return ret;
}
inline std::string base64Encode(std::string_view data,
                                bool urlSafe = false,
                                bool padded = true)
{
    return base64Encode((unsigned char *)data.data(),
                        data.size(),
                        urlSafe,
                        padded);
}
inline void base64EncodeUnpadded(const unsigned char *bytesToEncode,
                                 size_t inLen,
                                 unsigned char *outputBuffer,
                                 bool urlSafe = false)
{
    base64Encode(bytesToEncode, inLen, outputBuffer, urlSafe, false);
}
inline std::string base64EncodeUnpadded(const unsigned char *bytesToEncode,
                                        size_t inLen,
                                        bool urlSafe = false)
{
    return base64Encode(bytesToEncode, inLen, urlSafe, false);
}
inline std::string base64EncodeUnpadded(std::string_view data,
                                        bool urlSafe = false)
{
    return base64Encode(data, urlSafe, false);
}
constexpr size_t base64DecodedLength(size_t inLen)
{
    return (inLen * 3) / 4;
}
DROGON_EXPORT size_t base64Decode(const char *encodedString,
                                  size_t inLen,
                                  unsigned char *outputBuffer);
inline std::string base64Decode(std::string_view encodedString)
{
    auto inLen = encodedString.size();
    std::string ret;
    ret.resize(base64DecodedLength(inLen));
    ret.resize(
        base64Decode(encodedString.data(), inLen, (unsigned char *)ret.data()));
    return ret;
}
DROGON_EXPORT std::vector<char> base64DecodeToVector(
    std::string_view encodedString);
DROGON_EXPORT bool needUrlDecoding(const char *begin, const char *end);
DROGON_EXPORT std::string urlDecode(const char *begin, const char *end);
inline std::string urlDecode(const std::string &szToDecode)
{
    auto begin = szToDecode.data();
    return urlDecode(begin, begin + szToDecode.length());
}
inline std::string urlDecode(const std::string_view &szToDecode)
{
    auto begin = szToDecode.data();
    return urlDecode(begin, begin + szToDecode.length());
}
DROGON_EXPORT std::string urlEncode(const std::string &);
DROGON_EXPORT std::string urlEncodeComponent(const std::string &);
DROGON_EXPORT std::string getMd5(const char *data, const size_t dataLen);
inline std::string getMd5(const std::string &originalString)
{
    return getMd5(originalString.data(), originalString.length());
}
DROGON_EXPORT std::string getSha1(const char *data, const size_t dataLen);
inline std::string getSha1(const std::string &originalString)
{
    return getSha1(originalString.data(), originalString.length());
}
DROGON_EXPORT std::string getSha256(const char *data, const size_t dataLen);
inline std::string getSha256(const std::string &originalString)
{
    return getSha256(originalString.data(), originalString.length());
}
DROGON_EXPORT std::string getSha3(const char *data, const size_t dataLen);
inline std::string getSha3(const std::string &originalString)
{
    return getSha3(originalString.data(), originalString.length());
}
DROGON_EXPORT std::string getBlake2b(const char *data, const size_t dataLen);
inline std::string getBlake2b(const std::string &originalString)
{
    return getBlake2b(originalString.data(), originalString.length());
}
DROGON_EXPORT std::string gzipCompress(const char *data, const size_t ndata);
DROGON_EXPORT std::string gzipDecompress(const char *data, const size_t ndata);
DROGON_EXPORT std::string brotliCompress(const char *data, const size_t ndata);
DROGON_EXPORT std::string brotliDecompress(const char *data,
                                           const size_t ndata);
DROGON_EXPORT char *getHttpFullDate(
    const trantor::Date &date = trantor::Date::now());
DROGON_EXPORT const std::string &getHttpFullDateStr(
    const trantor::Date &date = trantor::Date::now());
DROGON_EXPORT void dateToCustomFormattedString(const std::string &fmtStr,
                                               std::string &str,
                                               const trantor::Date &date);
DROGON_EXPORT trantor::Date getHttpDate(const std::string &httpFullDateString);
DROGON_EXPORT std::string formattedString(const char *format, ...);
DROGON_EXPORT int createPath(const std::string &path);
inline std::string fromWidePath(const std::wstring &strPath)
{
    return trantor::utils::fromWidePath(strPath);
}
inline std::wstring toWidePath(const std::string &strUtf8Path)
{
    return trantor::utils::toWidePath(strUtf8Path);
}
#if defined(_WIN32) && !defined(__MINGW32__)
inline std::wstring toNativePath(const std::string &strPath)
{
    return trantor::utils::toNativePath(strPath);
}
inline const std::wstring &toNativePath(const std::wstring &strPath)
{
    return trantor::utils::toNativePath(strPath);
}
#else   
inline const std::string &toNativePath(const std::string &strPath)
{
    return trantor::utils::toNativePath(strPath);
}
inline std::string toNativePath(const std::wstring &strPath)
{
    return trantor::utils::toNativePath(strPath);
}
#endif  
inline const std::string &fromNativePath(const std::string &strPath)
{
    return trantor::utils::fromNativePath(strPath);
}
inline std::string fromNativePath(const std::wstring &strPath)
{
    return trantor::utils::fromNativePath(strPath);
}
DROGON_EXPORT void replaceAll(std::string &s,
                              const std::string &from,
                              const std::string &to);
DROGON_EXPORT bool secureRandomBytes(void *ptr, size_t size);
DROGON_EXPORT std::string secureRandomString(size_t size);
template <typename T>
T fromString(const std::string &p) noexcept(false)
{
    if constexpr (std::is_integral<T>::value && std::is_signed<T>::value)
    {
        std::size_t pos;
        auto v = std::stoll(p, &pos);
        if (pos != p.size())
            throw std::invalid_argument("Invalid value");
        if ((v < static_cast<long long>((std::numeric_limits<T>::min)())) ||
            (v > static_cast<long long>((std::numeric_limits<T>::max)())))
            throw std::out_of_range("Value out of range");
        return static_cast<T>(v);
    }
    else if constexpr (std::is_integral<T>::value &&
                       (!std::is_signed<T>::value))
    {
        std::size_t pos;
        auto v = std::stoull(p, &pos);
        if (pos != p.size())
            throw std::invalid_argument("Invalid value");
        if (v >
            static_cast<unsigned long long>((std::numeric_limits<T>::max)()))
            throw std::out_of_range("Value out of range");
        return static_cast<T>(v);
    }
    else if constexpr (std::is_floating_point<T>::value)
    {
        std::size_t pos;
        auto v = std::stold(p, &pos);
        if (pos != p.size())
            throw std::invalid_argument("Invalid value");
        if ((v < static_cast<long double>((std::numeric_limits<T>::min)())) ||
            (v > static_cast<long double>((std::numeric_limits<T>::max)())))
            throw std::out_of_range("Value out of range");
        return static_cast<T>(v);
    }
    else if constexpr (internal::CanConvertFromStringStream<T>::value)
    {
        T value{};
        if (!p.empty())
        {
            std::stringstream ss(p);
            ss.exceptions(std::ios_base::failbit);
            ss >> value;
            if (!ss.eof())
                std::runtime_error("Bad type conversion");
        }
        return value;
    }
    else
    {
        throw std::runtime_error("Bad type conversion");
    }
}
template <>
inline std::string fromString<std::string>(const std::string &p) noexcept(false)
{
    return p;
}
template <>
inline bool fromString<bool>(const std::string &p) noexcept(false)
{
    if (!p.empty() && std::all_of(p.begin(), p.end(), [](unsigned char c) {
            return std::isdigit(c);
        }))
        return (std::stoll(p) != 0);
    std::string l{p};
    std::transform(p.begin(), p.end(), l.begin(), [](unsigned char c) {
        return (char)tolower(c);
    });
    if (l == "true")
    {
        return true;
    }
    else if (l == "false")
    {
        return false;
    }
    throw std::runtime_error("Can't convert from string '" + p + "' to bool");
}
DROGON_EXPORT bool supportsTls() noexcept;
namespace internal
{
DROGON_EXPORT extern const size_t fixedRandomNumber;
struct SafeStringHash
{
    size_t operator()(const std::string &str) const
    {
        const size_t A = 6665339;
        const size_t B = 2534641;
        size_t h = fixedRandomNumber;
        for (char ch : str)
            h = (h * A) ^ (ch * B);
        return h;
    }
};
}  
}  
template <typename T>
using SafeStringMap =
    std::unordered_map<std::string, T, utils::internal::SafeStringHash>;
}  
namespace trantor
{
inline LogStream &operator<<(LogStream &ls, const std::string_view &v)
{
    if (!v.empty())
        ls.append(v.data(), v.length());
    return ls;
}
inline LogStream &operator<<(LogStream &ls, const std::filesystem::path &p)
{
    return ls << p.string();
}
}  