/*
 * Copyright (c) Likewise Software.  All rights Reserved.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the license, or (at
 * your option) any later version.
 *
 * This library 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 Lesser
 * General Public License for more details.  You should have received a copy
 * of the GNU Lesser General Public License along with this program.  If
 * not, see <http://www.gnu.org/licenses/>.
 *
 * LIKEWISE SOFTWARE MAKES THIS SOFTWARE AVAILABLE UNDER OTHER LICENSING
 * TERMS AS WELL.  IF YOU HAVE ENTERED INTO A SEPARATE LICENSE AGREEMENT
 * WITH LIKEWISE SOFTWARE, THEN YOU MAY ELECT TO USE THE SOFTWARE UNDER THE
 * TERMS OF THAT SOFTWARE LICENSE AGREEMENT INSTEAD OF THE TERMS OF THE GNU
 * LESSER GENERAL PUBLIC LICENSE, NOTWITHSTANDING THE ABOVE NOTICE.  IF YOU
 * HAVE QUESTIONS, OR WISHTO REQUEST A COPY OF THE ALTERNATE LICENSING
 * TERMS OFFERED BY LIKEWISE SOFTWARE, PLEASE CONTACT LIKEWISE SOFTWARE AT
 * license@likewisesoftware.com
 */

/*
 * Module Name:
 *
 *        threadpool-timer.c
 *
 * Abstract:
 *
 *        Thread pool timer API
 *
 * Authors: Brian Koropoff (bkoropoff@likewise.com)
 *
 */

#include "includes.h"
#include "threadpool-common.h"

#define TIMER_SCHEDULE  0x2
#define TIMER_CANCEL    0x4
#define TIMER_RUN       0x8

#define GCOS(s) GOTO_CLEANUP_ON_STATUS(s)

static
VOID
FreeTimer(
    PLW_TIMER pTimer
    )
{
    LwRtlFreeWorkItem(&pTimer->pItem);
    LwRtlReleaseTask(&pTimer->pTask);
    RTL_FREE(&pTimer);
}

static
VOID
ReleaseTimer(
    PLW_TIMER pTimer
    )
{
    if (LwInterlockedDecrement(&pTimer->RefCount) == 0)
    {
        FreeTimer(pTimer);
    }
}

static
VOID
TimerTask(
    PLW_TASK pTask,
    PVOID pContext,
    LW_TASK_EVENT_MASK WakeMask,
    LW_TASK_EVENT_MASK* pWaitMask,
    PLONG64 pTime
    )
{
    ULONG Flags;
    BOOLEAN bSet;
    PLW_TIMER pTimer = pContext;

    if (WakeMask & LW_TASK_EVENT_CANCEL)
    {
        *pWaitMask = LW_TASK_EVENT_COMPLETE;
        ReleaseTimer(pTimer);
        return;
    }

    for (;;)
    {
        Flags = pTimer->Flags;

        if (Flags & TIMER_CANCEL)
        {
            bSet = lw_atomic_cmpset_u32(
                        &pTimer->Flags,
                        Flags,
                        Flags & ~(TIMER_SCHEDULE | TIMER_CANCEL));
            if (bSet)
            {
                *pWaitMask = LW_TASK_EVENT_EXPLICIT;
                *pTime = 0;
                break;
            }
        }
        else if (Flags & TIMER_SCHEDULE)
        {
            bSet = lw_atomic_cmpset_u32(
                        &pTimer->Flags,
                        Flags,
                        Flags & ~TIMER_SCHEDULE);
            if (bSet)
            {
                *pWaitMask = LW_TASK_EVENT_TIME;
                *pTime = pTimer->NextDuration;
                break;
            }
        }
        else if (WakeMask & LW_TASK_EVENT_TIME)
        {
            bSet = lw_atomic_cmpset_u32(
                        &pTimer->Flags,
                        Flags,
                        Flags | TIMER_RUN);
            if (bSet)
            {
                *pWaitMask = LW_TASK_EVENT_EXPLICIT;
                *pTime = 0;

                LwInterlockedIncrement(&pTimer->RefCount);
                if (pTimer->pItem)
                {
                    LwRtlScheduleWorkItem(pTimer->pItem, 0);
                }
                else
                {
                    pTimer->Callback(pTimer, pTimer->pContext);
                    ReleaseTimer(pTimer);
                }
                break;
            }
        }
    }
}

static
VOID
TimerWorker(
    PLW_WORK_ITEM pItem,
    PVOID pContext
    )
{
    PLW_TIMER pTimer = (PLW_TIMER) pContext;

    pTimer->Callback(pTimer, pTimer->pContext);
    ReleaseTimer(pTimer);
}

LW_NTSTATUS
LwRtlCreateTimer(
    LW_IN PLW_THREAD_POOL pPool,
    LW_OUT PLW_TIMER* ppTimer,
    LW_IN LW_TIMER_FUNCTION Callback,
    LW_IN LW_PVOID pContext,
    LW_IN LW_TIMER_FLAGS Flags
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    PLW_TIMER pTimer = NULL;

    status = LW_RTL_ALLOCATE_AUTO(&pTimer);
    GCOS(status);

    pTimer->Callback = Callback;
    pTimer->pContext = pContext;
    pTimer->RefCount = 1;

    if (Flags & LW_TIMER_FLAG_BLOCKING)
    {
        status = LwRtlCreateWorkItem(pPool, &pTimer->pItem, TimerWorker, pTimer);
        GCOS(status);
    }

    pTimer->RefCount++;
    status = LwRtlCreateTask(pPool, &pTimer->pTask, NULL, TimerTask, pTimer);
    if (status)
    {
        pTimer->RefCount--;
    }
    GCOS(status);

cleanup:

    if (!NT_SUCCESS(status) && pTimer)
    {
        ReleaseTimer(pTimer);
        pTimer = NULL;
    }

    *ppTimer = pTimer;

    return status;
}

VOID
LwRtlFreeTimer(
    LW_IN LW_OUT PLW_TIMER* ppTimer
    )
{
    PLW_TIMER pTimer = *ppTimer;

    if (pTimer)
    {
        LwRtlCancelTask(pTimer->pTask);
        ReleaseTimer(pTimer);
    }
}

VOID
LwRtlScheduleTimer(
    LW_IN PLW_TIMER pTimer,
    LW_IN LW_ULONG64 Duration
    )
{
    ULONG flags = 0;

    pTimer->NextDuration = Duration;
    do
    {
        flags = pTimer->Flags;
    }
    while (!lw_atomic_cmpset_u32(
                &pTimer->Flags,
                flags,
                (flags & ~(TIMER_RUN | TIMER_CANCEL)) | TIMER_SCHEDULE));

    LwRtlWakeTask(pTimer->pTask);
}

LW_NTSTATUS
LwRtlCancelTimer(
    LW_IN PLW_TIMER pTimer
    )
{
    ULONG flags = 0;

    do
    {
        flags = pTimer->Flags;
    }
    while (!lw_atomic_cmpset_u32(
                &pTimer->Flags,
                flags,
                (flags & ~TIMER_RUN) | TIMER_CANCEL));

    LwRtlWakeTask(pTimer->pTask);

    return flags & TIMER_RUN ? STATUS_TIMEOUT : STATUS_SUCCESS;
}
