/*
 *   Copyright (c) 2007 Endace Technology Ltd, Hamilton, New Zealand.
 *   All rights reserved.
 *  
 *   This source code is proprietary to Endace Technology Limited and no part
 *   of it may be redistributed, published or disclosed except as outlined in
 *   the written contract supplied with this product.
 *   
 *   DESCRITION:
 *   DAG IRIG-B test program
 *
 *   $Id: dag_irigb.c 14310 2011-06-17 01:52:35Z peter.thomas $
 *   
 */


#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <time.h>

#if defined(__FreeBSD__) || defined(__linux__) || (defined(__SVR4) && defined(__sun))
#include <sys/time.h>
#include <inttypes.h>
#include <unistd.h>
#include <sys/shm.h>
#include <sys/mman.h>
#endif

/* Endace headers. */
#include "dagapi.h"
#include "dag_platform.h"
#include "dagutil.h"
#include "dagclarg.h"
#include "dag_config_api.h"


/* CVS Header. */
static const char* const kCvsHeader __attribute__ ((unused)) = "$Id: dag_irigb.c 14310 2011-06-17 01:52:35Z peter.thomas $";
static const char* const kRevisionString = "$Revision: 14310 $";

/*****************************************************************************/
/* Macros and constants                                                      */
/*****************************************************************************/
#define NTPD_BASE	0x4e545030	/* "NTP0" */

#define IRIGB_R(iom, offset)	(*(volatile unsigned*)((uintptr_t)iom + offset))
#define IRIGB_W(iom, offset, value)	( *(volatile unsigned*) ((uintptr_t)iom + offset) = value)

const uint16_t yday_regular[13] =
{ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 };

/* Leap years.  */
const uint16_t yday_leap[13] =
{ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 };


/*****************************************************************************/
/* Data structures                                                           */
/*****************************************************************************/

typedef struct shmTime
{
    int mode;			/* 0 - if valid set
				 *       use values, 
				 *       clear valid
				 * 1 - if valid set 
				 *       if count before and after read of values is equal,
				 *         use values 
				 *       clear valid
				 */
    int count;
    time_t clockTimeStampSec;   /* external clock */
    int clockTimeStampUSec;     /* external clock */
    time_t receiveTimeStampSec; /* internal clock, when external value was received */
    int receiveTimeStampUSec;   /* internal clock, when external value was received */
    int leap;
    int precision;
    int nsamples;
    int valid;
    int pad[10];
}ntp_shm_segment_t;

enum {
	IRIGB_config	= 0x00,
	IRIGB_data	= 0x04
};

/* Commandline argument codes. */
enum
{
	CLA_HELP,
	CLA_VERBOSE,
	CLA_HOST,
	CLA_IRIGB,
	CLA_SET,
	CLA_DELTA,
	CLA_VERSION,
	CLA_DEVICE,
	CLA_DAEMON,
	CLA_INTERVAL,
	CLA_DELTA_HOUR,
	CLA_NTP_SHM_UNIT,
	CLA_DUCK_REFERENCE
};

typedef struct irigb_auto {
	int pps;
	int irigb;
}irigb_auto_t;

typedef struct irigb_status {
	irigb_auto_t auto_detect;
	int pps;
	int irigb;
}irigb_status_t;

typedef struct {
	uint32_t is_host_year;
	uint32_t is_irigb_year;
	uint32_t is_set_irigb;
	int daemon;
	int interval;
	int32_t delta;
	int32_t delta_hour;
	int32_t ntp_shm_unit;
	uint8_t is_duck_reader_timestamp;
}global_options_t;


typedef struct _dag_unit {
	int		dagfd;
	uint32_t	irigb_base;
	uint8_t		*iom;
	dag_card_ref_t ref_card ;
    attr_uuid_t attr_duck_reader;
} dag_unit_t;

static int open_irig(char *dagname, dag_unit_t *irig_dev);
static int close_irig(dag_unit_t *irig_dev);
static ntp_shm_segment_t* get_shm_time(int unit);
void print_info(irigb_status_t *status, struct timespec *ts, struct timespec *host, struct tm *date, time_t now, time_t t);
static int update_shm(ntp_shm_segment_t *shm_seg, struct timespec *ts_local, struct timespec *ts_refclk);
static int  get_time_from_duck_reader(dag_unit_t *in_dag, struct timespec *in_time );


/*****************************************************************************/
/* File-scope variables                                                      */
/*****************************************************************************/
static char dagname_buf[DAGNAME_BUFSIZE] = "dag0";
static char dagname[DAGNAME_BUFSIZE];
static global_options_t global_options;
static int continue_run = 1;
//#define IRIG_SIM

#ifdef IRIG_SIM
typedef struct sim_time
{
    int32_t    nsec;
    int32_t    sec; 
} sim_time_t;

typedef struct tag_irig_sim_data
{
	uint32_t irig_frame[4];
	sim_time_t irq_time;
	sim_time_t diff_irq_time;
	uint64_t last_tsc;
	int64_t diff_tsc;
}
irig_sim_data_t;

irig_sim_data_t irig_sim;

static int read_irig_sim_data(void)
{
	FILE *irig_data_file;
	irig_data_file = fopen("irig_sim.dat", "rb");
	if(irig_data_file == NULL)
	{
		return -1;
	}

	fread(&irig_sim, 1, sizeof(irig_sim_data_t), irig_data_file);
	fclose(irig_data_file);

	return 0;
}

