#include "foundation/serialization/JsonFlatParser.h"
#include "foundation/text/StringUtils.h"

#include <cstddef>
#include <string>
#include <utility>
#include <vector>

namespace
{
    void append_utf8(std::string& out, const unsigned codepoint)
    {
        if (codepoint <= 0x7F)
            out.push_back(static_cast<char>(codepoint));
        else if (codepoint <= 0x7FF)
        {
            out.push_back(static_cast<char>(0xC0 | ((codepoint >> 6) & 0x1F)));
            out.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
        }
        else if (codepoint <= 0xFFFF)
        {
            out.push_back(static_cast<char>(0xE0 | ((codepoint >> 12) & 0x0F)));
            out.push_back(static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F)));
            out.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
        }
        else
        {
            out.push_back(static_cast<char>(0xF0 | ((codepoint >> 18) & 0x07)));
            out.push_back(static_cast<char>(0x80 | ((codepoint >> 12) & 0x3F)));
            out.push_back(static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F)));
            out.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
        }
    }

    void skip_ws(const std::string& text, std::size_t& pos)
    {
        while (pos < text.size())
        {
            const char c = text[pos];
            if (c != ' ' && c != '\t' && c != '\r' && c != '\n') break;
            ++pos;
        }
    }

    bool parse_hex4(const std::string& text, std::size_t& pos, unsigned& value)
    {
        if (pos + 4 > text.size()) return false;
        value = 0;
        for (int i = 0; i < 4; ++i)
        {
            const char c = text[pos++];
            value <<= 4;
            if (c >= '0' && c <= '9') value += static_cast<unsigned>(c - '0');
            else if (c >= 'a' && c <= 'f') value += static_cast<unsigned>(10 + c - 'a');
            else if (c >= 'A' && c <= 'F') value += static_cast<unsigned>(10 + c - 'A');
            else return false;
        }
        return true;
    }

    bool parse_json_string(const std::string& text, std::size_t& pos, std::string& out)
    {
        skip_ws(text, pos);
        if (pos >= text.size() || text[pos] != '"') return false;
        ++pos;
        out.clear();

        while (pos < text.size())
        {
            const char c = text[pos++];
            if (c == '"') return true;
            if (c != '\\')
            {
                out.push_back(c);
                continue;
            }

            if (pos >= text.size()) return false;
            const char esc = text[pos++];
            switch (esc)
            {
            case '"': out.push_back('"'); break;
            case '\\': out.push_back('\\'); break;
            case '/': out.push_back('/'); break;
            case 'b': out.push_back('\b'); break;
            case 'f': out.push_back('\f'); break;
            case 'n': out.push_back('\n'); break;
            case 'r': out.push_back('\r'); break;
            case 't': out.push_back('\t'); break;
            case 'u':
            {
                unsigned codepoint = 0;
                if (!parse_hex4(text, pos, codepoint)) return false;
                append_utf8(out, codepoint);
                break;
            }
            default:
                return false;
            }
        }

        return false;
    }

    bool skip_json_value(const std::string& text, std::size_t& pos)
    {
        skip_ws(text, pos);
        if (pos >= text.size()) return false;

        if (text[pos] == '"')
        {
            std::string ignored;
            return parse_json_string(text, pos, ignored);
        }

        int depth = 0;
        bool in_string = false;
        bool escaping = false;
        while (pos < text.size())
        {
            const char c = text[pos];
            if (in_string)
            {
                if (escaping)
                    escaping = false;
                else if (c == '\\')
                    escaping = true;
                else if (c == '"')
                    in_string = false;
                ++pos;
                continue;
            }

            if (c == '"')
            {
                in_string = true;
                ++pos;
                continue;
            }

            if (c == '{' || c == '[')
            {
                ++depth;
                ++pos;
                continue;
            }
            if (c == '}' || c == ']')
            {
                if (depth == 0) return true;
                --depth;
                ++pos;
                continue;
            }

            if (depth == 0 && (c == ',' || c == '}'))
                return true;

            ++pos;
        }

        return true;
    }


    std::string join_path(const std::vector<std::string>& path,
                          const std::string& key,
                          const std::string& separator)
    {
        std::string out;
        for (const std::string& part : path)
        {
            if (part.empty()) continue;
            if (!out.empty()) out += separator;
            out += part;
        }
        if (!key.empty())
        {
            if (!out.empty()) out += separator;
            out += key;
        }
        return out;
    }

    bool looks_like_mongo_key(const std::string& key)
    {
        if (key.size() != 24) return false;

        for (const char c : key)
        {
            const bool hex = (c >= '0' && c <= '9') ||
                             (c >= 'a' && c <= 'f') ||
                             (c >= 'A' && c <= 'F');
            if (!hex) return false;
        }
        return true;
    }

    void add_flattened_leaf(const std::vector<std::string>& path,
                            const std::string& key,
                            const std::string& value,
                            ta::foundation::serialization::json::StringMap& out)
    {
        if (key.empty() || value.empty())
            return;

        if (path.empty())
        {
            out.emplace(key, value);
            return;
        }

        out.emplace(join_path(path, key, " "), value);
        out.emplace(join_path(path, key, "_"), value);

        if (looks_like_mongo_key(key) || key.find(' ') != std::string::npos)
            out.emplace(key, value);
    }

    bool parse_object_flattened(const std::string& text,
                                std::size_t& pos,
                                std::vector<std::string>& path,
                                ta::foundation::serialization::json::StringMap& out,
                                const ta::foundation::serialization::json::LocaleContainerPredicate& is_locale_container)
    {
        skip_ws(text, pos);
        if (pos >= text.size() || text[pos] != '{') return false;
        ++pos;

        while (pos < text.size())
        {
            skip_ws(text, pos);
            if (pos < text.size() && text[pos] == '}')
            {
                ++pos;
                return true;
            }

            std::string key;
            if (!parse_json_string(text, pos, key)) return false;

            skip_ws(text, pos);
            if (pos >= text.size() || text[pos] != ':') return false;
            ++pos;
            skip_ws(text, pos);

            if (pos < text.size() && text[pos] == '"')
            {
                std::string value;
                if (!parse_json_string(text, pos, value)) return false;
                add_flattened_leaf(path, key, value, out);
            }
            else if (pos < text.size() && text[pos] == '{')
            {
                const bool locale_container = path.empty() && is_locale_container && is_locale_container(key);
                if (!locale_container)
                    path.push_back(key);
                if (!parse_object_flattened(text, pos, path, out, is_locale_container)) return false;
                if (!locale_container)
                    path.pop_back();
            }
            else
            {
                if (!skip_json_value(text, pos)) return false;
            }

            skip_ws(text, pos);
            if (pos < text.size() && text[pos] == ',')
            {
                ++pos;
                continue;
            }
            if (pos < text.size() && text[pos] == '}')
            {
                ++pos;
                return true;
            }
            return false;
        }

        return false;
    }
}

