/**
 * @file pthread-timedwait.c
 * @brief Demonstrates the effects on pthread_timedwait upon system wall clock
 * change
 */
#define _POSIX_C_SOURCE 199309L
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <pthread.h>

#define ARG0 "pthread_timedwait"


struct {
	// desired sleep time
	struct timespec t;
} opts;

/**
 * @brief Raise nanosecond fraction part
 * @details "05" -> 50000000, "123" -> 123000000, "005" -> 5000000
 * @note used to preserve precision
 *
 * @param str fraction part, excluding the leading decimal point
 * @param len length of \param str
 * @return long the fraction part raised to nanosecond scale
 */
long raise_nsec_frac (const char *str, const size_t len) {
	char m[] = { '0', '0', '0', '0', '0', '0', '0', '0', '0', 0 };
	char *p;
	long ret = -1;

	memcpy(m, str, len > 9 ? 9 : len);

	for (p = m; *p != 0 && *p == '0'; p += 1);

	if (*p == 0 && m[0] == '0') {
		return 0;
	}
	if (sscanf(p, "%ld", &ret) != 1) {
		return -1;
	}

	return ret;
}

/**
 * @brief Parse timespec from string, preserving precision
 *
 * @param str string to parse
 * @param ts_out (optional) pointer to timespec struct
 * @return true if parsed successfully and \param ts_out is set if non-null
 * @return false otherwise. errno set to EINVAL if \param str is NULL. errno set
 * to EBADMSG on format error
 */
bool parse_ts (const char *str, struct timespec *ts_out) {
	long long sec = 0;
	long nsec = 0;
	int fr;
	const char *frac, *ipart;

	if (str == NULL) {
		errno = EINVAL;
		return false;
	}

	if (str[0] == '.' || str[0] == ',') {
		ipart = NULL;
		frac = str + 1;
	}
	else {
		ipart = str;
		frac = strchr(str, '.');
		if (frac == NULL) {
			frac = strchr(str, ',');
		}
	}

	if (ipart != NULL) {
		fr = sscanf(ipart, "%lld", &sec);
		if (fr < 1) {
			errno = EBADMSG;
			return false;
		}
	}
	if (frac != NULL) {
		frac += 1;
		nsec = raise_nsec_frac(frac, strlen(frac));
		if (nsec < 0) {
			errno = EBADMSG;
			return false;
		}
	}

	if (ts_out != NULL) {
		ts_out->tv_sec = sec;
		ts_out->tv_nsec = nsec;
	}

	return true;
}

bool parse_args (const int argc, const char **argv) {
	if (argc <= 1) {
		fprintf(stderr, ARG0" <TIME>\n");
		return false;
	}

	if (!parse_ts(argv[1], &opts.t)) {
		perror(ARG0);
		return false;
	}

	return true;
}

int main (const int argc, const char **argv) {
	pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
	pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
	struct timespec ts[2];
	struct timespec tp_deadline;

	if (!parse_args(argc, argv)) {
		return 1;
	}

	clock_gettime(CLOCK_REALTIME, &tp_deadline);
	tp_deadline.tv_nsec += opts.t.tv_nsec;
	tp_deadline.tv_sec += opts.t.tv_sec + (tp_deadline.tv_nsec / 1000000000);
	tp_deadline.tv_nsec %= 1000000000;

	clock_gettime(CLOCK_MONOTONIC, ts);
	pthread_mutex_lock(&mtx);
	pthread_cond_timedwait(&cond, &mtx, &tp_deadline);
	clock_gettime(CLOCK_MONOTONIC, ts + 1);

	ts[1].tv_nsec -= ts[0].tv_nsec;
	ts[1].tv_sec -= ts[0].tv_sec;
	if (ts[1].tv_nsec < 0) {
		ts[1].tv_sec -= 1;
		ts[1].tv_nsec += 1000000000;
	}

	printf("%lld.%03ld\n", (long long)ts[1].tv_sec, ts[1].tv_nsec / 1000000);

	return 0;
}