/*---------------------------------------------------------------------------*\

    FILE....: VPBTIMER.CPP
    TYPE....: C++ Module
    AUTHOR..: David Rowe
    DATE....: 4/3/98

    This module implements VPB API functions for a simple timer that posts
    an event on the API queue after a defined time.


         Voicetronix Voice Processing Board (VPB) Software

         Copyright (C) 1999-2006 Voicetronix www.voicetronix.com.au

         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 library; if not, write to the Free Software
         Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
         MA  02110-1301  USA

\*---------------------------------------------------------------------------*/

#include <assert.h>

#include "timer.h"
#include "apifunc.h"
#include "generic.h"


// This structure holds the state info for each timer, and is created when
// a timer is opened.  When a timer is started, this structure is placed
// in a linked list of active timers.  These timer are examined each 
// time the VPB boards are polled to check for expiry.  On expiry the timer
// is removed from the "active" linked list and an event posted to the API
// message Q.  The timer is not destroyed when it is removed, only using
// the close function in this module.

typedef struct TMRS {
	Timer		t;	// low level timer object
	int		chdev;	// channel device for this timer
	ULONG		period;	// period before timer expires
	int		id;	// optional ID for this timer
	int		active;	// asserted if timer is active
	struct TMRS	*next;	// next VPB_TRANSFER in linked list
	struct TMRS	*prev;	// previous VPB_TRANSFER in linked list
} TMR;


// Critical section for vpb_timer functions, prevents polling timer and
// api functions accessing timer data at the same time.

static GENERIC_CRITICAL_SECTION	TimerSect;

static TMR *timers;

static int timer_ids = 0;


static void vpbtimer_remove_from_active_list(TMR *pt);


/*--------------------------------------------------------------------------*\

	FUNCTION.: vpbtimer_open
	AUTHOR...: David Rowe
	DATE.....: 4/3/98

	Initialises this module, call before any other function.

\*--------------------------------------------------------------------------*/

void vpbtimer_open() {
	GenericInitializeCriticalSection(&TimerSect);
//	mprintf("Initialized CritSect for \"TimerSect\" [0x%x]\n",&TimerSect);
	timers = NULL;
}

/*--------------------------------------------------------------------------*\

	FUNCTION.: vpbtimer_close
	AUTHOR...: David Rowe
	DATE.....: 4/3/98

	Closes down this module, call during shutdown.

\*--------------------------------------------------------------------------*/

void vpbtimer_close() {
	GenericDeleteCriticalSection(&TimerSect);
}

/*--------------------------------------------------------------------------*\

	FUNCTION.: vpb_timer_open
	AUTHOR...: David Rowe
	DATE.....: 4/3/98

	Initialises a timer object for the specified channel device.

\*--------------------------------------------------------------------------*/

int WINAPI vpb_timer_open
(
 void **ppv,          // ptr to timer state variables
 int chdev,           // channel device handle
 int timer_id,        // Optional ID for this timer
 unsigned long period // period in ms before posting event
)
{
	TMR *pt;

	try {
		ValidHandleCheck(chdev);

		pt = new TMR;
		pt->chdev = chdev;
		pt->period = period;
		pt->id = timer_id;
		pt->active = 0;
		pt->next = NULL;
		pt->prev = NULL;

		*ppv = (void*)pt;
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_timer_open");
	}
	return VPB_OK;
}

/*--------------------------------------------------------------------------*\

	FUNCTION.: vpb_timer_close
	AUTHOR...: David Rowe
	DATE.....: 4/3/98

	Deletes a timer object.

\*--------------------------------------------------------------------------*/

int WINAPI vpb_timer_close
(
 void *timer // ptr to timer state variables
)
{
	TMR  *pt = (TMR*)timer;

	try {
		if (timer == NULL)
			throw Wobbly(VPBAPI_TIMER_INVALID_TIMER);

		// if timer active remove from active linked list
		if (pt->active)
			vpbtimer_remove_from_active_list(pt);

		delete pt;
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_timer_close");
	}
	return VPB_OK;
}

/*--------------------------------------------------------------------------*\

	FUNCTION.: vpb_timer_start
	AUTHOR...: David Rowe
	DATE.....: 4/3/98

	Starts a timer.  When the period specified in vpb_timer_open expires,
	and event for this channel device is generated.  If timer is already
	active, this function throws a wobbly.

\*--------------------------------------------------------------------------*/

int WINAPI vpb_timer_start
(
 void *pv // ptr to timer state variables
)
{
	TMR *pt = (TMR*)pv;

	try {
		if (pt == NULL)
			throw Wobbly(VPBAPI_TIMER_INVALID_TIMER);

		// prevent App and poll timer accessing data at the same time

		GenericEnterCriticalSection(&TimerSect);

		if (!pt->active){
			// Add timer to start of list
			pt->active = 1;
			if (timers != NULL)
				timers->prev = pt;
			pt->next = timers;
			pt->prev = NULL;
			timers = pt;

			// Start timer 

			pt->t.start();
			GenericLeaveCriticalSection(&TimerSect);
		} else {
			GenericLeaveCriticalSection(&TimerSect);
			throw Wobbly(VPBAPI_TIMER_ALREADY_ACTIVE);
		}
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_timer_start");
	}
	return VPB_OK;
}

