// Buffer-formatting siblings of printf — kept in their own translation
// unit so the shared writeXxx helpers don't have to take a function-
// pointer sink (indirect call cost on this target) and so adding the
// formatter to libc.c can't shift vprintf's branch distances out of
// range (per the strtol.c precedent).
//
// Functions:
//   int vsnprintf(char *buf, size_t n, const char *fmt, va_list ap);
//   int snprintf (char *buf, size_t n, const char *fmt, ...);
//   int vsprintf (char *buf, const char *fmt, va_list ap);
//   int sprintf  (char *buf, const char *fmt, ...);
//
// Format support matches vprintf: %d %i %u %x %X %c %s %p %f and the
// `l` length modifier (%ld %lu).  Width is honoured for %x.  %f
// precision is capped at 9 fractional digits.
//
// Return value: number of characters that would have been written had
// the buffer been unbounded (C99 vsnprintf semantics), not just the
// number actually written.  This lets callers detect truncation.
//
// Sink state lives in file-static globals (gCur/gEnd/gTotal) rather
// than a per-call context.  Single-threaded use only, but that matches
// the rest of this runtime.

typedef unsigned long size_t;
typedef __builtin_va_list  va_list;
#define va_start(ap, last) __builtin_va_start(ap, last)
#define va_arg(ap, ty)     __builtin_va_arg(ap, ty)
#define va_end(ap)         __builtin_va_end(ap)


static char   *gCur;
static char   *gEnd;
static size_t  gTotal;


static void emit(char c) {
    if (gCur < gEnd) {
        *gCur++ = c;
    }
    gTotal++;
}


static void emitStr(const char *p) {
    if (!p) {
        p = "(null)";
    }
    while (*p) {
        emit(*p++);
    }
}


static void emitUDec(unsigned int n) {
    char buf[6];
    int  i = 0;
    if (n == 0) {
        emit('0');
        return;
    }
    while (n > 0) {
        buf[i++] = '0' + (n % 10);
        n /= 10;
    }
    while (i > 0) emit(buf[--i]);
}


static void emitDec(int n) {
    // -n on INT_MIN is signed-overflow UB; negate as unsigned.
    if (n < 0) {
        emit('-');
        emitUDec(0u - (unsigned int)n);
    } else {
        emitUDec((unsigned int)n);
    }
}


static void emitULong(unsigned long n) {
    char buf[11];
    int  i = 0;
    if (n == 0) {
        emit('0');
        return;
    }
    while (n > 0) {
        buf[i++] = '0' + (n % 10);
        n /= 10;
    }
    while (i > 0) emit(buf[--i]);
}


static void emitSignedLong(long n) {
    // See emitDec: avoid the signed-overflow UB on LONG_MIN.
    if (n < 0) {
        emit('-');
        emitULong(0ul - (unsigned long)n);
    } else {
        emitULong((unsigned long)n);
    }
}


static void emitHex(unsigned int n, int width) {
    static const char digits[] = "0123456789abcdef";
    // unsigned int is 16-bit on this target -> at most 4 hex digits.
    // Cap width to that; without it `snprintf("%08x", ...)` blew past
    // the buf[] tail and corrupted the stack.
    char buf[4];
    if (width > 4) width = 4;
    int  i = 0;
    if (n == 0) {
        buf[i++] = '0';
    }
    while (n > 0 && i < 4) {
        buf[i++] = digits[n & 0xF];
        n >>= 4;
    }
    while (i < width) {
        buf[i++] = '0';
    }
    while (i > 0) emit(buf[--i]);
}


static void emitDouble(double v, int prec, char spec) {
    // For %g / %G, "precision" is total significant digits.  Real glibc
    // would compute exponent and choose between %e and %f styles, but
    // we keep things simple and just emit `X.YYY` with trailing zeros
    // stripped at the end.  For %f / %e, prec is decimal places.
    int isG = (spec == 'g' || spec == 'G');
    if (prec < 0) {
        prec = 6;
    }
    if (prec > 9) {
        prec = 9;
    }
    // Avoid `if (v < 0)` (which calls __ltdf2) — the W65816 codegen
    // for that comparison passes its double arg with a missing word,
    // and the test silently returns false for negatives.  Read the
    // IEEE-754 sign bit and clear it inline instead.
    unsigned long long bits;
    __builtin_memcpy(&bits, &v, 8);
    if (bits & ((unsigned long long)1 << 63)) {
        emit('-');
        bits &= ~((unsigned long long)1 << 63);
        __builtin_memcpy(&v, &bits, 8);
    }
    // Split int part first, then scale only the fractional part.  The
    // earlier "multiply v by 10^prec then split via integer divide"
    // approach silently overflowed long for v*10^prec > 2^31 (e.g. any
    // value ≥ 2.15 with prec=9 in `%.12g`).  We've since reworked the
    // libcall ABI, so the previously-buggy `v - (double)ipart` chain
    // works now — smoke catches a regression of either bug.
    unsigned long intPart = (unsigned long)(long)v;
    double frac = v - (double)intPart;
    unsigned long mul = 1;
    for (int i = 0; i < prec; i++) {
        frac = frac * 10.0;
        mul *= 10;
    }
    // Round-half-up before truncation: 0.314 * 100 = 31.3999... in
    // soft-double, but `%.2f` of 3.14 should print "3.14".  Adding 0.5
    // then truncating is round-half-up for the non-negative frac here.
    frac = frac + 0.5;
    unsigned long frcPart = (unsigned long)(long)frac;
    // Carry-up if rounding pushed frac to a full integer (e.g. 0.9995
    // → 0.9995*1000+0.5 = 1000 = mul; the "0.9995" wanted to become
    // "1.000", not "0.1000").
    if (frcPart >= mul) {
        intPart += 1;
        frcPart = 0;
    }
    emitULong(intPart);
    if (prec == 0) {
        return;
    }
    // Build fractional digits into a local buffer (reverse order to
    // forward) so we can trim trailing zeros for %g before emitting.
    char buf[10];
    for (int i = prec - 1; i >= 0; i--) {
        buf[i] = (char)('0' + (frcPart % 10));
        frcPart /= 10;
    }
    int emitCount = prec;
    if (isG) {
        // Strip trailing zeros.  If the whole fractional part is
        // zeros, skip the '.' too.
        while (emitCount > 0 && buf[emitCount - 1] == '0') {
            emitCount -= 1;
        }
    }
    if (emitCount == 0) {
        return;  // No fractional digits to emit → no '.' either.
    }
    emit('.');
    for (int i = 0; i < emitCount; i++) {
        emit(buf[i]);
    }
}


