/*******************************************************************************
 *
 * Copyright (c) 2000-2003 Intel Corporation
 * All rights reserved.
 * Copyright (c) 2012 France Telecom All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * - Neither name of Intel Corporation nor the names of its contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 ******************************************************************************/

/*!
 * \file
 */

#include "TimerThread.h"

#include <assert.h>

/*!
 * \brief Deallocates a dynamically allocated TimerEvent.
 */
static void FreeTimerEvent(
	/*! [in] Valid timer thread pointer. */
	TimerThread *timer,
	/*! [in] Must be allocated with CreateTimerEvent*/
	TimerEvent *event)
{
	assert(timer != NULL);

	FreeListFree(&timer->freeEvents, event);
}

/*!
 * \brief Implements timer thread.
 *
 * Waits for next event to occur and schedules associated job into threadpool.
 */
static void TimerThreadWorker(
	/*! [in] arg is cast to (TimerThread *). */
	void *arg)
{
    TimerThread *timer = ( TimerThread * ) arg;
    ListNode *head = NULL;
    TimerEvent *nextEvent = NULL;
    time_t currentTime = 0;
    time_t nextEventTime = 0;
    struct timespec timeToWait;
    int tempId;

    assert( timer != NULL );

    ithread_mutex_lock( &timer->mutex );
    while (1) {
	/* mutex should always be locked at top of loop */
	/* Check for shutdown. */
	if (timer->shutdown) {
	    timer->shutdown = 0;
	    ithread_cond_signal( &timer->condition );
	    ithread_mutex_unlock( &timer->mutex );
	    return;
	}
	nextEvent = NULL;
	/* Get the next event if possible. */
	if (timer->eventQ.size > 0) {
	    head = ListHead( &timer->eventQ );
	    if (head == NULL) {
		ithread_mutex_unlock( &timer->mutex );
		return;
	    }
	    nextEvent = ( TimerEvent * ) head->item;
	    nextEventTime = nextEvent->eventTime;
	}
	currentTime = time(NULL);
	/* If time has elapsed, schedule job. */
	if (nextEvent && currentTime >= nextEventTime) {
	    if( nextEvent->persistent ) {
		if (ThreadPoolAddPersistent( timer->tp, &nextEvent->job, &tempId ) != 0) {
			if (nextEvent->job.arg != NULL && nextEvent->job.free_func != NULL) {
				nextEvent->job.free_func(nextEvent->job.arg);
			}
		}
	    } else {
		if (ThreadPoolAdd( timer->tp, &nextEvent->job, &tempId ) != 0) {
			if (nextEvent->job.arg != NULL && nextEvent->job.free_func != NULL) {
				nextEvent->job.free_func(nextEvent->job.arg);
			}
		}
	    }
	    ListDelNode( &timer->eventQ, head, 0 );
	    FreeTimerEvent( timer, nextEvent );
	    continue;
	}
	if (nextEvent) {
	    timeToWait.tv_nsec = 0;
	    timeToWait.tv_sec = (long)nextEvent->eventTime;
	    ithread_cond_timedwait( &timer->condition, &timer->mutex,
				    &timeToWait );
	} else {
	    ithread_cond_wait( &timer->condition, &timer->mutex );
	}
    }
}


/*!
 * \brief Calculates the appropriate timeout in absolute seconds
 * since Jan 1, 1970.
 *
 * \return
 */
static int CalculateEventTime(
	/*! [in] Timeout. */
	time_t *timeout,
	/*! [in] Timeout type. */
	TimeoutType type)
{
    time_t now;

    assert( timeout != NULL );

    switch (type) {
    case ABS_SEC:
	return 0;
    default: /* REL_SEC) */
	time(&now);
	( *timeout ) += now;
	return 0;
    }

    return -1;
}

/*!
 * \brief Creates a Timer Event. (Dynamically allocated).
 *
 * \return (TimerEvent *) on success, NULL on failure.
 */
static TimerEvent *CreateTimerEvent(
	/*! [in] Valid timer thread pointer. */
	TimerThread *timer,
	/*! [in] . */
	ThreadPoolJob *job,
	/*! [in] . */
	Duration persistent,
	/*! [in] The absoule time of the event in seconds from Jan, 1970. */
	time_t eventTime,
	/*! [in] Id of job. */
	int id)
{
    TimerEvent *temp = NULL;

    assert( timer != NULL );
    assert( job != NULL );

    temp = ( TimerEvent * ) FreeListAlloc( &timer->freeEvents );
    if( temp == NULL )
	return temp;
    temp->job = ( *job );
    temp->persistent = persistent;
    temp->eventTime = eventTime;
    temp->id = id;

    return temp;
}


