/*
 *   timer.c - A generic implementaion of the ICA client's PAL
 *             timers.  It needs a single OS timer.
 *
 *   $Id$
 *
 *   Copyright 2010-2013 Citrix Systems, Inc. All rights reserved.
 */

#include "citrix.h"
#include <stdlib.h>
#include <stdio.h>

#include "mainloop.h"
#include "ctxntmr.h"

extern UINT32 Os_GetTickCountInMs(void);

/* A TMR has a callback and repeat time. */
struct _TMR {
    struct pal_callback callback;
    UINT32              period;         /* MillSeconds */
    BOOLEAN             running;
    UINT32              expires;        /* MilliSeconds since start-up.*/
    struct _TMR        *next;
};


/* The active timer list and one currently being called. */

static struct _TMR *active, *current;

/* Remove a timer from the active list. */

static void remove_timer(struct _TMR *this)
{
    struct _TMR **pp;

    for (pp = &active; *pp && (*pp) != this;  pp = &(*pp)->next)
        ;
    if (*pp)
        *pp = this->next;
}

/* Insert a timer into the active list. */

static void insert_timer(struct _TMR *this, UINT32 now)
{
    struct _TMR **pp;

    this->expires = now + this->period;
    for (pp = &active;
         *pp && (*pp)->expires < this->expires;
         pp = &(*pp)->next)
        ;
    this->next = *pp;
    *pp = this;
}

/* Run the active timer list.
 * Returns the time to wait for the next one due, or zero.
 */

static UINT32 tmr_run(void)
{
    UINT32 now = Os_GetTickCountInMs();

    for (current = active;
         current && current->expires <= now;
         current = active) {
        active = current->next;  /* Remove from queue. */
        CALL_IT(current);
        if (current && current->running)
            insert_timer(current, now);
    }
    current = NULL;
    return active ? active->expires - now : 0;
}

static void start_timer(struct _TMR *pt)
{
    extern struct mainloop_plugin Plug;

    if (!active)
        (*Plug.start_timer)(pt->period, tmr_run);
    insert_timer(pt, Os_GetTickCountInMs());
}

/*****************************************************************************
 *
 *  Tmr_setEnabled
 *
 *    Enables or disables a timer.
 *
 *****************************************************************************/
VPSTATUS Tmr_setEnabled(PTMR pt, BOOLEAN enabled)
{
    if (enabled != pt->running) {
        if (enabled)
            start_timer(pt);
        else
            remove_timer(pt);
        pt->running = enabled;
    }
    return TMR_SUCCESS;
}

/*****************************************************************************
 *
 *  Tmr_setPeriod
 *
 *    Sets the period for a timer.
 *
 *****************************************************************************/
VPSTATUS Tmr_setPeriod(PTMR pt, UINT32 new_period)
{
    pt->period = new_period;
    if (pt->running && pt != current) {
        remove_timer(pt);
        start_timer(pt);
    }
    return TMR_SUCCESS;
}

/*****************************************************************************
 *
 *  Tmr_create
 *
 *  Creates a timer TMR object and inserts it into the active list.
 *  Upon failure, the timer handle to be initialized is set to NULL.
 *
 *****************************************************************************/
VPSTATUS Tmr_create(void       *hTC,          /* Dummy - PAL thread id. */
                    UINT32      uiPeriod,
                    void       *pSubscriber,
                    PFNDELIVER  pfnDeliver,
                    PTMR       *phTmr)
{
    struct _TMR *new;
                
    new = malloc(sizeof *new);
    *phTmr = new;
    if (!new)
        return TMR_OBJ_CREATE_FAILED;

    /* Populate data members of the timer. */

    new->callback.pSubscriber = pSubscriber;
    new->callback.pfnDeliver  = pfnDeliver;
    new->period               = uiPeriod;
    new->running              = FALSE;
    return TMR_SUCCESS;
}

/******************************************************************************
 *
 *  Tmr_destroy
 *
 *    Destroys a timer object given the address of a pointer to it
 *    and sets the pointer to NULL.
 *
 *****************************************************************************/
VPSTATUS Tmr_destroy(PTMR * phTmr)
{
    PTMR pt = *phTmr;

    if (pt == current)  /* Guard against deletion in callback. */
        current = NULL;
    remove_timer(pt);
    free(pt);
    *phTmr = NULL;
    return TMR_SUCCESS;
}