// fmt is arg0 (A register); see banner comment for why the order matters.
// Previously optnone (slot-alias bug under p:16:16; see
// feedback_snprintf_va_arg_slot_alias.md).  Re-enabled greedy under
// ptr32 — testing whether the bug recurs.
static int format(const char *fmt, va_list ap) {
    while (*fmt) {
        char c = *fmt++;
        if (c != '%') {
            emit(c);
            continue;
        }
        int width = 0;
        while (*fmt >= '0' && *fmt <= '9') {
            width = width * 10 + (*fmt - '0');
            fmt++;
        }
        int prec = -1;
        if (*fmt == '.') {
            fmt++;
            prec = 0;
            while (*fmt >= '0' && *fmt <= '9') {
                prec = prec * 10 + (*fmt - '0');
                fmt++;
            }
        }
        int isLong = 0;
        if (*fmt == 'l') {
            isLong = 1;
            fmt++;
        }
        char spec = *fmt++;
        if (spec == 'd' || spec == 'i') {
            if (isLong) {
                emitSignedLong(va_arg(ap, long));
            } else {
                emitDec(va_arg(ap, int));
            }
        } else if (spec == 'u') {
            if (isLong) {
                emitULong(va_arg(ap, unsigned long));
            } else {
                emitUDec(va_arg(ap, unsigned int));
            }
        } else if (spec == 'x' || spec == 'X') {
            emitHex(va_arg(ap, unsigned int), width);
        } else if (spec == 'c') {
            emit((char)va_arg(ap, int));
        } else if (spec == 's') {
            emitStr(va_arg(ap, const char *));
        } else if (spec == 'f' || spec == 'F' ||
                   spec == 'g' || spec == 'G' ||
                   spec == 'e' || spec == 'E') {
            emitDouble(va_arg(ap, double), prec, spec);
        } else if (spec == 'p') {
            emit('0');
            emit('x');
            emitHex(va_arg(ap, unsigned int), 4);
        } else if (spec == '%') {
            emit('%');
        } else {
            emit('%');
            emit(spec);
        }
    }
    if (gCur < gEnd) {
        *gCur = '\0';
    } else if (gEnd > (char *)0) {
        // Truncated, but n > 0: overwrite the last byte with NUL so
        // the result is a valid C string.  snprintf with n=0 sets
        // gEnd = NULL up front so this branch correctly skips —
        // previously it wrote `gEnd[-1]` to `buf[-1]`, clobbering
        // memory before the buffer.
        gEnd[-1] = '\0';
    }
    return (int)gTotal;
}



int snprintf(char *buf, size_t n, const char *fmt, ...) {
    gCur   = buf;
    // n == 0 must NOT touch the buffer (C99 7.19.6.5).  Setting
    // gEnd = NULL here makes both `gCur < gEnd` and `gEnd > 0`
    // false, so no NUL terminator gets written.
    gEnd   = n ? buf + n : (char *)0;
    gTotal = 0;
    va_list ap;
    va_start(ap, fmt);
    int r = format(fmt, ap);
    va_end(ap);
    return r;
}


int sprintf(char *buf, const char *fmt, ...) {
    gCur   = buf;
    // sprintf is unbounded.  Setting gEnd = buf + 0xFFFE looks innocuous
    // but clang lowers the +0xFFFE to a `dec a; dec a` peephole (since
    // 0xFFFE is -2 in 16-bit), giving gEnd = buf - 2 — and then the
    // emit() bounds test `cur < end` is always false, so nothing gets
    // written.  Use the absolute top-of-bank sentinel instead.
    gEnd   = (char *)0xFFFF;
    gTotal = 0;
    va_list ap;
    va_start(ap, fmt);
    int r = format(fmt, ap);
    va_end(ap);
    return r;
}


int vsnprintf(char *buf, size_t n, const char *fmt, va_list ap) {
    gCur   = buf;
    gEnd   = n ? buf + n : (char *)0;
    gTotal = 0;
    return format(fmt, ap);
}


int vsprintf(char *buf, const char *fmt, va_list ap) {
    gCur   = buf;
    gEnd   = (char *)0xFFFF;
    gTotal = 0;
    return format(fmt, ap);
}
