#include "w3cs_packed_position.h"

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

static void round_trip(uint32_t count, uint32_t axes, const uint8_t *mask,
                       unsigned drop_bits)
{
    int16_t *source = (int16_t *)calloc(count ? count : 1u, sizeof(*source));
    int16_t *decoded = (int16_t *)calloc(count ? count : 1u, sizeof(*decoded));
    uint8_t *packed;
    size_t packed_size = 0;
    size_t consumed = 0;
    uint32_t index;
    int scale = 1 << drop_bits;
    assert(source && decoded);
    for (index = 0; index < count; ++index) {
        int value = ((int)(index * 7919u % 511u) - 255) * scale;
        if (value < -32768 || value > 32767)
            value = 0;
        source[index] = (int16_t)value;
        if (mask && !(mask[index >> 3] & (1u << (index & 7u))))
            source[index] = 0;
    }
    assert(w3cs_packed_position_encode(source, count, axes, mask, drop_bits,
                                       NULL, 0, &packed_size));
    packed = (uint8_t *)malloc(packed_size ? packed_size : 1u);
    assert(packed);
    assert(w3cs_packed_position_encode(source, count, axes, mask, drop_bits,
                                       packed, packed_size, &packed_size));
    assert(w3cs_packed_position_decode(packed, packed_size, decoded, count,
                                       axes, mask, drop_bits, &consumed));
    assert(consumed == packed_size);
    assert(memcmp(source, decoded, (size_t)count * sizeof(*source)) == 0);
    free(packed);
    free(decoded);
    free(source);
}

static void test_exact_vector(void)
{
    const int16_t source[] = {2, 4, 6, 4, 0, -2};
    const uint8_t expected[] = {3, 0xa2, 0xb5, 0x03};
    uint8_t output[sizeof(expected)] = {0};
    int16_t decoded[6] = {0};
    size_t size = 0;
    size_t consumed = 0;
    assert(w3cs_packed_position_encode(source, 6, 3, NULL, 1, output,
                                       sizeof(output), &size));
    assert(size == sizeof(expected));
    assert(memcmp(output, expected, sizeof(expected)) == 0);
    assert(w3cs_packed_position_decode(output, size, decoded, 6, 3, NULL, 1,
                                       &consumed));
    assert(consumed == size);
    assert(memcmp(source, decoded, sizeof(source)) == 0);
}

static void test_sparse_predictor_reset(void)
{
    int16_t source[9] = {5, 0, 0, 0, 0, 0, 7, 0, 0};
    int16_t decoded[9];
    uint8_t mask[2] = {0x41, 0};
    uint8_t output[16];
    size_t size = 0;
    size_t consumed = 0;
    /* Index 3 is an unselected X. It resets the X predictor before index 6,
     * so the two encoded residuals are 5 and 7, not 5 and 2. */
    assert(w3cs_packed_position_encode(source, 9, 3, mask, 0, output,
                                       sizeof(output), &size));
    assert(size == 2);
    assert(output[0] == 4);
    assert(output[1] == 0xeau);
    assert(w3cs_packed_position_decode(output, size, decoded, 9, 3, mask, 0,
                                       &consumed));
    assert(consumed == size);
    assert(memcmp(source, decoded, sizeof(source)) == 0);
}

static void test_boundaries_and_blocks(void)
{
    int16_t source[33] = {0};
    int16_t decoded[33] = {0};
    uint8_t output[80];
    size_t size = 0;
    size_t consumed = 0;
    source[0] = INT16_MIN;
    source[3] = INT16_MAX;
    source[32] = -1234;
    assert(w3cs_packed_position_encode(source, 33, 3, NULL, 0, output,
                                       sizeof(output), &size));
    assert(output[0] == 17u);
    /* A second independent block starts after 1 + ceil(32*17/8) bytes. */
    assert(output[69] <= 17u);
    assert(w3cs_packed_position_decode(output, size, decoded, 33, 3, NULL, 0,
                                       &consumed));
    assert(consumed == size);
    assert(memcmp(source, decoded, sizeof(source)) == 0);
}

static void test_errors(void)
{
    int16_t one[1] = {1};
    int16_t decoded[9] = {0};
    uint8_t output[8] = {0xa5, 0xa5, 0xa5, 0xa5,
                         0xa5, 0xa5, 0xa5, 0xa5};
    uint8_t mask[2] = {1, 0};
    size_t size = 0;
    size_t consumed = 0;
    assert(!w3cs_packed_position_encode(one, 1, 1, NULL, 1, NULL, 0,
                                        &size));
    assert(!w3cs_packed_position_encode(one, 1, 0, NULL, 0, NULL, 0,
                                        &size));
    assert(!w3cs_packed_position_encode(one, 1, 5, NULL, 0, NULL, 0,
                                        &size));
    assert(w3cs_packed_position_encode(one, 1, 1, NULL, 0, NULL, 0,
                                       &size));
    assert(size == 2);
    assert(!w3cs_packed_position_encode(one, 1, 1, NULL, 0, output, 1,
                                        &size));
    for (size_t index = 0; index < sizeof(output); ++index)
        assert(output[index] == 0xa5);
    {
        const uint8_t bad_width[] = {18, 0};
        assert(!w3cs_packed_position_decode(bad_width, sizeof(bad_width),
                                            decoded, 1, 1, NULL, 0,
                                            &consumed));
    }
    {
        const uint8_t truncated[] = {1};
        assert(!w3cs_packed_position_decode(truncated, sizeof(truncated),
                                            decoded, 1, 1, NULL, 0,
                                            &consumed));
    }
    {
        const uint8_t bad_padding[] = {1, 0x81};
        assert(!w3cs_packed_position_decode(bad_padding,
                                            sizeof(bad_padding), decoded,
                                            1, 1, NULL, 0, &consumed));
    }
    {
        /* Zigzag value 2 needs two bits. Width three is decodable but not
         * canonical, so a sender cannot smuggle redundant high zero bits. */
        const uint8_t noncanonical[] = {3, 0x02};
        assert(!w3cs_packed_position_decode(noncanonical,
                                            sizeof(noncanonical), decoded,
                                            1, 1, NULL, 0, &consumed));
    }
    {
        /* width 17, zigzag(+32768) cannot reconstruct an int16 quantum. */
        const uint8_t overflow[] = {17, 0x00, 0x00, 0x01};
        assert(!w3cs_packed_position_decode(overflow, sizeof(overflow),
                                            decoded, 1, 1, NULL, 0,
                                            &consumed));
    }
    mask[1] = 0x80; /* Sparse bits past total_count are non-canonical. */
    assert(!w3cs_packed_position_decode(NULL, 0, decoded, 9, 3, mask, 0,
                                        &consumed));
}

int main(void)
{
    uint8_t sparse[9] = {0};
    uint32_t index;
    test_exact_vector();
    test_sparse_predictor_reset();
    test_boundaries_and_blocks();
    for (index = 0; index < 65; ++index)
        if (index % 3u != 1u)
            sparse[index >> 3] |= (uint8_t)(1u << (index & 7u));
    round_trip(0, 3, NULL, 0);
    round_trip(1, 1, NULL, 0);
    round_trip(31, 3, NULL, 0);
    round_trip(32, 3, NULL, 0);
    round_trip(33, 3, NULL, 0);
    round_trip(65, 3, NULL, 0);
    round_trip(65, 3, sparse, 0);
    round_trip(65, 3, sparse, 5);
    round_trip(65, 4, NULL, 3);
    test_errors();
    puts("w3cs_packed_position_test: all tests passed");
    return 0;
}