#endif /* IRIG_SIM */

/*****************************************************************************/
/*** Functions                                                             ***/
/*****************************************************************************/

static void anysig(int sig)
{
	continue_run = 0;
}

static void 
irigb_read(uint8_t *iom, uint32_t irigb_base, uint32_t *irigb_frame)
{
#ifndef IRIG_SIM
	int i;

	for (i = 0; i < 4; i ++)
	{
		IRIGB_W(iom, irigb_base + IRIGB_config, i << 8);
		usleep(1);
		irigb_frame[i] = IRIGB_R(iom, irigb_base + IRIGB_data);
	}
#else  /* IRIG_SIM */
	memcpy(irigb_frame, irig_sim.irig_frame, sizeof(irig_sim.irig_frame));
#endif /* IRIG_SIM */
}

/*****************************************************************************
 * Given the start bit and width of bits. it will return the bits in the array 
 ****************************************************************************/
uint32_t 
select_bits(uint32_t *resp, int start, int size)
{
	int mask = (size < 32 ? 1 << size : 0) - 1;
	int off = ((start) / 32);
	int shft = (start) & 31;
	uint32_t res;

	res = resp[off] >> shft;
	if (size + shft > 32)
		res |= resp[off+1] << ((32 - shft) % 32);
	return res & mask;
}

/*****************************************************************************/
static time_t
irigb_mktime(struct tm *date)
{
	if (date->tm_year < 70)
		return -1;
	else
		return (date->tm_sec + date->tm_min*60 + date->tm_hour*3600 + 
			date->tm_yday*86400 + (date->tm_year-70)*31536000 + 
			((date->tm_year-69)/4)*86400 -((date->tm_year-1)/100)*86400 + 
			((date->tm_year+299)/400)*86400);

}

/*****************************************************************************/
static int 
calc_posix_year(time_t t_curr)
{
	int year_approx;
	struct tm t;

	if(t_curr < 0) return -1;

	year_approx = t_curr/(366 * 86400);
	year_approx += 70;

	/* fill t with the beggining of the year */
	memset(&t, 0, sizeof(struct tm));
	t.tm_year = year_approx;

	/* make sure that t_beginning_of_the_year <= t_curr */
	if(irigb_mktime(&t) <= t_curr)
	{
		while(1)
		{	
			t.tm_year = year_approx + 1;
			if(irigb_mktime(&t) > t_curr)
				return year_approx;
			else
				year_approx++;
		}
	}

	return -1;
}

/*****************************************************************************/
static int
irigb_decode(uint32_t *irigb_frame, struct tm *date)
{
	uint32_t secs, mins, hours, days;
	uint32_t years;

	years = 0;

	memset(date, 0, sizeof(struct tm));

	/* decode the IRIG-B time */
	secs = select_bits(irigb_frame, 0, 4) + select_bits(irigb_frame, 5, 3) * 10;
	mins = select_bits(irigb_frame, 9, 4) + select_bits(irigb_frame, 14, 3) * 10;
	hours = select_bits(irigb_frame, 19, 4) + select_bits(irigb_frame, 24, 2) * 10;
	days = select_bits(irigb_frame, 29, 4) + select_bits(irigb_frame, 34, 4) * 10
		+ select_bits(irigb_frame, 39, 2) * 100;

	/* if the IRIG-B device sends the year information then read it from the device */
	if (global_options.is_irigb_year)
	{
		years = select_bits(irigb_frame, 49, 4) + select_bits(irigb_frame, 54, 4) *10;
		if (!years)
			return -1;
		years = 100 + years;
	}
	else if (global_options.is_host_year)
	{
		years = date->tm_year;
	}
	else
	{
		return -1;
	}

	/* fill up break-down time structure */
	date->tm_sec = secs;         		/* seconds */
	date->tm_min = mins;         		/* minutes */
	date->tm_hour = hours;        		/* hours */
	date->tm_yday = days - 1;        	/* day in the year */
	date->tm_year = years;			/* year */
#if 0
	printf("%d %d:%d:%d %d\n", days, hours, mins, secs, years);
#endif
	return 0;
}