namespace ta::foundation::serialization::json
{
    bool parse_flat_object(const std::string& text, StringMap& out)
    {
        out.clear();
        std::size_t pos = ta::foundation::text::utf8_bom_size(text);
        skip_ws(text, pos);
        if (pos >= text.size() || text[pos] != '{') return false;
        ++pos;

        while (pos < text.size())
        {
            skip_ws(text, pos);
            if (pos < text.size() && text[pos] == '}') return true;

            std::string key;
            std::string value;
            if (!parse_json_string(text, pos, key)) return false;
            skip_ws(text, pos);
            if (pos >= text.size() || text[pos] != ':') return false;
            ++pos;
            skip_ws(text, pos);
            if (pos < text.size() && text[pos] == '"')
            {
                if (!parse_json_string(text, pos, value)) return false;
                out.emplace(std::move(key), std::move(value));
            }
            else if (!skip_json_value(text, pos))
            {
                return false;
            }

            skip_ws(text, pos);
            if (pos < text.size() && text[pos] == ',')
            {
                ++pos;
                continue;
            }
            if (pos < text.size() && text[pos] == '}') return true;
            return false;
        }

        return false;
    }

    bool parse_flattened_object(const std::string& text,
                                StringMap& out,
                                const LocaleContainerPredicate& is_locale_container)
    {
        out.clear();
        std::size_t pos = ta::foundation::text::utf8_bom_size(text);
        std::vector<std::string> path;
        return parse_object_flattened(text, pos, path, out, is_locale_container);
    }
}
