/*
    Copyright 2001, 2002 Georges Menie (www.menie.org)
    stdarg version contributed by Christian Ettinger

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

********************************** important precaution (start) **********************************

    There is a problem with interrupts occuring at the start of the sprintf function (probably
    including all its derivatives like printf). As a result of that problem, instead of the
    desired data, the string "33554766" is inserted into the output string. This may vary under
    other conditions, but it is repeatable here and can be used as a trigger.

    The described problem seems to be independent of the actions performed in the interrupt
    handler, i.e. the acknowledged interrupt including start and end of the interrupt handler is
    enough.

    The problem seems to be caused by the handling of the va_list. It may also well be, that the
    interrupt handler does not properly safe and restore all significant registers, but I did not
    dig deeper.

    As a workaround, global interrupts are disabled in this program, before sprintf is called. To
    keep interrupt disable times short, global interrupts are unconditionally enabled in the
    sprintf function, immediately after va_start(). This seems to fix the problem.

    Take care to adapt the code, if interrupts must be kept disabled.

    Michael@dk8pp.de, 07.01.2026

********************************** important precaution (end) ************************************

*/

/*
    putchar is the only external dependency for this file,
    if you have a working putchar, leave it commented out.
    If not, uncomment the define below and
    replace outbyte(c) by your own function call.
*/
#include <stdarg.h>
#include "picorv32.h"
#include "wbuart.h"
#include "irq.h"
#include "simpleuart.h"
#include "project.h"

#ifdef USE_WBUART
#define putchar(c) wbuart_outbyte(c)
#else
#define putchar(c) outbyte(c)
#endif

static void  __attribute__((optimize("Os"))) printchar(char **str, int c)
{
    extern int putchar(int c);

    if (str) {
        **str = c;
        ++(*str);
    }
    else (void)putchar(c);
}

#define PAD_RIGHT 1
#define PAD_ZERO 2

static int  __attribute__((optimize("Os"))) prints(char **out, const char *string, int width, int pad)
{
    register int pc = 0, padchar = ' ';

    if (width > 0) {
        register int len = 0;
        register const char *ptr;
        for (ptr = string; *ptr; ++ptr) ++len;
        if (len >= width) width = 0;
        else width -= len;
        if (pad & PAD_ZERO) padchar = '0';
    }
    if (!(pad & PAD_RIGHT)) {
        for ( ; width > 0; --width) {
            printchar (out, padchar);
            ++pc;
        }
    }
    for ( ; *string ; ++string) {
        printchar (out, *string);
        ++pc;
    }
    for ( ; width > 0; --width) {
        printchar (out, padchar);
        ++pc;
    }

    return pc;
}

/* The following should be enough for 32 bit int */
#define PRINT_BUF_LEN 12

static int  __attribute__((optimize("Os"))) printi(char **out, int i, int b, int sg, int width, int pad, int letbase)
{
    char print_buf[PRINT_BUF_LEN];
    register char *s;
    register int t, neg = 0, pc = 0;
    register unsigned int u = i;

    if (i == 0) {
        print_buf[0] = '0';
        print_buf[1] = '\0';
        return prints (out, print_buf, width, pad);
    }

    if (sg && b == 10 && i < 0) {
        neg = 1;
        u = -i;
    }

    s = print_buf + PRINT_BUF_LEN-1;
    *s = '\0';

    while (u) {
        t = u % b;
        if( t >= 10 )
            t += letbase - '0' - 10;
        *--s = t + '0';
        u /= b;
    }

    if (neg) {
        if( width && (pad & PAD_ZERO) ) {
            printchar (out, '-');
            ++pc;
            --width;
        }
        else {
            *--s = '-';
        }
    }

    return pc + prints (out, s, width, pad);
}

static int  __attribute__((optimize("Os"))) print( char **out, const char *format, va_list args )
{
    register int width, pad;
    register int pc = 0;
    char scr[2];

    for (; *format != 0; ++format) {
        if (*format == '%') {
            ++format;
            width = pad = 0;
            if (*format == '\0') break;
            if (*format == '%') goto out;
            if (*format == '-') {
                ++format;
                pad = PAD_RIGHT;
            }
            while (*format == '0') {
                ++format;
                pad |= PAD_ZERO;
            }
            for ( ; *format >= '0' && *format <= '9'; ++format) {
                width *= 10;
                width += *format - '0';
            }
            if( *format == 's' ) {
                register char *s = (char *)((long)va_arg( args, int ));
                pc += prints (out, s?s:"(null)", width, pad);
                continue;
            }
            if( *format == 'd' ) {
                pc += printi (out, va_arg( args, int ), 10, 1, width, pad, 'a');
                continue;
            }
            if( *format == 'x' ) {
                pc += printi (out, va_arg( args, int ), 16, 0, width, pad, 'a');
                continue;
            }
            if( *format == 'X' ) {
                pc += printi (out, va_arg( args, int ), 16, 0, width, pad, 'A');
                continue;
            }
            if( *format == 'u' ) {
                pc += printi (out, va_arg( args, int ), 10, 0, width, pad, 'a');
                continue;
            }
            if( *format == 'c' ) {
                /* char are converted to int then pushed on the stack */
                scr[0] = (char)va_arg( args, int );
                scr[1] = '\0';
                pc += prints (out, scr, width, pad);
                continue;
            }
        }
        else {
        out:
            printchar (out, *format);
            ++pc;
        }
    }
    if (out) **out = '\0';
    va_end( args );
    return pc;
}

int  __attribute__((optimize("Os"))) printf(const char *format, ...)
{
        va_list args;

        va_start( args, format );
        return print( 0, format, args );
}

int  __attribute__((optimize("Os"))) sprintf(char *out, const char *format, ...)
{
    register int pc;
        va_list args;
//    *TRIGGER_LA = TRIGGER4 ;
        va_start( args, format );
    enable_interrupt_global();          /* Enable global interrupts, see "important precaution" above */
//    *TRIGGER_LA = TRIGGER5 ;
        pc = print( &out, format, args );
//    *TRIGGER_LA = TRIGGER4 ;
        return (pc);
}

int  __attribute__((optimize("Os"))) snprintf( char *buf, unsigned int count, const char *format, ... )
{
        va_list args;

        ( void ) count;

        va_start( args, format );
        return print( &buf, format, args );
}

/* To keep linker happy. */
int  __attribute__((optimize("Os"))) write( int i, char* c, int n)
{
    return 0;
}