/*****************************************************************************/
static irigb_status_t
irigb_status(uint8_t *iom, uint32_t irigb_base)
{
	irigb_status_t status;
	int val;

	/* init status */
	status.auto_detect.pps = 0;
	status.auto_detect.irigb = 0;
	status.pps = 0;
	status.irigb = 0;

#ifndef IRIG_SIM
	val = IRIGB_R(iom, irigb_base + IRIGB_config);

	switch(val&0x3)
	{
		case 0x0:
			if ((val&0xC) == 0x4) {
				status.auto_detect.pps = 1;
			}
			else if((val&0xC) == 0x8) {
				status.auto_detect.irigb = 1;
			}
			break;
		case 0x1:
			status.pps = 1;
			break;
		case 0x2:
			status.irigb = 1;
			break;
		//case 0x3:
		default:
			dagutil_warning("Unknown source\n");
	}
#else  /* IRIG_SIM */
	if(read_irig_sim_data() != -1)
		status.irigb = 1;
#endif /* IRIG_SIM */
	return status;
}
/*****************************************************************************/
static void 
print_irigb_status(const irigb_status_t* in_status)
{
	printf("INPUT SOURCE: ");
	if ( (1 == in_status->auto_detect.pps) || ( 1 == in_status->auto_detect.irigb))
	{
		printf("Auto-Detect");
		if( 1 == in_status->auto_detect.irigb)
			printf(" (IRIG-B)");
		else if (1 == in_status->auto_detect.pps)
			printf(" (PPS)");
		else 
			printf(" (None) ");
	}
	else if ( 1 == in_status->pps )
	{
		printf("Forced PPS");
	}
	else if (1 == in_status->irigb)
	{
		printf("Forced IRIG-B");
	}
	else
	{
		printf("None");
	}
	printf("\n");
	return;
}
/*****************************************************************************/
static uint32_t
get_cpu_freq(void)
{
#if defined(__FreeBSD__) || defined(__linux__) || (defined(__SVR4) && defined(__sun))
	struct timeval before;
	struct timeval after;
	struct timeval diff;
	uint64_t tsc1 = 0;
	uint64_t tsc2 = 0;
	uint64_t tdiff;
	uint64_t temp;
	uint32_t thiscpu;

	gettimeofday(&before, NULL);
	dagutil_tsc64_read(&tsc1);

	usleep(10*1000);

	gettimeofday(&after, NULL);
	dagutil_tsc64_read(&tsc2);

	timersub(&after, &before, &diff);
	temp = (uint64_t) diff.tv_sec * 1000 * 1000 + diff.tv_usec;

	tdiff = tsc2 - tsc1;

	thiscpu = tdiff / temp;
	return thiscpu;

#elif defined(_WIN32)

	LARGE_INTEGER freq;
	uint32_t thiscpu;

	QueryPerformanceFrequency(&freq);

	thiscpu = freq.LowPart/1000000;
	return thiscpu;

#endif /* Platform-specific code. */
}
/*****************************************************************************/
static uint32_t
get_nsecond(int dagfd, uint32_t freq)
{

	duckinf_t duckinf;
	struct timespec tp;
	uint64_t tsc_now;
	int64_t delta_tsc, res64;
	uint32_t res;
	duck_irq_time_t irq_time;

#if defined(_WIN32)
	DWORD BytesTransfered = 0;
#elif defined (__linux__) || defined (__FreeBSD__) || (defined(__APPLE__) && defined(__ppc__))
	int error;
#endif

	memset(&duckinf, 0, sizeof(duckinf_t));
	memset(&irq_time, 0, sizeof(duck_irq_time_t));

#if defined CLOCK_REALTIME && HAVE_CLOCK_SETTIME
#if defined (__linux__) || defined (__FreeBSD__) || (defined(__APPLE__) && defined(__ppc__))
#ifndef IRIG_SIM
	if((error = ioctl(dagfd, DAGIOCIRQTIME, &irq_time)))
		dagutil_panic("DAGIOCIRQTIME failed with %d\n", error);
#endif /* IRIG_SIM */

	clock_gettime(CLOCK_REALTIME, &tp);
#ifdef IRIG_SIM
#if 0
	printf("%d %d %d %d\n", irig_sim.irq_time.sec, irig_sim.irq_time.nsec, irig_sim.diff_irq_time.sec, irig_sim.diff_irq_time.nsec);
#endif
	if(irig_sim.diff_irq_time.sec || irig_sim.diff_irq_time.nsec)
	{
		irq_time.sec = tp.tv_sec + irig_sim.diff_irq_time.sec;
		if(irig_sim.diff_irq_time.nsec)
		{
			int32_t nsec_res;

			irq_time.sec = tp.tv_sec + irig_sim.diff_irq_time.sec;

			nsec_res = tp.tv_nsec + irig_sim.diff_irq_time.nsec;
			if(nsec_res < 0)
			{
				irq_time.sec += ((nsec_res / 1000000000) - 1);
				nsec_res = 1000000000 - (nsec_res % 1000000000);
			}
			else
			{
				irq_time.sec += (nsec_res / 1000000000);
				nsec_res = nsec_res % 1000000000;
			}
			irq_time.nsec = nsec_res;
		}
		else
		{
			irq_time.nsec = tp.tv_nsec;
		}
	}
	else
	{
		irq_time.sec  = irig_sim.irq_time.sec;
		irq_time.nsec = irig_sim.irq_time.nsec;
	}
#endif /* IRIG_SIM */

#ifndef NDEBUG
	printf("NOW nsecond:%ld.%09ld, duckinf.Last_NS:%ld.%09d\n", tp.tv_sec, tp.tv_nsec, irq_time.sec, irq_time.nsec);
#endif
	if (irq_time.sec && irq_time.nsec)
	{
		res64 = (((uint64_t)tp.tv_sec - (uint64_t)irq_time.sec) * 1000 * 1000 * 1000 + (uint64_t)tp.tv_nsec) - (uint64_t)irq_time.nsec;

		if((res64 < (uint64_t)2000000000) && (res64 > 0))
		return (uint32_t)res64;
	}
#endif /* defined (__linux__) || defined (__FreeBSD__) || (defined(__APPLE__) && defined(__ppc__)) */
#endif /* CLOCK_REALTIME && HAVE_CLOCK_SETTIME */

#if defined (__linux__) || defined (__FreeBSD__) || (defined(__APPLE__) && defined(__ppc__))
#ifndef IRIG_SIM
	if((error = ioctl(dagfd, DAGIOCDUCK, &duckinf)))
		dagutil_panic("DAGIOCDUCK failed with %d\n", error);
#endif /* IRIG_SIM */
	dagutil_tsc64_read(&tsc_now);

#ifdef IRIG_SIM
	if(irig_sim.diff_tsc)
		duckinf.Last_TSC = tsc_now + irig_sim.diff_tsc;
	else
		duckinf.Last_TSC = irig_sim.last_tsc;

#endif /* IRIG_SIM */

#elif defined (_WIN32)
	if(DeviceIoControl(dag_gethandle(dagfd),
		IOCTL_GET_DUCKINFO,
		&duckinf,
		sizeof(duckinf_t),
		&duckinf,
		sizeof(duckinf_t),
		&BytesTransfered,
		NULL) == FALSE)
		panic("DeviceIoControl IOCTL_GET_DUCKINFO: %s\n",strerror(errno));

	dagutil_tsc64_read(&tsc_now);
#endif /*  defined (__linux__) || defined (__FreeBSD__) || (defined(__APPLE__) && defined(__ppc__)) */

	delta_tsc = tsc_now - duckinf.Last_TSC;
	res64 = (delta_tsc * 1000) / (int64_t)freq;
	if((res64 < (uint64_t)2000000000) && (res64 > 0))
		res = (uint32_t)res64;
	else
		res = 0xFFFFFFFF;

#ifndef NDEBUG
	printf("NOW tsc:%"PRIu64", duckinf.Last_TSC:%"PRIu64", res:%d, freq:%d\n", tsc_now, duckinf.Last_TSC, res, freq );
#endif
	return res;

}

