// Crude implementation of JSON value object and parser.
//
// VERSION 0.1
//
// LICENSE
//   This software is dual-licensed to the public domain and under the following
//   license: you are granted a perpetual, irrevocable license to copy, modify,
//   publish, and distribute this file as you see fit.
//
// CREDITS
//   Written by Michal Cichon
# include "crude_json.h"
# include <iomanip>
# include <limits>
# include <cstdlib>
# include <clocale>
# include <cmath>
# include <cstring>
# if CRUDE_JSON_IO
#     include <stdio.h>
#     include <memory>
# endif

namespace crude_json {

value::value(value&& other)
    : m_Type(other.m_Type)
{
    switch (m_Type)
    {
        case type_t::object:    construct(m_Storage, std::move( *object_ptr(other.m_Storage))); break;
        case type_t::array:     construct(m_Storage, std::move(  *array_ptr(other.m_Storage))); break;
        case type_t::string:    construct(m_Storage, std::move( *string_ptr(other.m_Storage))); break;
        case type_t::boolean:   construct(m_Storage, std::move(*boolean_ptr(other.m_Storage))); break;
        case type_t::number:    construct(m_Storage, std::move( *number_ptr(other.m_Storage))); break;
        default: break;
    }
    destruct(other.m_Storage, other.m_Type);
    other.m_Type = type_t::null;
}

value::value(const value& other)
    : m_Type(other.m_Type)
{
    switch (m_Type)
    {
        case type_t::object:    construct(m_Storage,  *object_ptr(other.m_Storage)); break;
        case type_t::array:     construct(m_Storage,   *array_ptr(other.m_Storage)); break;
        case type_t::string:    construct(m_Storage,  *string_ptr(other.m_Storage)); break;
        case type_t::boolean:   construct(m_Storage, *boolean_ptr(other.m_Storage)); break;
        case type_t::number:    construct(m_Storage,  *number_ptr(other.m_Storage)); break;
        default: break;
    }
}

value& value::operator[](size_t index)
{
    if (is_null())
        m_Type = construct(m_Storage, type_t::array);

    if (is_array())
    {
        auto& v = *array_ptr(m_Storage);
        if (index >= v.size())
            v.insert(v.end(), index - v.size() + 1, value());

        return v[index];
    }

    CRUDE_ASSERT(false && "operator[] on unsupported type");
    std::terminate();
}

const value& value::operator[](size_t index) const
{
    if (is_array())
        return (*array_ptr(m_Storage))[index];

    CRUDE_ASSERT(false && "operator[] on unsupported type");
    std::terminate();
}

value& value::operator[](const string& key)
{
    if (is_null())
        m_Type = construct(m_Storage, type_t::object);

    if (is_object())
        return (*object_ptr(m_Storage))[key];

    CRUDE_ASSERT(false && "operator[] on unsupported type");
    std::terminate();
}

const value& value::operator[](const string& key) const
{
    if (is_object())
    {
        auto& o = *object_ptr(m_Storage);
        auto it = o.find(key);
        CRUDE_ASSERT(it != o.end());
        return it->second;
    }

    CRUDE_ASSERT(false && "operator[] on unsupported type");
    std::terminate();
}

bool value::contains(const string& key) const
{
    if (is_object())
    {
        auto& o = *object_ptr(m_Storage);
        auto it = o.find(key);
        return it != o.end();
    }

    return false;
}

void value::push_back(const value& value)
{
    if (is_null())
        m_Type = construct(m_Storage, type_t::array);

    if (is_array())
    {
        auto& v = *array_ptr(m_Storage);
        v.push_back(value);
    }
    else
    {
        CRUDE_ASSERT(false && "operator[] on unsupported type");
        std::terminate();
    }
}

void value::push_back(value&& value)
{
    if (is_null())
        m_Type = construct(m_Storage, type_t::array);

    if (is_array())
    {
        auto& v = *array_ptr(m_Storage);
        v.push_back(std::move(value));
    }
    else
    {
        CRUDE_ASSERT(false && "operator[] on unsupported type");
        std::terminate();
    }
}

size_t value::erase(const string& key)
{
    if (!is_object())
        return 0;

    auto& o = *object_ptr(m_Storage);
    auto it = o.find(key);

    if (it == o.end())
        return 0;

    o.erase(it);

    return 1;
}

void value::swap(value& other)
{
    using std::swap;

    if (m_Type == other.m_Type)
    {
        switch (m_Type)
        {
            case type_t::object:    swap(*object_ptr(m_Storage),  *object_ptr(other.m_Storage));  break;
            case type_t::array:     swap(*array_ptr(m_Storage),   *array_ptr(other.m_Storage));   break;
            case type_t::string:    swap(*string_ptr(m_Storage),  *string_ptr(other.m_Storage));  break;
            case type_t::boolean:   swap(*boolean_ptr(m_Storage), *boolean_ptr(other.m_Storage)); break;
            case type_t::number:    swap(*number_ptr(m_Storage),  *number_ptr(other.m_Storage));  break;
            default: break;
        }
    }
    else
    {
        value tmp(std::move(other));
        other.~value();
        new (&other) value(std::move(*this));
        this->~value();
        new (this) value(std::move(tmp));
    }
}

string value::dump(const int indent, const char indent_char) const
{
    dump_context_t context(indent, indent_char);

    context.out.precision(std::numeric_limits<double>::max_digits10 + 1);
    context.out << std::defaultfloat;

    dump(context, 0);
    return context.out.str();
}

void value::dump_context_t::write_indent(int level)
{
    if (indent <= 0 || level == 0)
        return;

    out.fill(indent_char);
    out.width(indent * level);
    out << indent_char;
    out.width(0);
}

void value::dump_context_t::write_separator()
{
    if (indent < 0)
        return;

    out.put(' ');
}

void value::dump_context_t::write_newline()
{
    if (indent < 0)
        return;

    out.put('\n');
}

void value::dump(dump_context_t& context, int level) const
{
    context.write_indent(level);

    switch (m_Type)
    {
        case type_t::null:
            context.out << "null";
            break;

        case type_t::object:
            context.out << '{';
            {
                context.write_newline();
                bool first = true;
                for (auto& entry : *object_ptr(m_Storage))
                {
                    if (!first) { context.out << ','; context.write_newline(); } else first = false;
                    context.write_indent(level + 1);
                    context.out << '\"' << entry.first << "\":";
                    if (!entry.second.is_structured())
                    {
                        context.write_separator();
                        entry.second.dump(context, 0);
                    }
                    else
                    {
                        context.write_newline();
                        entry.second.dump(context, level + 1);
                    }
                }
                if (!first)
                    context.write_newline();
            }
            context.write_indent(level);
            context.out << '}';
            break;

        case type_t::array:
            context.out << '[';
            {
                context.write_newline();
                bool first = true;
                for (auto& entry : *array_ptr(m_Storage))
                {
                    if (!first) { context.out << ','; context.write_newline(); } else first = false;
                    if (!entry.is_structured())
                    {
                        context.write_indent(level + 1);
                        entry.dump(context, 0);
                    }
                    else
                    {
                        entry.dump(context, level + 1);
                    }
                }
                if (!first)
                    context.write_newline();
            }
            context.write_indent(level);
            context.out << ']';
            break;

        case type_t::string:
            context.out << '\"';

            if (string_ptr(m_Storage)->find_first_of("\"\\/\b\f\n\r") != string::npos || string_ptr(m_Storage)->find('\0') != string::npos)
            {
                for (auto c : *string_ptr(m_Storage))
                {
                         if (c == '\"')  context.out << "\\\"";
                    else if (c == '\\')  context.out << "\\\\";
                    else if (c == '/')   context.out << "\\/";
                    else if (c == '\b')  context.out << "\\b";
                    else if (c == '\f')  context.out << "\\f";
                    else if (c == '\n')  context.out << "\\n";
                    else if (c == '\r')  context.out << "\\r";
                    else if (c == '\t')  context.out << "\\t";
                    else if (c == 0)     context.out << "\\u0000";
                    else                 context.out << c;
                }
            }
            else
                context.out << *string_ptr(m_Storage);
            context.out << '\"';
            break;


        case type_t::boolean:
            if (*boolean_ptr(m_Storage))
                context.out << "true";
            else
                context.out << "false";
            break;

        case type_t::number:
            context.out << *number_ptr(m_Storage);
            break;

        default:
            break;
    }
}

struct value::parser
{
    parser(const char* begin, const char* end)
        : m_Cursor(begin)
        , m_End(end)
    {
    }

