/* Host-side math harness for sys/kern/subr_fattime.c (verbatim function bodies,
 * types adapted: time_t = int64_t). Verifies:
 *  A) read-path round-trip across the FULL valid FAT date range (find DAY*day wrap)
 *  B) write-path with post-2107 / huge tv_sec (year-field + t2 truncation)
 *  C) read-path month 0/13-15, hour>23, min>59 fields (garbage-but-bounded?)
 *  D) day=0 (known DF-0199, confirm only)
 */
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <time.h>

typedef int64_t time_t2;
#define DAY	(24 * 60 * 60)
#define YEAR	365
#define LYC	(4 * YEAR + 1)
#define T1980	(10 * 365 + 2)

#define JAN	31
#define FEB	(JAN + 28)
#define MAR	(FEB + 31)
#define APR	(MAR + 30)
#define MAY	(APR + 31)
#define JUN	(MAY + 30)
#define JUL	(JUN + 31)
#define AUG	(JUL + 31)
#define SEP	(AUG + 30)
#define OCT	(SEP + 31)
#define NOV	(OCT + 30)
#define DEC	(NOV + 31)

#define ENC(y,m)	(((y) << 9) | ((m) << 5))

static const struct { uint16_t days; uint16_t coded; } mtab[48] = {
	{   0 + 0 * YEAR,     ENC(0, 1)  },
	{ JAN + 0 * YEAR,     ENC(0, 2)  }, { FEB + 0 * YEAR + 1, ENC(0, 3)  },
	{ MAR + 0 * YEAR + 1, ENC(0, 4)  }, { APR + 0 * YEAR + 1, ENC(0, 5)  },
	{ MAY + 0 * YEAR + 1, ENC(0, 6)  }, { JUN + 0 * YEAR + 1, ENC(0, 7)  },
	{ JUL + 0 * YEAR + 1, ENC(0, 8)  }, { AUG + 0 * YEAR + 1, ENC(0, 9)  },
	{ SEP + 0 * YEAR + 1, ENC(0, 10) }, { OCT + 0 * YEAR + 1, ENC(0, 11) },
	{ NOV + 0 * YEAR + 1, ENC(0, 12) }, { DEC + 0 * YEAR + 1, ENC(1, 1)  },
	{ JAN + 1 * YEAR + 1, ENC(1, 2)  }, { FEB + 1 * YEAR + 1, ENC(1, 3)  },
	{ MAR + 1 * YEAR + 1, ENC(1, 4)  }, { APR + 1 * YEAR + 1, ENC(1, 5)  },
	{ MAY + 1 * YEAR + 1, ENC(1, 6)  }, { JUN + 1 * YEAR + 1, ENC(1, 7)  },
	{ JUL + 1 * YEAR + 1, ENC(1, 8)  }, { AUG + 1 * YEAR + 1, ENC(1, 9)  },
	{ SEP + 1 * YEAR + 1, ENC(1, 10) }, { OCT + 1 * YEAR + 1, ENC(1, 11) },
	{ NOV + 1 * YEAR + 1, ENC(1, 12) }, { DEC + 1 * YEAR + 1, ENC(2, 1)  },
	{ JAN + 2 * YEAR + 1, ENC(2, 2)  }, { FEB + 2 * YEAR + 1, ENC(2, 3)  },
	{ MAR + 2 * YEAR + 1, ENC(2, 4)  }, { APR + 2 * YEAR + 1, ENC(2, 5)  },
	{ MAY + 2 * YEAR + 1, ENC(2, 6)  }, { JUN + 2 * YEAR + 1, ENC(2, 7)  },
	{ JUL + 2 * YEAR + 1, ENC(2, 8)  }, { AUG + 2 * YEAR + 1, ENC(2, 9)  },
	{ SEP + 2 * YEAR + 1, ENC(2, 10) }, { OCT + 2 * YEAR + 1, ENC(2, 11) },
	{ NOV + 2 * YEAR + 1, ENC(2, 12) }, { DEC + 2 * YEAR + 1, ENC(3, 1)  },
	{ JAN + 3 * YEAR + 1, ENC(3, 2)  }, { FEB + 3 * YEAR + 1, ENC(3, 3)  },
	{ MAR + 3 * YEAR + 1, ENC(3, 4)  }, { APR + 3 * YEAR + 1, ENC(3, 5)  },
	{ MAY + 3 * YEAR + 1, ENC(3, 6)  }, { JUN + 3 * YEAR + 1, ENC(3, 7)  },
	{ JUL + 3 * YEAR + 1, ENC(3, 8)  }, { AUG + 3 * YEAR + 1, ENC(3, 9)  },
	{ SEP + 3 * YEAR + 1, ENC(3, 10) }, { OCT + 3 * YEAR + 1, ENC(3, 11) },
	{ NOV + 3 * YEAR + 1, ENC(3, 12) }
};

