#include "w3cs_rain_quad.h"

#include <assert.h>
#include <stdio.h>
#include <string.h>

static void store_u32(uint8_t *target, uint32_t value)
{
    memcpy(target, &value, sizeof(value));
}

static void store_f32(uint8_t *target, float value)
{
    memcpy(target, &value, sizeof(value));
}

static void make_vertex(uint8_t *target, const float position[3],
                        unsigned corner)
{
    store_f32(target, position[0]);
    store_f32(target + 4, position[1]);
    store_f32(target + 8, position[2]);
    store_u32(target + 12, 0);
    store_u32(target + 16, 0);
    store_u32(target + 20, 0x3f800000u);
    store_u32(target + 24, 0xff649696u);
    store_u32(target + 28, (corner & 2u) ? 0x3f800000u : 0u);
    store_u32(target + 32, (corner & 1u) ? 0x3f800000u : 0u);
}

static void make_quad(uint8_t output[W3CS_RAIN_QUAD_BYTES])
{
    static const float positions[4][3] = {
        {-0.25f, -0.25f, 0.5f},
        {-0.25f, 0.0f, 0.5f},
        {-0.125f, -0.25f, 0.5f},
        {-0.125f, 0.0f, 0.5f}
    };
    for (unsigned corner = 0; corner < 4u; ++corner)
        make_vertex(output + corner * W3CS_RAIN_QUAD_STRIDE,
                    positions[corner], corner);
}

static struct w3cs_rain_quad_projection identity_projection(void)
{
    struct w3cs_rain_quad_projection projection = {0};
    projection.object_to_clip[0] = 1.0f;
    projection.object_to_clip[5] = 1.0f;
    projection.object_to_clip[10] = 1.0f;
    projection.object_to_clip[15] = 1.0f;
    projection.viewport_width = 1280.0f;
    projection.viewport_height = 720.0f;
    return projection;
}

static void test_exact_wire_vector(void)
{
    static const uint8_t expected[] = {
        0x51, 0x52, 0x01, 0x03,
        0x00, 0x00, 0x80, 0xbe,
        0x00, 0x00, 0x80, 0xbe,
        0x00, 0x00, 0x00, 0x3f,
        0x00, 0x00, 0x00,
        0x02, 0x02,
        0x00, 0x00, 0x00,
        0x03, 0x04,
        0x00
    };
    struct w3cs_rain_quad_projection projection = identity_projection();
    struct w3cs_rain_quad_result result;
    uint8_t source[W3CS_RAIN_QUAD_BYTES];
    uint8_t reconstructed[W3CS_RAIN_QUAD_BYTES];
    uint8_t decoded[W3CS_RAIN_QUAD_BYTES];
    uint8_t body[64];
    size_t body_size = 0;
    unsigned drop_bits = 0;
    make_quad(source);
    assert(w3cs_rain_quad_encode_body(source, sizeof(source), &projection,
        3.25f, 3, body, sizeof(body), &body_size, reconstructed,
        sizeof(reconstructed), &result));
    assert(body_size == sizeof(expected));
    assert(memcmp(body, expected, sizeof(expected)) == 0);
    assert(memcmp(reconstructed, source, sizeof(source)) == 0);
    assert(result.position_drop_bits == 3u);
    assert(result.quad_count == 1u);
    assert(result.maximum_combined_error_px == 0.0f);
    assert(w3cs_rain_quad_decode_body(body, body_size, sizeof(decoded),
        decoded, sizeof(decoded), &drop_bits));
    assert(drop_bits == 3u);
    assert(memcmp(decoded, reconstructed, sizeof(decoded)) == 0);
}