    value parse()
    {
        value v;

        // Switch to C locale to make strtod and strtol work as expected
        auto previous_locale = std::setlocale(LC_NUMERIC, "C");

        // Accept single value only when end of the stream is reached.
        if (!accept_element(v) || !eof())
            v = value(type_t::discarded);

        if (previous_locale && strcmp(previous_locale, "C") != 0)
            std::setlocale(LC_NUMERIC, previous_locale);

        return v;
    }

private:
    struct cursor_state
    {
        cursor_state(parser* p)
            : m_Owner(p)
            , m_LastCursor(p->m_Cursor)
        {
        }

        void reset()
        {
            m_Owner->m_Cursor = m_LastCursor;
        }

        bool operator()(bool accept)
        {
            if (!accept)
                reset();
            else
                m_LastCursor = m_Owner->m_Cursor;
            return accept;
        }

    private:
        parser*     m_Owner;
        const char* m_LastCursor;
    };

    cursor_state state()
    {
        return cursor_state(this);
    }

    bool accept_value(value& result)
    {
        return accept_object(result)
            || accept_array(result)
            || accept_string(result)
            || accept_number(result)
            || accept_boolean(result)
            || accept_null(result);
    }

    bool accept_object(value& result)
    {
        auto s = state();

        object o;
        if (s(accept('{') && accept_ws() && accept('}')))
        {
            result = o;
            return true;
        }
        else if (s(accept('{') && accept_members(o) && accept('}')))
        {
            result = std::move(o);
            return true;
        }

        return false;
    }

