//////////////////////////////////////////////////////////////////////////////////
//
// File:        NSPR_CondVars.cpp
//
// Description: This file has functions to emulate the NSPR conditional variable functions.
//              
//
// Author:      Ian Honcharuk - 5/04/06
//
//////////////////////////////////////////////////////////////////////////////////
#include "NSPR_Locks.h"
#include "NSPR_CondVars.h"

#define assert(x) if(x) 

// we are not using the passed in lock BUT 
CNCondVar* CN_NewCondVar(CNLock *lock)
{
    CNCondVar* new_cvar = (CNCondVar*)calloc(1,sizeof(CNCondVar));

	new_cvar->gate = CreateSemaphore(0, 1, 1, 0);
    //m_queue = reinterpret_cast<void*>(CreateSemaphore(0, 0, (std::numeric_limits<long>::max)(), 0));
	new_cvar->queue = CreateSemaphore(0, 0, 2^32, 0);
    new_cvar->mutex = lock;

    if (!new_cvar->gate || !new_cvar->queue || !new_cvar->mutex)
    {
        int res = 0;
        if (new_cvar->gate)
        {
            res = CloseHandle(new_cvar->gate);
            assert(res);
        }
        if (new_cvar->queue)
        {
            res = CloseHandle(new_cvar->queue);
            assert(res);
        }
        if (new_cvar->mutex)
        {
            res = CloseHandle(new_cvar->mutex);
            assert(res);
        }

        return NULL;
    }
	else
		return new_cvar;
}

void CN_DestroyCondVar(CNCondVar *cvar)
{
    int res = 0;
    res = CloseHandle(cvar->gate);
    assert(res);
    res = CloseHandle(cvar->queue);
    assert(res);
    //res = CloseHandle(cvar->mutex);
    //assert(res);
}

// this does not care about the timeout!
CNStatus CN_WaitCondVar(CNCondVar *cvar, CNIntervalTime timeout)
{
    int res = 0;
    unsigned int was_waiting=0;
    unsigned int was_gone=0;

    res = WaitForSingleObject(cvar->queue, INFINITE);
    //assert(res == WAIT_OBJECT_0);


    res = WaitForSingleObject(cvar->mutex, INFINITE);
	assert(res == WAIT_OBJECT_0);

	CN_Lock(cvar->mutex);
    
    was_waiting = cvar->waiting;
    was_gone = cvar->gone;
    if (was_waiting != 0)
    {
        if (--cvar->waiting == 0)
        {
            if (cvar->blocked != 0)
            {
                res = ReleaseSemaphore(cvar->gate, 1,
                    0); // open m_gate
                assert(res);
                was_waiting = 0;
            }
            else if (cvar->gone != 0)
                cvar->gone = 0;
        }
    }
    //else if (++cvar->gone == ((std::numeric_limits<unsigned>::max)() / 2))
	else if (++cvar->gone == ((2^32) / 2))
    {
        // timeout occured, normalize the m_gone count
        // this may occur if many calls to wait with a timeout are made and
        // no call to notify_* is made
        res = WaitForSingleObject(cvar->gate, INFINITE);
        assert(res == WAIT_OBJECT_0);
        cvar->blocked -= cvar->gone;
        res = ReleaseSemaphore(cvar->gate, 1, 0);
        assert(res);
        cvar->gone = 0;
    }
    //res = ReleaseMutex(reinterpret_cast<HANDLE>(cvar->mutex));
	CN_Unlock(cvar->mutex);
    assert(res);

    if (was_waiting == 1)
    {
        for (/**/ ; was_gone; --was_gone)
        {
            // better now than spurious later
            res = WaitForSingleObject(cvar->queue,
                INFINITE);
            assert(res == WAIT_OBJECT_0);
        }
        res = ReleaseSemaphore(cvar->gate, 1, 0);
        assert(res);
    }

	return PR_SUCCESS;
}

CNStatus CN_NotifyCondVar(CNCondVar *cvar)
{
    unsigned signals = 0;

    int res = 0;
    //res = WaitForSingleObject(reinterpret_cast<HANDLE>(m_mutex), INFINITE);
	//assert(res == WAIT_OBJECT_0);
	
	CN_Lock(cvar->mutex);
    
    if (cvar->waiting != 0) // the m_gate is already closed
    {
        if (cvar->blocked == 0)
        {
            //res = ReleaseMutex(reinterpret_cast<HANDLE>(m_mutex));
			CN_Unlock(cvar->mutex);
            assert(res);
            return PR_FAILURE;
        }

        ++cvar->waiting;
        --cvar->blocked;
        signals = 1;
    }
    else
    {
        res = WaitForSingleObject(cvar->gate, INFINITE);
        assert(res == WAIT_OBJECT_0);
        if (cvar->blocked > cvar->gone)
        {
            if (cvar->gone != 0)
            {
                cvar->blocked -= cvar->gone;
                cvar->gone = 0;
            }
            signals = cvar->waiting = 1;
            --cvar->blocked;
        }
        else
        {
            res = ReleaseSemaphore(cvar->gate, 1, 0);
            assert(res);
        }
    }

    //res = ReleaseMutex(reinterpret_cast<HANDLE>(m_mutex));
    //assert(res);
	CN_Unlock(cvar->mutex);

    if (signals)
    {
        res = ReleaseSemaphore(cvar->queue, signals, 0);
        assert(res);
    }

	return PR_SUCCESS;
}

CNStatus CN_NotifyAllCondVar(CNCondVar *cvar)
{
    unsigned signals = 0;

    int res = 0;
    //res = WaitForSingleObject(reinterpret_cast<HANDLE>(m_mutex), INFINITE);
    //assert(res == WAIT_OBJECT_0);
	CN_Lock(cvar->mutex);

    if (cvar->waiting != 0) // the m_gate is already closed
    {
        if (cvar->blocked == 0)
        {
            //res = ReleaseMutex(reinterpret_cast<HANDLE>(m_mutex));
            //assert(res);
			CN_Unlock(cvar->mutex);
            return PR_FAILURE;
        }

        cvar->waiting += (signals = cvar->blocked);
        cvar->blocked = 0;
    }
    else
    {
        res = WaitForSingleObject(cvar->gate, INFINITE);
        assert(res == WAIT_OBJECT_0);
        if (cvar->blocked > cvar->gone)
        {
            if (cvar->gone != 0)
            {
                cvar->blocked -= cvar->gone;
                cvar->gone = 0;
            }
            signals = cvar->waiting = cvar->blocked;
            cvar->blocked = 0;
        }
        else
        {
            res = ReleaseSemaphore(cvar->gate, 1, 0);
            assert(res);
        }
    }

    //res = ReleaseMutex(reinterpret_cast<HANDLE>(m_mutex));
    //assert(res);
	CN_Unlock(cvar->mutex);

    if (signals)
    {
        res = ReleaseSemaphore(cvar->queue, signals, 0);
        assert(res);
    }

	return PR_SUCCESS;
}