#include "tile/base/string.h"

#include "tile/base/demangle.h"
#include "tile/base/internal/logging.h"
#include "tile/base/likely.h"

#include <cinttypes>
#include <cmath>
#include <cstring>

namespace tile {

namespace {
struct LastErrorStasher {
    LastErrorStasher() : last_errno(errno) { errno = 0; }

    ~LastErrorStasher() { errno = last_errno; }

    int last_errno;
};
}// namespace

template<typename T, typename F, typename... Args>
std::optional<T>
TryParseImpl(F fptr, const char *s, std::initializer_list<T> invs, Args &&...args)
{
    if (s[0] == 0) { return std::nullopt; }

    LastErrorStasher _;
    char *end;
    auto result = fptr(s, &end, args...);
    if (end != s + strlen(s)) { return std::nullopt; }

    for (auto &&e : invs) {
        if (result == e && errno == ERANGE) { return std::nullopt; }
    }

    return result;
}

bool
StartsWith(Slice s, Slice prefix)
{
    return s.size() >= prefix.size() && s.substr(0, prefix.size()) == prefix;
}

bool
EndsWith(Slice s, Slice suffix)
{
    return s.size() >= suffix.size() && s.substr(s.size() - suffix.size()) == suffix;
}

bool
StartsWithIgnoreCase(Slice s, Slice prefix)
{
    return s.size() >= prefix.size() && EqualsIgnoreCase(s.substr(0, prefix.size()), prefix);
}

bool
EndsWithIgnoreCase(Slice s, Slice suffix)
{
    return s.size() >= suffix.size() && EqualsIgnoreCase(s.substr(s.size() - suffix.size()), suffix);
}

void
Replace(Slice from, Slice to, std::string *str, std::size_t count)
{
    TILE_CHECK(!from.empty(), "`from` may not be empty");
    const std::string from_str = from.ToString();
    const std::string to_str   = to.ToString();
    auto pos                   = str->find(from_str);
    while (pos != std::string::npos && count--) {
        str->replace(pos, from.size(), to_str);
        pos = str->find(from_str, pos + to.size());
    }
}

std::string
Replace(Slice str, Slice from, Slice to, std::size_t count)
{
    std::string result = str.ToString();
    Replace(from, to, &result, count);
    return result;
}

std::vector<Slice>
Split(Slice s, char delim, bool keep_empty, std::size_t max_split_parts)
{
    return Split(s, Slice(delim), keep_empty, max_split_parts);
}

std::vector<Slice>
Split(Slice s, Slice delim, bool keep_empty, std::size_t max_split_parts)
{

    TILE_CHECK_GE(max_split_parts, 0, "`max_split_parts` must be greater than 0");

    constexpr std::size_t kMaxSplit = std::numeric_limits<std::size_t>::max();
    std::vector<Slice> splited;
    if (s.empty() || max_split_parts == 1) { return splited; }
    auto current = s;
    assert(!delim.empty());
    int split_count = 0;
    while (max_split_parts == kMaxSplit || split_count < max_split_parts) {
        auto next_pos = current.find(delim);
        auto part     = current.substr(0, next_pos);
        if (!part.empty() || keep_empty) { splited.push_back(part); }

        ++split_count;

        if (next_pos == Slice::npos) {
            break;
        } else if (split_count + 1 >= max_split_parts) {
            splited.push_back(current.substr(next_pos + delim.size()));
            break;
        } else {
            current = current.substr(next_pos + delim.size());
        }
    }

    return splited;
}

template<typename T>
void
JoinImpl(const T &parts, Slice delim, std::string *result)
{
    auto size = 0;
    for (auto &&e : parts) { size += e.size() + delim.size(); }
    result->clear();
    if (!size) { return; }

    size -= delim.size();
    result->reserve(size);
    for (auto iter = parts.begin(); iter != parts.end(); ++iter) {
        if (iter != parts.begin()) { result->append(delim.begin(), delim.end()); }
        result->append(iter->begin(), iter->end());
    }
}

std::string
Join(const std::vector<std::string> &parts, Slice delim)
{
    std::string result;
    JoinImpl(parts, delim, &result);
    return result;
}

std::string
Join(const std::vector<Slice> &parts, Slice delim)
{
    std::string result;
    JoinImpl(parts, delim, &result);
    return result;
}

std::string
Join(const std::initializer_list<Slice> &parts, Slice delim)
{
    std::string result;
    JoinImpl(parts, delim, &result);
    return result;
}

char
ToUpper(char c)
{
    return std::toupper(c);
}

char
ToLower(char c)
{
    return std::tolower(c);
}

void
ToUpper(std::string *str)
{
    std::for_each(str->begin(), str->end(), [](char &c) { c = ToUpper(c); });
}

void
ToLower(std::string *str)
{
    std::for_each(str->begin(), str->end(), [](char &c) { c = ToLower(c); });
}

std::string
ToUpper(Slice s)
{
    std::string result;
    result.reserve(s.size());
    for (auto &&c : s) { result.push_back(ToUpper(c)); }
    return result;
}

std::string
ToLower(Slice s)
{
    std::string result;
    result.reserve(s.size());
    for (auto &&c : s) { result.push_back(ToLower(c)); }
    return result;
}

bool
Equals(Slice a, Slice b, std::size_t len)
{
    if (a.size() > len) { a.RemoveSuffix(a.size() - len); }
    if (b.size() > len) { b.RemoveSuffix(b.size() - len); }
    return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin());
}

