/**
 * @file json_helpers.c
 * @brief Implements the json helpers subsystem.
 */

#include <jansson.h>
#include <string.h>
#include <akerror.h>
#include <akstdlib.h>

#include <akgl/json_helpers.h>
#include <akgl/game.h>
#include <akgl/heap.h>
#include <akgl/staticstring.h>
#include <akgl/registry.h>

akerr_ErrorContext *akgl_get_json_object_value(json_t *obj, char *key, json_t **dest)
{
    PREPARE_ERROR(errctx);
    FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL object pointer");
    FAIL_ZERO_RETURN(errctx, key, AKERR_NULLPOINTER, "NULL key string");
    FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL destination pointer");
    json_t *value = json_object_get(obj, key);
    FAIL_ZERO_RETURN(errctx, value, AKERR_KEY, "Key %s not found in object", key);
    FAIL_ZERO_RETURN(errctx, (json_is_object(value)), AKERR_TYPE, "Key %s in object has incorrect type", key);
    *dest = value;
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akgl_get_json_boolean_value(json_t *obj, char *key, bool *dest)
{
    PREPARE_ERROR(errctx);
    FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL object pointer");
    FAIL_ZERO_RETURN(errctx, key, AKERR_NULLPOINTER, "NULL key string");
    FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL destination pointer");
    json_t *value = json_object_get(obj, key);
    FAIL_ZERO_RETURN(errctx, value, AKERR_KEY, "Key %s not found in object", key);
    FAIL_ZERO_RETURN(errctx, (json_is_boolean(value)), AKERR_TYPE, "Key %s in object has incorrect type", key);
    *dest = json_boolean_value(value);
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akgl_get_json_integer_value(json_t *obj, char *key, int *dest)
{
    PREPARE_ERROR(errctx);
    FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL object pointer");
    FAIL_ZERO_RETURN(errctx, key, AKERR_NULLPOINTER, "NULL key string");
    FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL destination pointer");
    json_t *value = json_object_get(obj, key);
    FAIL_ZERO_RETURN(errctx, value, AKERR_KEY, "Key %s not found in object", key);
    FAIL_ZERO_RETURN(errctx, (json_is_integer(value)), AKERR_TYPE, "Key %s in object has incorrect type", key);
    *dest = json_integer_value(value);
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akgl_get_json_number_value(json_t *obj, char *key, float32_t *dest)
{
    PREPARE_ERROR(errctx);
    FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL pointer reference");
    FAIL_ZERO_RETURN(errctx, key, AKERR_NULLPOINTER, "NULL key string");
    FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL destination pointer");
    json_t *value = json_object_get(obj, key);
    FAIL_ZERO_RETURN(errctx, value, AKERR_KEY, "Key %s not found in object", key);
    FAIL_ZERO_RETURN(errctx, (json_is_number(value)), AKERR_TYPE, "Key %s in object has incorrect type", key);
    *dest = json_number_value(value);
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akgl_get_json_double_value(json_t *obj, char *key, float64_t *dest)
{
    PREPARE_ERROR(errctx);
    FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL pointer reference");
    FAIL_ZERO_RETURN(errctx, key, AKERR_NULLPOINTER, "NULL key string");
    FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL destination pointer");
    json_t *value = json_object_get(obj, key);
    FAIL_ZERO_RETURN(errctx, value, AKERR_KEY, "Key %s not found in object", key);
    FAIL_ZERO_RETURN(errctx, (json_is_number(value)), AKERR_TYPE, "Key %s in object has incorrect type", key);
    *dest = json_number_value(value);
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akgl_get_json_string_value(json_t *obj, char *key, akgl_String **dest)
{
    json_t *value = NULL;
    PREPARE_ERROR(errctx);
    FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL pointer reference");
    FAIL_ZERO_RETURN(errctx, key, AKERR_NULLPOINTER, "NULL key string");
    FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL destination pointer");

    value = json_object_get(obj, key);
    FAIL_ZERO_RETURN(errctx, value, AKERR_KEY, "Key %s not found in object", key);
    FAIL_ZERO_RETURN(errctx, (json_is_string(value)), AKERR_TYPE, "Key %s in object has incorrect type", key);
    ATTEMPT {
	if ( *dest == NULL ) {
	    CATCH(errctx, akgl_heap_next_string(dest));
	    CATCH(errctx, akgl_string_initialize(*dest, NULL));
	}
	// FINISH(errctx, true), not false. With `false` a failed
	// akgl_heap_next_string was swallowed and the strncpy below ran through
	// the pointer it never set, so an exhausted string pool arrived as a
	// segfault inside strncpy rather than as AKGL_ERR_HEAP.
    } CLEANUP {
    } PROCESS(errctx) {
    } FINISH(errctx, true);

    PASS(errctx, aksl_strncpy(
	     (char *)&(*dest)->data,
	     sizeof((*dest)->data),
	     json_string_value(value),
	     sizeof((*dest)->data) - 1));
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akgl_get_json_array_value(json_t *obj, char *key, json_t **dest)
{
    PREPARE_ERROR(errctx);
    FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL pointer reference");
    FAIL_ZERO_RETURN(errctx, key, AKERR_NULLPOINTER, "NULL key string");
    FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL destination pointer");
    json_t *value = json_object_get(obj, key);
    FAIL_ZERO_RETURN(errctx, value, AKERR_KEY, "Key %s not found in object", key);
    FAIL_ZERO_RETURN(errctx, (json_is_array(value)), AKERR_TYPE, "Key %s in object has incorrect type", key);
    *dest = value;
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akgl_get_json_array_index_object(json_t *array, int index, json_t **dest)
{
    PREPARE_ERROR(errctx);
    FAIL_ZERO_RETURN(errctx, array, AKERR_NULLPOINTER, "NULL pointer reference");
    FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL destination pointer");
    json_t *value = json_array_get(array, index);
    FAIL_ZERO_RETURN(errctx, value, AKERR_OUTOFBOUNDS, "Index %d out of bounds for array", index);
    FAIL_ZERO_RETURN(errctx, (json_is_object(value)), AKERR_TYPE, "Index %d in object has incorrect type", index);
    *dest = value;
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akgl_get_json_array_index_integer(json_t *array, int index, int *dest)
{
    PREPARE_ERROR(errctx);
    FAIL_ZERO_RETURN(errctx, array, AKERR_NULLPOINTER, "NULL pointer reference");
    FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL destination pointer");
    json_t *value = json_array_get(array, index);
    FAIL_ZERO_RETURN(errctx, value, AKERR_OUTOFBOUNDS, "Index %d out of bounds for array", index);
    FAIL_ZERO_RETURN(errctx, (json_is_integer(value)), AKERR_TYPE, "Index %d in object has incorrect type", index);
    *dest = json_integer_value(value);
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akgl_get_json_array_index_string(json_t *array, int index, akgl_String **dest)
{
    PREPARE_ERROR(errctx);
    FAIL_ZERO_RETURN(errctx, array, AKERR_NULLPOINTER, "NULL pointer reference");
    FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL destination pointer");
    json_t *value = json_array_get(array, index);
    FAIL_ZERO_RETURN(errctx, value, AKERR_OUTOFBOUNDS, "Index %d out of bounds for array", index);
    FAIL_ZERO_RETURN(errctx, (json_is_string(value)), AKERR_TYPE, "Index %d in object has incorrect type", index);
    ATTEMPT {
	if ( *dest == NULL ) {
	    CATCH(errctx, akgl_heap_next_string(dest));
	    CATCH(errctx, akgl_string_initialize(*dest, NULL));
	}
	// FINISH(errctx, true), not false. With `false` a failed
	// akgl_heap_next_string was swallowed and the strncpy below ran through
	// the pointer it never set, so an exhausted string pool arrived as a
	// segfault inside strncpy rather than as AKGL_ERR_HEAP.
    } CLEANUP {
    } PROCESS(errctx) {
    } FINISH(errctx, true);

    PASS(errctx, aksl_strncpy(
	     (char *)&(*dest)->data,
	     sizeof((*dest)->data),
	     json_string_value(value),
	     sizeof((*dest)->data) - 1));
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akgl_get_json_with_default(akerr_ErrorContext *e, void *defval, void *dest, uint32_t defsize)
{
    // Two contexts, and the names now say which is which: `e` is the incoming
    // one being inspected, `errctx` is this function's own. They were `err` and
    // `e`, which put the incoming context under the name the convention
    // reserves for exactly that and the local one under a name that reads like
    // it.
    PREPARE_ERROR(errctx);
    if ( e == NULL ) {
	SUCCEED_RETURN(errctx);
    }

    FAIL_ZERO_RETURN(errctx, defval, AKERR_NULLPOINTER, "defval");
    FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "dest");

    ATTEMPT {
    } CLEANUP {
    } PROCESS(e) {
	// All three arms fall into the memcpy: HANDLE_GROUP emits no `break`,
	// so a new status has to be added *above* the arm holding the body, not
	// below it.
	//
	// AKERR_OUTOFBOUNDS is what akgl_get_json_array_index_object, _integer
	// and _string report for a short array. Without it here, "this element
	// is optional" worked for an object member and silently did not work
	// for an array element -- the error propagated instead of being
	// replaced by the default.
    } HANDLE_GROUP(e, AKERR_KEY) {
    } HANDLE_GROUP(e, AKERR_OUTOFBOUNDS) {
    } HANDLE_GROUP(e, AKERR_INDEX) {
	memcpy(dest, defval, defsize);
    } FINISH(e, true);

    SUCCEED_RETURN(errctx);
}
