#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>

/*
 * Post Office Specification PR 479C requires a pulse length of
 * 200-500 ms.
 *
 * <http://gpoclocksystems.byethost22.com/pages/pr479b.html>
 */
static long const pulsewidth = 250000000; /* nanoseconds */

/*
 * Maximum error to correct by advancing the clock rather than
 * stopping it, in seconds.  The point where it's faster to stop the
 * clock than to advance it is around 11:35 forwards (0:25 backwards),
 * but this is set a little lower so that setting the clock back for
 * the end of summer time is done by stopping, in accordance with Post
 * Office Engineering Instruction B 5301.
 *
 * <http://gpoclocksystems.byethost22.com/pages/b5301.html>
 */
static int const maxadvance = (10 * 3600) + (50 * 60);

static struct tm displayed;

static int statefd = -1;

static sigset_t signals_to_block;

/*
 * The fallback timer protects us from occasions when CLOCK_REALTIME
 * goes backwards so our nice absolute clock_nanosleep() end up
 * sleeping far too long.
 */
static timer_t fallback_timer;

static void
dummy_out(bool state)
{

	/* Dummy implementation */
	if (state) {
		printf("ker...");
		fflush(stdout);
	} else {
		printf("chunk!\n");
		fflush(stdout);
	}
}
static void
record_tick()
{
	ssize_t ret;
	char buf[10];

	displayed.tm_sec += 30;
	while (displayed.tm_sec >= 60) {
		displayed.tm_min++;
		displayed.tm_sec -= 60;
	}
	while (displayed.tm_min >= 60) {
		displayed.tm_hour++;
		displayed.tm_min -= 60;
	}
	displayed.tm_hour %= 12;
	sprintf(buf, "%2d:%02d:%02d\n",
		displayed.tm_hour, displayed.tm_min, displayed.tm_sec);
	if (statefd != -1) {
		ret = pwrite(statefd, buf, 9, 0);
		if (ret == -1)
			err(1, "write to state file");
		if (ret != 9)
			errx(1, "short write to state file");
	}
}

static void
pulse()
{
	struct timespec const ts = { 0, pulsewidth };
	sigset_t saved_mask;

	if (sigprocmask(SIG_BLOCK, &signals_to_block, &saved_mask) != 0)
		err(1, "sigprocmask(block)");
	dummy_out(true);
	record_tick();
	errno = clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL);
	if (errno != 0)
		err(1, "clock_nanosleep");
	dummy_out(false);
	if (sigprocmask(SIG_SETMASK, &saved_mask, NULL) != 0)
		err(1, "sigprocmask(restore)");
}

static void
init_statestring(char const *str)
{
	if (sscanf(str, "%d:%d:%d", &displayed.tm_hour,
		   &displayed.tm_min, &displayed.tm_sec) != 3)
		errx(1, "usage");
}

static void
init_statefile(char const *statefilename)
{
	char buf[10];
	ssize_t len;

	statefd = open(optarg, O_RDWR | O_CREAT | O_DSYNC, 0666);
	if (statefd == -1)
		err(1, "%s", optarg);
	len = pread(statefd, buf, 9, 0);
	if (len == -1)
		err(1, "read %s", optarg);
	if (len > 0) {
		buf[len] = '\0';
		init_statestring(buf);
	}
}

static void
alrm_handler(int signo)
{

	/* Do nothing.  The mere presence of this handler is enough. */
}

static void
init(int argc, char **argv)
{
	struct timespec ts;
	struct sigevent sev;
	struct sigaction sa;
	char *statefile = NULL, *statestr = NULL;
	int opt;

	tzset();
	if (sigemptyset(&signals_to_block) != 0 ||
	    sigaddset(&signals_to_block, SIGINT) != 0 ||
	    sigaddset(&signals_to_block, SIGTERM) != 0)
		err(1, "sigset");
	sev.sigev_notify = SIGEV_SIGNAL;
	sev.sigev_signo = SIGALRM;
	if (timer_create(CLOCK_MONOTONIC, &sev, &fallback_timer) != 0)
		err(1, "timer_create");
	sa.sa_handler = alrm_handler;
	if (sigemptyset(&sa.sa_mask) != 0)
		err(1, "sigemptyset");
	sa.sa_flags = 0;
	if (sigaction(SIGALRM, &sa, NULL) != 0)
		err(1, "sigaction");
	if (clock_gettime(CLOCK_REALTIME, &ts) != 0)
		err(1, "clock_gettime");
	if (localtime_r(&ts.tv_sec, &displayed) == NULL)
		err(1, "localtime_r");
	displayed.tm_sec = (displayed.tm_sec >= 30) ? 30 : 0;
	while ((opt = getopt(argc, argv, "f:s:")) != -1) {
		switch (opt) {
		case 'f':
			statefile = optarg;
			break;
		case 's':
			statestr = optarg;
			break;
		}
	}
	if (statefile != NULL)
		init_statefile(statefile);
	/* Allow -s to override state read from file. */
	if (statestr != NULL)
		init_statestring(statestr);
}

enum need_adjust { STOP, TICK, ADVANCE };

static enum need_adjust
need_adjust(struct tm const *now)
{
	long diff;

	diff = now->tm_sec - displayed.tm_sec +
		60 * (now->tm_min - displayed.tm_min) +
		3600 * (now->tm_hour - displayed.tm_hour);
	if (diff < 0) diff += 3600 * 12;
	diff %= 3600 * 12;
	printf("diff = %ld\n", diff);
	if (diff < 30 || diff >= maxadvance) return STOP;
	if (diff < 60) return TICK;
	if (diff < (10 * 3600)) return ADVANCE;
}

static void
ts_advance(struct timespec *tp, long nsec)
{

	tp->tv_nsec += nsec;
	if (tp->tv_nsec >= 1000000000) tp->tv_sec++;
}

static void
run()
{
	struct timespec ts;
	const struct itimerspec its = {
		.it_value    = { .tv_sec = 35, .tv_nsec = 0},
		.it_interval = { .tv_sec = 1,  .tv_nsec = 0},
	};
	const struct itimerspec its_disarm = {
		.it_value =  { .tv_sec = 0, .tv_nsec = 0 },
	};
	struct tm tm;
	int tick;

	while (true) {
		if (clock_gettime(CLOCK_REALTIME, &ts) != 0)
			err(1, "clock_gettime");
		ts_advance(&ts, pulsewidth);
		if (localtime_r(&ts.tv_sec, &tm) == NULL)
			err(1, "localtime_r");
		tick = 30;
		switch (need_adjust(&tm)) {
		case ADVANCE:
			tick = 1;
			/* FALLTHROUGH */
		case TICK:
			pulse();
			/* FALLTHROUGH */
		case STOP:
			/* nothing */ ;
		}
		/* Round down to the last tick. */
		ts.tv_nsec = 0;
		ts.tv_sec -= tm.tm_sec % tick;
		/* Choose when next tick will be. */
		ts.tv_nsec = 1000000000 - pulsewidth;
		ts.tv_sec += tick - 1;
		if (timer_settime(fallback_timer, 0, &its, NULL) != 0)
			err(1, "timer_settime (arm)");
		errno =	clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME,
					&ts, NULL);
		if (errno != 0 && errno != EINTR)
			err(1, "clock_nanosleep");
		if (timer_settime(fallback_timer, 0, &its_disarm, NULL) != 0)
			err(1, "timer_settime (disarm)");
	}
}

int
main(int argc, char **argv)
{

	init(argc, argv);
	run();
	return 0;
}
