/*
 * zio - scanf and printf routines for arbitrary precision integers
 *
 * Copyright (C) 1999-2007,2021-2023  David I. Bell
 *
 * Calc is open software; you can redistribute it and/or modify it under
 * the terms of the version 2.1 of the GNU Lesser General Public License
 * as published by the Free Software Foundation.
 *
 * Calc 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.
 *
 * A copy of version 2.1 of the GNU Lesser General Public License is
 * distributed with calc under the filename COPYING-LGPL.  You should have
 * received a copy with calc; if not, write to Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Under source code control:   1993/07/30 19:42:48
 * File existed as early as:    1993
 *
 * Share and enjoy!  :-)        http://www.isthe.com/chongo/tech/comp/calc/
 */


#include <stdio.h>
#include "alloc.h"
#include "config.h"
#include "zmath.h"
#include "args.h"


#include "errtbl.h"
#include "banned.h"     /* include after system header <> includes */


#define OUTBUFSIZE      200             /* realloc size for output buffers */

#define PUTCHAR(ch)             math_chr(ch)
#define PUTSTR(str)             math_str(str)
#define PRINTF1(fmt, a1)                math_fmt(fmt, a1)
#define PRINTF2(fmt, a1, a2)            math_fmt(fmt, a1, a2)
#define PRINTF3(fmt, a1, a2, a3)        math_fmt(fmt, a1, a2, a3)
#define PRINTF4(fmt, a1, a2, a3, a4)    math_fmt(fmt, a1, a2, a3, a4)


/*
 * Output state that has been saved when diversions are done.
 */
typedef struct iostate IOSTATE;
struct iostate {
        IOSTATE *oldiostates;           /* previous saved state */
        long outdigits;                 /* digits for output */
        int outmode;                    /* output mode */
        int outmode2;                   /* secondary output mode */
        FILE *outfp;                    /* file unit for output (if any) */
        char *outbuf;                   /* output string buffer (if any) */
        size_t outbufsize;              /* current size of string buffer */
        size_t outbufused;              /* space used in string buffer */
        bool outputisstring;            /* true if output is to string buffer */
};


STATIC IOSTATE  *oldiostates = NULL;    /* list of saved output states */
STATIC FILE     *outfp = NULL;          /* file unit for output */
STATIC char     *outbuf = NULL;         /* current diverted buffer */
STATIC bool     outputisstring = false;
STATIC size_t   outbufsize;
STATIC size_t   outbufused;


/*
 * zio_init - perform needed initialization work
 *
 * On some systems, one cannot initialize a pointer to a FILE *.
 * This routine, called once at startup is a work-a-round for
 * systems with such bogons.
 */
void
zio_init(void)
{
        STATIC int done = 0;    /* 1 => routine already called */

        if (!done) {
                outfp = stdout;
                done = 1;
        }
}


/*
 * Routine to output a character either to a FILE
 * handle or into a string.
 */
void
math_chr(int ch)
{
        char    *cp;

        if (!outputisstring) {
                fputc(ch, outfp);
                return;
        }
        if (outbufused >= outbufsize) {
                cp = (char *)realloc(outbuf, outbufsize + OUTBUFSIZE + 1);
                if (cp == NULL) {
                        math_error("Cannot realloc output string");
                        not_reached();
                }
                outbuf = cp;
                outbufsize += OUTBUFSIZE;
        }
        outbuf[outbufused++] = (char)ch;
}


/*
 * Routine to output a null-terminated string either
 * to a FILE handle or into a string.
 */
void
math_str(char *str)
{
        char    *cp;
        size_t  len;

        /* firewall */
        if (str == NULL) {
                math_error("%s: str NULL", __func__);
                not_reached();
        }

        if (!outputisstring) {
                fputs(str, outfp);
                return;
        }
        len = strlen(str);
        if ((outbufused + len) > outbufsize) {
                cp = (char *)realloc(outbuf, outbufsize + len + OUTBUFSIZE + 1);
                if (cp == NULL) {
                        math_error("Cannot realloc output string");
                        not_reached();
                }
                outbuf = cp;
                outbufsize += (len + OUTBUFSIZE);
        }
        memcpy(&outbuf[outbufused], str, len);
        outbufused += len;
}


