/*******************************************************************************
    OpenAirInterface
    Copyright(c) 1999 - 2014 Eurecom

    OpenAirInterface is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.


    OpenAirInterface 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with OpenAirInterface.The full GNU General Public License is
   included in this distribution in the file called "COPYING". If not,
   see <http://www.gnu.org/licenses/>.

  Contact Information
  OpenAirInterface Admin: openair_admin@eurecom.fr
  OpenAirInterface Tech : openair_tech@eurecom.fr
  OpenAirInterface Dev  : openair4g-devel@eurecom.fr

  Address      : Eurecom, Compus SophiaTech 450, route des chappes, 06451 Biot, France.

 *******************************************************************************/
/*****************************************************************************
Source		timer.c

Version		0.1

Date		2012/11/27

Product		Test

Subsystem	Timer utility main test

Author		Frederic Maurel

Description	Tests the timer utility functions

*****************************************************************************/

#include "nas_timer.h"
#include "commonDef.h"

#include <pthread.h>
#include <stdio.h>	// printf
#include <stdlib.h>	// exit
#include <time.h>	// clock_gettime
#include <poll.h>	// poll

/****************************************************************************/
/****************  E X T E R N A L    D E F I N I T I O N S  ****************/
/****************************************************************************/

/****************************************************************************/
/*******************  L O C A L    D E F I N I T I O N S  *******************/
/****************************************************************************/

#define TIMER_1S	1	/* 1 second  */
#define TIMER_2S	2	/* 2 seconds */
#define TIMER_3S	3	/* 3 seconds */
#define TIMER_4S	4	/* 4 seconds */

typedef struct {
    unsigned int id;
    unsigned int sec;
    struct timespec start;
} _timer_t;

static void* _timer_callback(void* args);
static void* _timer_callback_joinable(void* args);

static pthread_mutex_t _mutex = PTHREAD_MUTEX_INITIALIZER;
 
static unsigned int _nb_timers = 0;
static int _start(_timer_t* timer, unsigned int sec);
static int _stop(_timer_t* timer);

/****************************************************************************/
/******************  E X P O R T E D    F U N C T I O N S  ******************/
/****************************************************************************/

int main (int argc, const char* argv[])
{
    int rc;
    _timer_t timer1, timer2, timer3, timer4;

#define NB_TIMERS_MAX 10
    _timer_t timer[NB_TIMERS_MAX];

    /* Initialize timer utility */
    rc = timer_init();
    if (rc == RETURNerror) {
	printf("ERROR: timer_init() failed\n");
	exit (EXIT_FAILURE);
    }

    /* Start NB_TIMERS_MAX timers to expire at time interval of 1s */
    for (int i=0; i < NB_TIMERS_MAX; i++) {
	if (_start(&timer[i], i) != RETURNok) {
	    printf("ERROR: timer_start(i=%u) failed\n", i);
	}
    }

    /* Start timer 1 to expire in 1s */
    if (_start(&timer1, TIMER_1S) != RETURNok) {
	printf("ERROR: timer_start() failed\n");
    }

    /* Start timer 2 to expire in 3s */
    if (_start(&timer2, TIMER_3S) != RETURNok) {
	printf("ERROR: timer_start() failed\n");
    }

    /* Start timer 3 to expire in 2s */
    if (_start(&timer3, TIMER_2S) != RETURNok) {
	printf("ERROR: timer_start() failed\n");
    }

    /* Stop timer 1 */
    if (_stop(&timer1) != RETURNok) {
	printf("ERROR: timer_stop(id=%u) failed\n", timer3.id);
    }

    /* Wait for the first timer to expire */
    poll(0, 0, -1);

    /* Start timer 4 to expire in 4s */
    if (_start(&timer4, TIMER_4S) != RETURNok) {
	printf("ERROR: timer_start() failed\n");
    }

    /* Wait for all timers to expire */
    while (_nb_timers > 0) {
	poll(0, 0, 10000);
    }

    exit(EXIT_SUCCESS);
}

/****************************************************************************/
/*********************  L O C A L    F U N C T I O N S  *********************/
/****************************************************************************/

static int _timespec_sub(struct timespec* a, struct timespec* b, struct timespec* result)
{
    if (a->tv_sec <  b->tv_sec) return -1;
    else if (a->tv_nsec <  b->tv_nsec) return -1;

    result->tv_sec = a->tv_sec - b->tv_sec;
    result->tv_nsec = a->tv_nsec - b->tv_nsec;
    if (result->tv_nsec < 0) {
        result->tv_sec--;
        result->tv_nsec += 1000000000;
    }
    return 0;
}

static void* _timer_callback(void* args)
{
    _timer_t* timer = (_timer_t*) args;
    struct timespec ts;

    clock_gettime(CLOCK_MONOTONIC, &ts);

    if (_timespec_sub(&ts, &timer->start, &ts) < 0) {
	printf("ERROR:_timespec_sub() failed: %ld.%.9ld > %ld.%.9ld\n",
	       timer->start.tv_sec, timer->start.tv_nsec,
	       ts.tv_sec, ts.tv_nsec);
    }
    else {
	printf("%s\t: Timer %u expired after %ld.%.9ld seconds (%u s)\n",
	       __FUNCTION__, timer->id, ts.tv_sec, ts.tv_nsec, timer->sec);
	timer->id = timer_stop(timer->id);
	pthread_mutex_lock(&_mutex);
	_nb_timers -= 1;
	pthread_mutex_unlock(&_mutex);
    }

    return NULL;
}

static void* _timer_callback_joinable(void* args)
{
    _timer_t* timer = (_timer_t*) args;
    struct timespec ts;
    int* rc = (int*)malloc(sizeof(int));

    clock_gettime(CLOCK_MONOTONIC, &ts);

    if (_timespec_sub(&ts, &timer->start, &ts) < 0) {
	printf("ERROR:_timespec_sub() failed: %ld.%.9ld > %ld.%.9ld\n",
	       timer->start.tv_sec, timer->start.tv_nsec,
	       ts.tv_sec, ts.tv_nsec);
	*rc = RETURNerror;
    }
    else {
	printf("%s\t: Timer %u expired after %ld.%.9ld seconds (%u s)\n",
	       __FUNCTION__, timer->id, ts.tv_sec, ts.tv_nsec, timer->sec);
	timer->id = timer_stop(timer->id);
	pthread_mutex_lock(&_mutex);
	_nb_timers -= 1;
	pthread_mutex_unlock(&_mutex);
	*rc = timer->id;
    }

    return rc;
}

static int _start(_timer_t* timer, unsigned int sec)
{
    int rc = RETURNerror;

    timer->sec = sec;
    clock_gettime(CLOCK_MONOTONIC, &timer->start);
    timer->id = timer_start(timer->sec, _timer_callback_joinable,
			    (void*) timer);
    if (timer->id != TIMER_INACTIVE_ID) {
	printf("Timer id=%u scheduled to expire in %u seconds\n",
	       timer->id, timer->sec);
	_nb_timers += 1;
	rc = RETURNok;
    }
    return (rc);
}

static int _stop(_timer_t* timer)
{
    int rc = RETURNerror;

    printf("Stop timer id=%u\n", timer->id);
    timer->id = timer_stop(timer->id);
    if (timer->id == TIMER_INACTIVE_ID) {
	_nb_timers -= 1;
	rc = RETURNok;
    }
    return (rc);
}