/*****************************************************************************/
static int
set_time(struct timespec *ts)
{
#if defined CLOCK_REALTIME && HAVE_CLOCK_SETTIME
	int r = clock_settime (CLOCK_REALTIME, ts);
	if (r == 0 || errno == EPERM)
		return r;
#endif /* CLOCK_REALTIME && HAVE_CLOCK_SETTIME */

#if HAVE_SETTIMEOFDAY
	struct timeval tv;
	tv.tv_sec = ts->tv_sec;
	tv.tv_usec = ts->tv_nsec / 1000;

	return settimeofday(&tv, 0);
#endif /* HAVE_SETTIMEOFDAY */
	errno = ENOSYS;
	return -1;
}

/*****************************************************************************/
static int
get_time(struct timespec *ts)
{
#if defined CLOCK_REALTIME && HAVE_CLOCK_GETTIME
	return clock_gettime (CLOCK_REALTIME, ts);
#endif /* CLOCK_REALTIME && HAVE_CLOCK_GETTIME */

#if HAVE_GETTIMEOFDAY
	{
		int ret_val;
		struct timeval tv;
		ret_val = gettimeofday(&tv, NULL);

		ts->tv_sec = tv.tv_sec;
		ts->tv_nsec = tv.tv_usec * 1000;
		return ret_val;
	}
#endif /* HAVE_GETTIMEOFDAY */
	errno = ENOSYS;
	return -1;
}

/*****************************************************************************/
static void
init_global_options(void)
{
	global_options.is_host_year = 1;
	global_options.is_irigb_year = 0;
	global_options.is_set_irigb = 0;
	global_options.delta = 0;
	global_options.delta_hour = 0;
	global_options.daemon = 0;
	global_options.interval = 60;
	global_options.ntp_shm_unit = -1;
	global_options.is_duck_reader_timestamp = 0 ;
}
/*****************************************************************************/
static void
print_version(void)
{
	printf("dag_irigb (DAG %s) %s\n", kDagReleaseVersion, kRevisionString);
}


/*****************************************************************************/
static void print_usage(ClArgPtr clarg)
{
	printf("%s - IRIG-B test program.\n", dagutil_get_progname());
	printf("Usage: %s [options]\n", dagutil_get_progname());
	dagclarg_display_usage(clarg, stdout);
}

