int64_t AtomicIncrement(volatile int64_t *i) {
    return InterlockedIncrement64(i);
}

int64_t AtomicCompareAndSwap(volatile int64_t *dst, int64_t exchange, int64_t comperand) {
    return InterlockedCompareExchange64(dst, exchange, comperand);
}

void PushWork(WorkQueue *wq, void *data, WorkQueueCallback *callback) {
    uint32_t new_index = (wq->index_to_write + 1) % Lengthof(wq->entries);
    assert(new_index != wq->index_to_read);

    WorkQueueEntry *entry = wq->entries + wq->index_to_write;
    entry->data           = data;
    entry->callback       = callback;

    wq->completion_goal += 1;
    _WriteBarrier();
    wq->index_to_write = new_index;
    ReleaseSemaphore(wq->semaphore, 1, 0);
}

bool TryDoingWork(WorkQueue *wq) {
    bool    should_sleep           = false;
    int64_t original_index_to_read = wq->index_to_read;
    int64_t new_index_to_read      = (original_index_to_read + 1) % Lengthof(wq->entries);
    if (original_index_to_read != wq->index_to_write) {
        int64_t index = AtomicCompareAndSwap(&wq->index_to_read, new_index_to_read, original_index_to_read);
        if (index == original_index_to_read) {
            WorkQueueEntry *entry = wq->entries + index;
            entry->callback(entry->data);
            AtomicIncrement(&wq->completion_index);
        }
    } else {
        should_sleep = true;
    }
    return should_sleep;
}

DWORD WINAPI WorkQueueThreadEntry(LPVOID param) {
    auto ti = (ThreadStartupInfo *)param;

    ThreadCtx ctx    = {};
    ctx.thread_index = ti->thread_index;
    InitScratch();
    for (;;) {
        if (TryDoingWork(ti->queue)) {
            WaitForSingleObject(ti->queue->semaphore, INFINITE);
        }
    }
}

void InitWorkQueue(WorkQueue *queue, uint32_t thread_count, ThreadStartupInfo *info) {
    queue->thread_count     = thread_count;
    queue->index_to_read    = 0;
    queue->index_to_write   = 0;
    queue->completion_index = 0;
    queue->completion_goal  = 0;
    queue->semaphore        = CreateSemaphoreExA(0, 0, thread_count, 0, 0, SEMAPHORE_ALL_ACCESS);
    Assert(queue->semaphore != INVALID_HANDLE_VALUE);

    for (uint32_t i = 0; i < thread_count; i++) {
        ThreadStartupInfo *ti = info + i;
        ti->thread_index      = i;
        ti->queue             = queue;

        DWORD  thread_id     = 0;
        HANDLE thread_handle = CreateThread(0, 0, WorkQueueThreadEntry, ti, 0, &thread_id);
        Assert(thread_handle != INVALID_HANDLE_VALUE);
        ti->thread_id = thread_id;
        CloseHandle(thread_handle);
    }
}

void WaitUntilCompletion(WorkQueue *wq) {
    while (wq->completion_goal != wq->completion_index) {
        TryDoingWork(wq);
    }
}

bool IsWorkCompleted(WorkQueue *wq) {
    bool result = wq->completion_goal == wq->completion_index;
    return result;
}