#include <cstring>

#include "../Tests.hpp"
#include "core/Test.hpp"
#include "core/ToString.hpp"
#include "core/Utility.hpp"

static void testPopCount() {
    TEST(4, Core::popCount(0xF));
    TEST(0, Core::popCount(0x0));
    TEST(2, Core::popCount(0x6));
    TEST(7, Core::popCount(0x7F));
    TEST(3, Core::popCount(0x2A));
    TEST(32, Core::popCount(0xFFFF'FFFF));
    TEST(64, Core::popCount(0xFFFF'FFFF'FFFF'FFFF));
    TEST(44, Core::popCount(0xFFFF'0FFF'FFFF));
}

static void testIf() {
    TEST_TRUE((Core::IsSame<Core::If<true, int, double>, int>));
    TEST_TRUE((Core::IsSame<Core::If<false, int, double>, double>));
}

static void testZeroRellocate() {
    void* buffer = Core::reallocateRaw(nullptr, 16);
    TEST_NOT_NULL(buffer);
    buffer = Core::reallocateRaw(buffer, 0);
    TEST_NULL(buffer);
}

static void testMemoryInfoList() {
    void* a = Core::allocateRaw(8);
    void* b = Core::allocateRaw(8);
    void* c = Core::allocateRaw(8);
    void* d = Core::allocateRaw(8);
    Core::deallocateRaw(b); // remove middle element
    Core::deallocateRaw(a); // remove first
    Core::deallocateRaw(d); // remove last
    Core::deallocateRaw(c); // remove single
    Core::deallocateRaw(nullptr);
}

static void testZeroAllocate() {
    constexpr size_t n = 1024 * 1024;
    void* a = Core::allocateRaw(n);
    memset(a, 0, n);
    void* b = Core::zeroAllocateRaw(n);
    TEST_TRUE(memcmp(a, b, n) == 0);
    Core::deallocateRaw(a);
    Core::deallocateRaw(b);
}

typedef struct {
    int i;
    i64 d;
} SwapTest;

static void testSwap() {
    SwapTest a = {3, 20};
    SwapTest b = {7, 30};
    Core::swap(a, b);
    TEST(7, a.i);
    TEST(3, b.i);
    TEST(30l, a.d);
    TEST(20l, b.d);
}

static void testSort() {
    size_t data[] = {9, 0, 3, 1, 8, 4, 6, 2, 5, 7};
    size_t n = 10;
    Core::bubbleSort(data, n);
    Core::bubbleSort(data, n);
    Core::bubbleSort(data, 0);
    for(size_t i = 0; i < n; i++) {
        TEST(data[i], i);
    }
}

static void testToString() {
    char buffer[512];
    TEST(10, Core::formatBuffer(buffer, sizeof(buffer), "a#a##a", 1.0, 2, 3));
    TEST_STRING("a1.00a#a23", buffer);
    TEST(5, Core::formatBuffer(buffer, sizeof(buffer), "aa##ab"));
    TEST_STRING("aa#ab", buffer);
    TEST(6, Core::formatBuffer(buffer, 3, "aaaaaa"));
    TEST_STRING("aa", buffer);
    TEST(4, Core::formatBuffer(buffer, 4, "a#", 456));
    TEST_STRING("a45", buffer);
    TEST(10, Core::formatBuffer(buffer, 4, "# # #", 456, 567, 78));
    TEST_STRING("456", buffer);
    TEST(10, Core::formatBuffer(buffer, 1, "# # #", 456, 567, 78));
    TEST_STRING("", buffer);
    TEST(10, Core::formatBuffer(nullptr, 0, "# # #", 456ll, 567l, 78ull));

    char c = 'a';
    short s = 4;
    unsigned char cu = 'h';
    unsigned short su = 67;
    signed char cs = 'x';
    TEST(
        10, Core::formatBuffer(
                buffer, sizeof(buffer), "# # # # #", c, s, cu, su, cs));
    TEST_STRING("a 4 h 67 x", buffer);

    unsigned char text[] = "fgsdf";
    TEST(5, Core::toString(text, buffer, sizeof(buffer)));
    TEST_STRING("fgsdf", buffer);
}

void testUtility() {
    testPopCount();
    testIf();
    testZeroRellocate();
    testMemoryInfoList();
    testZeroAllocate();
    testSwap();
    testSort();
    testToString();
}

static void outOfMemory(void*) {
    Core::setOutOfMemoryHandler(nullptr, nullptr);
}

[[noreturn]] void testInvalidAllocate() {
    Core::setOutOfMemoryHandler(outOfMemory, nullptr);
    Core::allocateRaw(0xFFF'FFFF'FFFF);
    TEST_TRUE(false);
    Core::finalizeTests();
    EXIT(0);
}

[[noreturn]] void testInvalidReallocate() {
    Core::setOutOfMemoryHandler(outOfMemory, nullptr);
    void* p = Core::allocateRaw(0xFF);
    Core::printMemoryReport();
    Core::reallocateRaw(p, 0xFFF'FFFF'FFFF);
    TEST_TRUE(false);
    Core::finalizeTests();
    EXIT(0);
}

[[noreturn]] void testInvalidNew() {
    Core::setOutOfMemoryHandler(outOfMemory, nullptr);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpragmas"
#pragma GCC diagnostic ignored "-Wunknown-warning-option"
#pragma GCC diagnostic error "-Wlarger-than=17592186044416"
    volatile char* p = coreNewN(char, 0xFFF'FFFF'FFFF);
    p[3] = 3;
#pragma GCC diagnostic pop
    TEST_TRUE(false);
    Core::finalizeTests();
    EXIT(0);
}

[[noreturn]] void testPreCanary() {
#ifdef CHECK_MEMORY
    char* p = static_cast<char*>(Core::allocateRaw(16));
    p[-1] = 0;
    Core::deallocateRaw(p);
    TEST_TRUE(false);
#endif
    Core::finalizeTests();
    EXIT(0);
}

[[noreturn]] void testPreCanaryNew() {
#ifdef CHECK_MEMORY
    volatile char* p2 = new char[3];
    volatile char* p = coreNewN(char, 16);
    delete[] p2;
    coreDeleteN(p);
    p = coreNewN(char, 16);
    p[-1] = 0;
    coreDeleteN(p);
    TEST_TRUE(false);
#endif
    Core::finalizeTests();
    EXIT(0);
}

[[noreturn]] void testPreCanaryNewArray() {
#ifdef CHECK_MEMORY
    volatile char* p = coreNew(char);
    p[-1] = 0;
    coreDelete(p);
    TEST_TRUE(false);
#endif
    Core::finalizeTests();
    EXIT(0);
}

[[noreturn]] void testPostCanary() {
#ifdef CHECK_MEMORY
    char* p = static_cast<char*>(Core::allocateRaw(16));
    p[17] = 0;
    Core::deallocateRaw(p);
    TEST_TRUE(false);
#endif
    Core::finalizeTests();
    EXIT(0);
}