#include "tile/base/encoding/percent.h"

#include "tile/base/encoding/detail/hex_chars.h"
#include "tile/base/likely.h"

namespace tile {

// [0-9a-zA-Z]
namespace {
std::array<bool, 256>
GenerateUnescapedCharBitmap(Slice unescaped_chars)
{
    std::array<bool, 256> result{};
    for (auto &&e : unescaped_chars) { result[e] = true; }

    for (int i = 0; i != 10; ++i) { result[i + '0'] = true; }

    for (int i = 0; i != 26; ++i) {
        result[i + 'A'] = true;
        result[i + 'a'] = true;
    }

    return result;
}

const std::array<std::array<bool, 256>, 2> &
GetUnescapedCharBitmap(const PercentEncodingStyle &style)
{
    static const std::array<std::array<bool, 256>, 2> kUnescapedChars[] = {
        /* emca262 = */ {GenerateUnescapedCharBitmap("_-,;:!?.'()@*/&#+=~$"),   GenerateUnescapedCharBitmap("_-!.*~'()")   },

        /* rfc3986 = */
        {GenerateUnescapedCharBitmap("_-,;:!?.'()[]@*/&#+=~$"), GenerateUnescapedCharBitmap("_-.~")

        },
        /* rfc5987 = */
        {GenerateUnescapedCharBitmap("!#$&+-.^_`|~"),           GenerateUnescapedCharBitmap("!#$&+-.^_`|~")}
    };

    return kUnescapedChars[static_cast<int>(style)];
}
}// namespace

PercentEncodingOptions::PercentEncodingOptions(PercentEncodingStyle s, bool er) : style(s), escape_reserved(er) {}

std::string
EncodePercent(Slice from, const PercentEncodingOptions &options)
{
    std::string result;
    EncodePercent(from, &result, options);
    return result;
}

std::optional<std::string>
DecodePercent(Slice from, bool decode_plus_sign_as_whitespace)
{
    std::string result;
    if (DecodePercent(from, &result, decode_plus_sign_as_whitespace)) { return result; }
    return std::nullopt;
}

void
EncodePercent(Slice from, std::string *to, const PercentEncodingOptions &options)
{

    auto &&unescaped      = GetUnescapedCharBitmap(options.style)[options.escape_reserved];

    int escape_char_count = 0;
    for (auto &&e : from) {
        if (!unescaped[static_cast<std::uint8_t>(e)]) { ++escape_char_count; }
    }

    to->clear();
    to->reserve(from.size() + escape_char_count * 2);

    for (auto &&e : from) {
        if (TILE_UNLIKELY(unescaped[static_cast<std::uint8_t>(e)])) {
            to->push_back(e);
        } else {
            auto hex_pair = detail::AsciiCodeToCharPair(static_cast<std::uint8_t>(e));
            to->append({'%', hex_pair.first, hex_pair.second});
        }
    }
}

bool
DecodePercent(Slice from, std::string *to, bool decode_plus_sign_as_whitespace)
{
    to->clear();
    to->reserve(from.size());
    for (auto iter = from.begin(); iter != from.end();) {
        if (*iter == '%') {
            if (iter + 3 > from.end()) { return false; }
            auto v = detail::AsciiCodeFromCharPair(*(iter + 1), *(iter + 2));
            if (TILE_LIKELY(v != -1)) {
                to->push_back(v);
                iter += 3;
            } else {
                // invalid char
                return false;
            }
        } else {
            if (decode_plus_sign_as_whitespace && *iter == '+') {
                to->push_back(' ');
                ++iter;
            } else {
                to->push_back(*iter++);
            }
        }
    }
    return true;
}
}// namespace tile
