/*
 * libc printf and friends
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Library General Public License version 2.
 */

#include "libcflat.h"

#define BUFSZ 2000

typedef struct pstream {
    char *buffer;
    int remain;
    int added;
} pstream_t;

typedef struct strprops {
    char pad;
    int npad;
    bool alternate;
} strprops_t;

static void addchar(pstream_t *p, char c)
{
    if (p->remain) {
	*p->buffer++ = c;
	--p->remain;
    }
    ++p->added;
}

static void print_str(pstream_t *p, const char *s, strprops_t props)
{
    const char *s_orig = s;
    int npad = props.npad;

    if (npad > 0) {
	npad -= strlen(s_orig);
	while (npad > 0) {
	    addchar(p, props.pad);
	    --npad;
	}
    }

    while (*s)
	addchar(p, *s++);

    if (npad < 0) {
	props.pad = ' '; /* ignore '0' flag with '-' flag */
	npad += strlen(s_orig);
	while (npad < 0) {
	    addchar(p, props.pad);
	    ++npad;
	}
    }
}

static char digits[16] = "0123456789abcdef";

static void print_int(pstream_t *ps, long long n, int base, strprops_t props)
{
    char buf[sizeof(long) * 3 + 2], *p = buf;
    int s = 0, i;

    if (n < 0) {
	n = -n;
	s = 1;
    }

    while (n) {
	*p++ = digits[n % base];
	n /= base;
    }

    if (s)
	*p++ = '-';

    if (p == buf)
	*p++ = '0';

    for (i = 0; i < (p - buf) / 2; ++i) {
	char tmp;

	tmp = buf[i];
	buf[i] = p[-1-i];
	p[-1-i] = tmp;
    }

    *p = 0;

    print_str(ps, buf, props);
}

static void print_unsigned(pstream_t *ps, unsigned long long n, int base,
			   strprops_t props)
{
    char buf[sizeof(long) * 3 + 3], *p = buf;
    int i;

    while (n) {
	*p++ = digits[n % base];
	n /= base;
    }

    if (p == buf)
	*p++ = '0';
    else if (props.alternate && base == 16) {
	if (props.pad == '0') {
	    addchar(ps, '0');
	    addchar(ps, 'x');

	    if (props.npad > 0)
		props.npad = MAX(props.npad - 2, 0);
	} else {
	    *p++ = 'x';
	    *p++ = '0';
	}
    }

    for (i = 0; i < (p - buf) / 2; ++i) {
	char tmp;

	tmp = buf[i];
	buf[i] = p[-1-i];
	p[-1-i] = tmp;
    }

    *p = 0;

    print_str(ps, buf, props);
}

static int fmtnum(const char **fmt)
{
    const char *f = *fmt;
    int len = 0, num;

    if (*f == '-')
	++f, ++len;

    while (*f >= '0' && *f <= '9')
	++f, ++len;

    num = atol(*fmt);
    *fmt += len;
    return num;
}

int vsnprintf(char *buf, int size, const char *fmt, va_list va)
{
    pstream_t s;

    s.buffer = buf;
    s.remain = size - 1;
    s.added = 0;
    while (*fmt) {
	char f = *fmt++;
	int nlong = 0;
	strprops_t props;
	memset(&props, 0, sizeof(props));
	props.pad = ' ';

	if (f != '%') {
	    addchar(&s, f);
	    continue;
	}
    morefmt:
	f = *fmt++;
	switch (f) {
	case '%':
	    addchar(&s, '%');
	    break;
	case 'c':
            addchar(&s, va_arg(va, int));
	    break;
	case '\0':
	    --fmt;
	    break;
	case '#':
	    props.alternate = true;
	    goto morefmt;
	case '0':
	    props.pad = '0';
	    ++fmt;
	    /* fall through */
	case '1'...'9':
	case '-':
	    --fmt;
	    props.npad = fmtnum(&fmt);
	    goto morefmt;
	case 'l':
	    ++nlong;
	    goto morefmt;
	case 't':
	case 'z':
	    /* Here we only care that sizeof(size_t) == sizeof(long).
	     * On a 32-bit platform it doesn't matter that size_t is
	     * typedef'ed to int or long; va_arg will work either way.
	     * Same for ptrdiff_t (%td).
	     */
	    nlong = 1;
	    goto morefmt;
	case 'd':
	    switch (nlong) {
	    case 0:
		print_int(&s, va_arg(va, int), 10, props);
		break;
	    case 1:
		print_int(&s, va_arg(va, long), 10, props);
		break;
	    default:
		print_int(&s, va_arg(va, long long), 10, props);
		break;
	    }
	    break;
	case 'u':
	    switch (nlong) {
	    case 0:
		print_unsigned(&s, va_arg(va, unsigned), 10, props);
		break;
	    case 1:
		print_unsigned(&s, va_arg(va, unsigned long), 10, props);
		break;
	    default:
		print_unsigned(&s, va_arg(va, unsigned long long), 10, props);
		break;
	    }
	    break;
	case 'x':
	    switch (nlong) {
	    case 0:
		print_unsigned(&s, va_arg(va, unsigned), 16, props);
		break;
	    case 1:
		print_unsigned(&s, va_arg(va, unsigned long), 16, props);
		break;
	    default:
		print_unsigned(&s, va_arg(va, unsigned long long), 16, props);
		break;
	    }
	    break;
	case 'p':
	    props.alternate = true;
	    print_unsigned(&s, (unsigned long)va_arg(va, void *), 16, props);
	    break;
	case 's':
	    print_str(&s, va_arg(va, const char *), props);
	    break;
	default:
	    addchar(&s, f);
	    break;
	}
    }
    *s.buffer = 0;
    return s.added;
}


int snprintf(char *buf, int size, const char *fmt, ...)
{
    va_list va;
    int r;

    va_start(va, fmt);
    r = vsnprintf(buf, size, fmt, va);
    va_end(va);
    return r;
}

int vprintf(const char *fmt, va_list va)
{
    char buf[BUFSZ];
    int r;

    r = vsnprintf(buf, sizeof(buf), fmt, va);
    puts(buf);
    return r;
}

int printf(const char *fmt, ...)
{
    va_list va;
    char buf[BUFSZ];
    int r;

    va_start(va, fmt);
    r = vsnprintf(buf, sizeof buf, fmt, va);
    va_end(va);
    puts(buf);
    return r;
}

void binstr(unsigned long x, char out[BINSTR_SZ])
{
	int i;
	char *c;
	int n;

	n = sizeof(unsigned long) * 8;
	i = 0;
	c = &out[0];
	for (;;) {
		*c++ = (x & (1ul << (n - i - 1))) ? '1' : '0';
		i++;

		if (i == n) {
			*c = '\0';
			break;
		}
		if (i % 4 == 0)
			*c++ = '\'';
	}
	assert(c + 1 - &out[0] == BINSTR_SZ);
}

void print_binstr(unsigned long x)
{
	char out[BINSTR_SZ];
	binstr(x, out);
	printf("%s", out);
}
