/**
 * Properties test suite
 */

#include <SDL3/SDL.h>
#include <SDL3/SDL_test.h>
#include "testautomation_suites.h"

/* Test case functions */

/**
 * Test basic functionality
 */
static void SDLCALL count_properties(void *userdata, SDL_PropertiesID props, const char *name)
{
    int *count = (int *)userdata;
    ++(*count);
}
static void SDLCALL count_foo_properties(void *userdata, SDL_PropertiesID props, const char *name)
{
    int *count = (int *)userdata;
    if (SDL_strcmp(name, "foo") == 0) {
        ++(*count);
    }
}
static int SDLCALL properties_testBasic(void *arg)
{
    SDL_PropertiesID props;
    char key[2], expected_value[2];
    SDL_PropertyType type;
    void *value;
    const char *value_string;
    Sint64 value_number;
    float value_float;
    bool value_bool;
    int i, result, count;

    props = SDL_CreateProperties();
    SDLTest_AssertPass("Call to SDL_CreateProperties()");
    SDLTest_AssertCheck(props != 0,
        "Verify props were created, got: %" SDL_PRIu32, props);

    for (i = 0; i < 10; ++i) {
        SDL_snprintf(key, SDL_arraysize(key), "%c", 'a' + i);
        SDL_snprintf(expected_value, SDL_arraysize(expected_value), "%c", 'a' + i);
        result = SDL_SetPointerProperty(props, key, expected_value);
        SDLTest_AssertPass("Call to SDL_SetPointerProperty()");
        SDLTest_AssertCheck(result == true,
            "Verify property value was set, got: %d", result);
        value = SDL_GetPointerProperty(props, key, NULL);
        SDLTest_AssertPass("Call to SDL_GetPointerProperty()");
        SDLTest_AssertCheck(value && SDL_strcmp((const char *)value, expected_value) == 0,
            "Verify property value was set, got %s, expected %s", value ? (const char *)value : "NULL", expected_value);
    }

    count = 0;
    SDL_EnumerateProperties(props, count_properties, &count);
    SDLTest_AssertCheck(count == 10,
            "Verify property count, expected 10, got: %d", count);

    for (i = 0; i < 10; ++i) {
        SDL_snprintf(key, SDL_arraysize(key), "%c", 'a' + i);
        result = SDL_SetPointerProperty(props, key, NULL);
        SDLTest_AssertPass("Call to SDL_SetPointerProperty(NULL)");
        SDLTest_AssertCheck(result == true,
            "Verify property value was set, got: %d", result);
        value = SDL_GetPointerProperty(props, key, NULL);
        SDLTest_AssertPass("Call to SDL_GetPointerProperty()");
        SDLTest_AssertCheck(value == NULL,
            "Verify property value was set, got %s, expected NULL", (const char *)value);
    }

    count = 0;
    SDL_EnumerateProperties(props, count_properties, &count);
    SDLTest_AssertCheck(count == 0,
            "Verify property count, expected 0, got: %d", count);

    /* Check default values */
    value = SDL_GetPointerProperty(props, "foo", (void *)0xabcd);
    SDLTest_AssertCheck(value == (void *)0xabcd,
            "Verify property, expected 0xabcd, got: %p", value);
    value_string = SDL_GetStringProperty(props, "foo", "abcd");
    SDLTest_AssertCheck(value_string && SDL_strcmp(value_string, "abcd") == 0,
            "Verify string property, expected abcd, got: %s", value_string);
    value_number = SDL_GetNumberProperty(props, "foo", 1234);
    SDLTest_AssertCheck(value_number == 1234,
            "Verify number property, expected 1234, got: %" SDL_PRIu64, value_number);
    value_float = SDL_GetFloatProperty(props, "foo", 1234.0f);
    SDLTest_AssertCheck(value_float == 1234.0f,
            "Verify float property, expected 1234, got: %f", value_float);
    value_bool = SDL_GetBooleanProperty(props, "foo", true);
    SDLTest_AssertCheck(value_bool == true,
            "Verify boolean property, expected true, got: %s", value_bool ? "true" : "false");

    /* Check data value */
    SDLTest_AssertPass("Call to SDL_SetPointerProperty(\"foo\", 0x01)");
    SDL_SetPointerProperty(props, "foo", (void *)0x01);
    type = SDL_GetPropertyType(props, "foo");
    SDLTest_AssertCheck(type == SDL_PROPERTY_TYPE_POINTER,
            "Verify property type, expected %d, got: %d", SDL_PROPERTY_TYPE_POINTER, type);
    value = SDL_GetPointerProperty(props, "foo", NULL);
    SDLTest_AssertCheck(value == (void *)0x01,
            "Verify property, expected 0x01, got: %p", value);
    value_string = SDL_GetStringProperty(props, "foo", NULL);
    SDLTest_AssertCheck(value_string == NULL,
            "Verify string property, expected NULL, got: %s", value_string);
    value_number = SDL_GetNumberProperty(props, "foo", 0);
    SDLTest_AssertCheck(value_number == 0,
            "Verify number property, expected 0, got: %" SDL_PRIu64, value_number);
    value_float = SDL_GetFloatProperty(props, "foo", 0.0f);
    SDLTest_AssertCheck(value_float == 0.0f,
            "Verify float property, expected 0, got: %f", value_float);
    value_bool = SDL_GetBooleanProperty(props, "foo", false);
    SDLTest_AssertCheck(value_bool == false,
            "Verify boolean property, expected false, got: %s", value_bool ? "true" : "false");

    /* Check string value */
    SDLTest_AssertPass("Call to SDL_SetStringProperty(\"foo\", \"bar\")");
    SDL_SetStringProperty(props, "foo", "bar");
    type = SDL_GetPropertyType(props, "foo");
    SDLTest_AssertCheck(type == SDL_PROPERTY_TYPE_STRING,
            "Verify property type, expected %d, got: %d", SDL_PROPERTY_TYPE_STRING, type);
    value = SDL_GetPointerProperty(props, "foo", NULL);
    SDLTest_AssertCheck(value == NULL,
            "Verify property, expected NULL, got: %p", value);
    value_string = SDL_GetStringProperty(props, "foo", NULL);
    SDLTest_AssertCheck(value_string != NULL && SDL_strcmp(value_string, "bar") == 0,
            "Verify string property, expected bar, got: %s", value_string);
    value_number = SDL_GetNumberProperty(props, "foo", 0);
    SDLTest_AssertCheck(value_number == 0,
            "Verify number property, expected 0, got: %" SDL_PRIu64, value_number);
    value_float = SDL_GetFloatProperty(props, "foo", 0.0f);
    SDLTest_AssertCheck(value_float == 0.0f,
            "Verify float property, expected 0, got: %f", value_float);
    value_bool = SDL_GetBooleanProperty(props, "foo", false);
    SDLTest_AssertCheck(value_bool == true,
            "Verify boolean property, expected true, got: %s", value_bool ? "true" : "false");

    /* Check number value */
    SDLTest_AssertPass("Call to SDL_SetNumberProperty(\"foo\", 1)");
    SDL_SetNumberProperty(props, "foo", 1);
    type = SDL_GetPropertyType(props, "foo");
    SDLTest_AssertCheck(type == SDL_PROPERTY_TYPE_NUMBER,
            "Verify property type, expected %d, got: %d", SDL_PROPERTY_TYPE_NUMBER, type);
    value = SDL_GetPointerProperty(props, "foo", NULL);
    SDLTest_AssertCheck(value == NULL,
            "Verify property, expected NULL, got: %p", value);
    value_string = SDL_GetStringProperty(props, "foo", NULL);
    SDLTest_AssertCheck(value_string && SDL_strcmp(value_string, "1") == 0,
            "Verify string property, expected 1, got: %s", value_string);
    value_number = SDL_GetNumberProperty(props, "foo", 0);
    SDLTest_AssertCheck(value_number == 1,
            "Verify number property, expected 1, got: %" SDL_PRIu64, value_number);
    value_float = SDL_GetFloatProperty(props, "foo", 0.0f);
    SDLTest_AssertCheck(value_float == 1.0f,
            "Verify float property, expected 1, got: %f", value_float);
    value_bool = SDL_GetBooleanProperty(props, "foo", false);
    SDLTest_AssertCheck(value_bool == true,
            "Verify boolean property, expected true, got: %s", value_bool ? "true" : "false");

    /* Check float value */
    SDLTest_AssertPass("Call to SDL_SetFloatProperty(\"foo\", 1)");
    SDL_SetFloatProperty(props, "foo", 1.75f);
    type = SDL_GetPropertyType(props, "foo");
    SDLTest_AssertCheck(type == SDL_PROPERTY_TYPE_FLOAT,
            "Verify property type, expected %d, got: %d", SDL_PROPERTY_TYPE_FLOAT, type);
    value = SDL_GetPointerProperty(props, "foo", NULL);
    SDLTest_AssertCheck(value == NULL,
            "Verify property, expected NULL, got: %p", value);
    value_string = SDL_GetStringProperty(props, "foo", NULL);
    SDLTest_AssertCheck(value_string && SDL_strcmp(value_string, "1.750000") == 0,
            "Verify string property, expected 1.750000, got: %s", value_string);
    value_number = SDL_GetNumberProperty(props, "foo", 0);
    SDLTest_AssertCheck(value_number == 2,
            "Verify number property, expected 2, got: %" SDL_PRIu64, value_number);
    value_float = SDL_GetFloatProperty(props, "foo", 0.0f);
    SDLTest_AssertCheck(value_float == 1.75f,
            "Verify float property, expected 1.75, got: %f", value_float);
    value_bool = SDL_GetBooleanProperty(props, "foo", false);
    SDLTest_AssertCheck(value_bool == true,
            "Verify boolean property, expected true, got: %s", value_bool ? "true" : "false");

    /* Check boolean value */
    SDLTest_AssertPass("Call to SDL_SetBooleanProperty(\"foo\", true)");
    SDL_SetBooleanProperty(props, "foo", 3); /* Note we're testing non-true/false value here */
    type = SDL_GetPropertyType(props, "foo");
    SDLTest_AssertCheck(type == SDL_PROPERTY_TYPE_BOOLEAN,
            "Verify property type, expected %d, got: %d", SDL_PROPERTY_TYPE_BOOLEAN, type);
    value = SDL_GetPointerProperty(props, "foo", NULL);
    SDLTest_AssertCheck(value == NULL,
            "Verify property, expected NULL, got: %p", value);
    value_string = SDL_GetStringProperty(props, "foo", NULL);
    SDLTest_AssertCheck(value_string && SDL_strcmp(value_string, "true") == 0,
            "Verify string property, expected true, got: %s", value_string);
    value_number = SDL_GetNumberProperty(props, "foo", 0);
    SDLTest_AssertCheck(value_number == 1,
            "Verify number property, expected 1, got: %" SDL_PRIu64, value_number);
    value_float = SDL_GetFloatProperty(props, "foo", 0.0f);
    SDLTest_AssertCheck(value_float == 1.0f,
            "Verify float property, expected 1, got: %f", value_float);
    value_bool = SDL_GetBooleanProperty(props, "foo", false);
    SDLTest_AssertCheck(value_bool == true,
            "Verify boolean property, expected true, got: %s", value_bool ? "true" : "false");

    /* Make sure we have exactly one property named foo */
    count = 0;
    SDL_EnumerateProperties(props, count_foo_properties, &count);
    SDLTest_AssertCheck(count == 1,
            "Verify foo property count, expected 1, got: %d", count);

    SDL_DestroyProperties(props);

    return TEST_COMPLETED;
}