    bool accept_members(object& o)
    {
        if (!accept_member(o))
            return false;

        while (true)
        {
            auto s = state();
            if (!s(accept(',') && accept_member(o)))
                break;
        }

        return true;
    }

    bool accept_member(object& o)
    {
        auto s = state();

        value key;
        value v;
        if (s(accept_ws() && accept_string(key) && accept_ws() && accept(':') && accept_element(v)))
        {
            o.emplace(std::move(key.get<string>()), std::move(v));
            return true;
        }

        return false;
    }

    bool accept_array(value& result)
    {
        auto s = state();

        if (s(accept('[') && accept_ws() && accept(']')))
        {
            result = array();
            return true;
        }

        array a;
        if (s(accept('[') && accept_elements(a) && accept(']')))
        {
            result = std::move(a);
            return true;
        }

        return false;
    }

    bool accept_elements(array& a)
    {
        value v;
        if (!accept_element(v))
            return false;

        a.emplace_back(std::move(v));
        while (true)
        {
            auto s = state();
            v = nullptr;
            if (!s(accept(',') && accept_element(v)))
                break;
            a.emplace_back(std::move(v));
        }

        return true;
    }

    bool accept_element(value& result)
    {
        auto s = state();
        return s(accept_ws() && accept_value(result) && accept_ws());
    }

    bool accept_string(value& result)
    {
        auto s = state();

        string v;
        if (s(accept('\"') && accept_characters(v) && accept('\"')))
        {
            result = std::move(v);
            return true;
        }
        else
            return false;
    }

    bool accept_characters(string& result)
    {
        int c;
        while (accept_character(c))
        {
            CRUDE_ASSERT(c < 128); // #todo: convert characters > 127 to UTF-8
            result.push_back(static_cast<char>(c));
        }

        return true;
    }

    bool accept_character(int& c)
    {
        auto s = state();

        if (accept('\\'))
        {
            return accept_escape(c);
        }
        else if (expect('\"'))
            return false;

        // #todo: Handle UTF-8 sequences.
        return s((c = peek()) >= 0) && advance();
    }

    bool accept_escape(int& c)
    {
        if (accept('\"')) { c = '\"'; return true; }
        if (accept('\\')) { c = '\\'; return true; }
        if (accept('/'))  { c = '/';  return true; }
        if (accept('b'))  { c = '\b'; return true; }
        if (accept('f'))  { c = '\f'; return true; }
        if (accept('n'))  { c = '\n'; return true; }
        if (accept('r'))  { c = '\r'; return true; }
        if (accept('t'))  { c = '\t'; return true; }

        auto s = state();

        string hex;
        hex.reserve(4);
        if (s(accept('u') && accept_hex(hex) && accept_hex(hex) && accept_hex(hex) && accept_hex(hex)))
        {
            char* end = nullptr;
            auto v = std::strtol(hex.c_str(), &end, 16);
            if (end != hex.c_str() + hex.size())
                return false;

            c = v;
            return true;
        }

        return false;
    }

