#include "listpool.h"

#include "test.h"

#define NUM_BLOCKS 10

DEF_LISTPOOL(test_pool, int, NUM_BLOCKS);

static int count(test_pool* pool) {
  int count = 0;
  listpool_foreach(pool, n, { count++; });
  return count;
}

static int sum(test_pool* pool) {
  int sum = 0;
  listpool_foreach(pool, n, { sum += *n; });
  return sum;
}

// Create a pool.
TEST_CASE(listpool_create) {
  test_pool pool;
  listpool_make(&pool);
}

// Allocate all N blocks.
TEST_CASE(listpool_fully_allocate) {
  test_pool pool;
  listpool_make(&pool);

  for (int i = 0; i < NUM_BLOCKS; ++i) {
    const int* block = listpool_alloc(&pool);
    TEST_TRUE(block != 0);
  }
}

// Allocate all N blocks, then free them.
TEST_CASE(listpool_fill_then_free) {
  test_pool pool;
  listpool_make(&pool);

  int* blocks[NUM_BLOCKS] = {0};
  for (int i = 0; i < NUM_BLOCKS; i++) {
    blocks[i] = listpool_alloc(&pool);
    TEST_TRUE(blocks[i] != 0);
  }

  for (int i = 0; i < NUM_BLOCKS; i++) {
    listpool_free(&pool, &blocks[i]);
    TEST_EQUAL(blocks[i], 0); // Pointer should be set to 0 on free.
  }

  TEST_EQUAL(count(&pool), 0);
}

// Attempt to allocate blocks past the maximum pool size.
// The pool should handle the failed allocations gracefully.
TEST_CASE(listpool_allocate_beyond_max_size) {
  test_pool pool;
  listpool_make(&pool);

  // Fully allocate the pool.
  for (int i = 0; i < NUM_BLOCKS; ++i) {
    TEST_TRUE(listpool_alloc(&pool) != 0);
  }

  // Past the end.
  for (int i = 0; i < NUM_BLOCKS; ++i) {
    TEST_EQUAL(listpool_alloc(&pool), 0);
  }
}

// Free blocks should always remain zeroed out.
// This tests the invariant right after creating the pool.
TEST_CASE(listpool_zero_free_blocks_after_creation) {
  test_pool pool;
  listpool_make(&pool);

  const int zero = 0;
  for (int i = 0; i < NUM_BLOCKS; ++i) {
    const int* block = (const int*)(pool.blocks) + i;
    TEST_EQUAL(memcmp(block, &zero, sizeof(int)), 0);
  }
}

// Free blocks should always remain zeroed out.
// This tests the invariant after freeing a block.
TEST_CASE(listpool_zero_free_block_after_free) {
  test_pool pool;
  listpool_make(&pool);

  int* val = listpool_alloc(&pool);
  TEST_TRUE(val != 0);
  *val = 177;

  int* old_val = val;
  listpool_free(&pool, &val); // val pointer is set to 0.
  TEST_EQUAL(*old_val, 0);    // Block is zeroed out after free.
}

// Traverse an empty pool.
TEST_CASE(listpool_traverse_empty) {
  test_pool pool;
  listpool_make(&pool);

  TEST_EQUAL(count(&pool), 0);
}

// Traverse a partially full pool.
TEST_CASE(listpool_traverse_partially_full) {
  const int N = NUM_BLOCKS / 2;

  test_pool pool;
  listpool_make(&pool);

  for (int i = 0; i < N; ++i) {
    int* val = listpool_alloc(&pool);
    TEST_TRUE(val != 0);
    *val = i + 1;
  }

  TEST_EQUAL(sum(&pool), (N) * (N + 1) / 2);
}

// Traverse a full pool.
TEST_CASE(listpool_traverse_full) {
  test_pool pool;
  listpool_make(&pool);

  for (int i = 0; i < NUM_BLOCKS; ++i) {
    int* val = listpool_alloc(&pool);
    TEST_TRUE(val != 0);
    *val = i + 1;
  }

  TEST_EQUAL(sum(&pool), (NUM_BLOCKS) * (NUM_BLOCKS + 1) / 2);
}

// Remove a value from the list.
TEST_CASE(listpool_remove_value) {
  test_pool pool;
  listpool_make(&pool);

  int* x = listpool_alloc(&pool);
  int* y = listpool_alloc(&pool);
  TEST_TRUE(x != 0);
  TEST_TRUE(y != 0);

  *x = 155;
  *y = 177;

  listpool_remove(&pool, 155); // x

  TEST_EQUAL(count(&pool), 1);
  TEST_EQUAL(sum(&pool), *y);
}

// Stress test.
//
// 1. Allocate the pool, either fully or partially. If fully, attempt to
//    allocate some items past the end.
//
// 2. Free all allocated items in some random order.

int main() { return 0; }