/*
 * Output a null-terminated string either to a FILE handle or into a string,
 * padded with spaces as needed so as to fit within the specified width.
 * If width is positive, the spaces are added at the front of the string.
 * If width is negative, the spaces are added at the end of the string.
 * The complete string is always output, even if this overflows the width.
 * No characters within the string are handled specially.
 */
void
math_fill(char *str, long width)
{
        /* firewall */
        if (str == NULL) {
                math_error("%s: str NULL", __func__);
                not_reached();
        }

        if (width > 0) {
                width -= (long)strlen(str);
                while (width-- > 0)
                        PUTCHAR(' ');
                PUTSTR(str);
        } else {
                width += (long)strlen(str);
                PUTSTR(str);
                while (width++ < 0)
                        PUTCHAR(' ');
        }
}


/*
 * Routine to output a printf-style formatted string either
 * to a FILE handle or into a string.
 */
void
math_fmt(char *fmt, ...)
{
        va_list ap;
        char buf[BUFSIZ+1];

        /* firewall */
        if (fmt == NULL) {
                math_error("%s: fmt NULL", __func__);
                not_reached();
        }

        va_start(ap, fmt);
        vsnprintf(buf, BUFSIZ, fmt, ap);
        va_end(ap);
        buf[BUFSIZ] = '\0';     /* paranoia */
        math_str(buf);
}


/*
 * Flush the current output stream.
 */
void
math_flush(void)
{
        if (!outputisstring)
                fflush(outfp);
}


/*
 * Divert further output so that it is saved into a string that will be
 * returned later when the diversion is completed.  The current state of
 * output is remembered for later restoration.  Diversions can be nested.
 * Output diversion is only intended for saving output to "stdout".
 */
void
math_divertio(void)
{
        register IOSTATE *sp;

        sp = (IOSTATE *) malloc(sizeof(IOSTATE));
        if (sp == NULL) {
                math_error("No memory for diverting output");
                not_reached();
        }
        sp->oldiostates = oldiostates;
        sp->outdigits = conf->outdigits;
        sp->outmode = conf->outmode;
        sp->outmode2 = conf->outmode2;
        sp->outfp = outfp;
        sp->outbuf = outbuf;
        sp->outbufsize = outbufsize;
        sp->outbufused = outbufused;
        sp->outputisstring = outputisstring;

        outbufused = 0;
        outbufsize = 0;
        outbuf = (char *) malloc(OUTBUFSIZE + 1);
        if (outbuf == NULL) {
                math_error("Cannot allocate divert string");
                not_reached();
        }
        outbufsize = OUTBUFSIZE;
        outputisstring = true;
        oldiostates = sp;
}


/*
 * Un-divert output and return the saved output as a string.  This also
 * restores the output state to what it was before the diversion began.
 * The string needs freeing by the caller when it is no longer needed.
 */
char *
math_getdivertedio(void)
{
        register IOSTATE *sp;
        char *cp;

        sp = oldiostates;
        if (sp == NULL) {
                math_error("No diverted state to restore");
                not_reached();
        }
        cp = outbuf;
        cp[outbufused] = '\0';
        oldiostates = sp->oldiostates;
        conf->outdigits = sp->outdigits;
        conf->outmode = sp->outmode;
        conf->outmode2 = sp->outmode2;
        outfp = sp->outfp;
        outbuf = sp->outbuf;
        outbufsize = sp->outbufsize;
        outbufused = sp->outbufused;
        outbuf = sp->outbuf;
        outputisstring = sp->outputisstring;
        free(sp);
        return cp;
}


/*
 * Clear all diversions and set output back to the original destination.
 * This is called when resetting the global state of the program.
 */
void
math_cleardiversions(void)
{
        while (oldiostates)
                free(math_getdivertedio());
}