    bool accept_hex(string& result)
    {
        if (accept_digit(result))
            return true;

        auto c = peek();
        if ((c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'))
        {
            advance();
            result.push_back(static_cast<char>(c));
            return true;
        }

        return false;
    }

    bool accept_number(value& result)
    {
        auto s = state();

        string n;
        if (s(accept_int(n) && accept_frac(n) && accept_exp(n)))
        {
            char* end = nullptr;
            auto v = std::strtod(n.c_str(), &end);
            if (end != n.c_str() + n.size())
                return false;

            if (v != 0 && !std::isnormal(v))
                return false;

            result = v;
            return true;
        }

        return false;
    }

    bool accept_int(string& result)
    {
        auto s = state();

        string part;
        if (s(accept_onenine(part) && accept_digits(part)))
        {
            result += std::move(part);
            return true;
        }

        part.resize(0);
        if (accept_digit(part))
        {
            result += std::move(part);
            return true;
        }

        part.resize(0);
        if (s(accept('-') && accept_onenine(part) && accept_digits(part)))
        {
            result += '-';
            result += std::move(part);
            return true;
        }

        part.resize(0);
        if (s(accept('-') && accept_digit(part)))
        {
            result += '-';
            result += std::move(part);
            return true;
        }

        return false;
    }

    bool accept_digits(string& result)
    {
        string part;
        if (!accept_digit(part))
            return false;

        while (accept_digit(part))
            ;

        result += std::move(part);

        return true;
    }

    bool accept_digit(string& result)
    {
        if (accept('0'))
        {
            result.push_back('0');
            return true;
        }
        else if (accept_onenine(result))
            return true;

        return false;
    }

    bool accept_onenine(string& result)
    {
        auto c = peek();
        if (c >= '1' && c <= '9')
        {
            result.push_back(static_cast<char>(c));
            return advance();
        }

        return false;
    }

    bool accept_frac(string& result)
    {
        auto s = state();

        string part;
        if (s(accept('.') && accept_digits(part)))
        {
            result += '.';
            result += std::move(part);
        }

        return true;
    }

    bool accept_exp(string& result)
    {
        auto s = state();

        string part;
        if (s(accept('e') && accept_sign(part) && accept_digits(part)))
        {
            result += 'e';
            result += std::move(part);
            return true;
        }
        part.resize(0);
        if (s(accept('E') && accept_sign(part) && accept_digits(part)))
        {
            result += 'E';
            result += std::move(part);
        }

        return true;
    }

    bool accept_sign(string& result)
    {
        if (accept('+'))
            result.push_back('+');
        else if (accept('-'))
            result.push_back('-');

        return true;
    }

    bool accept_ws()
    {
        while (expect('\x09') || expect('\x0A') || expect('\x0D') || expect('\x20'))
            advance();
        return true;
    }

    bool accept_boolean(value& result)
    {
        if (accept("true"))
        {
            result = true;
            return true;
        }
        else if (accept("false"))
        {
            result = false;
            return true;
        }

        return false;
    }

    bool accept_null(value& result)
    {
        if (accept("null"))
        {
            result = nullptr;
            return true;
        }

        return false;
    }

    bool accept(char c)
    {
        if (expect(c))
            return advance();
        else
            return false;
    }

    bool accept(const char* str)
    {
        auto last = m_Cursor;

        while (*str)
        {
            if (eof() || *str != *m_Cursor)
            {
                m_Cursor = last;
                return false;
            }

            advance();
            ++str;
        }

        return true;
    }

    int peek() const
    {
        if (!eof())
            return *m_Cursor;
        else
            return -1;
    }

    bool expect(char c)
    {
        return peek() == c;
    }

    bool advance(int count = 1)
    {
        if (m_Cursor + count > m_End)
        {
            m_Cursor = m_End;
            return false;
        }

        m_Cursor += count;

        return true;
    }

    bool eof() const
    {
        return m_Cursor == m_End;
    }

    const char* m_Cursor;
    const char* m_End;
};

value value::parse(const string& data)
{
    auto p = parser(data.c_str(), data.c_str() + data.size());

    auto v = p.parse();

    return v;
}

# if CRUDE_JSON_IO
std::pair<value, bool> value::load(const string& path)
{
    // Modern C++, so beautiful...
    std::unique_ptr<FILE, void(*)(FILE*)> file{nullptr, [](FILE* file) { if (file) fclose(file); }};
# if defined(_MSC_VER) || (defined(__STDC_LIB_EXT1__) && __STDC_WANT_LIB_EXT1__)
    FILE* handle = nullptr;
    if (fopen_s(&handle, path.c_str(), "rb") != 0)
        return {value{}, false};
    file.reset(handle);
# else
    file.reset(fopen(path.c_str(), "rb"));
# endif

    if (!file)
        return {value{}, false};

    fseek(file.get(), 0, SEEK_END);
    auto size = static_cast<size_t>(ftell(file.get()));
    fseek(file.get(), 0, SEEK_SET);

    string data;
    data.resize(size);
    if (fread(const_cast<char*>(data.data()), size, 1, file.get()) != 1)
        return {value{}, false};

    return {parse(data), true};
}

bool value::save(const string& path, const int indent, const char indent_char) const
{
    // Modern C++, so beautiful...
    std::unique_ptr<FILE, void(*)(FILE*)> file{nullptr, [](FILE* file) { if (file) fclose(file); }};
# if defined(_MSC_VER) || (defined(__STDC_LIB_EXT1__) && __STDC_WANT_LIB_EXT1__)
    FILE* handle = nullptr;
    if (fopen_s(&handle, path.c_str(), "wb") != 0)
        return false;
    file.reset(handle);
# else
    file.reset(fopen(path.c_str(), "wb"));
# endif

    if (!file)
        return false;

    auto data = dump(indent, indent_char);

    if (fwrite(data.data(), data.size(), 1, file.get()) != 1)
        return false;

    return true;
}

# endif

} // namespace crude_json