bool
EqualsIgnoreCase(Slice a, Slice b, std::size_t len)
{
    if (a.size() > len) { a.RemoveSuffix(a.size() - len); }
    if (b.size() > len) { b.RemoveSuffix(b.size() - len); }
    return a.size() == b.size()
        && std::equal(a.begin(), a.end(), b.begin(), [](char a, char b) { return ToLower(a) == ToLower(b); });
}

std::optional<bool>
TryParseTraits<bool>::TryParse(Slice s, bool recognizes_alpbabet_symbol, bool ignore_case)
{
    auto num_opt = tile::TryParse<int>(s);
    if (num_opt) {
        return *num_opt != 0;
    } else {
        const std::vector<std::string> true_arr{"true", "yes", "on", "y"};
        const std::vector<std::string> false_arr{"false", "no", "off", "n"};
        if (s.size() > 5) { return false; }

        auto InArray = [](const std::vector<std::string> &arr, Slice s) {
            for (auto &&e : arr) {
                if (EqualsIgnoreCase(e, s)) { return true; }
            }
            return false;
        };
        if (InArray(true_arr, s)) {
            return true;
        } else if (InArray(false_arr, s)) {
            return false;
        }
    }

    return std::nullopt;
}

template<typename T>
bool
TryParseIngegralImpl(T &value, Slice s)
{
    s = Trim(s);
    if (s.empty()) { return false; }

    T sign = 1;
    if (s[0] == '+' || s[0] == '-') {
        if (s[0] == '-') { sign = -1; }
        s.RemovePrefix(1);
        s = TrimLeft(s);

        if (s.empty()) { return false; }
    }
}

template<typename T>
std::optional<T>
TryParseTraits<T, typename std::enable_if<std::is_integral<T>::value>::type>::TryParse(Slice s, int base)
{
    assert(base >= 2 && base <= 36);

    if (s.empty()) { return std::nullopt; }

    const std::string str = s.ToString();
    char *endptr          = nullptr;
    T value;

    LastErrorStasher _;
    if (std::is_same<T, unsigned long long>::value || std::is_same<T, std::uint64_t>::value) {
        unsigned long long temp_value = std::strtoull(str.data(), &endptr, base);
        if (endptr != str.data() + str.size()) { return std::nullopt; }

        if (errno == ERANGE || errno == EINVAL) { return std::nullopt; }
        value = temp_value;
    } else {
        long long temp_value = std::strtoll(str.data(), &endptr, base);
        if (endptr != str.data() + str.size()) { return std::nullopt; }

        if (errno == ERANGE || errno == EINVAL) { return std::nullopt; }
        if (temp_value < std::numeric_limits<T>::min() || temp_value > std::numeric_limits<T>::max()) {
            return std::nullopt;
        }

        value = temp_value;
    }

    return value;
}

template<typename T>
std::optional<T>
TryParseTraits<T, typename std::enable_if<std::is_floating_point<T>::value>::type>::TryParse(Slice s)
{
    char temp_buffer[129];
    std::string heap_buffer;
    const char *ptr;
    if (TILE_LIKELY(s.size() < 128)) {
        ptr = temp_buffer;
        memcpy(temp_buffer, s.data(), s.size());
        temp_buffer[s.size()] = 0;
    } else {
        heap_buffer.assign(s.data(), s.size());
        ptr = heap_buffer.data();
    }

    if (std::is_same<T, float>::value) {
        return TryParseImpl<float>(&strtof, ptr, {-HUGE_VALF, HUGE_VALF});
    } else if (std::is_same<T, double>::value) {
        return TryParseImpl<double>(&strtod, ptr, {-HUGE_VAL, HUGE_VAL});
    } else if (std::is_same<T, long double>::value) {
        return TryParseImpl<long double>(&strtold, ptr, {-HUGE_VALL, HUGE_VALL});
    }

    TILE_UNREACHABLE("unknown floating point type, {}", GetTypeName<T>());
    return std::nullopt;
}

#define INSTANIATE_TRY_PARSE_TRAITS(type) template struct TryParseTraits<type>;
INSTANIATE_TRY_PARSE_TRAITS(char);
INSTANIATE_TRY_PARSE_TRAITS(signed char);
INSTANIATE_TRY_PARSE_TRAITS(unsigned char);
INSTANIATE_TRY_PARSE_TRAITS(unsigned int);
INSTANIATE_TRY_PARSE_TRAITS(unsigned short);
INSTANIATE_TRY_PARSE_TRAITS(unsigned long);
INSTANIATE_TRY_PARSE_TRAITS(unsigned long long);
INSTANIATE_TRY_PARSE_TRAITS(signed int);
INSTANIATE_TRY_PARSE_TRAITS(signed short);
INSTANIATE_TRY_PARSE_TRAITS(signed long);
INSTANIATE_TRY_PARSE_TRAITS(signed long long);
INSTANIATE_TRY_PARSE_TRAITS(float);
INSTANIATE_TRY_PARSE_TRAITS(double);
INSTANIATE_TRY_PARSE_TRAITS(long double);

}// namespace tile