/**
 * Test copy functionality
 */
static void SDLCALL copy_cleanup(void *userdata, void *value)
{
}
static int SDLCALL properties_testCopy(void *arg)
{
    SDL_PropertiesID a, b;
    int num;
    const char *string;
    void *data;
    int result;

    a = SDL_CreateProperties();
    SDL_SetNumberProperty(a, "num", 1);
    SDL_SetStringProperty(a, "string", "foo");
    SDL_SetPointerProperty(a, "data", &a);
    SDL_SetPointerPropertyWithCleanup(a, "cleanup", &a, copy_cleanup, &a);

    b = SDL_CreateProperties();
    SDL_SetNumberProperty(b, "num", 2);

    SDLTest_AssertPass("Call to SDL_CopyProperties(a, 0)");
    result = SDL_CopyProperties(a, 0);
    SDLTest_AssertCheck(result == false,
                        "SDL_CopyProperties() result, got %d, expected false", result);

    SDLTest_AssertPass("Call to SDL_CopyProperties(0, b)");
    result = SDL_CopyProperties(0, b);
    SDLTest_AssertCheck(result == false,
                        "SDL_CopyProperties() result, got %d, expected false", result);

    SDLTest_AssertPass("Call to SDL_CopyProperties(a, b)");
    result = SDL_CopyProperties(a, b);
    SDLTest_AssertCheck(result == true,
        "SDL_CopyProperties() result, got %d, expected true", result);

    SDL_DestroyProperties(a);

    num = (int)SDL_GetNumberProperty(b, "num", 0);
    SDLTest_AssertCheck(num == 1,
        "Checking number property, got %d, expected 1", num);

    string = SDL_GetStringProperty(b, "string", NULL);
    SDLTest_AssertCheck(string && SDL_strcmp(string, "foo") == 0,
        "Checking string property, got \"%s\", expected \"foo\"", string);

    data = SDL_GetPointerProperty(b, "data", NULL);
    SDLTest_AssertCheck(data == &a,
        "Checking data property, got %p, expected %p", data, &a);

    data = SDL_GetPointerProperty(b, "cleanup", NULL);
    SDLTest_AssertCheck(data == NULL,
        "Checking cleanup property, got %p, expected NULL", data);

    SDL_DestroyProperties(b);

    return TEST_COMPLETED;
}