/*
 * Set the output routines to output to the specified FILE stream.
 * This interacts with output diversion in the following manner.
 *      STDOUT  diversion       action
 *      ----    ---------       ------
 *      yes     yes             set output to diversion string again.
 *      yes     no              set output to stdout.
 *      no      yes             set output to specified file.
 *      no      no              set output to specified file.
 */
void
math_setfp(FILE *newfp)
{
        /* firewall */
        if (newfp == NULL) {
                math_error("%s: newfp NULL", __func__);
                not_reached();
        }

        outfp = newfp;
        outputisstring = (oldiostates && (newfp == stdout));
}


/*
 * Set the output mode for numeric output.
 * This also returns the previous mode.
 */
int
math_setmode(int newmode)
{
        int oldmode;

        if ((newmode <= MODE_DEFAULT) || (newmode > MODE_MAX)) {
                math_error("Setting illegal output mode");
                not_reached();
        }
        oldmode = conf->outmode;
        conf->outmode = newmode;
        return oldmode;
}


/*
 * Set the secondary output mode for numeric output.
 * This also returns the previous mode.
 */
int
math_setmode2(int newmode)
{
        int oldmode;

        if (newmode != MODE2_OFF && ((newmode <= MODE_DEFAULT) ||
            (newmode > MODE_MAX))) {
                math_error("Setting illegal secondary output mode");
                not_reached();
        }
        oldmode = conf->outmode2;
        conf->outmode2 = newmode;
        return oldmode;
}


/*
 * Set the number of digits for float or exponential output.
 * This also returns the previous number of digits.
 */
LEN
math_setdigits(LEN newdigits)
{
        LEN olddigits;

        if (newdigits < 0) {
                math_error("Setting illegal number of digits");
                not_reached();
        }
        olddigits = conf->outdigits;
        conf->outdigits = newdigits;
        return olddigits;
}


/*
 * Print an integer value as a hex number.
 * Width is the number of columns to print the number in, including the
 * sign if required.  If zero, no extra output is done.  If positive,
 * leading spaces are typed if necessary. If negative, trailing spaces are
 * typed if necessary.  The special characters 0x appear to indicate the
 * number is hex.
 */
/*ARGSUSED*/
void
zprintx(ZVALUE z, long width)
{
        register HALF *hp;      /* current word to print */
        int len;                /* number of halfwords to type */
        char *str;

        if (width) {
                math_divertio();
                zprintx(z, 0L);
                str = math_getdivertedio();
                math_fill(str, width);
                free(str);
                return;
        }
        len = z.len - 1;
        if (zisneg(z))
                PUTCHAR('-');
        if ((len == 0) && (*z.v <= (HALF) 9)) {
                len = '0' + (int)(*z.v);
                PUTCHAR(len & 0xff);
                return;
        }
        hp = z.v + len;
#if BASEB == 32
        PRINTF1("0x%lx", (PRINT) *hp--);
        while (--len >= 0) {
                PRINTF1("%08lx", (PRINT) *hp--);
        }
#else /* BASEB == 32 */
        PRINTF1("0x%lx", (FULL) *hp--);
        while (--len >= 0) {
                PRINTF1("%04lx", (FULL) *hp--);
        }
#endif /* BASEB == 32 */
}


/*
 * Print an integer value as a binary number.
 * The special characters 0b appear to indicate the number is binary.
 */
