// ------------------------------------------------------------------------------------------------
#include "Library/JSON.hpp"

// ------------------------------------------------------------------------------------------------
#include <sqratConst.h>

// ------------------------------------------------------------------------------------------------
#include <cstdio>

// ------------------------------------------------------------------------------------------------
namespace SqMod {

// ------------------------------------------------------------------------------------------------
static SQInteger SqToJSON(HSQUIRRELVM vm) noexcept
{
    return sq_throwerror(vm, _SC("Not implemented yet!"));
}

// ------------------------------------------------------------------------------------------------
static SQInteger SqFromJson_Push(HSQUIRRELVM vm, const sajson::value & node) noexcept
{
    // Operation result
    SQInteger r = SQ_OK;
    // Identify element type
    switch (node.get_type())
    {
        case sajson::TYPE_INTEGER: {
            sq_pushinteger(vm, static_cast< SQInteger >(node.get_integer_value()));
        } break;
        case sajson::TYPE_DOUBLE: {
            sq_pushfloat(vm, static_cast< SQFloat >(node.get_double_value()));
        } break;
        case sajson::TYPE_NULL: {
            sq_pushnull(vm);
        } break;
        case sajson::TYPE_FALSE: {
            sq_pushbool(vm, SQFalse);
        } break;
        case sajson::TYPE_TRUE: {
            sq_pushbool(vm, SQTrue);
        } break;
        case sajson::TYPE_STRING: {
            sq_pushstring(vm, node.as_cstring(), static_cast< SQInteger >(node.get_string_length()));
        } break;
        case sajson::TYPE_ARRAY: {
            // Array length
            const size_t n = node.get_length();
            // Create a new array on the stack
            sq_newarrayex(vm, static_cast< SQInteger >(n));
            // Process array elements
            for (size_t i = 0; i < n; ++i)
            {
                // Transform the value into a script object on the stack
                r = SqFromJson_Push(vm, node.get_array_element(i));
                // Did we fail?
                if (SQ_FAILED(r))
                {
                    break; // Abort
                }
                // At this point we have a value on the stack
                r = sq_arrayappend(vm, -2);
                // Did we fail?
                if (SQ_FAILED(r))
                {
                    // Discard the value
                    sq_poptop(vm);
                    // Abort
                    break;
                }
            }
            // Anything bad happened?
            if (SQ_FAILED(r))
            {
                sq_poptop(vm); // Discard the array
            }
        } break;
        case sajson::TYPE_OBJECT: {
            // Object length
            const size_t n = node.get_length();
            // Create a new table on the stack
            sq_newtableex(vm, static_cast< SQInteger >(n));
            //
            for (size_t i = 0; i < n; ++i)
            {
                const auto k = node.get_object_key(i);
                // Transform the key into a script object on the stack
                sq_pushstring(vm, k.data(), static_cast< SQInteger >(k.length()));
                // Transform the value into a script object on the stack
                r = SqFromJson_Push(vm, node.get_object_value(i));
                // Did we fail?
                if (SQ_FAILED(r))
                {
                    // Discard the key
                    sq_poptop(vm);
                    // Abort
                    break;
                }
                // At this point we have a key and a value on the stack
                r = sq_newslot(vm, -3, SQFalse);
                // Did we fail?
                if (SQ_FAILED(r))
                {
                    // Discard the key/value pair
                    sq_pop(vm, 2);
                    // Abort
                    break;
                }
            }
            // Anything bad happened?
            if (SQ_FAILED(r))
            {
                sq_poptop(vm); // Discard the table
            }
        } break;
        default:
            // Should never really get here because it should be sanitized by the JSON parser
            // But doesn't hurt to have it here in case something out of our scope goes wrong
            r = sq_throwerror(vm, _SC("Unrecognized JSON type"));
    }
    // Return the result
    return r;
}

// ------------------------------------------------------------------------------------------------
static SQInteger SqFromJSON(HSQUIRRELVM vm) noexcept
{
    // Remember the current stack size
    const SQInteger top = sq_gettop(vm);
    // Was the JSON string specified?
    if (top < 2)
    {
        return sq_throwerror(vm, _SC("Please specify the JSON string to parse"));
    }
    // JSON string storage
    StackStrF s(vm, 2);
    // Attempt to retrieve the specified JSON string
    if (SQ_FAILED(s.Proc(true)))
    {
        return s.mRes; // Propagate the error
    }
    // Attempt to parse the specified JSON string
    const sajson::document & document = sajson::parse(sajson::dynamic_allocation(), sajson::string(s.mPtr, static_cast<size_t>(s.mLen)));
    // See if there was an error
    if (!document.is_valid())
    {
        return sq_throwerror(vm, document.get_error_message_as_cstring());
    }
    // Process the nodes that were parsed from the string
    SQInteger r = SqFromJson_Push(vm, document.get_root());
    // We either have a value to return or we propagate some error
    return SQ_SUCCEEDED(r) ? 1 : r;
}

// ================================================================================================
void Register_JSON(HSQUIRRELVM vm)
{
    RootTable(vm).SquirrelFunc(_SC("SqToJSON"), SqToJSON);
    RootTable(vm).SquirrelFunc(_SC("SqFromJSON"), SqFromJSON);
}

} // Namespace:: SqMod