/*--------------------------------------------------------------------------*\

	FUNCTION.: vpb_timer_restart
	AUTHOR...: David Rowe
	DATE.....: 22/4/98

	ReStarts an already active timer.
	
\*--------------------------------------------------------------------------*/

int WINAPI vpb_timer_restart
(
 void *pv // ptr to timer state variables
)
{
	TMR *pt = (TMR*)pv;

	try {
		if (pt == NULL)
			throw Wobbly(VPBAPI_TIMER_INVALID_TIMER);

		// prevent App and poll timer accessing data at the same time

		GenericEnterCriticalSection(&TimerSect);

		// Restart timer if it is enabled
		// (MMQ may have removed it due to time out)
		// Bugger that! Start the timer even if it hasnt been 
		// started. That way we can use the vpb_timer_restart
		// function when we are not sure of the timers state!

		pt->t.start();

		GenericLeaveCriticalSection(&TimerSect);
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_timer_restart");
	}
	return VPB_OK;
}

/*--------------------------------------------------------------------------*\

	FUNCTION.: vpb_timer_stop
	AUTHOR...: David Rowe
	DATE.....: 23/4/98

	Stops an active timer by removing it from the active list.  This
	function allows the timer to be stopped from the API.
	
\*--------------------------------------------------------------------------*/

int WINAPI vpb_timer_stop
(
 void *pv // ptr to timer state variables
)
{
	vpbtimer_remove_from_active_list((TMR*)pv);

	return VPB_OK;
}

/*--------------------------------------------------------------------------*\

	FUNCTION.: vpb_timer_change_period
	AUTHOR...: David Rowe
	DATE.....: 24/4/98

	Changes the time out value of a timer that has already been
	initialised.
	
\*--------------------------------------------------------------------------*/

int WINAPI vpb_timer_change_period
(
 void *pv,              // ptr to timer state variables
unsigned long newperiod // period in ms before posting event
)
{
	GenericEnterCriticalSection(&TimerSect);

	((TMR*)pv)->period = newperiod;
	GenericLeaveCriticalSection(&TimerSect);

	return VPB_OK;
}

/*--------------------------------------------------------------------------*\

	FUNCTION.: vpbtimer_remove_from_active_list
	AUTHOR...: David Rowe
	DATE.....: 4/3/98

	Removes a TMR structure from the active linked list.  Should be thread
	safe if called by MMQ then API at same time to remove same link at same
	time.

\*--------------------------------------------------------------------------*/

static void vpbtimer_remove_from_active_list
(
  TMR *pt // ptr to timer state variables
)
{
	TMR			*next,*prev;

	assert(pt != NULL);

	// prevent App and poll timer accessing data at the same time
	GenericEnterCriticalSection(&TimerSect);

	// In case called again (ie by API and MMQ at same time for same link),
	// this will cause no action on second call.

	if (pt->active) {
		pt->active = 0;

		next = pt->next;
		prev = pt->prev;

		// forward link

		if (prev != NULL)
			prev->next = next;

		// backwards link

		if (next != NULL)
			next->prev = prev;

		// if this is first node in list, move first node ptr
		// this will "empty" list if next node is a null

		if (timers == pt)
			timers = timers->next;

		pt->next = NULL;
		pt->prev = NULL;
	}

	GenericLeaveCriticalSection(&TimerSect);
}

/*--------------------------------------------------------------------------*\

	FUNCTION.: vpbtimer_check_active_timers
	AUTHOR...: David Rowe
	DATE.....: 4/3/98

	Checks all active timers.  If a timer is found to have expired, an 
	event is posted and the timer is removed from the active list.

	This function is only called by the timer polling function, not via an
	API call.

\*--------------------------------------------------------------------------*/

void vpbtimer_check_active_timers()
{
	TMR	*t = timers;

	GenericEnterCriticalSection(&TimerSect);

	while(t != NULL) {
	//	mprintf("vpbtimer_check_active_timers: checking timer...\n");
		//assert(t->active);
		if(t->active==1){
	//		printf("Checking timer for %.02d \n",t->chdev);
			if( t->t.check_timeout_ms(t->period) ) {
				VPB_EVENT e;
				e.type = VPB_TIMEREXP;
				e.handle = t->chdev;
				e.data = t->id;
				vpbtimer_remove_from_active_list(t);
				putevt(&e, 0);
			}
		}

		t = t->next;
	}

	GenericLeaveCriticalSection(&TimerSect);
}

/*--------------------------------------------------------------------------*\

	FUNCTION.: vpb_timer_get_unique_timer_id
	AUTHOR...: David Rowe
	DATE.....: 22/4/98

	Allows the timer module to generate unique IDs for all of the VPB 
	timer objects in the system.

\*--------------------------------------------------------------------------*/

int WINAPI vpb_timer_get_unique_timer_id() {
	return(timer_ids++);
}