/*ARGSUSED*/
void
zprintb(ZVALUE z, long width)
{
        register HALF *hp;      /* current word to print */
        int len;                /* number of halfwords to type */
        HALF val;               /* current value */
        HALF mask;              /* current mask */
        int didprint;           /* nonzero if printed some digits */
        int ch;                 /* current char */
        char *str;

        if (width) {
                math_divertio();
                zprintb(z, 0L);
                str = math_getdivertedio();
                math_fill(str, width);
                free(str);
                return;
        }
        len = z.len - 1;
        if (zisneg(z))
                PUTCHAR('-');
        if ((len == 0) && (*z.v <= (FULL) 1)) {
                len = '0' + (int)(*z.v);
                PUTCHAR(len & 0xff);
                return;
        }
        hp = z.v + len;
        didprint = 0;
        PUTSTR("0b");
        while (len-- >= 0) {
                val = ((len >= 0) ? *hp-- : *hp);
                mask = ((HALF)1 << (BASEB - 1));
                while (mask) {
                        ch = '0' + ((mask & val) != 0);
                        if (didprint || (ch != '0')) {
                                PUTCHAR(ch & 0xff);
                                didprint = 1;
                        }
                        mask >>= 1;
                }
        }
}


/*
 * Print an integer value as an octal number.
 * The number begins with a leading 0 to indicate that it is octal.
 */
/*ARGSUSED*/
void
zprinto(ZVALUE z, long width)
{
        register HALF *hp;      /* current word to print */
        int len;                /* number of halfwords to type */
#if BASEB == 32                 /* Yes, the larger base needs a smaller type! */
        HALF num1='0';          /* numbers to type */
        HALF num2=(HALF)0;      /* numbers to type */
        HALF num3;              /* numbers to type */
        HALF num4;              /* numbers to type */
#else
        FULL num1='0';          /* numbers to type */
        FULL num2=(FULL)0;      /* numbers to type */
#endif
        int rem;                /* remainder number of halfwords */
        char *str;

        if (width) {
                math_divertio();
                zprinto(z, 0L);
                str = math_getdivertedio();
                math_fill(str, width);
                free(str);
                return;
        }
        if (zisneg(z))
                PUTCHAR('-');
        len = z.len;
        if ((len == 1) && (*z.v <= (FULL) 7)) {
                num1 = '0' + (int)(*z.v);
                PUTCHAR((int)(num1 & 0xff));
                return;
        }
        hp = z.v + len - 1;
        rem = len % 3;
#if BASEB == 32
        switch (rem) {  /* handle odd amounts first */
        case 0:
                num1 = ((hp[0]) >> 8);
                num2 = (((hp[0] & 0xff) << 16) + (hp[-1] >> 16));
                num3 = (((hp[-1] & 0xffff) << 8) + (hp[-2] >> 24));
                num4 = (hp[-2] & 0xffffff);
                if (num1) {
                        PRINTF4("0%lo%08lo%08lo%08lo",
                            (PRINT) num1, (PRINT) num2,
                            (PRINT) num3, (PRINT) num4);
                } else {
                        PRINTF3("0%lo%08lo%08lo",
                            (PRINT) num2, (PRINT) num3, (PRINT) num4);
                }
                rem = 3;
                break;
        case 1:
                PRINTF1("0%lo", (PRINT) hp[0]);
                break;
        case 2:
                num1 = ((hp[0]) >> 16);
                num2 = (((hp[0] & 0xffff) << 8) + (hp[-1] >> 24));
                num3 = (hp[-1] & 0xffffff);
                if (num1) {
                        PRINTF3("0%lo%08lo%08lo",
                            (PRINT) num1, (PRINT) num2, (PRINT) num3);
                } else {
                        PRINTF2("0%lo%08lo", (PRINT) num2, (PRINT) num3);
                }
                break;
        }
        len -= rem;
        if (len > 0) {
                hp -= rem;
                while (len > 0) {       /* finish in groups of 3 words */
                        PRINTF4("%08lo%08lo%08lo%08lo",
                            (PRINT) ((hp[0]) >> 8),
                            (PRINT) (((hp[0] & 0xff) << 16) + (hp[-1] >> 16)),
                            (PRINT) (((hp[-1] & 0xffff) << 8) + (hp[-2] >> 24)),
                            (PRINT) (hp[-2] & 0xffffff));
                        hp -= 3;
                        len -= 3;
                }
        }
#else
        switch (rem) {  /* handle odd amounts first */
        case 0:
                num1 = ((((FULL) hp[0]) << 8) + (((FULL) hp[-1]) >> 8));
                num2 = ((((FULL) (hp[-1] & 0xff)) << 16) + ((FULL) hp[-2]));
                rem = 3;
                break;
        case 1:
                num1 = 0;
                num2 = (FULL) hp[0];
                break;
        case 2:
                num1 = (((FULL) hp[0]) >> 8);
                num2 = ((((FULL) (hp[0] & 0xff)) << 16) + ((FULL) hp[-1]));
                break;
        }
        if (num1) {
                PRINTF2("0%lo%08lo", num1, num2);
        } else {
                PRINTF1("0%lo", num2);
        }
        len -= rem;
        if (len > 0) {
                hp -= rem;
                while (len > 0) {       /* finish in groups of 3 halfwords */
                        PRINTF2("%08lo%08lo",
                            ((((FULL) hp[0]) << 8) + (((FULL) hp[-1]) >> 8)),
                            ((((FULL) (hp[-1] & 0xff))<<16) + ((FULL) hp[-2])));
                        hp -= 3;
                        len -= 3;
                }
        }
#endif
}


