/*
*      minimal printf for Human68k DOS
*
*      written by ITOH Yasufumi
*      public domain
*
*      $NetBSD: xprintf.c,v 1.5 2011/02/21 02:31:58 itohy Exp $
*/

#include <sys/types.h>
#ifdef __STDC__
# include <stdarg.h>
#else
# include <varargs.h>
#endif

#include <dos.h>
#include <dos_errno.h>

#include "xprintf.h"

/*
* From ISO/IEC 9899:1990
* 7.9.6.1 The fprintf function
* ...
* Environment limit
*    The minimum value for the maximum number of characters
* produced by any single conversion shall be 509.
*
* so the following value shall not be smaller than 510
* if you want to conform ANSI C (it is only a guideline
* and maybe no sense on this code, of course :-).
*/
#define PRINTF_BUFSZ    4096

/*
* Shift-JIS kanji support
* (No special handling needed for EUC)
*/
#define SJIS

#ifdef SJIS
#define UC(c)           ((unsigned char) (c))
#define IS_SJIS1(c)     ((UC(c) > 0x80 && UC(c) < 0xa0) ||      \
                               (UC(c) >= 0xe0 && UC(c) <= 0xfc))
#define IS_SJIS2(c)     (UC(c) >= 0x40 && UC(c) <= 0xfc && UC(c) != 0x7f)
#endif

#if !defined(__STDC__) && !defined(const)
#define const
#endif

extern const char *const __progname;

static char * numstr(char *buf, long val, int base, int sign);

/*
* convert number to string
* buf must have enough space
*/
static char *
numstr(char *buf, long val, int base, int sign)
{
       unsigned long v;
       char rev[32];
       char *r = rev, *b = buf;

       /* negative? */
       if (sign && val < 0) {
               v = -val;
               *b++ = '-';
       } else {
               v = val;
       }

       /* inverse order */
       do {
               *r++ = "0123456789abcdef"[v % base];
               v /= base;
       } while (v);

       /* reverse string */
       while (r > rev)
               *b++ = *--r;

       *b = '\0';
       return buf;
}

/*
* supported format: %x, %p, %s, %c, %d, %u, %o
* \n is converted to \r\n
*
* XXX argument/parameter types are not strictly handled
*/
size_t
xvsnprintf(char *buf, size_t len, const char *fmt, va_list ap)
{
       char *b = buf;
       const char *s;
       char numbuf[32];

       while (*fmt && len > 1) {
               if (*fmt != '%') {
#ifdef SJIS
                       if (IS_SJIS1(*fmt) && IS_SJIS2(fmt[1])) {
                               if (len <= 2)
                                       break;  /* not enough space */
                               *b++ = *fmt++;
                               len--;
                       }
#endif
                       if (*fmt == '\n' && (b == buf || b[-1] != '\r')) {
                               if (len <= 2)
                                       break;
                               *b++ = '\r';
                               len--;
                       }
                       *b++ = *fmt++;
                       len--;
                       continue;
               }

               /* %? */
               fmt++;
               switch (*fmt++) {
               case '%':       /* "%%" -> literal % */
                       *b++ = '%';
                       len--;
                       break;

               case 'd':
                       s = numstr(numbuf, va_arg(ap, long), 10, 1);
               copy_string:
                       for ( ; *s && len > 1; len--)
                               *b++ = *s++;
                       break;

               case 'u':
                       s = numstr(numbuf, va_arg(ap, long), 10, 0);
                       goto copy_string;

               case 'p':
                       *b++ = '0';
                       len--;
                       if (len > 1) {
                               *b++ = 'x';
                               len--;
                       }
                       /* FALLTHROUGH */
               case 'x':
                       s = numstr(numbuf, va_arg(ap, long), 16, 0);
                       goto copy_string;

               case 'o':
                       s = numstr(numbuf, va_arg(ap, long), 8, 0);
                       goto copy_string;

               case 's':
                       s = va_arg(ap, char *);
                       while (*s && len > 1) {
#ifdef SJIS
                               if (IS_SJIS1(*s) && IS_SJIS2(s[1])) {
                                       if (len <= 2)
                                               goto break_loop;
                                       *b++ = *s++;
                                       len--;
                               }
#endif
                               if (*s == '\n' && (b == buf || b[-1] != '\r')) {
                                       if (len <= 2)
                                               goto break_loop;
                                       *b++ = '\r';
                                       len--;
                               }
                               *b++ = *s++;
                               len--;
                       }
                       break;

               case 'c':
                       *b++ = va_arg(ap, int);
                       len--;
                       break;
               }
       }
break_loop:

       *b = '\0';
       return (char *)b - buf;
}