/*****************************************************************************/
static int 
parse_commandline(int argc, char *argv[])
{

	ClArgPtr clarg = NULL;
	FILE *errorfile = NULL;
	int arg_index = 0;
	int clarg_result;
	int code;
	int dagstream = 0;
	int interval = 1;
	int ntp_shm_unit = -1;

	int32_t delta, delta_hour;
	delta = delta_hour = 0;

	init_global_options();
	/* Set up the command line options. */
	clarg = dagclarg_init(argc, (const char * const *)argv);

	dagclarg_add(clarg, "display help (this page)", "--help", 'h', CLA_HELP);
	dagclarg_add_long_option(clarg, CLA_HELP, "--usage");
	dagclarg_add_short_option(clarg, CLA_HELP, '?');
	dagclarg_add(clarg, "display version information", "--version", 'V', CLA_VERSION);
	dagclarg_add(clarg, "increase verbosity", "--verbose", 'v', CLA_VERBOSE);
	dagclarg_add_string(clarg, "DAG device to use.", "--device", 'd', "device", dagname_buf, DAGNAME_BUFSIZE, CLA_DEVICE);
	dagclarg_add(clarg, "year information provided by host (default)", "--host", 't', CLA_HOST);
	dagclarg_add(clarg, "year information provided by IRIG-B", "--irigb", 'i', CLA_IRIGB);
	dagclarg_add(clarg, "fork into background, run as daemon in Linux and update NTP shared memory unit", "--daemon", 'm', CLA_DAEMON);
	/* TODO Just commenting out the interval, as it is not used right now. For NTP daemon , 1 second interval is used*/
/*	dagclarg_add_int (clarg, "interval to synchronize host system clock time in minutes (default 1 min)"
				, "--interval", 'l', "interval", &interval, CLA_INTERVAL);
*/
	dagclarg_add(clarg, "set host time to IRIG-B time", "--set", 's', CLA_SET);
	dagclarg_add_int (clarg, "set the difference between UTC and the IRIG-B device time in second."
				, "--sec", 'b', "delta", &delta, CLA_DELTA);
	dagclarg_add_int (clarg, "set the difference between UTC and the IRIG-B device time in hours."
				, "--hour", 'r', "delta_hour", &delta_hour, CLA_DELTA_HOUR);
	dagclarg_add_int (clarg, "ntp shared memory-segment unit(0 - 3)to use"
				, "--ntp-shm-unit", 'u', "shm_unit", &ntp_shm_unit, CLA_NTP_SHM_UNIT);
	dagclarg_add(clarg, "use DUCK time as the ntp shared memory reference clock(default is IRIG-B time)", "--duck", 'D', CLA_DUCK_REFERENCE);
	clarg_result = dagclarg_parse(clarg, errorfile, &arg_index, &code);

	while (1 == clarg_result)
	{
		switch (code)
		{
			case CLA_HELP:
				print_usage(clarg);
				exit(EXIT_SUCCESS);
				break;

			case CLA_VERBOSE:
				dagutil_inc_verbosity();
				errorfile = stderr;
				break;

			case CLA_VERSION:
				print_version();
				exit(EXIT_SUCCESS);
				break;

			case CLA_HOST:
				global_options.is_host_year = 1;
				break;

			case CLA_IRIGB:
				global_options.is_irigb_year = 1;
				global_options.is_host_year = 0;
				break;

			case CLA_DAEMON:
				global_options.daemon = 1;
				break;

			case CLA_INTERVAL:
				global_options.interval *= interval;
				break;

			case CLA_SET:
				global_options.is_set_irigb = 1;
				break;

			case CLA_DELTA:
				global_options.delta = delta;
				break;

			case CLA_DELTA_HOUR:
				global_options.delta_hour = delta_hour;
				break;
/*
			case CLA_NTP:
				global_options.ntp_mode = 1;
				break;
*/
			case CLA_NTP_SHM_UNIT:
				global_options.ntp_shm_unit = ntp_shm_unit;
				break;
			case CLA_DUCK_REFERENCE:
				global_options.is_duck_reader_timestamp = 1;
				break;

			case CLA_DEVICE:
				break;

			default:
				if (argv[arg_index][0] == '-')
				{
					/* Unknown option. */
					dagutil_error("unknown option %s\n", argv[arg_index]); 
					print_usage(clarg);
					return EXIT_FAILURE;
				}
				break;
		}
		clarg_result = dagclarg_parse(clarg, errorfile, &arg_index, &code);
	}

	if (-1 == clarg_result)
	{
		if (arg_index < argc)
		{
			dagutil_error("while processing option %s\n", argv[arg_index]);
		}
		dagclarg_display_usage(clarg, stderr);
		return EXIT_FAILURE;
	}
	/* ClargPtr should no longer be necessary. */
	dagclarg_dispose(clarg);
	if (-1 == dag_parse_name(dagname_buf, dagname, DAGNAME_BUFSIZE, &dagstream))
	{
		dagutil_panic("dag_parse_name(%s): %s\n", dagname_buf, strerror(errno));
	}

	return EXIT_SUCCESS;

}