#define DCOD(m, y, l)	((m) + YEAR * (y) + (l))
static const uint16_t daytab[64] = {
	0, 		 DCOD(  0, 0, 0), DCOD(JAN, 0, 0), DCOD(FEB, 0, 1),
	DCOD(MAR, 0, 1), DCOD(APR, 0, 1), DCOD(MAY, 0, 1), DCOD(JUN, 0, 1),
	DCOD(JUL, 0, 1), DCOD(AUG, 0, 1), DCOD(SEP, 0, 1), DCOD(OCT, 0, 1),
	DCOD(NOV, 0, 1), DCOD(DEC, 0, 1), 0,               0,
	0, 		 DCOD(  0, 1, 1), DCOD(JAN, 1, 1), DCOD(FEB, 1, 1),
	DCOD(MAR, 1, 1), DCOD(APR, 1, 1), DCOD(MAY, 1, 1), DCOD(JUN, 1, 1),
	DCOD(JUL, 1, 1), DCOD(AUG, 1, 1), DCOD(SEP, 1, 1), DCOD(OCT, 1, 1),
	DCOD(NOV, 1, 1), DCOD(DEC, 1, 1), 0,               0,
	0,		 DCOD(  0, 2, 1), DCOD(JAN, 2, 1), DCOD(FEB, 2, 1),
	DCOD(MAR, 2, 1), DCOD(APR, 2, 1), DCOD(MAY, 2, 1), DCOD(JUN, 2, 1),
	DCOD(JUL, 2, 1), DCOD(AUG, 2, 1), DCOD(SEP, 2, 1), DCOD(OCT, 2, 1),
	DCOD(NOV, 2, 1), DCOD(DEC, 2, 1), 0,               0,
	0,		 DCOD(  0, 3, 1), DCOD(JAN, 3, 1), DCOD(FEB, 3, 1),
	DCOD(MAR, 3, 1), DCOD(APR, 3, 1), DCOD(MAY, 3, 1), DCOD(JUN, 3, 1),
	DCOD(JUL, 3, 1), DCOD(AUG, 3, 1), DCOD(SEP, 3, 1), DCOD(OCT, 3, 1),
	DCOD(NOV, 3, 1), DCOD(DEC, 3, 1), 0,               0
};

static void
timespec2fattime(const struct timespec *tsp, int utc, uint16_t *ddp,
    uint16_t *dtp, uint8_t *dhp)
{
	time_t2 t1;
	unsigned t2, l, m;

	t1 = tsp->tv_sec;
	if (!utc)
		t1 -= 0;

	if (dhp != NULL)
		*dhp = (tsp->tv_sec & 1) * 100 + tsp->tv_nsec / 10000000;
	if (dtp != NULL) {
		*dtp = (t1 / 2) % 30;
		*dtp |= ((t1 / 60) % 60) << 5;
		*dtp |= ((t1 / 3600) % 24) << 11;
	}
	if (ddp != NULL) {
		t2 = t1 / DAY;
		if (t2 < T1980) {
			*ddp = 0x0021;
		} else {
			t2 -= T1980;
			if (t2 >= ((2100 - 1980) / 4 * LYC + FEB))
				t2++;
			l = t2 / LYC;
			*ddp = (l * 4) << 9;
			t2 -= l * LYC;
			m = t2 / 32;
			while (m < 47 && mtab[m + 1].days <= t2)
				m++;
			*ddp += mtab[m].coded;
			t2 -= mtab[m].days - 1;
			*ddp |= t2;
		}
	}
}

static void
fattime2timespec(unsigned dd, unsigned dt, unsigned dh, int utc,
    struct timespec *tsp)
{
	unsigned day;

	tsp->tv_sec = (dt & 0x1f) << 1;
	tsp->tv_sec += ((dt & 0x7e0) >> 5) * 60;
	tsp->tv_sec += ((dt & 0xf800) >> 11) * 3600;
	tsp->tv_sec += dh / 100;
	tsp->tv_nsec = (dh % 100) * 10000000;

	day = (dd & 0x1f) - 1;
	day += LYC * ((dd >> 11) & 0x1f);
	day += daytab[(dd >> 5) & 0x3f];
	if (day >= ((2100 - 1980) / 4 * LYC + FEB))
		day--;
	day += T1980;
	tsp->tv_sec += DAY * day;
	if (!utc)
		tsp->tv_sec += 0;
}

static void show(const char *tag, int64_t sec, long nsec)
{
	char b[64];
	time_t t = (time_t)sec;
	struct tm tm;
	gmtime_r(&t, &tm);
	strftime(b, sizeof b, "%Y-%m-%d %H:%M:%S", &tm);
	printf("%-28s %14lld.%09ld  %s UTC\n", tag, (long long)sec, nsec, b);
}

