#include "test_queue.h"
#include "queue.h"
#include "test_utils.h"
#include <stdlib.h>
#include <threads.h>
#include <stdbool.h>
#include <stdio.h>

static void test_queue_basic() {
  Queue* q = queue_create(10, NULL);
  EXPECT_NOT_NULL(q);
  EXPECT_TRUE(queue_is_empty(q));
  EXPECT_FALSE(queue_is_full(q));

  int* vals[5];
  for (int i = 0; i < 5; i++) {
    vals[i] = malloc(sizeof(int));
    *vals[i] = (i + 1) * 10;
    queue_enqueue(q, vals[i]);
  }

  EXPECT_FALSE(queue_is_empty(q));
  EXPECT_FALSE(queue_is_full(q));
  EXPECT_EQ_INT(q->size, 5);

  int* v1 = (int*)queue_dequeue(q);
  EXPECT_NOT_NULL(v1);
  EXPECT_EQ_INT(*v1, 10);
  free(v1);

  int* v2 = (int*)queue_dequeue(q);
  EXPECT_NOT_NULL(v2);
  EXPECT_EQ_INT(*v2, 20);
  free(v2);

  EXPECT_EQ_INT(q->size, 3);

  int* vals2[3];
  for (int i = 0; i < 3; i++) {
    vals2[i] = malloc(sizeof(int));
    *vals2[i] = (i + 6) * 10;
    queue_enqueue(q, vals2[i]);
  }

  EXPECT_EQ_INT(q->size, 6);

  const int expected_vals[] = {30, 40, 50, 60, 70, 80};
  for (int i = 0; i < 6; i++) {
    int* v = (int*)queue_dequeue(q);
    EXPECT_NOT_NULL(v);
    EXPECT_EQ_INT(*v, expected_vals[i]);
    free(v);
  }

  EXPECT_TRUE(queue_is_empty(q));
  queue_destroy(q);
}

static void test_queue_resize() {
  Queue* q = queue_create(3, NULL);
  EXPECT_NOT_NULL(q);
  EXPECT_EQ_INT(q->capacity, 3);

  int a = 1, b = 2, c = 3, d = 4, e = 5;

  queue_enqueue(q, &a);
  queue_enqueue(q, &b);
  queue_enqueue(q, &c);

  EXPECT_TRUE(queue_is_full(q));

  const int* v1 = (const int*)queue_dequeue(q);
  EXPECT_NOT_NULL(v1);
  EXPECT_EQ_INT(*v1, 1);

  // Now front = 1, rear = 0, size = 2 (wrapped state)
  queue_enqueue(q, &d);
  EXPECT_TRUE(queue_is_full(q));

  // This enqueue triggers capacity doubling
  queue_enqueue(q, &e);
  EXPECT_FALSE(queue_is_full(q));
  EXPECT_EQ_INT(q->capacity, 6);
  EXPECT_EQ_INT(q->size, 4);

  // Dequeue all and check order: B, C, D, E
  const int* v2 = (const int*)queue_dequeue(q);
  EXPECT_NOT_NULL(v2);
  EXPECT_EQ_INT(*v2, 2);

  const int* v3 = (const int*)queue_dequeue(q);
  EXPECT_NOT_NULL(v3);
  EXPECT_EQ_INT(*v3, 3);

  const int* v4 = (const int*)queue_dequeue(q);
  EXPECT_NOT_NULL(v4);
  EXPECT_EQ_INT(*v4, 4);

  const int* v5 = (const int*)queue_dequeue(q);
  EXPECT_NOT_NULL(v5);
  EXPECT_EQ_INT(*v5, 5);

  EXPECT_TRUE(queue_is_empty(q));
  queue_destroy(q);
}

static int destroyer_calls = 0;
static void my_destroyer(void* item) {
  destroyer_calls++;
  free(item);
}

static void test_queue_destroyer() {
  destroyer_calls = 0;
  Queue* q = queue_create(5, my_destroyer);
  EXPECT_NOT_NULL(q);

  for (int i = 0; i < 3; i++) {
    int* val = malloc(sizeof(int));
    *val = i;
    queue_enqueue(q, val);
  }

  int* v = (int*)queue_dequeue(q);
  EXPECT_NOT_NULL(v);
  EXPECT_EQ_INT(*v, 0);
  free(v);

  queue_destroy(q);
  EXPECT_EQ_INT(destroyer_calls, 2);
}

typedef struct {
  Queue* q;
  mtx_t* mutex;
  cnd_t* cnd_empty;
  cnd_t* cnd_full;
  bool done;
  int sum;
} ThreadContext;

static int consumer_func(void* arg) {
  ThreadContext* ctx = (ThreadContext*)arg;
  while (true) {
    int* val = (int*)queue_dequeue_multithreaded(ctx->q, ctx->mutex, ctx->cnd_empty, ctx->cnd_full,
                                                 &ctx->done);
    if (val == NULL) {
      break;
    }
    ctx->sum += *val;
    free(val);
  }
  return 0;
}

static void test_queue_multithreaded() {
  Queue* q = queue_create(2, NULL);
  mtx_t mutex;
  cnd_t cnd_empty;
  cnd_t cnd_full;

  mtx_init(&mutex, mtx_plain);
  cnd_init(&cnd_empty);
  cnd_init(&cnd_full);

  ThreadContext ctx = {.q = q,
                       .mutex = &mutex,
                       .cnd_empty = &cnd_empty,
                       .cnd_full = &cnd_full,
                       .done = false,
                       .sum = 0};

  thrd_t consumer;
  int res = thrd_create(&consumer, consumer_func, &ctx);
  EXPECT_EQ_INT(res, thrd_success);

  for (int i = 1; i <= 100; i++) {
    int* val = malloc(sizeof(int));
    *val = i;
    queue_enqueue_multithreaded(q, val, &mutex, &cnd_empty, &cnd_full);
  }

  mtx_lock(&mutex);
  ctx.done = true;
  cnd_signal(&cnd_empty);
  mtx_unlock(&mutex);

  int join_res;
  thrd_join(consumer, &join_res);

  EXPECT_EQ_INT(ctx.sum, 5050);
  EXPECT_TRUE(queue_is_empty(q));

  queue_destroy(q);
  mtx_destroy(&mutex);
  cnd_destroy(&cnd_empty);
  cnd_destroy(&cnd_full);
}

void test_queue() {
  test_queue_basic();
  test_queue_resize();
  test_queue_destroyer();
  test_queue_multithreaded();
}
