/* Independent C11 host: only the public header and native library are needed. */
#include "sqs_map.h"
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if !defined(_WIN32)
#include <pthread.h>
#endif

static void ok(int32_t status) {
    if (status != SQSM_MAP_OK) {
        fprintf(stderr, "map status=%" PRId32 ": %s\n", status, sqsm_map_last_error());
        exit(1);
    }
}
static int contains(const sqsm_map_bytes_t *bytes, const char *text) {
    char *copy = malloc((size_t)bytes->len + 1);
    assert(copy);
    memcpy(copy, bytes->data, (size_t)bytes->len);
    copy[bytes->len] = 0;
    int found = strstr(copy, text) != NULL;
    free(copy);
    return found;
}
#if !defined(_WIN32)
struct thread_args { sqsm_map_t map; uint64_t allocation; };
static void *other_thread(void *raw) {
    struct thread_args *args = raw;
    sqsm_map_bytes_t state = {0};
    assert(sqsm_map_state_json(args->map, &state) == SQSM_MAP_CONFLICT);
    assert(state.data == NULL && state.allocation == 0);
    ok(sqsm_map_bytes_free(args->allocation));
    return NULL;
}
#endif

int main(void) {
    assert(sqsm_map_api_version() == 1);
    assert(sqsm_map_create(0, 32, NULL) == SQSM_MAP_INVALID_ARGUMENT);
    sqsm_map_t map = 99;
    assert(sqsm_map_create(0, 32, &map) == SQSM_MAP_INVALID_ARGUMENT && map == 0);
    assert(sqsm_map_destroy(0) == SQSM_MAP_STALE_HANDLE);
    ok(sqsm_map_bytes_free(0));
    if (!(sqsm_map_runtime_flags() & SQSM_MAP_UNWIND_SESSION)) {
        fprintf(stderr, "build with --profile release-capi\n");
        return 1;
    }
    int32_t created = sqsm_map_create(64, 64, &map);
    if (created != SQSM_MAP_OK) {
        fprintf(stderr, "GPU initialization failed: %s\n", sqsm_map_last_error());
        return getenv("SQS_REQUIRE_GPU") ? 1 : 77;
    }
    sqsm_map_t second = 0;
    ok(sqsm_map_create(32, 32, &second));
    assert(second != map);
    sqsm_map_bytes_t state = {0}, events = {0}, frame = {0};
    ok(sqsm_map_poll_events_json(map, &events));
    assert(contains(&events, "\"type\":\"load\""));
    ok(sqsm_map_bytes_free(events.allocation));
    ok(sqsm_map_render_rgba(map, &frame));
    assert(frame.len == 64 * 64 * 4);
    uint8_t *blank = malloc((size_t)frame.len);
    assert(blank);
    memcpy(blank, frame.data, (size_t)frame.len);
    ok(sqsm_map_bytes_free(frame.allocation));
    assert(sqsm_map_bytes_free(frame.allocation) == SQSM_MAP_STALE_HANDLE);

    const char geojson[] = "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[0,0],[1,0],[1,1],[0,1],[0,0]]]},\"properties\":{}}]}";
    uint64_t layer = 0;
    ok(sqsm_map_add_geojson(map, (const uint8_t *)geojson, sizeof(geojson) - 1, &layer));
    assert(layer != 0);
    ok(sqsm_map_render_rgba(map, &frame));
    assert(frame.len == 64 * 64 * 4 && memcmp(blank, frame.data, (size_t)frame.len) != 0);
    free(blank);
    ok(sqsm_map_bytes_free(frame.allocation));
    ok(sqsm_map_resize(map, 128, 64));
    ok(sqsm_map_set_native_bearing(map, 25));
    assert(sqsm_map_set_native_bearing(map, 0.0 / 0.0) == SQSM_MAP_INVALID_ARGUMENT);
    ok(sqsm_map_state_json(map, &state));
    assert(contains(&state, "\"width\":128") && contains(&state, "\"bearing\":25.0") && contains(&state, "\"layerCount\":1"));
    ok(sqsm_map_bytes_free(state.allocation));
    ok(sqsm_map_render_rgba(map, &frame));
    assert(frame.len == 128 * 64 * 4);
#if !defined(_WIN32)
    struct thread_args args = {map, frame.allocation};
    pthread_t worker;
    assert(pthread_create(&worker, NULL, other_thread, &args) == 0);
    assert(pthread_join(worker, NULL) == 0);
#else
    ok(sqsm_map_bytes_free(frame.allocation));
#endif
    ok(sqsm_map_state_json(second, &state));
    assert(contains(&state, "\"width\":32") && contains(&state, "\"layerCount\":0"));
    ok(sqsm_map_bytes_free(state.allocation));
    ok(sqsm_map_poll_events_json(map, &events));
    assert(contains(&events, "sqs.layerschange") && contains(&events, "resize"));
    ok(sqsm_map_bytes_free(events.allocation));
    ok(sqsm_map_remove_layer(map, layer));
    assert(sqsm_map_remove_layer(map, layer) == SQSM_MAP_NOT_FOUND);
    /* Fill the event budget; rejected operation must not change state. */
    for (unsigned i = 0; i < 127; ++i) ok(sqsm_map_set_native_bearing(map, (double)(i + 100)));
    assert(sqsm_map_set_native_bearing(map, 359) == SQSM_MAP_RESOURCE_LIMIT);
    ok(sqsm_map_poll_events_json(map, &events));
    ok(sqsm_map_bytes_free(events.allocation));
    ok(sqsm_map_set_native_bearing(map, 359));
    /* A returned allocation outlives map destruction. */
    ok(sqsm_map_state_json(map, &state));
    ok(sqsm_map_destroy(map));
    assert(contains(&state, "\"bearing\":359.0"));
    ok(sqsm_map_bytes_free(state.allocation));
    assert(sqsm_map_destroy(map) == SQSM_MAP_STALE_HANDLE);
    assert(sqsm_map_resize(map, 1, 1) == SQSM_MAP_STALE_HANDLE);
    ok(sqsm_map_destroy(second));
    puts("public C map lifecycle, GPU GeoJSON pixels, isolation, thread ownership, events and buffer lifetime: PASS");
    return 0;
}