int main(void)
{
	struct timespec ts;
	uint16_t d, t; uint8_t p;

	/* ---- A: full-range round trip ---- */
	printf("=== A: round-trip every day 1980-01-01 .. 2107-12-31 ===\n");
	int64_t first_bad = 0, bad_count = 0;
	for (int64_t sec = 315532800LL; sec <= 4354819199LL; sec += 86400) {
		ts.tv_sec = sec; ts.tv_nsec = 0;
		d = t = p = 0;
		timespec2fattime(&ts, 0, &d, &t, &p);
		ts.tv_sec = 0; ts.tv_nsec = 0;
		fattime2timespec(d, t, p, 0, &ts);
		if (ts.tv_sec != sec) {
			if (!first_bad) first_bad = sec;
			bad_count++;
		}
	}
	show("first round-trip failure", first_bad, 0);
	printf("total failing days (of 46699): %lld\n", (long long)bad_count);
	/* demonstrate a specific late date */
	int64_t s2107 = 4354761599LL; /* 2107-12-31 23:59:59 minus odd sec */
	ts.tv_sec = 4354750800LL; ts.tv_nsec = 0; /* 2107-12-31 ~20:20 */
	d = t = p = 0;
	timespec2fattime(&ts, 0, &d, &t, &p);
	printf("encoded dd=%04x t=%04x (y=%d m=%d d=%d)\n", d, t,
	    ((d >> 9) & 0x7f) + 1980, (d >> 5) & 0x0f, d & 0x1f);
	ts.tv_sec = 0; ts.tv_nsec = 0;
	fattime2timespec(d, t, p, 0, &ts);
	show("2107-12-31 decodes to", ts.tv_sec, ts.tv_nsec);

	/* ---- B: write path beyond 2107 ---- */
	printf("=== B: write path, post-2107 tv_sec (utimes, no upper bound) ===\n");
	int64_t inputs[] = {
	    4354819200LL,	/* 2108-01-01 00:00:00 */
	    68718441600LL,	/* 3917-06-06ish? no: 2200-11-... compute below */
	    103072070400LL,	/* ~3566 */
	    0x7fffffffffffLL,	/* huge */
	    4611686018427387904LL /* 2^62 */
	};
	for (unsigned i = 0; i < sizeof(inputs)/sizeof(inputs[0]); i++) {
		ts.tv_sec = inputs[i]; ts.tv_nsec = 0;
		d = t = p = 0;
		timespec2fattime(&ts, 0, &d, &t, &p);
		ts.tv_sec = 0; ts.tv_nsec = 0;
		fattime2timespec(d, t, p, 0, &ts);
		printf("in ");
		show("", inputs[i], 0);
		printf("    -> dd=%04x dt=%04x -> ", d, t);
		show("", ts.tv_sec, ts.tv_nsec);
	}

	/* ---- C: crafted read-path fields ---- */
	printf("=== C: crafted dd/dt fields (month 0/13-15, hh=31, mm=63) ===\n");
	unsigned cases[][3] = {
	    { (127u<<9)|(12u<<5)|31u, (23u<<11)|(59u<<5)|29u, 0 }, /* worst valid */
	    { (127u<<9)|(0u<<5)|15u,  0, 0 },  /* month 0 */
	    { (127u<<9)|(13u<<5)|15u, 0, 0 },  /* month 13 */
	    { (127u<<9)|(15u<<5)|31u, 0, 0 },  /* month 15 */
	    { (0u<<9)|(1u<<5)|1u, (31u<<11)|(63u<<5)|31u, 255 }, /* hh31 mm63 ss62 dh255 */
	};
	for (unsigned i = 0; i < sizeof(cases)/sizeof(cases[0]); i++) {
		ts.tv_sec = 0; ts.tv_nsec = 0;
		fattime2timespec(cases[i][0], cases[i][1], cases[i][2], 0, &ts);
		printf("dd=%04x dt=%04x dh=%02x -> ", cases[i][0], cases[i][1], cases[i][2]);
		show("", ts.tv_sec, ts.tv_nsec);
		printf("    nsec in [0,1e9)? %s\n",
		    (ts.tv_nsec >= 0 && ts.tv_nsec < 1000000000) ? "YES" : "NO!");
	}

	/* ---- D: day=0 known DF-0199 ---- */
	printf("=== D: day=0 (known DF-0199) ===\n");
	ts.tv_sec = 0; ts.tv_nsec = 0;
	fattime2timespec((1u<<9)|(1u<<5)|0u, 0, 0, 0, &ts);
	show("day 0 decodes to", ts.tv_sec, ts.tv_nsec);
	return 0;
}
