/*
 * COPYRIGHT:         See COPYING in the top level directory
 * PROJECT:           ReactOS system libraries
 * PURPOSE:           Rtl user wait functions
 * FILE:              lib/rtl/wait.c
 * PROGRAMERS:
 *                    Alex Ionescu (alex@relsoft.net)
 *                    Eric Kohl
 *                    KJK::Hyperion
 */

/* INCLUDES *****************************************************************/

#include <rtl.h>

#define NDEBUG
#include <debug.h>

typedef struct _RTLP_WAIT
{
    HANDLE Object;
    BOOLEAN CallbackInProgress;
    HANDLE CancelEvent;
    LONG DeleteCount;
    HANDLE CompletionEvent;
    ULONG Flags;
    WAITORTIMERCALLBACKFUNC Callback;
    PVOID Context;
    ULONG Milliseconds;
} RTLP_WAIT, *PRTLP_WAIT;

/* PRIVATE FUNCTIONS *******************************************************/

static inline PLARGE_INTEGER get_nt_timeout( PLARGE_INTEGER pTime, ULONG timeout )
{
    if (timeout == INFINITE) return NULL;
    pTime->QuadPart = (ULONGLONG)timeout * -10000;
    return pTime;
}

static VOID
NTAPI
Wait_thread_proc(LPVOID Arg)
{
    PRTLP_WAIT Wait = (PRTLP_WAIT) Arg;
    NTSTATUS Status;
    BOOLEAN alertable = (Wait->Flags & WT_EXECUTEINIOTHREAD) != 0;
    HANDLE handles[2] = { Wait->CancelEvent, Wait->Object };
    LARGE_INTEGER timeout;
    HANDLE completion_event;

//    TRACE("\n");

    while (TRUE)
    {
        Status = NtWaitForMultipleObjects( 2,
                                           handles,
                                           WaitAny,
                                           alertable,
                                           get_nt_timeout( &timeout, Wait->Milliseconds ) );

        if (Status == STATUS_WAIT_1 || Status == STATUS_TIMEOUT)
        {
            BOOLEAN TimerOrWaitFired;

            if (Status == STATUS_WAIT_1)
            {
   //             TRACE( "object %p signaled, calling callback %p with context %p\n",
   //                 Wait->Object, Wait->Callback,
   //                 Wait->Context );
                TimerOrWaitFired = FALSE;
            }
            else
            {
    //            TRACE( "wait for object %p timed out, calling callback %p with context %p\n",
    //                Wait->Object, Wait->Callback,
    //                Wait->Context );
                TimerOrWaitFired = TRUE;
            }
            Wait->CallbackInProgress = TRUE;
            Wait->Callback( Wait->Context, TimerOrWaitFired );
            Wait->CallbackInProgress = FALSE;

            if (Wait->Flags & WT_EXECUTEONLYONCE)
                break;
        }
        else if (Status != STATUS_USER_APC)
            break;
    }

    completion_event = Wait->CompletionEvent;
    if (completion_event) NtSetEvent( completion_event, NULL );

    if (InterlockedIncrement( &Wait->DeleteCount ) == 2 )
    {
       NtClose( Wait->CancelEvent );
       RtlFreeHeap( RtlGetProcessHeap(), 0, Wait );
    }
}


/* FUNCTIONS ***************************************************************/


/***********************************************************************
 *              RtlRegisterWait
 *
 * Registers a wait for a handle to become signaled.
 *
 * PARAMS
 *  NewWaitObject [I] Handle to the new wait object. Use RtlDeregisterWait() to free it.
 *  Object   [I] Object to wait to become signaled.
 *  Callback [I] Callback function to execute when the wait times out or the handle is signaled.
 *  Context  [I] Context to pass to the callback function when it is executed.
 *  Milliseconds [I] Number of milliseconds to wait before timing out.
 *  Flags    [I] Flags. See notes.
 *
 * RETURNS
 *  Success: STATUS_SUCCESS.
 *  Failure: Any NTSTATUS code.
 *
 * NOTES
 *  Flags can be one or more of the following:
 *|WT_EXECUTEDEFAULT - Executes the work item in a non-I/O worker thread.
 *|WT_EXECUTEINIOTHREAD - Executes the work item in an I/O worker thread.
 *|WT_EXECUTEINPERSISTENTTHREAD - Executes the work item in a thread that is persistent.
 *|WT_EXECUTELONGFUNCTION - Hints that the execution can take a long time.
 *|WT_TRANSFER_IMPERSONATION - Executes the function with the current access token.
 */