static int validate_global_params(void)
{
	if ( !global_options.daemon && global_options.is_duck_reader_timestamp)
	{
		dagutil_error("DUCK timestamp could be used only in NTP reference clock mode\n");
		return EXIT_FAILURE;
	}

	if( (global_options.ntp_shm_unit < 0) || (global_options.ntp_shm_unit > 3) )
	{
		if ( global_options.daemon)
		{
			dagutil_error("Invalid NTP shared memory-segment unit.\n");
			return EXIT_FAILURE;
		}
	}
	if ( (global_options.ntp_shm_unit > 0)  && !global_options.daemon)
	{
		dagutil_warning(" NTP shared memory-segment unit will be ignored\n");
	}
	return EXIT_SUCCESS;
}
/*****************************************************************************/
int 
dagirigb_main(int argc, char *argv[])
{
	dag_unit_t dag;

	uint32_t frame[4];

	irigb_status_t status;
	struct timespec ts, host;
	struct tm *date, date_stack;
	uint32_t freq;

	time_t t, now;

	date = &date_stack;
	/* initalize the dag unit*/
	memset(&dag, 0, sizeof(dag_unit_t));

#ifndef IRIG_SIM
	if(open_irig(dagname, &dag))
		return EXIT_FAILURE;

	status = irigb_status(dag.iom, dag.irigb_base);
	print_irigb_status(&status);
	if(!(status.auto_detect.irigb || status.irigb))
	{
		close_irig(&dag);
		return EXIT_FAILURE;
	}
#endif /* IRIG_SIM */

	t = 0;
	freq = get_cpu_freq();

	get_time(&host);

	/* subtract delta to approximate time for IRIG-B device */
	global_options.delta += global_options.delta_hour * 3600;
	now = host.tv_sec - global_options.delta;

	irigb_read(dag.iom, dag.irigb_base, frame);
	if(irigb_decode(frame, date))
	{
		dagutil_error("irigb_decode failed\n");
		return EXIT_FAILURE;
	}

	if(global_options.is_host_year)
	{
		time_t t_irig, t_abs, t_abs_min;
		int host_year;
		int i_min, i;

		t_abs_min = (time_t)INT_MAX;
		i_min = INT_MAX;

		host_year = calc_posix_year(now);

		for(i = -1; i < 2; i++)
		{
			date->tm_year = host_year + i;
			t_irig = irigb_mktime(date);
			if(t_irig == -1)
			{
				return -3;
			}

			t_irig++;  /* add +1 sec because the IRIG-B time stamp was received for the previous second */
			t_abs = abs(now - t_irig);
			if(t_abs < t_abs_min)
			{
				i_min = i;
				t = t_irig;
				t_abs_min = t_abs;
			}
		}
		date->tm_year = host_year + i_min;
	}
	else
	{
		t = irigb_mktime(date);
		if(t == -1)
		{
			return -3;
		}
		t++;
	}


	if ((global_options.is_set_irigb) && (status.auto_detect.irigb || status.irigb))
	{
		uint32_t nsec, nsec_sec;
		ts.tv_sec = t + global_options.delta;
		nsec = get_nsecond(dag.dagfd, freq);
		ts.tv_nsec = nsec % 1000000000;
		nsec_sec   = nsec / 1000000000;
		if(nsec_sec > 1)
		{
			dagutil_error("get_nsecond returned %d\n", nsec_sec);
			return EXIT_FAILURE;
		}
		ts.tv_sec += nsec_sec;

		if (set_time (&ts) != 0)
		{
			dagutil_error("can't set time\n");
			return EXIT_FAILURE;
		}
	}

	print_info(&status, &ts, &host, date, now, t);

#ifndef IRIG_SIM
	/* closes the dag card */
	close_irig(&dag);
#endif /* IRIG_SIM */

	return 0;
}


static int get_irigb_seconds(dag_unit_t *dag, time_t *result)
{
	uint32_t frame[4];

	struct timespec host;
	struct tm *date, date_stack;
	uint32_t freq;

	time_t t, now;

	*result = 0;

	date = &date_stack;

	t = 0;
	freq = get_cpu_freq();

	get_time(&host);

	/* subtract delta to approximate time for IRIG-B device */
	global_options.delta += global_options.delta_hour * 3600;
	now = host.tv_sec - global_options.delta;

	irigb_read(dag->iom, dag->irigb_base, frame);
	if(irigb_decode(frame, date))
	{
		dagutil_verbose_level(2, "error : irigb_decode failed\n");
		return EXIT_FAILURE;
	}

	if(global_options.is_host_year)
	{
		time_t t_irig, t_abs, t_abs_min;
		int host_year;
		int i_min, i;

		t_abs_min = (time_t)INT_MAX;
		i_min = INT_MAX;

		host_year = calc_posix_year(now);

		for(i = -1; i < 2; i++)
		{
			date->tm_year = host_year + i;
			t_irig = irigb_mktime(date);
			if(t_irig == -1)
			{
				dagutil_error("irigb_mktime failed\n");
				return -3;
			}

			t_irig++;  /* add +1 sec because the IRIG-B time stamp was received for the previous second */
			t_abs = abs(now - t_irig);
			if(t_abs < t_abs_min)
			{
				i_min = i;
				t = t_irig;
				t_abs_min = t_abs;
			}
		}
		date->tm_year = host_year + i_min;
	}
	else
	{
		t = irigb_mktime(date);
		if(t == -1)
		{
			dagutil_error("irigb_mktime failed\n");
			return -3;
		}
		t++;
	}

	*result = t;
	return 0;
}


void print_info(irigb_status_t *status, struct timespec *ts, struct timespec *host, struct tm *date, time_t now, time_t t)
{
	if(!global_options.daemon)
	{
		/*TODO:added a function to display the time.*/
		printf("HOST:LOCAL:\t\t%s", asctime(localtime(&(host->tv_sec))));
		printf("HOST:UTC  :\t\t%s", asctime(gmtime(&(host->tv_sec))));

		if (status->auto_detect.irigb || status->irigb)
		{
			if (global_options.is_irigb_year)
			{
				/* tm_year = 100 + irig year = years since 1900, tm_yday = DoY - 1 */
				printf("IRIG-B    :\t\t  %04d %03d %02d:%02d:%02d + 1 sec\n",
				       (date->tm_year + 1900),
				       (date->tm_yday + 1),
				       date->tm_hour,
				       date->tm_min,
				       date->tm_sec);
			}
			else
			{
				printf("IRIG-B    :\t\t       %03d %02d:%02d:%02d + 1 sec\n",
				       (date->tm_yday + 1),
				       date->tm_hour,
				       date->tm_min,
				       date->tm_sec);
			}
			printf("HOST-IRIG :\t\t%d sec\n", (int)(now - t));
		}

		if ((global_options.is_set_irigb) && (status->auto_detect.irigb || status->irigb))
		{
			printf("HOST:LOCAL:\t\t%s", asctime(localtime(&(ts->tv_sec))));
			printf("HOST:UTC  :\t\t%s", asctime(gmtime(&(ts->tv_sec))));
		}
	}
}

