/*

Copyright (c) 1998, Microsoft Corporation, all rights reserved

Description:

History:

*/

#include "timer_.h"

LONG			g_TimerLockUninit = 0;
/*

Returns:
    Win 32 error

Notes:
    Its a good idea to always call RasDhcpTimerUninitialize after calling 
    RasDhcpTimerInitialize, even if RasDhcpTimerInitialize failed.

*/

DWORD
RasDhcpTimerInitialize(
    VOID
)
{
    NTSTATUS    Status;
    DWORD       dwErr           = NO_ERROR;

	g_TimerLockUninit = 0;
    RasDhcpTimerShutdown = CreateEvent(NULL, TRUE, FALSE, NULL);

    if (NULL == RasDhcpTimerShutdown)
    {
        dwErr = GetLastError();
        goto LEnd;
    }

    Status = RtlCreateTimerQueue(&RasDhcpTimerQueueHandle);

    if (STATUS_SUCCESS != Status)
    {
        RasDhcpTimerQueueHandle = NULL;
        dwErr = Status;
        TraceHlp("RtlCreateTimerQueue failed and returned %d", dwErr);
        goto LEnd;
    }

    RasDhcpTimerPrevTime = time(NULL);
    Status = RtlCreateTimer(RasDhcpTimerQueueHandle, &RasDhcpTimerHandle, 
                RasDhcpTimerFunc, NULL, 0, TIMER_PERIOD,
                WT_EXECUTELONGFUNCTION);

    if (STATUS_SUCCESS != Status)
    {
        dwErr = Status;
        TraceHlp("RtlCreateTimer failed and returned %d", dwErr);
        goto LEnd;
    }

LEnd:

    if (NO_ERROR != dwErr)
    {
        if (NULL != RasDhcpTimerQueueHandle)
        {
            Status = RtlDeleteTimerQueueEx(RasDhcpTimerQueueHandle, (HANDLE)-1);
            RTASSERT(STATUS_SUCCESS == Status);
            RasDhcpTimerQueueHandle = NULL;
        }

        if (NULL != RasDhcpTimerShutdown)
        {
            CloseHandle(RasDhcpTimerShutdown);
            RasDhcpTimerShutdown = NULL;
        }
    }

    return(dwErr);
}

/*

Returns:
    VOID

Notes:
    RasDhcpTimerUninitialize can be called even if  RasDhcpTimerInitialize
    failed.

*/

VOID
RasDhcpTimerUninitialize(
    VOID
)
{
    NTSTATUS    Status;
    DWORD       dwRet;
	LONG		lPrev = 1;


    // Signal the timer thread to shutdown.
	lPrev = InterlockedExchangeAdd(&g_TimerLockUninit, 1);
	if ( lPrev > 0 )
    {
        InterlockedDecrement(&g_TimerLockUninit);
		return;
    }

    if (NULL != RasDhcpTimerQueueHandle)
    {
        RTASSERT(NULL != RasDhcpTimerShutdown);
        SetEvent(RasDhcpTimerShutdown);

        Status = RtlDeleteTimerQueueEx(RasDhcpTimerQueueHandle, (HANDLE)-1);
        RTASSERT(STATUS_SUCCESS == Status);
        RasDhcpTimerQueueHandle = NULL;
    }

    if (NULL != RasDhcpTimerShutdown)
    {
        CloseHandle(RasDhcpTimerShutdown);
        RasDhcpTimerShutdown = NULL;
    }

    // timer.c didn't alloc the nodes in the linked list.
    // So we don't free them here.
    RasDhcpTimerListHead = NULL;
}

/*

Returns:

Notes:

*/

VOID
RasDhcpTimerFunc(
    IN  VOID*   pArg1,
    IN  BOOLEAN fArg2
)
{
    TIMERLIST*      pTimer;
    TIMERLIST*      pTmp;
    TIMERFUNCTION   TimerFunc;
    time_t          now             = time(NULL);
    LONG            lElapsedTime;
    LONG            lTime;

    if (0 == TryEnterCriticalSection(&RasTimrCriticalSection))
    {
        // Another thread already owns the critical section
        return;
    }

    lElapsedTime = (LONG)(now - RasDhcpTimerPrevTime);
    RasDhcpTimerPrevTime = now;

    pTimer = NULL;

    while (RasDhcpTimerListHead != NULL)
    {
        lTime = RasDhcpTimerListHead->tmr_Delta;

        if ( lTime > 0)
        {
            RasDhcpTimerListHead->tmr_Delta -= lElapsedTime;
            lElapsedTime -= lTime;
        }

        if (RasDhcpTimerListHead->tmr_Delta <= 0)
        {
            pTmp = pTimer;
            pTimer = RasDhcpTimerListHead;
            RasDhcpTimerListHead = pTimer->tmr_Next;
            pTimer->tmr_Next = pTmp;
        }
        else
        {
            break;
        }
    }

    while (pTimer != NULL)
    {
        pTmp = pTimer->tmr_Next;
        TimerFunc = pTimer->tmr_TimerFunc;
        pTimer->tmr_TimerFunc = NULL;
        (*TimerFunc)(RasDhcpTimerShutdown, pTimer);
        pTimer = pTmp;
    }

    LeaveCriticalSection(&RasTimrCriticalSection);
}

/*

Returns:
    VOID

Notes:
    Once the timer thread starts, only it can call RasDhcpTimerSchedule (to 
    avoid race conditions). The timer thread will call rasDhcpMonitorAddresses 
    (and hence rasDhcpAllocateAddress), and rasDhcpRenewLease. These functions 
    will call RasDhcpTimerSchedule. No one else should call these functions or 
    RasDhcpTimerSchedule. The only exception is RasDhcpInitialize, which can 
    call RasDhcpTimerSchedule before it creates the timer thread.

*/

VOID
RasDhcpTimerSchedule(
    IN  TIMERLIST*      pNewTimer,
    IN  LONG            DeltaTime,
    IN  TIMERFUNCTION   TimerFunc
)
{
    TIMERLIST** ppTimer;
    TIMERLIST*  pTimer;

    pNewTimer->tmr_TimerFunc = TimerFunc;

    for (ppTimer = &RasDhcpTimerListHead;
         (pTimer = *ppTimer) != NULL;
         ppTimer = &pTimer->tmr_Next)
    {
        if (DeltaTime <= pTimer->tmr_Delta)
        {
            pTimer->tmr_Delta -= DeltaTime;
            break;
        }
        DeltaTime -= pTimer->tmr_Delta;
    }

    pNewTimer->tmr_Delta = DeltaTime;
    pNewTimer->tmr_Next = *ppTimer;
    *ppTimer = pNewTimer;
}

/*

Returns:

Notes:

*/

VOID
RasDhcpTimerRunNow(
    VOID
)
{
    RtlUpdateTimer(RasDhcpTimerQueueHandle, RasDhcpTimerHandle, 0, 
        TIMER_PERIOD);
}