typedef unsigned long va_list;

#define ACC    4
#define __read(source)                    \
({ va_list __res;                    \
    __asm__ __volatile__(                \
        "move\t%0, " #source "\n\t"        \
        : "=r" (__res));            \
    __res;                        \
})

enum format_type {
    FORMAT_TYPE_NONE,
    FORMAT_TYPE_HEX,
    FORMAT_TYPE_ULONG,
    FORMAT_TYPE_FLOAT
};

struct printf_spec {
    char    type;
};

static int format_decode(char *fmt, struct printf_spec *spec)
{
    char *start = fmt;

    for (; *fmt ; ++fmt) {
        if (*fmt == '%') {
            break;
        }
    }

    switch (*++fmt) {
    case 'x':
        spec->type = FORMAT_TYPE_HEX;
        break;

    case 'd':
        spec->type = FORMAT_TYPE_ULONG;
        break;

    case 'f':
        spec->type = FORMAT_TYPE_FLOAT;
        break;

    default:
        spec->type = FORMAT_TYPE_NONE;
    }

    return ++fmt - start;
}

void *memcpy(void *dest, void *src, int n)
{
    int i;
    char *s = src;
    char *d = dest;

    for (i = 0; i < n; i++) {
        d[i] = s[i];
    }
    return dest;
}

char *number(char *buf, va_list num)
{
    int i;
    char *str = buf;
    static char digits[16] = "0123456789abcdef";
    str = str + sizeof(num) * 2;

    for (i = 0; i < sizeof(num) * 2; i++) {
        *--str = digits[num & 15];
        num >>= 4;
    }

    return buf + sizeof(num) * 2;
}

char *__number(char *buf, va_list num)
{
    int i;
    va_list mm = num;
    char *str = buf;

    if (!num) {
        *str++ = '0';
        return str;
    }

    for (i = 0; mm; mm = mm/10, i++) {
        /* Do nothing. */
    }

    str = str + i;

    while (num) {
        *--str = num % 10 + 48;
        num = num / 10;
    }

    return str + i;
}

va_list modf(va_list args, va_list *integer, va_list *num)
{
    int i;
    double dot_v = 0;
    va_list E, DOT, DOT_V;

    if (!args) {
        return 0;
    }

    for (i = 0, args = args << 1 >> 1; i < 52; i++) {
        if ((args >> i) & 0x1) {
            break;
        }
    }

    *integer = 0;

    if ((args >> 56 != 0x3f) || (args >> 52 == 0x3ff)) {
        E = (args >> 52) - 1023;
        DOT = 52 - E - i;
        DOT_V = args << (12 + E) >> (12 + E) >> i;
        *integer = ((args << 12 >> 12) >> (i + DOT)) | (1 << E);
    } else {
        E = ~((args >> 52) - 1023) + 1;
        DOT_V = args << 12 >> 12;

        dot_v += 1.0 / (1 << E);

        for (i = 1; i <= 16; i++) {
            if ((DOT_V >> (52 - i)) & 0x1) {
                dot_v += 1.0 / (1 << E + i);
            }
        }

        for (i = 1, E = 0; i <= ACC; i++) {
            dot_v *= 10;
            if (!(va_list)dot_v) {
                E++;
            }
    }

    *num = E;

    return dot_v;
    }

    if (args & 0xf) {
        for (i = 1; i <= 16; i++) {
            if ((DOT_V >> (DOT - i)) & 0x1) {
                dot_v += 1.0 / (1 << i);
            }
        }

        for (i = 1, E = 0; i <= ACC; i++) {
            dot_v *= 10;
            if (!(va_list)dot_v) {
                E++;
            }
        }

        *num = E;

        return dot_v;
    } else if (DOT) {
        for (i = 1; i <= DOT; i++) {
            if ((DOT_V >> (DOT - i)) & 0x1) {
                dot_v += 1.0 / (1 << i);
            }
        }

        for (i = 1; i <= ACC; i++) {
            dot_v = dot_v * 10;
        }

    return dot_v;
    }

    return 0;
}

int vsnprintf(char *buf, int size, char *fmt, va_list args)
{
    char *str, *mm;
    struct printf_spec spec = {0};

    str = mm = buf;

    while (*fmt) {
        char *old_fmt = fmt;
        int read = format_decode(fmt, &spec);

        fmt += read;

        switch (spec.type) {
        case FORMAT_TYPE_NONE: {
            memcpy(str, old_fmt, read);
            str += read;
            break;
        }
        case FORMAT_TYPE_HEX: {
            memcpy(str, old_fmt, read);
            str = number(str + read, args);
            for (; *mm ; ++mm) {
                if (*mm == '%') {
                    *mm = '0';
                break;
                }
            }
        break;
        }
        case FORMAT_TYPE_ULONG: {
            memcpy(str, old_fmt, read - 2);
            str = __number(str + read - 2, args);
            break;
        }
        case FORMAT_TYPE_FLOAT: {
            va_list integer, dot_v, num;
            dot_v = modf(args, &integer, &num);
            memcpy(str, old_fmt, read - 2);
            str += read - 2;
            if ((args >> 63 & 0x1)) {
                *str++ = '-';
            }
            str = __number(str, integer);
            if (dot_v) {
                *str++ = '.';
                while (num--) {
                    *str++ = '0';
                }
                str = __number(str, dot_v);
            }
            break;
        }
        }
    }
    *str = '\0';

    return str - buf;
}

static void serial_out(char *str)
{
    while (*str) {
        *(char *)0xffffffffb80003f8 = *str++;
    }
}

int vprintf(char *fmt, va_list args)
{
    int printed_len = 0;
    static char printf_buf[512];
    printed_len = vsnprintf(printf_buf, sizeof(printf_buf), fmt, args);
    serial_out(printf_buf);
    return printed_len;
}

int printf(char *fmt, ...)
{
    return vprintf(fmt, __read($5));
}