static int	detach_ntp_shm(ntp_shm_segment_t* shared_seg)
{
	return shmdt((void*)shared_seg);
}

/*****************************************************************************/
int dagirigb_daemon_main()
{
#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || defined(__APPLE__)
	int temp;
	ntp_shm_segment_t * ntp_shm;
	dag_unit_t dag;
	irigb_status_t status;
	struct timespec ts_local;
	struct timespec ts_refclk;
	duck_irq_time_t irq_time;
	time_t t_irig;
	uint8_t run_status = 1; /* 0 = error occured. used to avoid too many log messages*/
	struct timeval now;/* to be used for duck reader mode to get system time*/

	/* initialize dag unit*/
	memset(&dag, 0, sizeof(dag_unit_t));
	memset(&now, 0, sizeof(now));
	/* open device */
	if(open_irig(dagname, &dag))
	{
		dagutil_error("Can not start the daemon\n");
		return EXIT_FAILURE;
	}

	/* open shm unit */
	ntp_shm = get_shm_time(global_options.ntp_shm_unit);
	if(ntp_shm == NULL)
	{
		dagutil_error("Can not start the daemon\n");
		return EXIT_FAILURE;
	}

	/* inits successful . Now fork into bg */
	dagutil_verbose_level(0, "Running as daemon: reading %s time from %s, writing to ntp 127.127.28.%u\n",((global_options.is_duck_reader_timestamp)?"duck":"irig-b"), dagname, global_options.ntp_shm_unit);
	temp = daemon(0,0);
	dagutil_daemon(LOG_PID | LOG_CONS, LOG_DAEMON);

	dagutil_verbose_level(0,"start: reading %s time from %s, writing to ntp 127.127.28.%u\n",((global_options.is_duck_reader_timestamp)?"duck":"irig-b"), dagname, global_options.ntp_shm_unit);
	while(continue_run)
	{
		/* sleep 1 second TODO make this time customizable */
		sleep(1);//global_options.interval * 1000 );

		if ( global_options.is_duck_reader_timestamp)
		{
			if ( get_time_from_duck_reader(&dag, &ts_refclk ))
			{
				if (run_status) 
				{
					dagutil_error("duck_timestamp read from %s failed \n",dagname);
				}
				run_status = 0; /* indicate error */
				continue;
			}
			/* get system time */
			(void)gettimeofday(&now, NULL);
			TIMEVAL_TO_TIMESPEC(&now, &ts_local);
		}
		else
		{
			status = irigb_status(dag.iom, dag.irigb_base);
			if(!(status.auto_detect.irigb || status.irigb))
			{
				if (run_status) 
				{
					dagutil_error("no irig-b signal detected on %s\n",dagname);
					run_status = 0;/* indicate error */
				}
				continue;
			}

			if(get_irigb_seconds(&dag, &t_irig))
			{
				if (run_status) 
				{
					dagutil_error("irig-b time decode failed on %s\n",dagname);
					run_status = 0;/* indicate error */
				}
				continue;
			}
			ts_refclk.tv_sec = t_irig + global_options.delta;
			ts_refclk.tv_nsec = 0;					
			/* get interrupt system time */
			if(ioctl(dag.dagfd, DAGIOCIRQTIME, &irq_time)) 
			{
				if (run_status)
				{
					dagutil_error("DAGIOCIRQTIME failed on %s\n",dagname);
					run_status = 0; /* indicate error */
				}
				continue;
			}
			/* FIXME may be make sense to check if it same as the previous one */
			ts_local.tv_sec = irq_time.sec;
			ts_local.tv_nsec = irq_time.nsec;
		}

		update_shm(ntp_shm, &ts_local, &ts_refclk);
		if(!run_status)
		{
			dagutil_verbose_level(0,"recovered previous error on %s. successfully updated 127.127.28.%u\n",dagname, global_options.ntp_shm_unit);
			run_status = 1; /* running successfully*/
		}
	}
	/* close shm unit */
	detach_ntp_shm(ntp_shm);
	/* closes the dag card */
	close_irig(&dag);
	dagutil_verbose_level(0,"stop: write to ntp 127.127.28.%u from %s\n", global_options.ntp_shm_unit, dagname);
	return EXIT_SUCCESS;
#else 
	dagutil_error("Daemon mode is not supported\n");
	return EXIT_FAILURE;
#endif
}

int
main(int argc, const char* const *argv)
{
	int retval;

	dagutil_set_progname("dag_irigb");

	if ( (retval = parse_commandline(argc, (char **) argv)) != EXIT_SUCCESS)
	{
		return retval;
	}
	
	if ( (retval = validate_global_params()) != EXIT_SUCCESS )
	{
		return retval;
    }
	 /* connect the signal handler */
    dagutil_set_signal_handler(anysig);

	if (global_options.daemon)
	{
		return dagirigb_daemon_main();
	}
	else
	{
		return dagirigb_main(argc, (char **) argv);
	}
	return 0;
}