/**
 * Test cleanup functionality
 */
static void SDLCALL cleanup(void *userdata, void *value)
{
    int *count = (int *)userdata;
    ++(*count);
}
static int SDLCALL properties_testCleanup(void *arg)
{
    SDL_PropertiesID props;
    char key[2], expected_value[2];
    int i, count;

    props = SDL_CreateProperties();

    SDLTest_AssertPass("Call to SDL_SetPointerProperty(cleanup)");
    count = 0;
    SDL_SetPointerPropertyWithCleanup(props, "a", "0", cleanup, &count);
    SDL_ClearProperty(props, "a");
    SDLTest_AssertCheck(count == 1,
        "Verify cleanup for deleting property, got %d, expected 1", count);

    SDLTest_AssertPass("Call to SDL_DestroyProperties()");
    count = 0;
    for (i = 0; i < 10; ++i) {
        SDL_snprintf(key, SDL_arraysize(key), "%c", 'a' + i);
        SDL_snprintf(expected_value, SDL_arraysize(expected_value), "%c", 'a' + i);
        SDL_SetPointerPropertyWithCleanup(props, key, expected_value, cleanup, &count);
    }
    SDL_DestroyProperties(props);
    SDLTest_AssertCheck(count == 10,
        "Verify cleanup for destroying properties, got %d, expected 10", count);

    return TEST_COMPLETED;
}