/*
 * Print a decimal integer to the terminal.
 * This works by dividing the number by 10^2^N for some N, and
 * then doing this recursively on the quotient and remainder.
 * Decimals supplies number of decimal places to print, with a decimal
 * point at the right location, with zero meaning no decimal point.
 * Width is the number of columns to print the number in, including the
 * decimal point and sign if required.  If zero, no extra output is done.
 * If positive, leading spaces are typed if necessary. If negative, trailing
 * spaces are typed if necessary.  As examples of the effects of these values,
 * (345,0,0) = "345", (345,2,0) = "3.45", (345,5,8) = "  .00345".
 *
 * given:
 *      z               number to be printed
 *      decimals        number of decimal places
 *      width           number of columns to print in
 */
void
zprintval(ZVALUE z, long decimals, long width)
{
        int depth;              /* maximum depth */
        int n;                  /* current index into array */
        long i;                 /* number to print */
        long leadspaces;        /* number of leading spaces to print */
        long putpoint;          /* digits until print decimal point */
        long digits;            /* number of digits of raw number */
        bool output;            /* true if have output something */
        bool neg;               /* true if negative */
        ZVALUE quo, rem;        /* quotient and remainder */
        ZVALUE leftnums[32];    /* left parts of the number */
        ZVALUE rightnums[32];   /* right parts of the number */

        if (decimals < 0)
                decimals = 0;
        if (width < 0)
                width = 0;
        neg = (z.sign != 0);

        leadspaces = width - neg - (decimals > 0);
        z.sign = 0;
        /*
         * Find the 2^N power of ten which is greater than or equal
         * to the number, calculating it the first time if necessary.
         */
        _tenpowers_[0] = _ten_;
        depth = 0;
        while ((_tenpowers_[depth].len < z.len) ||
               (zrel(_tenpowers_[depth], z) <= 0)) {
                depth++;
                if (_tenpowers_[depth].len == 0) {
                        if (depth <= TEN_MAX) {
                                zsquare(_tenpowers_[depth-1],
                                         &_tenpowers_[depth]);
                        } else {
                                math_error("cannot compute 10^2^(TEN_MAX+1)");
                                not_reached();
                        }
                }
        }
        /*
         * Divide by smaller 2^N powers of ten until the parts are small
         * enough to output.  This algorithm walks through a binary tree
         * where each node is a piece of the number to print, and such that
         * we visit left nodes first.  We do the needed recursion in line.
         */
        digits = 1;
        output = false;
        n = 0;
        putpoint = 0;
        rightnums[0].len = 0;
        leftnums[0] = z;
        for (;;) {
                while (n < depth) {
                        i = depth - n - 1;
                        zdiv(leftnums[n], _tenpowers_[i], &quo, &rem, 0);
                        if (!ziszero(quo))
                                digits += (1L << i);
                        n++;
                        leftnums[n] = quo;
                        rightnums[n] = rem;
                }
                i = (long)(leftnums[n].v[0]);
                if (output || i || (n == 0)) {
                        if (!output) {
                                output = true;
                                if (decimals < digits)
                                        leadspaces -= digits;
                                else
                                        leadspaces -= decimals+conf->leadzero;
                                while (--leadspaces >= 0)
                                        PUTCHAR(' ');
                                if (neg)
                                        PUTCHAR('-');
                                if (decimals) {
                                        putpoint = (digits - decimals);
                                        if (putpoint <= 0) {
                                                if (conf->leadzero)
                                                        PUTCHAR('0');
                                                PUTCHAR('.');
                                                while (++putpoint <= 0)
                                                        PUTCHAR('0');
                                                putpoint = 0;
                                        }
                                }
                        }
                        i += '0';
                        PUTCHAR((int)(i & 0xff));
                        if (--putpoint == 0)
                                PUTCHAR('.');
                }
                while (rightnums[n].len == 0) {
                        if (n <= 0)
                                return;
                        if (leftnums[n].len)
                                zfree(leftnums[n]);
                        n--;
                }
                zfree(leftnums[n]);
                leftnums[n] = rightnums[n];
                rightnums[n].len = 0;
        }
}