static ntp_shm_segment_t* get_shm_time(int unit)
{
#if defined(__FreeBSD__) || defined(__linux__) || (defined(__SVR4) && defined(__sun))
	int shmid;
	unsigned int perms;
	/* set the SHM perms */
	if (unit < 2) {
		/* root-only access for unit 0 and 1 */
		perms = 0600;
	} else {
		/* world access for unit 2 and 3*/
		perms = 0666;
	}

	shmid = shmget((key_t) (NTPD_BASE + unit),
		       sizeof(ntp_shm_segment_t), (int)(IPC_CREAT | perms));
	if (shmid == -1)
	{
		dagutil_error("NTPD shmget for unit %d (perms: %o) failed: %s\n",
							unit, (int)perms,  strerror(errno));
		return NULL;
	}
	else
	{
		ntp_shm_segment_t *p = (ntp_shm_segment_t *)shmat(shmid, 0, 0);

		if ((int)(long)p == -1)
		{
			dagutil_error("NTPD shmat for unit %d failed: %s\n", unit, strerror(errno));
			return NULL;
		}

		dagutil_verbose_level(1, "NTPD shmat(%d,0,0) succeeded for unit %d\n",
			      shmid, unit);

		return p;
	}
#else
	return NULL;
#endif
}


/* change when usec resolution will be available */
static int update_shm(ntp_shm_segment_t *shm_seg, struct timespec *ts_local, struct timespec *ts_refclk)
{
	int rv = 0;

	shm_seg->valid = 0;
	shm_seg->count++;

	/* timestamp from refclock */
	shm_seg->clockTimeStampSec = (time_t) ts_refclk->tv_sec;
	shm_seg->clockTimeStampUSec = (int)(ts_refclk->tv_nsec / 1000); /* FIXME : usec*/

	/* corresponding timestamp from local system clock */
	shm_seg->receiveTimeStampSec = (time_t) ts_local->tv_sec;
	shm_seg->receiveTimeStampUSec = (int)(ts_local->tv_nsec / 1000); /* FIXME : usec*/

	shm_seg->count++;
	shm_seg->valid = 1;

	return rv;
}

static int open_irig(char *dagname, dag_unit_t *irig_dev)
{
	/* If duck time to be used , open duck reader, else initialize irig-b parameters*/
	if (!global_options.is_duck_reader_timestamp)
	{
		int dagfd;
		dag_reg_t result[DAG_REG_MAX_ENTRIES];
		unsigned regn;
		dag_reg_t *regs;
		uint8_t *iom;
	
		dagfd = dag_open(dagname);
		if (dagfd < 0)
		{
			dagutil_error("dag_open %s: %s\n", dagname, strerror(errno));
			return -1;
		}
	
		iom = dag_iom(dagfd);
		if (iom == NULL)
		{
			dagutil_error("dag_irigb main(): dag_iom() failed: %s\n", strerror(errno));
			return -1;
		}
	
		regs = dag_regs(dagfd);
		regn = 0;
		if ((dag_reg_table_find(regs, 0, DAG_REG_IRIGB, result, &regn)) || (!regn))
		{
			dagutil_error("Dag device does not support IRIG-B functions\n");
			return -1;
		}
	
		irig_dev->irigb_base = DAG_REG_ADDR(*result);
		irig_dev->dagfd = dagfd;
		irig_dev->iom = iom;
	}
	else //(global_options.is_duck_reader_timestamp)
	{
		/* initialize CS API parameter if duck reader to be used */
		dag_component_t root_component = NULL;
		dag_component_t duck_component = NULL;

		irig_dev->ref_card = dag_config_init(dagname);
		if ( NULL == irig_dev->ref_card)
		{
			dagutil_error("dag_config_init failed\n");
			return -1;
		}
		root_component = dag_config_get_root_component(irig_dev->ref_card);
		duck_component = dag_component_get_subcomponent(root_component, kComponentDUCK, 0);
		if ( NULL == duck_component)
		{
			dagutil_error("No duck component\n");
			return -1;
		}
		irig_dev->attr_duck_reader = dag_component_get_attribute_uuid(duck_component, kUint64AttributeDuckTimestamp);
		if ( kNullAttributeUuid == irig_dev->attr_duck_reader)
		{
			dagutil_error("No duck timestamp attribute\n");
			return -1;
		}
	}
	return 0;
}

static int close_irig(dag_unit_t *irig_dev)
{
	dag_close(irig_dev->dagfd);
	if ( irig_dev->ref_card)
	{
		irig_dev->attr_duck_reader = kNullAttributeUuid;
		dag_config_dispose(irig_dev->ref_card);
		irig_dev->ref_card = NULL;
	}
	return 0;
}

int  get_time_from_duck_reader(dag_unit_t *irig_dev, struct timespec *in_time )
{
	if ( irig_dev->attr_duck_reader)
	{
		uint64_t value_64_read = 0;
		value_64_read = dag_config_get_uint64_attribute(irig_dev->ref_card, irig_dev->attr_duck_reader);
		in_time->tv_sec = value_64_read >> 32;
		value_64_read = ((value_64_read & 0xffffffffULL) * 1000 * 1000 * 1000);
		value_64_read += (value_64_read & 0x80000000ULL) << 1;        /* rounding */
		in_time->tv_nsec = value_64_read >> 32;
		if ( in_time->tv_nsec >= 1000000000)
		{
			in_time->tv_nsec -= 1000000000;
			in_time->tv_sec  += 1;
		}
		return 0;
	}
	return -1;
}
