#include <linux/string.h>
#include <linux/kernel.h>
#include <linux/timer.h>
#include <linux/jiffies.h>

#include "xdsl_types.h"
#include "xdsl_timer.h"

#if ( 1000 % HZ ) != 0 
#error "Not support this HZ"
#endif

#define PRINT_R(fmt, args...)       printk("\x1B[31m" fmt "\x1B[0m", ## args)

static timetick_t timetick = TIMETICK_SEED;

#define TIMER_ENTRIES_NUM		8	// up to 32 

typedef struct {
	timetick_t next_timetick;	// next announce timetick 
	uint32 period;			// timer period (ms)
	uint32 f_periodic:1;	// periodic timer ?
	fn_timer_t fn_timer;	// timer function 
	void *priv;				// private data for timer function 
} timer_entry_t;

static struct {
	timer_entry_t entries[ TIMER_ENTRIES_NUM ];
	uint32 mask;
} timer;

static void register_timer_core( fn_timer_t fn_timer, void *priv, 
									long period, int idx )
{
	timer_entry_t * const p_entry = &timer.entries[ idx ];
	// negative period means one shoot timer 
	p_entry ->f_periodic = ( period > 0 ? 1 : 0 );
	period = ( period < 0 ? period * ( -1 ) : period );
	
	p_entry ->next_timetick = timetick + period;	// save current + period 
	p_entry ->period = period;
	p_entry ->fn_timer = fn_timer;
	p_entry ->priv = priv;
	
	timer.mask |= ( 1 << idx );
}

static int find_unused_timer_entry_index( fn_timer_t fn_timer )
{
	int i;
	const timer_entry_t * p_entry;
	
	// check whether redundant ? 
	if( fn_timer != NULL ) {
		
		for( i = 0; i < TIMER_ENTRIES_NUM; i ++ ) {
			
			p_entry = &timer.entries[ i ];
			
			if( p_entry ->fn_timer == fn_timer )
				return i;
		}
	} 
	
	// normal process 
	for( i = 0; i < TIMER_ENTRIES_NUM; i ++ ) {
		
		if( ( timer.mask & ( 1 << i ) ) == 0 )
			return i;
	}
	
	return -1;
}

int register_dsl_timer( fn_timer_t fn_timer, void *priv, long period )
{
	int idx;
	
	idx = find_unused_timer_entry_index( ( period < 0 ? fn_timer : NULL ) );
	
	if( idx >= 0 ) {
	
		register_timer_core( fn_timer, priv, period, idx );
		
		return idx;
	}
	
	PRINT_R( "No more timer entry!!\n" );
	
	return -1;	// no more entry  
}

static inline void check_and_announce_timer( void )
{
	int i;
	uint32 mask_shift = timer.mask;	// always check LSB 
	timer_entry_t *p_entry;
	
//	CT_ASSERT( sizeof( mask_shift ) == sizeof( timer.mask ) );
	
	for( i = 0; i < TIMER_ENTRIES_NUM && mask_shift; i ++, mask_shift >>= 1 ) {
		
		if( ( mask_shift & 1 ) == 0 )
			continue;
		
		// check timeout ?
		p_entry = &timer.entries[ i ];
		
		if( timetick_after_eq( timetick, p_entry ->next_timetick ) ) {
			
			p_entry ->next_timetick += p_entry ->period;
			//p_entry ->next_timetick = timetick + p_entry ->period;	// another solution 
			
			p_entry ->fn_timer( p_entry ->priv );
			
			// unmask 
			if( !p_entry ->f_periodic )
				timer.mask &= ~( 1 << i );
		}		
	}
}

static void increase_timetick( uint32 inc_ms )
{
	// increase timetick 
	timetick += inc_ms;
	
	// do timer 
	check_and_announce_timer();
}


static struct timer_list os_linux_timer;

static void os_linux_timer_func( unsigned long data )
{
#if ( 1000 / HZ ) < 10
	// jiffies period < 10ms ==> announce timer in 10 ms 
	increase_timetick( 10 /* 10ms */ );
	
	mod_timer( &os_linux_timer, jiffies + 10 / ( 1000 / HZ ) );
#else
	// jiffies period >= 10ms 
	increase_timetick( 1000 / HZ );
	
	mod_timer( &os_linux_timer, jiffies + 1 );
#endif
}

void start_dsl_timer( void )
{
	init_timer( &os_linux_timer );
	
	os_linux_timer.expires = jiffies + 1;
	os_linux_timer.function = os_linux_timer_func;
	
	add_timer( &os_linux_timer );
}