NTSTATUS
NTAPI
RtlRegisterWait(PHANDLE NewWaitObject,
                HANDLE Object,
                WAITORTIMERCALLBACKFUNC Callback,
                PVOID Context,
                ULONG Milliseconds,
                ULONG Flags)
{
    PRTLP_WAIT Wait;
    NTSTATUS Status;

    //TRACE( "(%p, %p, %p, %p, %d, 0x%x)\n", NewWaitObject, Object, Callback, Context, Milliseconds, Flags );

    Wait = RtlAllocateHeap( RtlGetProcessHeap(), 0, sizeof(RTLP_WAIT) );
    if (!Wait)
        return STATUS_NO_MEMORY;

    Wait->Object = Object;
    Wait->Callback = Callback;
    Wait->Context = Context;
    Wait->Milliseconds = Milliseconds;
    Wait->Flags = Flags;
    Wait->CallbackInProgress = FALSE;
    Wait->DeleteCount = 0;
    Wait->CompletionEvent = NULL;

    Status = NtCreateEvent( &Wait->CancelEvent,
                             EVENT_ALL_ACCESS,
                             NULL,
                             NotificationEvent,
                             FALSE );

    if (Status != STATUS_SUCCESS)
    {
        RtlFreeHeap( RtlGetProcessHeap(), 0, Wait );
        return Status;
    }

    Flags = Flags & (WT_EXECUTEINIOTHREAD | WT_EXECUTEINPERSISTENTTHREAD |
                     WT_EXECUTELONGFUNCTION | WT_TRANSFER_IMPERSONATION);

    Status = RtlQueueWorkItem( Wait_thread_proc,
                               Wait,
                               Flags );

    if (Status != STATUS_SUCCESS)
    {
        NtClose( Wait->CancelEvent );
        RtlFreeHeap( RtlGetProcessHeap(), 0, Wait );
        return Status;
    }

    *NewWaitObject = Wait;
    return Status;
}

/***********************************************************************
 *              RtlDeregisterWaitEx
 *
 * Cancels a wait operation and frees the resources associated with calling
 * RtlRegisterWait().
 *
 * PARAMS
 *  WaitObject [I] Handle to the wait object to free.
 *
 * RETURNS
 *  Success: STATUS_SUCCESS.
 *  Failure: Any NTSTATUS code.
 */
NTSTATUS
NTAPI
RtlDeregisterWaitEx(HANDLE WaitHandle,
                    HANDLE CompletionEvent)
{
    PRTLP_WAIT Wait = (PRTLP_WAIT) WaitHandle;
    NTSTATUS Status = STATUS_SUCCESS;

    //TRACE( "(%p)\n", WaitHandle );

    NtSetEvent( Wait->CancelEvent, NULL );
    if (Wait->CallbackInProgress)
    {
        if (CompletionEvent != NULL)
        {
            if (CompletionEvent == INVALID_HANDLE_VALUE)
            {
                Status = NtCreateEvent( &CompletionEvent,
                                         EVENT_ALL_ACCESS,
                                         NULL,
                                         NotificationEvent,
                                         FALSE );

                if (Status != STATUS_SUCCESS)
                    return Status;

                (void)InterlockedExchangePointer( &Wait->CompletionEvent, CompletionEvent );

                if (Wait->CallbackInProgress)
                    NtWaitForSingleObject( CompletionEvent, FALSE, NULL );

                NtClose( CompletionEvent );
            }
            else
            {
                (void)InterlockedExchangePointer( &Wait->CompletionEvent, CompletionEvent );

                if (Wait->CallbackInProgress)
                    Status = STATUS_PENDING;
            }
        }
        else
            Status = STATUS_PENDING;
    }

    if (InterlockedIncrement( &Wait->DeleteCount ) == 2 )
    {
        Status = STATUS_SUCCESS;
        NtClose( Wait->CancelEvent );
        RtlFreeHeap( RtlGetProcessHeap(), 0, Wait );
    }

    return Status;
}

/***********************************************************************
 *              RtlDeregisterWait
 *
 * Cancels a wait operation and frees the resources associated with calling
 * RtlRegisterWait().
 *
 * PARAMS
 *  WaitObject [I] Handle to the wait object to free.
 *
 * RETURNS
 *  Success: STATUS_SUCCESS.
 *  Failure: Any NTSTATUS code.
 */
NTSTATUS
NTAPI
RtlDeregisterWait(HANDLE WaitHandle)
{
    return RtlDeregisterWaitEx(WaitHandle, NULL);
}

/* EOF */