static void test_capacity_and_strict_fallback(void)
{
    struct w3cs_rain_quad_projection projection = identity_projection();
    uint8_t source[W3CS_RAIN_QUAD_BYTES];
    uint8_t body[64];
    uint8_t reconstructed[W3CS_RAIN_QUAD_BYTES];
    uint8_t before_body[64];
    uint8_t before_reconstructed[W3CS_RAIN_QUAD_BYTES];
    size_t body_size = 0;
    make_quad(source);
    assert(w3cs_rain_quad_body_capacity(sizeof(source)) == 43u);
    assert(w3cs_rain_quad_encode_body(NULL, sizeof(source), NULL, 0.0f, 0,
        NULL, 0, &body_size, NULL, 0, NULL));
    assert(body_size == 43u);
    assert(w3cs_rain_quad_body_capacity(sizeof(source) - 1u) == 0u);

    memset(body, 0xa5, sizeof(body));
    memset(reconstructed, 0x5a, sizeof(reconstructed));
    memcpy(before_body, body, sizeof(body));
    memcpy(before_reconstructed, reconstructed, sizeof(reconstructed));
    assert(!w3cs_rain_quad_encode_body(source, sizeof(source), &projection,
        3.25f, 3, body, 1, &body_size, reconstructed,
        sizeof(reconstructed), NULL));
    assert(memcmp(body, before_body, sizeof(body)) == 0);
    assert(memcmp(reconstructed, before_reconstructed,
                  sizeof(reconstructed)) == 0);

    /* Any attribute change escapes to an older opcode. */
    source[24] ^= 1u;
    assert(!w3cs_rain_quad_encode_body(source, sizeof(source), &projection,
        3.25f, 3, body, sizeof(body), &body_size, reconstructed,
        sizeof(reconstructed), NULL));
    source[24] ^= 1u;

    /* A non-affine fourth corner cannot pass a zero-error gate. */
    store_f32(source + 3u * W3CS_RAIN_QUAD_STRIDE, -0.124f);
    assert(!w3cs_rain_quad_encode_body(source, sizeof(source), &projection,
        0.0f, 0, body, sizeof(body), &body_size, reconstructed,
        sizeof(reconstructed), NULL));
    make_quad(source);

    /* The source itself must be inside D3D's depth volume. */
    for (unsigned corner = 0; corner < 4u; ++corner)
        store_f32(source + corner * W3CS_RAIN_QUAD_STRIDE + 8u, -0.1f);
    assert(!w3cs_rain_quad_encode_body(source, sizeof(source), &projection,
        3.25f, 3, body, sizeof(body), &body_size, reconstructed,
        sizeof(reconstructed), NULL));
}

static void expect_decode_failure(const uint8_t *body, size_t size)
{
    uint8_t output[W3CS_RAIN_QUAD_BYTES];
    uint8_t before[W3CS_RAIN_QUAD_BYTES];
    memset(output, 0xa7, sizeof(output));
    memcpy(before, output, sizeof(output));
    assert(!w3cs_rain_quad_decode_body(body, size, sizeof(output), output,
                                       sizeof(output), NULL));
    assert(memcmp(output, before, sizeof(output)) == 0);
}

static void test_decoder_rejects_noncanonical_input(void)
{
    static const uint8_t canonical[] = {
        0x51, 0x52, 0x01, 0x03,
        0x00, 0x00, 0x80, 0xbe,
        0x00, 0x00, 0x80, 0xbe,
        0x00, 0x00, 0x00, 0x3f,
        0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00,
        0x03, 0x04, 0x00
    };
    uint8_t changed[sizeof(canonical) + 1u];
    expect_decode_failure(canonical, sizeof(canonical) - 1u);
    memcpy(changed, canonical, sizeof(canonical));
    changed[0] = 0;
    expect_decode_failure(changed, sizeof(canonical));
    memcpy(changed, canonical, sizeof(canonical));
    changed[2]++;
    expect_decode_failure(changed, sizeof(canonical));
    memcpy(changed, canonical, sizeof(canonical));
    changed[3] = 15;
    expect_decode_failure(changed, sizeof(canonical));
    memcpy(changed, canonical, sizeof(canonical));
    changed[16] = 17;
    expect_decode_failure(changed, sizeof(canonical));
    memcpy(changed, canonical, sizeof(canonical));
    changed[19] = 3; /* Value 2 requires two bits, not three. */
    expect_decode_failure(changed, sizeof(canonical));
    memcpy(changed, canonical, sizeof(canonical));
    changed[20] |= 0x80; /* One-value lane has six padding bits. */
    expect_decode_failure(changed, sizeof(canonical));
    memcpy(changed, canonical, sizeof(canonical));
    changed[sizeof(canonical)] = 0;
    expect_decode_failure(changed, sizeof(changed));
}

int main(void)
{
    test_exact_wire_vector();
    test_capacity_and_strict_fallback();
    test_decoder_rejects_noncanonical_input();
    puts("w3cs_rain_quad_test: all tests passed");
    return 0;
}
