int LbqPollQueueElement(PLINKED_BLOCKING_QUEUE queueHead, void** data) {
    PLINKED_BLOCKING_QUEUE_ENTRY entry;
    
    if (queueHead->head == NULL) {
        return LBQ_NO_ELEMENT;
    }
    
    PltLockMutex(&queueHead->mutex);
    
    if (queueHead->head == NULL) {
        PltUnlockMutex(&queueHead->mutex);
        return LBQ_NO_ELEMENT;
    }
    
    entry = queueHead->head;
    queueHead->head = entry->flink;
    queueHead->currentSize--;
    if (queueHead->head == NULL) {
        LC_ASSERT(queueHead->currentSize == 0);
        queueHead->tail = NULL;
        PltClearEvent(&queueHead->containsDataEvent);
    }
    else {
        LC_ASSERT(queueHead->currentSize != 0);
        queueHead->head->blink = NULL;
    }
    
    *data = entry->data;
    
    PltUnlockMutex(&queueHead->mutex);
    
    return LBQ_SUCCESS;
}
int LbqOfferQueueItem(PLINKED_BLOCKING_QUEUE queueHead, void* data, PLINKED_BLOCKING_QUEUE_ENTRY entry) {
	entry->flink = NULL;
	entry->data = data;

	PltLockMutex(&queueHead->mutex);

	if (queueHead->currentSize == queueHead->sizeBound) {
		PltUnlockMutex(&queueHead->mutex);
		return LBQ_BOUND_EXCEEDED;
	}

	if (queueHead->head == NULL) {
		LC_ASSERT(queueHead->currentSize == 0);
		LC_ASSERT(queueHead->tail == NULL);
		queueHead->head = entry;
		queueHead->tail = entry;
		entry->blink = NULL;
	}
	else {
		LC_ASSERT(queueHead->currentSize >= 1);
		LC_ASSERT(queueHead->head != NULL);
		queueHead->tail->flink = entry;
		entry->blink = queueHead->tail;
		queueHead->tail = entry;
	}

	queueHead->currentSize++;

	PltUnlockMutex(&queueHead->mutex);

	PltSetEvent(&queueHead->containsDataEvent);

	return LBQ_SUCCESS;
}
static PLT_THREAD* findCurrentThread(void) {
    PLT_THREAD* current_thread;

    PltLockMutex(&thread_list_lock);
    current_thread = thread_head;
    while (current_thread != NULL) {
#if defined(LC_WINDOWS)
        if (current_thread->tid == GetCurrentThreadId()) {
#else
        if (pthread_equal(current_thread->thread, pthread_self())) {
#endif
            break;
        }

        current_thread = current_thread->next;
    }
    PltUnlockMutex(&thread_list_lock);

    LC_ASSERT(current_thread != NULL);
    
    return current_thread;
}
#endif

int PltCreateMutex(PLT_MUTEX* mutex) {
#if defined(LC_WINDOWS)
    *mutex = CreateMutexEx(NULL, NULL, 0, MUTEX_ALL_ACCESS);
    if (!*mutex) {
        return -1;
    }
    return 0;
#else
    return pthread_mutex_init(mutex, NULL);
#endif
}
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
    struct thread_context* ctx = (struct thread_context*)lpParameter;
#else
void* ThreadProc(void* context) {
    struct thread_context* ctx = (struct thread_context*)context;
#endif
    
    // Add this thread to the thread list
    PltLockMutex(&thread_list_lock);
    ctx->thread->next = thread_head;
    thread_head = ctx->thread;
    PltUnlockMutex(&thread_list_lock);

    ctx->entry(ctx->context);

    free(ctx);

#if defined(LC_WINDOWS)
    return 0;
#else
    return NULL;
#endif
}

void PltSleepMs(int ms) {
#if defined(LC_WINDOWS)
    WaitForSingleObjectEx(GetCurrentThread(), ms, FALSE);
#else
    useconds_t usecs = ms * 1000;
    usleep(usecs);
#endif
}
void PltCloseThread(PLT_THREAD* thread) {
    PLT_THREAD* current_thread;

    PltLockMutex(&thread_list_lock);

    if (thread_head == thread)
    {
        // Remove the thread from the head
        thread_head = thread_head->next;
    }
    else
    {
        // Find the thread in the list
        current_thread = thread_head;
        while (current_thread != NULL) {
            if (current_thread->next == thread) {
                break;
            }

            current_thread = current_thread->next;
        }

        LC_ASSERT(current_thread != NULL);

        // Unlink this thread
        current_thread->next = thread->next;
    }

    PltUnlockMutex(&thread_list_lock);

#if defined(LC_WINDOWS)
    CloseHandle(thread->termRequested);
    CloseHandle(thread->handle);
#endif
}
// This must be synchronized with LbqFlushQueueItems by the caller
int LbqPeekQueueElement(PLINKED_BLOCKING_QUEUE queueHead, void** data) {
    if (queueHead->head == NULL) {
        return LBQ_NO_ELEMENT;
    }
    
    PltLockMutex(&queueHead->mutex);
    
    if (queueHead->head == NULL) {
        PltUnlockMutex(&queueHead->mutex);
        return LBQ_NO_ELEMENT;
    }
    
    *data = queueHead->head->data;
    
    PltUnlockMutex(&queueHead->mutex);
    
    return LBQ_SUCCESS;
}
/* Flush the queue */
PLINKED_BLOCKING_QUEUE_ENTRY LbqFlushQueueItems(PLINKED_BLOCKING_QUEUE queueHead) {
	PLINKED_BLOCKING_QUEUE_ENTRY head;
	
	PltLockMutex(&queueHead->mutex);

	// Save the old head
	head = queueHead->head;

	// Reinitialize the queue to empty
	queueHead->head = NULL;
	queueHead->tail = NULL;
	queueHead->currentSize = 0;
	PltClearEvent(&queueHead->containsDataEvent);

	PltUnlockMutex(&queueHead->mutex);

	return head;
}
int LbqWaitForQueueElement(PLINKED_BLOCKING_QUEUE queueHead, void** data) {
	PLINKED_BLOCKING_QUEUE_ENTRY entry;
	int err;

	for (;;) {
		err = PltWaitForEvent(&queueHead->containsDataEvent);
		if (err != PLT_WAIT_SUCCESS) {
			return LBQ_INTERRUPTED;
		}

		PltLockMutex(&queueHead->mutex);

		if (queueHead->head == NULL) {
			PltClearEvent(&queueHead->containsDataEvent);
			PltUnlockMutex(&queueHead->mutex);
			continue;
		}

		entry = queueHead->head;
		queueHead->head = entry->flink;
		queueHead->currentSize--;
		if (queueHead->head == NULL) {
			LC_ASSERT(queueHead->currentSize == 0);
			queueHead->tail = NULL;
			PltClearEvent(&queueHead->containsDataEvent);
		}
		else {
			LC_ASSERT(queueHead->currentSize != 0);
			queueHead->head->blink = NULL;
		}

		*data = entry->data;

		PltUnlockMutex(&queueHead->mutex);

		break;
	}

	return LBQ_SUCCESS;
}