/* Copyright 2013 Akira Ohta (akohta001@gmail.com)
    This file is part of ntch.

    The ntch 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.

    The ntch 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 ntch.  If not, see <http://www.gnu.org/licenses/>.
    
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <wchar.h>
#include <assert.h>

#include "error.h"
#include "utils/nt_std_t.h"
#include "utils/nt_timer.h"

#define NT_TIMER_CHK_SUM (1378428)


struct _timer_ctx{
	nt_link_tp timers;
}g_timer_ctx;


typedef struct tag_nt_timer_t *nt_timer_tp;
typedef struct tag_nt_timer_t{
	nt_timer_handle_t handle;
	int ref_count;
	int interval;/* -1. desable timer 0. immidiate expire NNN. inteval milli sec.
						*/
	struct timespec expire;
	
	int id;
	int status;
	nt_timer_fn func;
	
}nt_timer_t;

static BOOL set_current_time(struct timespec *pts);
static void add_interval(struct timespec *pts, int interval);
static void timer_free(void *ptr);
static BOOL is_expired(const struct timespec *pts, nt_timer_tp);


BOOL nt_timer_lib_init()
{
	g_timer_ctx.timers = NULL;
	return TRUE;
}


void nt_timer_lib_finish()
{
	if(g_timer_ctx.timers)
		nt_all_link_free(g_timer_ctx.timers, timer_free);
}

static BOOL is_expired(const struct timespec *pts, nt_timer_tp timerp)
{
	if(timerp->interval == 0)
		return TRUE;
	else if(timerp->interval < 0)
		return FALSE;
	else if(timerp->expire.tv_sec < pts->tv_sec)
		return TRUE;
	else if(timerp->expire.tv_sec > pts->tv_sec)
		return FALSE;
	else if(timerp->expire.tv_nsec < pts->tv_nsec)
		return TRUE;
	else
		return FALSE;
}

nt_timer_handle nt_timer_ring_a_bell()
{
	struct timespec ts;
	nt_timer_tp timerp;
	nt_link_tp linkp;
	
	if(!set_current_time(&ts))
		return NULL;
	
	if(!g_timer_ctx.timers)
		return NULL;
	linkp = g_timer_ctx.timers;
	do{
		timerp = linkp->data;
		if(is_expired(&ts, timerp)){
			timerp->status =
				(timerp->func)(timerp->id);
			add_interval(&timerp->expire, timerp->interval);
			nt_timer_add_ref(&timerp->handle);
			return &timerp->handle;
		}
		linkp = linkp->next;
	}while(linkp != g_timer_ctx.timers);
	return NULL;
}


nt_timer_handle nt_timer_alloc(
		int id, int interval, nt_timer_fn func)
{
	nt_timer_tp timerp;
	struct timespec ts;
	nt_link_tp linkp;
	
	if(interval > 0){
		if(!set_current_time(&ts))
			return NULL;
		add_interval(&ts, interval);
	}else{
		ts.tv_sec  = 0;
		ts.tv_nsec = 0;
	}
	
	timerp = malloc(sizeof(nt_timer_t));
	if(!timerp)
		return NULL;
	timerp->id = id;
	timerp->status = 0;
	timerp->interval = interval;
	timerp->func = func;
	timerp->expire = ts;
	timerp->handle.chk_sum = NT_TIMER_CHK_SUM;
	timerp->ref_count = 1;
	linkp = nt_link_add_data(g_timer_ctx.timers, timerp);
	if(!linkp){
		free(timerp);
		return NULL;
	}
	if(!g_timer_ctx.timers)
		g_timer_ctx.timers = linkp;
	
	return &timerp->handle;
}

BOOL nt_timer_set_interval(nt_timer_handle h_timer, int interval)
{
	nt_timer_tp timerp;
	assert(h_timer);
	assert(h_timer->chk_sum == NT_TIMER_CHK_SUM);
	timerp = (nt_timer_tp)h_timer;
	assert(timerp->ref_count > 0);
	if(interval > 0){
		if(!set_current_time(&timerp->expire))
			return FALSE;
		add_interval(&timerp->expire, interval);
	}else{
		timerp->expire.tv_sec  = 0;
		timerp->expire.tv_nsec = 0;
	}
	timerp->interval = interval;
	return TRUE;
}
int nt_timer_get_interval(nt_timer_handle h_timer)
{
	nt_timer_tp timerp;
	assert(h_timer);
	assert(h_timer->chk_sum == NT_TIMER_CHK_SUM);
	timerp = (nt_timer_tp)h_timer;
	assert(timerp->ref_count > 0);
	return timerp->interval;
}
int nt_timer_get_id(nt_timer_handle h_timer)
{
	nt_timer_tp timerp;
	assert(h_timer);
	assert(h_timer->chk_sum == NT_TIMER_CHK_SUM);
	timerp = (nt_timer_tp)h_timer;
	assert(timerp->ref_count > 0);
	return timerp->id;
}


int nt_timer_add_ref(nt_timer_handle h_timer)
{
	nt_timer_tp timerp;
	assert(h_timer);
	assert(h_timer->chk_sum == NT_TIMER_CHK_SUM);
	timerp = (nt_timer_tp)h_timer;
	assert(timerp->ref_count > 0);
	return ++timerp->ref_count;
}

int nt_timer_release_ref(nt_timer_handle h_timer)
{
	nt_timer_tp timerp;
	nt_link_tp linkp;
	
	assert(h_timer);
	assert(h_timer->chk_sum == NT_TIMER_CHK_SUM);
	timerp = (nt_timer_tp)h_timer;
	assert(timerp->ref_count > 0);
	if(0 != --timerp->ref_count)
		return timerp->ref_count;
	
	if(g_timer_ctx.timers){
		linkp = g_timer_ctx.timers;
		do{
			if(timerp == linkp->data){
				g_timer_ctx.timers =
					nt_link_remove2(g_timer_ctx.timers, linkp);
				free(linkp);
				free(h_timer);
				return 0;
			}
			linkp = linkp->next;
		}while(linkp != g_timer_ctx.timers);
	}
	assert(0);
	return 0;
}

static void timer_free(void *ptr)
{
	nt_timer_release_ref((nt_timer_handle)ptr);
}

static BOOL set_current_time(struct timespec *pts)
{
#ifdef CLOCK_MONOTONIC_COARSE 
	if(-1 == clock_gettime(CLOCK_MONOTONIC_COARSE , pts)){
#else
	if(-1 == clock_gettime(CLOCK_MONOTONIC , pts)){
#endif
		return FALSE;
	}
	return TRUE;
}

static void add_interval(struct timespec *pts, int interval)
{
	int i_sec, i_nsec;
	i_sec = interval / 1000;
	i_nsec = interval % 1000;
	i_nsec *= 1000000;
	pts->tv_sec += i_sec;
	pts->tv_nsec += i_nsec;
	if(pts->tv_nsec > 1000000000){
		pts->tv_sec++;
		pts->tv_nsec -= 1000000000;
	}
}