/**
 * Test locking functionality
 */
struct properties_thread_data
{
    bool done;
    SDL_PropertiesID props;
};
static int SDLCALL properties_thread(void *arg)
{
    struct properties_thread_data *data = (struct properties_thread_data *)arg;

    while (!data->done) {
        SDL_LockProperties(data->props);
        SDL_SetPointerProperty(data->props, "a", "thread_loop");
        SDL_UnlockProperties(data->props);
    }
    SDL_LockProperties(data->props);
    SDL_SetPointerProperty(data->props, "a", "thread_done");
    SDL_UnlockProperties(data->props);
    return 0;
}
static int SDLCALL properties_testLocking(void *arg)
{
    struct properties_thread_data data;
    SDL_Thread *thread;
    void *value;

    SDLTest_AssertPass("Testing property locking");
    data.done = false;
    data.props = SDL_CreateProperties();
    SDLTest_AssertPass("Setting property to 'init'");
    SDL_SetPointerProperty(data.props, "a", "init");
    thread = SDL_CreateThread(properties_thread, "properties_thread", &data);
    if (thread) {
        SDLTest_AssertPass("Waiting for property to change to 'thread_loop'");
        for ( ; ; )
        {
            SDL_Delay(10);
            SDL_LockProperties(data.props);
            value = SDL_GetPointerProperty(data.props, "a", NULL);
            SDL_UnlockProperties(data.props);

            if (!value || SDL_strcmp((const char *)value, "thread_loop") == 0) {
                break;
            }
        }
        SDLTest_AssertCheck(value && SDL_strcmp((const char *)value, "thread_loop") == 0,
            "After thread loop, property is %s, expected 'thread_loop'", value ? (const char *)value : "NULL");

        SDLTest_AssertPass("Setting property to 'main'");
        SDL_LockProperties(data.props);
        SDL_SetPointerProperty(data.props, "a", "main");
        SDL_Delay(100);
        value = SDL_GetPointerProperty(data.props, "a", NULL);
        SDLTest_AssertCheck(value && SDL_strcmp((const char *)value, "main") == 0,
            "After 100ms sleep, property is %s, expected 'main'", value ? (const char *)value : "NULL");
        SDL_UnlockProperties(data.props);

        data.done = true;
        SDL_WaitThread(thread, NULL);

        value = SDL_GetPointerProperty(data.props, "a", NULL);
        SDLTest_AssertCheck(value && SDL_strcmp((const char *)value, "thread_done") == 0,
            "After thread complete, property is %s, expected 'thread_done'", value ? (const char *)value : "NULL");
    }
    SDL_DestroyProperties(data.props);

    return TEST_COMPLETED;
}

/* ================= Test References ================== */

/* Properties test cases */
static const SDLTest_TestCaseReference propertiesTestBasic = {
    properties_testBasic, "properties_testBasic", "Test basic property functionality", TEST_ENABLED
};

static const SDLTest_TestCaseReference propertiesTestCopy = {
    properties_testCopy, "properties_testCopy", "Test property copy functionality", TEST_ENABLED
};

static const SDLTest_TestCaseReference propertiesTestCleanup = {
    properties_testCleanup, "properties_testCleanup", "Test property cleanup functionality", TEST_ENABLED
};

static const SDLTest_TestCaseReference propertiesTestLocking = {
    properties_testLocking, "properties_testLocking", "Test property locking functionality", TEST_ENABLED
};

/* Sequence of Properties test cases */
static const SDLTest_TestCaseReference *propertiesTests[] = {
    &propertiesTestBasic,
    &propertiesTestCopy,
    &propertiesTestCleanup,
    &propertiesTestLocking,
    NULL
};

/* Properties test suite (global) */
SDLTest_TestSuiteReference propertiesTestSuite = {
    "Properties",
    NULL,
    propertiesTests,
    NULL
};
