// time.h additions (struct tm, gmtime, localtime, mktime, asctime,
// ctime, difftime, strftime). Split out from libc.c to keep that
// translation unit's frame size below the W65816 stack-relative range.

typedef long          time_t;
typedef unsigned long clock_t;
typedef unsigned long size_t;

extern size_t strlen(const char *);

static const unsigned short __monthDays[12] = {
    0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
};

static int __isLeap(int y) {
    return (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0);
}

// ---- time.h: struct tm + calendar conversions -----------------------

struct tm {
    int tm_sec, tm_min, tm_hour;
    int tm_mday, tm_mon, tm_year;
    int tm_wday, tm_yday, tm_isdst;
};

// Internal mutable buffer for gmtime/localtime/asctime/ctime.
// Not thread-safe but matches the C standard's spec.
static struct tm __gmtimeBuf;
static char      __asctimeBuf[32];

double difftime(time_t end, time_t start) {
    return (double)(end - start);
}

struct tm *gmtime_r(const time_t *t, struct tm *out);

// gmtime / localtime: convert seconds-since-1970 to broken-down time.
// "local" is identical to "gm" — no timezone support.
//
// Returns a pointer to a static global (`__gmtimeBuf`).  Under GS/OS
// Loader (DBR != 0) caller-side pointer-deref reads need to land in
// the same bank where gmtime wrote; this requires the runtime build
// to enable `-mllvm -w65816-loader-bank-deref`, which makes
// LDAptr/STAptr load the bank byte from DP $BE (set by crt0 from
// PHK / current PBR).  Without the flag, gmtime still works under
// MAME / non-Loader runs where DBR=0 throughout.
struct tm *gmtime(const time_t *t) {
    return gmtime_r(t, &__gmtimeBuf);
}

struct tm *localtime(const time_t *t) {
    return gmtime(t);
}

// gmtime_r / localtime_r — POSIX reentrant variants.  Take a caller-
// supplied struct tm so the buffer lives on the user's stack (which
// is bank-0 in 65816 native mode regardless of DBR).  This avoids the
// bank-mismatch issue that breaks plain gmtime under Loader.
//
// Full broken-down time computation.  Earlier `optnone` workaround was
// masking a libgcc __udivmodsi_core bug (see libgcc.s) — the chained
// `secs /= 60; secs /= 60; secs /= 24` got fused by SDAG to a single
// `secs / 86400`, which has divisor high-half = 1 (b_hi != 0) and hit
// the contaminated-remainder path.  After fixing the core to STZ $EE
// instead of STA $EE, plain -O2 produces correct broken-down time.
// Verified against 1710484245L → 2024-03-15 06:30:45 UTC (Friday,
// day-of-year 74).
struct tm *gmtime_r(const time_t *t, struct tm *out) {
    long secs = *t;
    int  sec  = (int)(secs % 60L); secs /= 60L;
    int  min  = (int)(secs % 60L); secs /= 60L;
    int  hour = (int)(secs % 24L); secs /= 24L;
    long days = secs;
    int  wday = (int)((days + 4L) % 7L);
    if (wday < 0) wday += 7;

    int year = 70;  // years since 1900
    while (days >= 365L + (__isLeap(1900 + year) ? 1 : 0)) {
        days -= 365L + (__isLeap(1900 + year) ? 1 : 0);
        year++;
    }
    int yday = (int)days;
    int leap = __isLeap(1900 + year);
    int mon = 11;
    while (mon > 0) {
        int firstDayOfMon = __monthDays[mon] + (leap && mon > 1 ? 1 : 0);
        if ((int)days >= firstDayOfMon) break;
        mon--;
    }
    int firstDay = __monthDays[mon] + (leap && mon > 1 ? 1 : 0);
    int mday = (int)days - firstDay + 1;

    out->tm_sec   = sec;
    out->tm_min   = min;
    out->tm_hour  = hour;
    out->tm_mday  = mday;
    out->tm_mon   = mon;
    out->tm_year  = year;
    out->tm_wday  = wday;
    out->tm_yday  = yday;
    out->tm_isdst = -1;
    return out;
}

struct tm *localtime_r(const time_t *t, struct tm *out) {
    return gmtime_r(t, out);
}

// mktime: convert broken-down time → seconds-since-1970. Also fills
// in tm_wday and tm_yday if the caller didn't bother.
time_t mktime(struct tm *tm) {
    int year = tm->tm_year + 1900;
    int mon  = tm->tm_mon;
    long days = 0;
    for (int y = 1970; y < year; y++) {
        days += __isLeap(y) ? 366 : 365;
    }
    for (int m = 0; m < mon; m++) {
        days += __monthDays[m + 1] - __monthDays[m];
        // (the table starts at 0 for Jan, so each month is the diff)
    }
    if (mon > 1 && __isLeap(year)) days++;
    days += tm->tm_mday - 1;
    long secs = days * 86400L
              + (long)tm->tm_hour * 3600L
              + (long)tm->tm_min  * 60L
              + tm->tm_sec;
    tm->tm_wday = (int)((days + 4) % 7);
    if (tm->tm_wday < 0) tm->tm_wday += 7;
    tm->tm_yday = (int)(__monthDays[mon]
                        + ((mon > 1 && __isLeap(year)) ? 1 : 0)
                        + tm->tm_mday - 1);
    return secs;
}

