/* Copyright (c) 2003-2005 MySQL AB
   Use is subject to license terms

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1335  USA */


#include <ndb_global.h>
#include "NdbCondition.h"
#include <NdbMutex.h>

struct NdbCondition
{
    long nWaiters;
    NdbMutex* pNdbMutexWaitersLock;
    HANDLE hSemaphore;
    HANDLE hEventWaitersDone;
    int bWasBroadcast;
};


struct NdbCondition* 
NdbCondition_Create(void)
{
    int result = 0;
    struct NdbCondition* pNdbCondition = (struct NdbCondition*)malloc(sizeof(struct NdbCondition));
    if(!pNdbCondition)
        return 0;
    
    pNdbCondition->nWaiters = 0;
    pNdbCondition->bWasBroadcast = 0;
    if(!(pNdbCondition->hSemaphore = CreateSemaphore(0, 0, MAXLONG, 0)))
        result = -1;
    else if(!(pNdbCondition->pNdbMutexWaitersLock = NdbMutex_Create()))
        result = -1;
    else if(!(pNdbCondition->hEventWaitersDone = CreateEvent(0, 0, 0, 0)))
        result = -1;

    assert(!result);
    return pNdbCondition;
}


int 
NdbCondition_Wait(struct NdbCondition* p_cond,
                  NdbMutex* p_mutex)
{
    int result;
    int bLastWaiter;
    if(!p_cond || !p_mutex)
        return 1;
    
    NdbMutex_Lock(p_cond->pNdbMutexWaitersLock);
    p_cond->nWaiters++;
    NdbMutex_Unlock(p_cond->pNdbMutexWaitersLock);
    
    if(NdbMutex_Unlock(p_mutex))
        return -1;
    result = WaitForSingleObject (p_cond->hSemaphore, INFINITE);

    NdbMutex_Lock(p_cond->pNdbMutexWaitersLock);
    p_cond->nWaiters--;
    bLastWaiter = (p_cond->bWasBroadcast && p_cond->nWaiters==0);
    NdbMutex_Unlock(p_cond->pNdbMutexWaitersLock);
    
    if(result==WAIT_OBJECT_0 && bLastWaiter)
        SetEvent(p_cond->hEventWaitersDone);
    
    NdbMutex_Lock(p_mutex);
    return result;
}


int 
NdbCondition_WaitTimeout(struct NdbCondition* p_cond,
                         NdbMutex* p_mutex,
                         int msecs)
{
    int result;
    int bLastWaiter;
    if (!p_cond || !p_mutex)
        return 1;
    
    NdbMutex_Lock(p_cond->pNdbMutexWaitersLock);
    p_cond->nWaiters++;
    NdbMutex_Unlock(p_cond->pNdbMutexWaitersLock);
    if(msecs<0)
        msecs = 0;

    if(NdbMutex_Unlock(p_mutex))
        return -1;
    result = WaitForSingleObject(p_cond->hSemaphore, msecs);

    NdbMutex_Lock(p_cond->pNdbMutexWaitersLock);
    p_cond->nWaiters--;
    bLastWaiter = (p_cond->bWasBroadcast && p_cond->nWaiters==0);
    NdbMutex_Unlock(p_cond->pNdbMutexWaitersLock);

    if(result!=WAIT_OBJECT_0)
        result = -1;

    if(bLastWaiter)
        SetEvent(p_cond->hEventWaitersDone); 

    NdbMutex_Lock(p_mutex);
    return result;
}


int 
NdbCondition_Signal(struct NdbCondition* p_cond)
{
    int bHaveWaiters;
    if(!p_cond)
        return 1;
    
    NdbMutex_Lock(p_cond->pNdbMutexWaitersLock);
    bHaveWaiters = (p_cond->nWaiters > 0);
    NdbMutex_Unlock(p_cond->pNdbMutexWaitersLock);
    
    if(bHaveWaiters)
        return (ReleaseSemaphore(p_cond->hSemaphore, 1, 0) ? 0 : -1);
    else
        return 0;
}


int NdbCondition_Broadcast(struct NdbCondition* p_cond)
{
    int bHaveWaiters;
    int result = 0;
    if(!p_cond)
        return 1;
    
    NdbMutex_Lock(p_cond->pNdbMutexWaitersLock);
    bHaveWaiters = 0;
    if(p_cond->nWaiters > 0)
    {
        p_cond->bWasBroadcast = !0;
        bHaveWaiters = 1;
    }
    NdbMutex_Unlock(p_cond->pNdbMutexWaitersLock);
    if(bHaveWaiters)
    {
        if(!ReleaseSemaphore(p_cond->hSemaphore, p_cond->nWaiters, 0))
            result = -1;
        else if(WaitForSingleObject (p_cond->hEventWaitersDone, INFINITE) != WAIT_OBJECT_0)
            result = -1;
        p_cond->bWasBroadcast = 0;
    }
    return result;
}


int NdbCondition_Destroy(struct NdbCondition* p_cond)
{
    int result;
    if(!p_cond)
        return 1;
    
    CloseHandle(p_cond->hEventWaitersDone);
    NdbMutex_Destroy(p_cond->pNdbMutexWaitersLock);
    result = (CloseHandle(p_cond->hSemaphore) ? 0 : -1); 

    free(p_cond);
    return 0;
}