/*
 * Read an integer value in decimal, hex, octal, or binary.
 * Hex numbers are indicated by a leading "0x", binary with a leading "0b",
 * and octal by a leading "0".  Periods are skipped over, but any other
 * extraneous character stops the scan.
 */
void
str2z(char *s, ZVALUE *res)
{
        ZVALUE z, ztmp, digit;
        HALF digval;
        bool minus;
        long shift;

        /* firewall */
        if (s == NULL) {
                math_error("%s: s NULL", __func__);
                not_reached();
        }
        if (res == NULL) {
                math_error("%s: res NULL", __func__);
                not_reached();
        }

        minus = false;
        shift = 0;
        if (*s == '+')
                s++;
        else if (*s == '-') {
                minus = true;
                s++;
        }
        if (*s == '0') {                /* possibly hex, octal, or binary */
                s++;
                if ((*s >= '0') && (*s <= '7')) {
                        shift = 3;
                } else if ((*s == 'x') || (*s == 'X')) {
                        shift = 4;
                        s++;
                } else if ((*s == 'b') || (*s == 'B')) {
                        shift = 1;
                        s++;
                }
        }
        digit.v = &digval;
        digit.len = 1;
        digit.sign = 0;
        z = _zero_;
        while (*s) {
                digval = *s++;
                if ((digval >= '0') && (digval <= '9'))
                        digval -= '0';
                else if ((digval >= 'a') && (digval <= 'f') && shift)
                        digval -= ('a' - 10);
                else if ((digval >= 'A') && (digval <= 'F') && shift)
                        digval -= ('A' - 10);
                else if (digval == '.')
                        continue;
                else
                        break;
                if (shift)
                        zshift(z, shift, &ztmp);
                else
                        zmuli(z, 10L, &ztmp);
                zfree(z);
                zadd(ztmp, digit, &z);
                zfree(ztmp);
        }
        ztrim(&z);
        if (minus && !ziszero(z))
                z.sign = 1;
        *res = z;
}


void
fitzprint(ZVALUE z, long digits, long show)
{
        ZVALUE ztmp1, ztmp2;
        long i;

        if (digits <= show) {
                zprintval(z, 0, 0);
                return;
        }
        show /= 2;
        ztenpow(digits - show, &ztmp1);
        (void) zquo(z, ztmp1, &ztmp2, 1);
        zprintval(ztmp2, 0, 0);
        zfree(ztmp1);
        zfree(ztmp2);
        printf("...");
        ztenpow(show, &ztmp1);
        (void) zmod(z, ztmp1, &ztmp2, 0);
        i = zdigits(ztmp2);
        while (i++ < show)
                printf("0");
        zprintval(ztmp2, 0, 0);
        zfree(ztmp1);
        zfree(ztmp2);
}

/* END CODE */