#ifdef __STDC__
#define VA_START(a, v)  va_start(a, v)
#else
#define VA_START(a, v)  va_start(a)
#endif

#ifdef __STDC__
size_t
xsnprintf(char *buf, size_t len, const char *fmt, ...)
#else
size_t
xsnprintf(buf, len, fmt, va_alist)
       char *buf;
       size_t len;
       const char *fmt;
       va_dcl
#endif
{
       va_list ap;
       size_t ret;

       VA_START(ap, fmt);
       ret = xvsnprintf(buf, len, fmt, ap);
       va_end(ap);

       return ret;
}

size_t
xvfdprintf(int fd, const char *fmt, va_list ap)
{
       char buf[PRINTF_BUFSZ];
       size_t ret;

       ret = xvsnprintf(buf, sizeof buf, fmt, ap);
       if (ret)
               ret = DOS_WRITE(fd, buf, ret);

       return ret;
}

#ifdef __STDC__
size_t
xprintf(const char *fmt, ...)
#else
size_t
xprintf(fmt, va_alist)
       const char *fmt;
       va_dcl
#endif
{
       va_list ap;
       size_t ret;

       VA_START(ap, fmt);
       ret = xvfdprintf(1, fmt, ap);
       va_end(ap);

       return ret;
}

#ifdef __STDC__
size_t
xerrprintf(const char *fmt, ...)
#else
size_t
xerrprintf(fmt, va_alist)
       const char *fmt;
       va_dcl
#endif
{
       va_list ap;
       size_t ret;

       VA_START(ap, fmt);
       ret = xvfdprintf(2, fmt, ap);
       va_end(ap);

       return ret;
}

__dead void
#ifdef __STDC__
xerr(int eval, const char *fmt, ...)
#else
xerr(eval, fmt, va_alist)
       int eval;
       const char *fmt;
       va_dcl
#endif
{
       int e = dos_errno;
       va_list ap;

       xerrprintf("%s: ", __progname);
       if (fmt) {
               VA_START(ap, fmt);
               xvfdprintf(2, fmt, ap);
               va_end(ap);
               xerrprintf(": ");
       }
       xerrprintf("%s\n", dos_strerror(e));
       DOS_EXIT2(eval);
}

__dead void
#ifdef __STDC__
xerrx(int eval, const char *fmt, ...)
#else
xerrx(eval, fmt, va_alist)
       int eval;
       const char *fmt;
       va_dcl
#endif
{
       va_list ap;

       xerrprintf("%s: ", __progname);
       if (fmt) {
               VA_START(ap, fmt);
               xvfdprintf(2, fmt, ap);
               va_end(ap);
       }
       xerrprintf("\n");
       DOS_EXIT2(eval);
}

void
#ifdef __STDC__
xwarn(const char *fmt, ...)
#else
xwarn(fmt, va_alist)
       const char *fmt;
       va_dcl
#endif
{
       int e = dos_errno;
       va_list ap;

       xerrprintf("%s: ", __progname);
       if (fmt) {
               VA_START(ap, fmt);
               xvfdprintf(2, fmt, ap);
               va_end(ap);
               xerrprintf(": ");
       }
       xerrprintf("%s\n", dos_strerror(e));
}

void
#ifdef __STDC__
xwarnx(const char *fmt, ...)
#else
xwarnx(fmt, va_alist)
       const char *fmt;
       va_dcl
#endif
{
       va_list ap;

       xerrprintf("%s: ", __progname);
       if (fmt) {
               VA_START(ap, fmt);
               xvfdprintf(2, fmt, ap);
               va_end(ap);
       }
       xerrprintf("\n");
}