/*
 * Written by Tzafrir Cohen <tzafrir.cohen@xorcom.com>
 * Copyright (C) 2006, Xorcom
 *
 * Derived from zttest.c
 *
 * All rights reserved.
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */
#include <sys/timex.h>
#include <linux/param.h>
#include <syslog.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/signal.h>
#include <math.h>

static const char *rcsid = "$Id: adj_clock.c 1557 2006-11-06 18:12:16Z tzafrir $";

#define FREQ_PER_TICK		(64*1024)
#define USEC_PER_ZAP_TICK	1000	/* Zaptel tick in usec */
#define	USER_HZ			100
#define	SYNC_TOLERANCE		100
#define DEFAULT_INTERVAL	 60

static const char	*program;
static int		synced_time; /* time (seconds) we are so far synced */
static int 		verbose = 0;
static int		sync_tolerance = 100;

void usage() {
	fprintf(stderr, "%s: synchronize system clock from zaptel clock.\n"
		"Version: %s\n"
		"\n"
		"Usage: %s [ -c COUNT ] [-i INTERVAL] [ -t PERIOD ] [-v]\n"
		"       %s -h\n"
		"\n"
		"Options:\n"
		"     -c   only run COUNT cycles of synchronization (default: forever).\n"
		"     -i   set cycle Interval to INTERVAL (in seconds. Default: %d).\n"
		"     -t   sync tolerance to PERIOD (in usec. Default: %d). For debugging.\n"
		"     -v   Be more Verbose\n"
		"     -h   Print this Help text and exit.\n"
		"", program, rcsid, program, program, DEFAULT_INTERVAL, 
		sync_tolerance);
}

/* based on zttest.c */

/*
 * Print a message to the log according to the value of synced_time
 *
 * All parameters are for the "Not Synced" warning message. 
 * TODO: Find a better way.
 */
void status_message(void) {
	if (synced_time == 0)
		syslog(LOG_NOTICE, "Not Synced.\n");
	else
		syslog(LOG_INFO, "Good Sync for %d seconds\n", synced_time);

	return;
}

/*
 * interval: period, in seconds
 * diff_usec: Current offset, in micro-seconds.
 */
int clock_sync(int interval, int diff_usec)
{
	struct timex	cur_time  = {.modes=0}; /* just query */
	int		diff_tick;	/* Difference in adjtimex ticks */
	int		req_tick;	/* Required adjtimex tick value */
	long long	diff_freq;
	long long	req_freq;
	int		usec_left;	/* Usec adjusted by frequency	*/
	int		ret;

	ret = adjtimex(&cur_time);

	diff_tick = diff_usec / (interval * USER_HZ);
	usec_left = diff_usec - diff_tick * (interval * USER_HZ);
	diff_freq = usec_left * FREQ_PER_TICK / interval;

	req_tick = cur_time.tick - diff_tick;
	req_freq = cur_time.freq - diff_freq;

	if(!diff_tick && abs(usec_left) < sync_tolerance)
		synced_time += interval;
	else 
		synced_time = 0;
	status_message();
	if (verbose || !synced_time)
		syslog(LOG_INFO, "interval: %d, diff_tick: %d, diff_usec: %d, usec_left: %d.\n",
			interval, diff_tick, diff_usec, usec_left);

	/* set the clock rate */
	cur_time.freq = req_freq;
	cur_time.tick = req_tick;
	cur_time.modes = ADJ_TICK | ADJ_FREQUENCY;
	ret = adjtimex(&cur_time);
	if (ret < 0)
		syslog(LOG_ERR, "Adjtimex failed to set frequency. New frequency: %lld. Error: %d (%s)\n",
				req_freq, errno, strerror(errno));
	return 0;
}

static int pass = 0;

void hup_handler(int sig)
{
	if (verbose > 0) {
		syslog(LOG_INFO, "--- Results after %d passes ---\n", pass);
		status_message();
	}
	closelog();
	exit(0);
}

void sigusr1_handler(int sig) {
	verbose++;
	syslog(LOG_INFO, "Increased verbosity to %d (increase: SIGUSR1, reset: SIGUSR2).\n", verbose);
	status_message();
}

void sigusr2_handler(int sig) {
	verbose = 0;
	syslog(LOG_INFO, "Set verbosity to 0 (increase: SIGUSR1, reset: SIGUSR2).\n");
}

void sync_cycle(int fd, int interval)
{
	char		buf[1024]; /* TODO: why 1024 and not 1000? */
	struct timeval	start;
	struct timeval	end;
	long long	usec;
	int		count = 0;
	int		i;
	int		res;

	gettimeofday(&start, NULL);
	for (i=0; i < interval*8; i++) {
		res = read(fd, buf, sizeof(buf));
		if (res < 0) {
			syslog(LOG_ERR, "Failed to read from pseudo interface: %s\n", strerror(errno));
			exit(1);
		}
		count += res/8;
	}
	gettimeofday(&end, NULL);

	/* time difference in microseconds: */
	usec = (end.tv_sec - start.tv_sec) * 1000000;
	usec += end.tv_usec - start.tv_usec;

	clock_sync(interval, usec - (count * USEC_PER_ZAP_TICK));

	pass++;
}

int main(int argc, char *argv[])
{
	int fd;
	int opt;
	int interval = DEFAULT_INTERVAL;
	int cycles_to_run = 0;
	int i;

	program = argv[0];
	while((opt = getopt(argc, argv, "c:hi:t:v")) != -1) {
		switch (opt) {
			case 'h': usage(); exit(0); break;
			case 'c': cycles_to_run = atoi(optarg); break;
			case 'i': interval = atoi(optarg); break; /* number of seconds */
			case 't': sync_tolerance = atoi(optarg); break; /* usec */
			case 'v': verbose++; break;
			default: 
				  fprintf(stderr, "%s: Unknown option: %c. Aborting\n", argv[0], opt);
				  usage();
				  exit(1);
		}
	}

	openlog(program, LOG_PERROR | LOG_PID, LOG_DAEMON);
	fd = open("/dev/zap/pseudo", O_RDWR);
	if (fd < 0) {
		fprintf(stderr, "Unable to open zap interface: %s\n", strerror(errno));
		exit(1);
	}
	if (verbose >=1)
		syslog(LOG_INFO, "Opened pseudo zap interface, measuring accuracy...\n");

	signal(SIGHUP, hup_handler);
	signal(SIGINT, hup_handler);
	signal(SIGUSR1, sigusr1_handler);
	signal(SIGUSR2, sigusr2_handler);
	
	if (cycles_to_run > 0) {
		for(i=0; i<cycles_to_run; i++) {
			sync_cycle(fd, interval);
		}
		hup_handler(0); /* exit */;
	}
	
	/* else (option -c not provided): run forever. */
	for(;;) {
		sync_cycle(fd, interval);
	}
}