int TimerThreadInit(TimerThread *timer, ThreadPool *tp)
{

    int rc = 0;

    ThreadPoolJob timerThreadWorker;

    assert( timer != NULL );
    assert( tp != NULL );

    if( ( timer == NULL ) || ( tp == NULL ) ) {
	return EINVAL;
    }

    rc += ithread_mutex_init( &timer->mutex, NULL );

    assert( rc == 0 );

    rc += ithread_mutex_lock( &timer->mutex );
    assert( rc == 0 );

    rc += ithread_cond_init( &timer->condition, NULL );
    assert( rc == 0 );

    rc += FreeListInit( &timer->freeEvents, sizeof( TimerEvent ), 100 );
    assert( rc == 0 );

    timer->shutdown = 0;
    timer->tp = tp;
    timer->lastEventId = 0;
    rc += ListInit( &timer->eventQ, NULL, NULL );

    assert( rc == 0 );

    if( rc != 0 ) {
	rc = EAGAIN;
    } else {

	TPJobInit( &timerThreadWorker, TimerThreadWorker, timer );
	TPJobSetPriority( &timerThreadWorker, HIGH_PRIORITY );

	rc = ThreadPoolAddPersistent( tp, &timerThreadWorker, NULL );
    }

    ithread_mutex_unlock( &timer->mutex );

    if( rc != 0 ) {
	ithread_cond_destroy( &timer->condition );
	ithread_mutex_destroy( &timer->mutex );
	FreeListDestroy( &timer->freeEvents );
	ListDestroy( &timer->eventQ, 0 );
    }

    return rc;
}

int TimerThreadSchedule(
	TimerThread *timer,
	time_t timeout,
	TimeoutType type,
	ThreadPoolJob *job,
	Duration duration,
	int *id)
{
    int rc = EOUTOFMEM;
    int found = 0;
    int tempId = 0;

    ListNode *tempNode = NULL;
    TimerEvent *temp = NULL;
    TimerEvent *newEvent = NULL;

    assert( timer != NULL );
    assert( job != NULL );

    if( ( timer == NULL ) || ( job == NULL ) ) {
	return EINVAL;
    }

    CalculateEventTime( &timeout, type );
    ithread_mutex_lock( &timer->mutex );

    if( id == NULL )
	id = &tempId;

    ( *id ) = INVALID_EVENT_ID;

    newEvent = CreateTimerEvent( timer, job, duration, timeout,
				 timer->lastEventId );

    if( newEvent == NULL ) {
	ithread_mutex_unlock( &timer->mutex );
	return rc;
    }

    tempNode = ListHead( &timer->eventQ );
    /* add job to Q. Q is ordered by eventTime with the head of the Q being
     * the next event. */
    while( tempNode != NULL ) {
	temp = ( TimerEvent * ) tempNode->item;
	if( temp->eventTime >= timeout ) {
	    if (ListAddBefore( &timer->eventQ, newEvent, tempNode))
		rc = 0;
	    found = 1;
	    break;
	}
	tempNode = ListNext( &timer->eventQ, tempNode );
    }
    /* add to the end of Q. */
    if (!found) {
	if( ListAddTail( &timer->eventQ, newEvent ) != NULL )
	    rc = 0;
    }
    /* signal change in Q. */
    if( rc == 0 ) {
	ithread_cond_signal( &timer->condition );
    } else {
	FreeTimerEvent( timer, newEvent );
    }
    ( *id ) = timer->lastEventId++;
    ithread_mutex_unlock( &timer->mutex );

    return rc;
}

int TimerThreadRemove(
	TimerThread *timer,
	int id,
	ThreadPoolJob *out)
{
    int rc = INVALID_EVENT_ID;
    ListNode *tempNode = NULL;
    TimerEvent *temp = NULL;

    assert( timer != NULL );

    if( timer == NULL ) {
	return EINVAL;
    }

    ithread_mutex_lock( &timer->mutex );

    tempNode = ListHead( &timer->eventQ );

    while( tempNode != NULL ) {
	temp = ( TimerEvent * ) tempNode->item;
	if( temp->id == id )
	{

	    ListDelNode( &timer->eventQ, tempNode, 0 );
	    if( out != NULL )
		( *out ) = temp->job;
	    FreeTimerEvent( timer, temp );
	    rc = 0;
	    break;
	}
	tempNode = ListNext( &timer->eventQ, tempNode );
    }

    ithread_mutex_unlock( &timer->mutex );
    return rc;
}

int TimerThreadShutdown(TimerThread *timer)
{
    ListNode *tempNode2 = NULL;
    ListNode *tempNode = NULL;

    assert( timer != NULL );

    if( timer == NULL ) {
	return EINVAL;
    }

    ithread_mutex_lock( &timer->mutex );

    timer->shutdown = 1;
    tempNode = ListHead( &timer->eventQ );

    /* Delete nodes in Q. Call registered free function on argument. */
    while( tempNode != NULL ) {
	TimerEvent *temp = ( TimerEvent * ) tempNode->item;

	tempNode2 = ListNext( &timer->eventQ, tempNode );
	ListDelNode( &timer->eventQ, tempNode, 0 );
	if( temp->job.free_func ) {
	    temp->job.free_func( temp->job.arg );
	}
	FreeTimerEvent( timer, temp );
	tempNode = tempNode2;
    }

    ListDestroy( &timer->eventQ, 0 );
    FreeListDestroy( &timer->freeEvents );

    ithread_cond_broadcast( &timer->condition );

    while (timer->shutdown) {
	/* wait for timer thread to shutdown. */
	ithread_cond_wait( &timer->condition, &timer->mutex );
    }
    ithread_mutex_unlock(&timer->mutex);

    /* destroy condition. */
    while(ithread_cond_destroy(&timer->condition) != 0) {
    }
    /* destroy mutex. */
    while (ithread_mutex_destroy(&timer->mutex) != 0) {
    }

    return 0;
}

