#include "detlef_helpers.h"
#include <stdio.h>

#define TRY(call)                                                                      \
    do {                                                                               \
        DetlefStatus status_ = (call);                                                 \
        if (status_ != DETLEF_OK)                                                      \
            return status_;                                                            \
    } while (0)
typedef struct {
    unsigned reads;
    DetlefHandle retained_array;
} HostState;

static void output(void *data, const uint8_t *bytes, size_t length) {
    (void)data;
    if (length)
        fwrite(bytes, 1, length, stdout);
    fputc('\n', stdout);
}

static DetlefStatus input(void *data, DetlefSlice *line, uint32_t *present) {
    HostState *state = (HostState *)data;
    if (state->reads++ == 0) {
        *line = detlef_cstr("from C\r\n");
        *present = 1;
    } else {
        *present = 0;
    }
    return DETLEF_OK;
}

/* A Function receives eager values. argument_value returns an owned handle;
   putting it in result transfers that ownership to the evaluator. */
static DetlefStatus echo(void *data, DetlefFunctionAccess *access,
                         DetlefHandle *result) {
    (void)data;
    return detlef_function_argument_value(access, 0, result);
}

/* A Control can skip or repeat argument evaluation and return its result. */
static DetlefStatus twice_if(void *data, DetlefAccess *access, DetlefHandle *result) {
    (void)data;
    size_t count = 0;
    TRY(detlef_argument_count(access, &count));
    if (count != 2) {
        DetlefSpan span = {0, 0};
        return detlef_callback_error(
            access, detlef_cstr("twice_if expects two arguments"), span, 0);
    }
    uint32_t condition = 0;
    TRY(detlef_argument_boolean(access, 0, &condition));
    if (!condition)
        return detlef_value_none(access, result);
    DetlefHandle first = 0;
    TRY(detlef_argument_evaluate(access, 1, &first));
    TRY(detlef_handle_release(access, first));
    return detlef_argument_evaluate(access, 1, result);
}

static DetlefStatus setup(void *data, DetlefAccess *access) {
    HostState *state = (HostState *)data;
    TRY(detlef_stdlib_install(access, output, NULL, input, state));

    DetlefHandle package = 0, integer_ref = 0, string_ref = 0, app_name = 0;
    TRY(detlef_package_new(access, detlef_cstr("host"), &package));
    TRY(detlef_type_ref_named(access, detlef_cstr("integer"), &integer_ref));
    TRY(detlef_type_ref_named(access, detlef_cstr("string"), &string_ref));
    DetlefNamed fields[] = {detlef_named_cstr("x", integer_ref),
                            detlef_named_cstr("y", integer_ref)};
    TRY(detlef_package_record(access, package, detlef_cstr("Point"), fields, 2));
    DetlefNamed parameter = detlef_named_cstr("value", string_ref);
    TRY(detlef_package_function_cstr(access, package, "echo", &parameter, 1, NULL, echo,
                                     NULL));
    TRY(detlef_package_control_cstr(access, package, "twice_if", twice_if, NULL));
    TRY(detlef_value_string_cstr(access, "Embedded Detlef", &app_name));
    TRY(detlef_package_binding(access, package, detlef_cstr("app_name"), app_name));
    TRY(detlef_package_install(access, package));
    /* Installed callbacks and bindings no longer depend on the builder. */
    TRY(detlef_handle_release(access, package));
    TRY(detlef_handle_release(access, integer_ref));
    TRY(detlef_handle_release(access, string_ref));
    TRY(detlef_handle_release(access, app_name));

    DetlefHandle point_type = 0, x = 0, y = 0, point = 0;
    TRY(detlef_type_lookup(access, detlef_cstr("Point"), &point_type));
    TRY(detlef_value_integer(access, 7, &x));
    TRY(detlef_value_integer(access, 9, &y));
    DetlefHandle values[] = {x, y};
    TRY(detlef_record_create(access, point_type, values, 2, &point));
    TRY(detlef_variable_declare(access, detlef_cstr("host_point"), point_type, point));
    TRY(detlef_handle_release(access, point_type));
    TRY(detlef_handle_release(access, point));
    TRY(detlef_handle_release(access, x));
    TRY(detlef_handle_release(access, y));

    DetlefHandle result = 0;
    TRY(detlef_execute_cstr(access,
                            "print(echo(app_name)) "
                            "declare(counter, integer, 0) "
                            "twice_if(false, missing) "
                            "twice_if(true, {set(counter, add(counter, 1)) counter}) "
                            "print(counter) print(get_field(host_point, x)) "
                            "print(read_line()) "
                            "array_of(10, 20)",
                            &result));
    state->retained_array = result; /* Keep this object rooted between calls. */
    return DETLEF_OK;
}

static DetlefStatus inspect(void *data, DetlefAccess *access) {
    HostState *state = (HostState *)data;
    DetlefHandle element = 0;
    int64_t value = 0;
    TRY(detlef_array_get(access, state->retained_array, 1, &element));
    TRY(detlef_value_get_integer(access, element, &value));
    printf("Retained array element: %lld\n", (long long)value);
    TRY(detlef_handle_release(access, element));
    TRY(detlef_handle_release(access, state->retained_array));
    state->retained_array = 0;
    return DETLEF_OK;
}

int main(void) {
    DetlefRuntime *runtime = detlef_runtime_new();
    if (!runtime)
        return 1;
    HostState state = {0, 0};
    DetlefStatus status = detlef_runtime_access(runtime, setup, &state);
    if (status == DETLEF_OK)
        status = detlef_runtime_access(runtime, inspect, &state);
    if (status != DETLEF_OK) {
        DetlefText error = {0};
        fprintf(stderr, "Detlef status: %d\n", (int)status);
        if (detlef_last_error(runtime, &error) == DETLEF_OK) {
            if (error.len)
                fwrite(error.data, 1, error.len, stderr);
            fputc('\n', stderr);
            detlef_text_free(error);
        }
    }
    detlef_runtime_free(runtime);
    return status == DETLEF_OK ? 0 : 1;
}
