#include "wrap-string.hpp"

#include "eng-malloc.hpp"
#include "streambuffer.hpp"
#include "spookyv2.hpp"

#include <cassert>
#include <cstring>


int lua_writer_into_streambuffer(lua_State *L, const void* bytes, size_t sz, void* sbv) {
    StreamBuffer *sb = (StreamBuffer *)sbv;
    memcpy(sb->make_space(sz), bytes, sz);
    sb->wrote_space(sz);
    return 0;
}

static bool streq(const char *str, const char *data) {
    int len = strlen(str);
    return memcmp(str, data, len) == 0;
}

LuaDefine(unittests_streambuffer, "", "some unit tests") {
    // An 11-byte fixed-size stream buffer.
    StreamBuffer sb11(11, true);

    // Check the initial state.
    LuaAssert(L, sb11.layout_is(0, 0, 11));

    // Write a few bytes.
    sb11.write_bytes("abcdef");
    LuaAssert(L, sb11.layout_is(0, 6, 5));

    // Try reading some bytes.
    LuaAssert(L, streq("abcd", sb11.read_bytes(4)));
    LuaAssert(L, sb11.layout_is(4, 2, 5));
    
    // Put back two bytes.
    sb11.unread_to(2);
    LuaAssert(L, sb11.layout_is(2, 4, 5));

    // Read some more bytes.
    LuaAssert(L, streq("cdef", sb11.read_bytes(4)));
    LuaAssert(L, sb11.layout_is(6, 0, 5));

    // Reading bytes now should raise an EOF and should not alter layout
    try {
        sb11.read_bytes(1);
        LuaAssert(L, false && "This should have thrown an exception");
    } catch (const StreamEofOnRead &) {}
    LuaAssert(L, sb11.layout_is(6, 0, 5));

    // Write some more bytes into the stream, forcing a shift-left
    sb11.write_bytes("ghijkl");
    LuaAssert(L, sb11.layout_is(0, 6, 5));

    // Test buffer wrapping a little more.
    LuaAssert(L, streq("ghi", sb11.read_bytes(3)));
    LuaAssert(L, sb11.layout_is(3, 3, 5));
    sb11.write_bytes("mnopqr");
    LuaAssert(L, sb11.layout_is(0, 9, 2));
    LuaAssert(L, streq("jklmnopqr", sb11.read_bytes(9)));
    LuaAssert(L, sb11.layout_is(9, 0, 2));

    // Test 1-byte integer ops.
    sb11.clear();
    for (int i = 0; i < 10; i++) {
        sb11.write_int8(i);
        sb11.write_int8(i+100);
        LuaAssert(L, sb11.read_int8() == i);
        LuaAssert(L, sb11.read_int8() == i+100);
    }

    // Test 2-byte integer ops.
    for (int i = 0; i < 10; i++) {
        sb11.write_int16(i);
        sb11.write_int16(i+10000);
        LuaAssert(L, sb11.read_int16() == i);
        LuaAssert(L, sb11.read_int16() == i+10000);
    }

    // Test 4-byte integer ops.
    for (int i = 0; i < 10; i++) {
        sb11.write_int32(i);
        sb11.write_int32(i+1000000);
        LuaAssert(L, sb11.read_int32() == i);
        LuaAssert(L, sb11.read_int32() == i+1000000);
    }

    // Test 8-byte integer ops.
    for (int i = 0; i < 10; i++) {
        sb11.write_int64(i + 1000000);
        LuaAssert(L, sb11.read_int64() == i + 1000000);
    }

    // Check the write count and read count accumulator ability.
    sb11.clear();
    LuaAssert(L, sb11.total_writes() == 0);
    LuaAssert(L, sb11.total_reads() == 0);
    for (int i = 0; i < 10; i++) {
        LuaAssert(L, sb11.total_writes() == i * 8);
        sb11.write_int32(i);
        sb11.write_int32(i+1000000);
        LuaAssert(L, sb11.total_reads() == i * 8);
        LuaAssert(L, sb11.read_int32() == i);
        LuaAssert(L, sb11.read_int32() == i+1000000);
    }

    // Try clearing the buffer.
    sb11.clear();
    LuaAssert(L, sb11.layout_is(0, 0, 11));

    // Write a number then overwrite.
    for (int i = 0; i < 2; i++) {
        sb11.write_int16(12);
        sb11.write_int16(34);
        int64_t wc = sb11.total_writes();
        sb11.write_int16(56);
        sb11.write_int16(78);
        sb11.overwrite_int16(wc, 90);
        LuaAssert(L, sb11.read_int16() == 12);
        LuaAssert(L, sb11.read_int16() == 90);
        LuaAssert(L, sb11.read_int16() == 56);
        LuaAssert(L, sb11.read_int16() == 78);
    }

    // Try compact string encoding.
    sb11.clear();
    sb11.write_string("abc");
    sb11.write_string("");
    sb11.write_string("de");
    LuaAssert(L, sb11.layout_is(0, 8, 3));
    LuaAssert(L, sb11.read_string() == "abc");
    LuaAssert(L, sb11.read_string() == "");
    LuaAssert(L, sb11.read_string() == "de");

    // Make sure that contents_equal is not obviously broken.
    StreamBuffer eqsb1, eqsb2;
    eqsb1.write_int32(12);
    eqsb1.write_int32(34);
    eqsb2.write_int32(34);
    LuaAssert(L, !eqsb1.contents_equal(&eqsb2));
    eqsb1.read_int32();
    LuaAssert(L, eqsb1.contents_equal(&eqsb2));
    eqsb1.write_int32(34);
    LuaAssert(L, !eqsb1.contents_equal(&eqsb2));
    
    return 0;
}