static const char *const __wdayShort[7] = {
    "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
static const char *const __wdayLong[7] = {
    "Sunday", "Monday", "Tuesday", "Wednesday",
    "Thursday", "Friday", "Saturday"
};
static const char *const __monShort[12] = {
    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
static const char *const __monLong[12] = {
    "January", "February", "March", "April",
    "May", "June", "July", "August",
    "September", "October", "November", "December"
};

// Format N-digit zero-padded. Forces u32 (long) arithmetic — clang's
// strength reducer otherwise lowers /10 and %10 on small types into
// i8 mulhu by 0xCD (magic constant for div-by-10), which the W65816
// backend has no select pattern for.
// Use the `%` operator directly so the compiler picks `__umodhi3`
// (16-bit unsigned modulo) instead of synthesizing `v - q*10`.  The
// hand-built `v - q*10` triggers a strength-reducer bug that emits
// `q * 0xF6` (= `q * (-10)` with the high bits of -10 truncated) —
// fmt04(2024) returned "2224".  Letting the compiler emit the modulo
// libcall directly produces correct output.  Two libcalls per digit
// (__udivhi3 + __umodhi3) is slower than one __udivhi3 + multiply but
// is the only spelling that avoids the negation bug at this width.
// Calendar values stay under 65535 so u16 suffices.
static char *fmtN(char *p, unsigned long v, int n) {
    unsigned int v16 = (unsigned int)v;
    p += n;
    char *end = p;
    while (n--) {
        unsigned int r = v16 % 10u;
        v16             = v16 / 10u;
        p--;
        *p = (char)('0' + (int)r);
    }
    return end;
}
static char *fmt02(char *p, int v) { return fmtN(p, (unsigned long)v, 2); }
static char *fmt04(char *p, int v) { return fmtN(p, (unsigned long)v, 4); }

char *asctime(const struct tm *tm) {
    // "Sun Jan  1 00:00:00 1970\n\0" = 26 bytes
    char *p = __asctimeBuf;
    int wday = tm->tm_wday & 7; if (wday > 6) wday = 0;
    int mon  = tm->tm_mon  & 15; if (mon  > 11) mon = 0;
    *p++ = __wdayShort[wday][0];
    *p++ = __wdayShort[wday][1];
    *p++ = __wdayShort[wday][2];
    *p++ = ' ';
    *p++ = __monShort[mon][0];
    *p++ = __monShort[mon][1];
    *p++ = __monShort[mon][2];
    *p++ = ' ';
    int mday = tm->tm_mday;
    *p++ = (mday >= 10) ? ('0' + mday/10) : ' ';
    *p++ = '0' + mday % 10;
    *p++ = ' ';
    p = fmt02(p, tm->tm_hour); *p++ = ':';
    p = fmt02(p, tm->tm_min);  *p++ = ':';
    p = fmt02(p, tm->tm_sec);  *p++ = ' ';
    p = fmt04(p, tm->tm_year + 1900);
    *p++ = '\n';
    *p   = 0;
    return __asctimeBuf;
}

char *ctime(const time_t *t) {
    return asctime(gmtime(t));
}

// Spec dispatch.  Pre-session set restored — strftimeExtra split + new
// specs (%y %C %e %k %I %l) caused either backend mis-codegen on the
// indirect call or a stack-frame growth that made the merged switch
// return garbage.  Keeping the supported set as it was before the
// 2026-05-10 expansion attempt.
//
// Supported specs:
//   %Y %m %d %H %M %S %j %w %a %A %b %h %B %p %%
// Composite specs (expanded by main loop via strftimeComposite):
//   %D %F %R %T %r %x %X %c
static int strftimeOne(char dst[8], char spec, const struct tm *tm,
                       const char **strOut) {
    *strOut = 0;
    switch (spec) {
    case 'Y': fmt04(dst, tm->tm_year + 1900); return 4;
    case 'm': fmt02(dst, tm->tm_mon + 1);     return 2;
    case 'd': fmt02(dst, tm->tm_mday);        return 2;
    case 'H': fmt02(dst, tm->tm_hour);        return 2;
    case 'M': fmt02(dst, tm->tm_min);         return 2;
    case 'S': fmt02(dst, tm->tm_sec);         return 2;
    case 'j': fmtN(dst, (unsigned long)(tm->tm_yday + 1), 3); return 3;
    case 'w': dst[0] = (char)('0' + (tm->tm_wday & 7));       return 1;
    case 'a': *strOut = __wdayShort[tm->tm_wday & 7];         return 3;
    case 'A': *strOut = __wdayLong [tm->tm_wday & 7];
              return (int)strlen(*strOut);
    case 'b':
    case 'h': *strOut = __monShort[tm->tm_mon & 15];          return 3;
    case 'B': *strOut = __monLong [tm->tm_mon & 15];
              return (int)strlen(*strOut);
    case 'p': *strOut = (tm->tm_hour < 12) ? "AM" : "PM";     return 2;
    case '%': dst[0] = '%';                                   return 1;
    default:  return 0;  // unrecognized — caller emits literal
    }
}

size_t strftime(char *buf, size_t n, const char *fmt, const struct tm *tm) {
    char *p   = buf;
    char *end = buf + n;
    char  tmp[8];
    while (*fmt && p + 1 < end) {
        if (*fmt != '%') { *p++ = *fmt++; continue; }
        fmt++;
        if (!*fmt) break;
        const char *ins;
        int len = strftimeOne(tmp, *fmt, tm, &ins);
        const char *src = ins ? ins : tmp;
        for (int i = 0; i < len && p + 1 < end; i++) *p++ = src[i];
        fmt++;
    }
    if (p < end) *p = 0;
    else if (n > 0) buf[n - 1] = 0;
    return (size_t)(p - buf);
}
