/*
 * qio - scanf and printf routines for arbitrary precision rational numbers
 *
 * Copyright (C) 1999-2007,2021-2023,2026  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:46
 * File existed as early as:    1993
 *
 * Share and enjoy!  :-)        http://www.isthe.com/chongo/tech/comp/calc/
 */

/*
 * important <system> header includes
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdbool.h>

/*
 * calc local src includes
 */
#include "zmath.h"
#include "qmath.h"
#include "config.h"
#include "have_unused.h"
#include "attribute.h"
#include "errtbl.h"

#include "banned.h" /* include after all other includes */

#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)

static long scalefactor;
static ZVALUE scalenumber = {0, 0, 0};

/*
 * Print a formatted string containing arbitrary numbers, similar to printf.
 * ALL numeric arguments to this routine are rational NUMBERs.
 * Various forms of printing such numbers are supplied, in addition
 * to strings and characters.  Output can actually be to any FILE
 * stream or a string.
 */
void
qprintf(char *fmt, ...)
{
    va_list ap;
    NUMBER *q;
    int ch, sign = 1;
    long width = 0, precision = 0;
    int trigger = 0;

    va_start(ap, fmt);
    while ((ch = *fmt++) != '\0') {
        if (trigger == 0) {
            if (ch == '\\') {
                ch = *fmt++;
                switch (ch) {
                case 'a':
                    ch = '\a';
                    break;
                case 'b':
                    ch = '\b';
                    break;
                case 'f':
                    ch = '\f';
                    break;
                case 'n':
                    ch = '\n';
                    break;
                case 'r':
                    ch = '\r';
                    break;
                case 't':
                    ch = '\t';
                    break;
                case 'v':
                    ch = '\v';
                    break;
                case 0:
                    va_end(ap);
                    return;
                }
                PUTCHAR(ch);
                continue;
            }
            if (ch != '%') {
                PUTCHAR(ch);
                continue;
            }
            ch = *fmt++;
            width = 0;
            precision = 8;
            sign = 1;
            trigger = 1;
        }

        switch (ch) {
        case 'd':
            q = va_arg(ap, NUMBER *);
            qprintfd(q, width);
            break;
        case 'f':
            q = va_arg(ap, NUMBER *);
            qprintff(q, width, precision);
            break;
        case 'e':
            q = va_arg(ap, NUMBER *);
            qprintfe(q, width, precision);
            break;
        case 'g':
            q = va_arg(ap, NUMBER *);
#if 0 /* XXX - we need a qprintfg() function */
            qprintfg(q, width, precision);
#else
            qprintfe(q, width, precision);
#endif
            break;
        case 'r':
        case 'R':
            q = va_arg(ap, NUMBER *);
            qprintfr(q, width, (bool)(ch == 'R'));
            break;
        case 'N':
            q = va_arg(ap, NUMBER *);
            zprintval(q->num, 0L, width);
            break;
        case 'D':
            q = va_arg(ap, NUMBER *);
            zprintval(q->den, 0L, width);
            break;
        case 'o':
            q = va_arg(ap, NUMBER *);
            qprintfo(q, width);
            break;
        case 'x':
            q = va_arg(ap, NUMBER *);
            qprintfx(q, width);
            break;
        case 'b':
            q = va_arg(ap, NUMBER *);
            qprintfb(q, width);
            break;
        case 's':
            PUTSTR(va_arg(ap, char *));
            break;
        case 'c':
            PUTCHAR(va_arg(ap, int));
            break;
        case 0:
            va_end(ap);
            return;
        case '-':
            sign = -1;
            ch = *fmt++;
            /*FALLTHRU*/
        default:
            if (('0' <= ch && ch <= '9') || ch == '.' || ch == '*') {
                if (ch == '*') {
                    q = va_arg(ap, NUMBER *);
                    width = sign * qtoi(q);
                    ch = *fmt++;
                } else if (ch != '.') {
                    width = ch - '0';
                    while ('0' <= (ch = *fmt++) && ch <= '9') {
                        width = width * 10 + ch - '0';
                    }
                    width *= sign;
                }
                if (ch == '.') {
                    if ((ch = *fmt++) == '*') {
                        q = va_arg(ap, NUMBER *);
                        precision = qtoi(q);
                        ch = *fmt++;
                    } else {
                        precision = 0;
                        while ('0' <= (ch = *fmt++) && ch <= '9') {
                            precision *= 10 + ch - '0';
                        }
                    }
                }
            }
        }
    }
    va_end(ap);
}

/*
 * Print a number in the specified output mode.
 * If MODE_DEFAULT is given, then the default output mode is used.
 * Any approximate output is flagged with a leading tilde.
 * Integers are always printed as themselves.
 */
void
qprintnum(NUMBER *q, int outmode, LEN outdigits)
{
    NUMBER tmpval;
    long prec, exp;
    int outmode2 = MODE2_OFF;

    if (outmode == MODE_DEFAULT) {
        outmode = conf->outmode;
        outmode2 = conf->outmode2;
    }
    switch (outmode) {
    case MODE_INT:
        if (conf->tilde_ok && qisfrac(q)) {
            PUTCHAR('~');
            if (conf->tilde_space && qisfrac(q)) {
                PUTCHAR(' ');
            }
        }
        qprintfd(q, 0L);
        break;

    case MODE_REAL:
        prec = qdecplaces(q);
        if ((prec < 0) || (prec > outdigits)) {
            if (conf->tilde_ok) {
                PUTCHAR('~');
                if (conf->tilde_space) {
                    PUTCHAR(' ');
                }
            }
        }
        if (conf->fullzero || (prec < 0) || (prec > outdigits)) {
            prec = outdigits;
        }
        qprintff(q, 0L, prec);
        break;

    case MODE_FRAC:
        qprintfr(q, 0L, false);
        break;

    case MODE_EXP:
        if (qiszero(q)) {
            PUTCHAR('0');
            return;
        }
        tmpval = *q;
        tmpval.num.sign = 0;
        exp = qilog10(&tmpval);
        if (exp == 0) { /* in range to output as real */
            qprintnum(q, MODE_REAL, outdigits);
            return;
        }
        tmpval.num = _one_;
        tmpval.den = _one_;
        if (exp > 0) {
            ztenpow(exp, &tmpval.den);
        } else {
            ztenpow(-exp, &tmpval.num);
        }
        q = qmul(q, &tmpval);
        zfree(tmpval.num);
        zfree(tmpval.den);
        qprintnum(q, MODE_REAL, outdigits);
        qfree(q);
        PRINTF1("e%ld", exp);
        break;

    case MODE_ENG:
        if (qiszero(q)) {
            PUTCHAR('0');
            return;
        }
        tmpval = *q;
        tmpval.num.sign = 0;
        exp = qilog10(&tmpval);
        if (exp == 0) { /* in range to output as real */
            qprintnum(q, MODE_REAL, outdigits);
            return;
        }
        tmpval.num = _one_;
        tmpval.den = _one_;
        if (exp > 0) {
            exp -= exp % 3;
            ztenpow(exp, &tmpval.den);
        } else {
            long remainder = exp % 3;
            if (remainder) {
                exp -= remainder + 3;
            }
            ztenpow(-exp, &tmpval.num);
        }
        q = qmul(q, &tmpval);
        zfree(tmpval.num);
        zfree(tmpval.den);
        qprintnum(q, MODE_REAL, outdigits);
        qfree(q);
        if (exp) {
            PRINTF1("e%ld", exp);
        }
        break;

    case MODE_REAL_AUTO: {
        const int P = conf->outdigits ? conf->outdigits : 1;
        tmpval = *q;
        tmpval.num.sign = 0;
        exp = qilog10(&tmpval);
        if (P > exp && exp >= -P) {
            qprintnum(q, MODE_REAL, P - 1 - exp);
        } else {
            qprintnum(q, MODE_EXP, P - 1);
        }
        break;
    }

    case MODE_HEX:
        qprintfx(q, 0L);
        break;

    case MODE_OCTAL:
        qprintfo(q, 0L);
        break;

    case MODE_BINARY:
        qprintfb(q, 0L);
        break;

    default:
        math_error("Bad mode for print");
        not_reached();
    }

    if (outmode2 != MODE2_OFF) {
        PUTSTR(" /* ");
        qprintnum(q, outmode2, outdigits);
        PUTSTR(" */");
    }
}

/*
 * Print a number in floating point representation.
 * Example:  193.784
 */
void
qprintff(NUMBER *q, long width, long precision)
{
    ZVALUE z, z1;

    if (precision != scalefactor) {
        if (scalenumber.v) {
            zfree(scalenumber);
        }
        ztenpow(precision, &scalenumber);
        scalefactor = precision;
    }
    if (scalenumber.v) {
        zmul(q->num, scalenumber, &z);
    } else {
        z = q->num;
    }
    if (qisfrac(q)) {
        zquo(z, q->den, &z1, conf->outround);
        if (z.v != q->num.v) {
            zfree(z);
        }
        z = z1;
    }
    if (qisneg(q) && ziszero(z)) {
        PUTCHAR('-');
    }
    zprintval(z, precision, width);
    if (z.v != q->num.v) {
        zfree(z);
    }
}

/*
 * Print a number in exponential notation.
 * Example: 4.1856e34
 */
/*ARGSUSED*/
void
qprintfe(NUMBER *q, long UNUSED(width), long precision)
{
    long exponent;
    NUMBER q2;
    ZVALUE num, zden, tenpow, tmp;

    if (qiszero(q)) {
        PUTSTR("0.0");
        return;
    }
    num = q->num;
    zden = q->den;
    num.sign = 0;
    exponent = zdigits(num) - zdigits(zden);
    if (exponent > 0) {
        ztenpow(exponent, &tenpow);
        zmul(zden, tenpow, &tmp);
        zfree(tenpow);
        zden = tmp;
    }
    if (exponent < 0) {
        ztenpow(-exponent, &tenpow);
        zmul(num, tenpow, &tmp);
        zfree(tenpow);
        num = tmp;
    }
    if (zrel(num, zden) < 0) {
        zmuli(num, 10L, &tmp);
        if (num.v != q->num.v) {
            zfree(num);
        }
        num = tmp;
        exponent--;
    }
    q2.num = num;
    q2.den = zden;
    q2.num.sign = q->num.sign;
    qprintff(&q2, 0L, precision);
    if (exponent) {
        PRINTF1("e%ld", exponent);
    }
    if (num.v != q->num.v) {
        zfree(num);
    }
    if (zden.v != q->den.v) {
        zfree(zden);
    }
}

/*
 * Print a number in rational representation.
 * Example: 397/37
 */
void
qprintfr(NUMBER *q, long width, bool force)
{
    zprintval(q->num, 0L, width);
    if (force || qisfrac(q)) {
        if (conf->fraction_space) {
            PUTCHAR(' ');
        }
        PUTCHAR('/');
        if (conf->fraction_space) {
            PUTCHAR(' ');
        }
        zprintval(q->den, 0L, width);
    }
}

/*
 * Print a number as an integer (truncating fractional part).
 * Example: 958421
 */
void
qprintfd(NUMBER *q, long width)
{
    ZVALUE z;

    if (qisfrac(q)) {
        zquo(q->num, q->den, &z, conf->outround);
        zprintval(z, 0L, width);
        zfree(z);
    } else {
        zprintval(q->num, 0L, width);
    }
}

/*
 * Print a number in hex.
 * This prints the numerator and denominator in hex.
 */
void
qprintfx(NUMBER *q, long width)
{
    zprintx(q->num, width);
    if (qisfrac(q)) {
        if (conf->fraction_space) {
            PUTCHAR(' ');
        }
        PUTCHAR('/');
        if (conf->fraction_space) {
            PUTCHAR(' ');
        }
        zprintx(q->den, 0L);
    }
}

/*
 * Print a number in binary.
 * This prints the numerator and denominator in binary.
 */
void
qprintfb(NUMBER *q, long width)
{
    zprintb(q->num, width);
    if (qisfrac(q)) {
        if (conf->fraction_space) {
            PUTCHAR(' ');
        }
        PUTCHAR('/');
        if (conf->fraction_space) {
            PUTCHAR(' ');
        }
        zprintb(q->den, 0L);
    }
}

/*
 * Print a number in octal.
 * This prints the numerator and denominator in octal.
 */
void
qprintfo(NUMBER *q, long width)
{
    zprinto(q->num, width);
    if (qisfrac(q)) {
        if (conf->fraction_space) {
            PUTCHAR(' ');
        }
        PUTCHAR('/');
        if (conf->fraction_space) {
            PUTCHAR(' ');
        }
        zprinto(q->den, 0L);
    }
}

/*
 * Convert a string to a number in rational, floating point,
 * exponential notation, hex, or octal.
 *      q = str2q(string);
 */
NUMBER *
str2q(char *s)
{
    register NUMBER *q;
    register char *t;
    ZVALUE div, newnum, newden, tmp;
    long decimals, exp;
    bool hex, negexp;

    q = qalloc();
    decimals = 0;
    exp = 0;
    negexp = false;
    hex = false;
    t = s;
    if ((*t == '+') || (*t == '-')) {
        t++;
    }
    if ((*t == '0') && ((t[1] == 'x') || (t[1] == 'X'))) {
        hex = true;
        t += 2;
    }
    while (((*t >= '0') && (*t <= '9')) || (hex && (((*t >= 'a') && (*t <= 'f')) || ((*t >= 'A') && (*t <= 'F'))))) {
        t++;
    }
    if (*t == '/') {
        t++;
        str2z(t, &q->den);
    } else if ((*t == '.') || (*t == 'e') || (*t == 'E')) {
        if (*t == '.') {
            t++;
            while ((*t >= '0') && (*t <= '9')) {
                t++;
                decimals++;
            }
        }
        /*
         * Parse exponent if any
         */
        if ((*t == 'e') || (*t == 'E')) {
            t++;
            if (*t == '+') {
                t++;
            } else if (*t == '-') {
                negexp = true;
                t++;
            }
            while ((*t >= '0') && (*t <= '9')) {
                exp = (exp * 10) + *t++ - '0';
                if (exp > (MAXLONG / 10L)) {
                    math_error("Exponent too large");
                    not_reached();
                }
            }
        }
        ztenpow(decimals, &q->den);
    }
    str2z(s, &q->num);
    if (qiszero(q)) {
        qfree(q);
        return qlink(&_qzero_);
    }
    /*
     * Apply the exponential if any
     */
    if (exp) {
        ztenpow(exp, &tmp);
        if (negexp) {
            zmul(q->den, tmp, &newden);
            zfree(q->den);
            q->den = newden;
        } else {
            zmul(q->num, tmp, &newnum);
            zfree(q->num);
            q->num = newnum;
        }
        zfree(tmp);
    }
    /*
     * Reduce the fraction to lowest terms
     */
    if (!zisunit(q->num) && !zisunit(q->den)) {
        zgcd(q->num, q->den, &div);
        if (!zisunit(div)) {
            zequo(q->num, div, &newnum);
            zfree(q->num);
            zequo(q->den, div, &newden);
            zfree(q->den);
            q->num = newnum;
            q->den = newden;
        }
        zfree(div);
    }
    return q;
}

/*
 * Parse a number in any of the various legal forms, and return the count
 * of characters that are part of a legal number.  Numbers can be either a
 * decimal integer, possibly two decimal integers separated with a slash, a
 * floating point or exponential number, a hex number beginning with "0x",
 * a binary number beginning with "0b", or an octal number beginning with "0".
 * The flags argument modifies the end of number testing for ease in handling
 * fractions or complex numbers.  Minus one is returned if the number format
 * is definitely illegal.
 */
long
qparse(char *cp, int flags)
{
    char *oldcp;

    oldcp = cp;
    if ((*cp == '+') || (*cp == '-')) {
        cp++;
    }
    if ((*cp == '+') || (*cp == '-')) {
        return -1;
    }

    /* hex */
    if ((*cp == '0') && ((cp[1] == 'x') || (cp[1] == 'X'))) {
        cp += 2;
        while (((*cp >= '0') && (*cp <= '9')) || ((*cp >= 'a') && (*cp <= 'f')) || ((*cp >= 'A') && (*cp <= 'F'))) {
            cp++;
        }
        if (((*cp == 'i') || (*cp == 'I')) && (flags & QPF_IMAG)) {
            cp++;
        }
        if ((*cp == '.') || ((*cp == '/') && (flags & QPF_SLASH)) || ((*cp >= '0') && (*cp <= '9')) ||
            ((*cp >= 'a') && (*cp <= 'z')) || ((*cp >= 'A') && (*cp <= 'Z'))) {
            return -1;
        }
        return (cp - oldcp);
    }

    /* binary */
    if ((*cp == '0') && ((cp[1] == 'b') || (cp[1] == 'B'))) {
        cp += 2;
        while ((*cp == '0') || (*cp == '1')) {
            cp++;
        }
        if (((*cp == 'i') || (*cp == 'I')) && (flags & QPF_IMAG)) {
            cp++;
        }
        if ((*cp == '.') || ((*cp == '/') && (flags & QPF_SLASH)) || ((*cp >= '0') && (*cp <= '9')) ||
            ((*cp >= 'a') && (*cp <= 'z')) || ((*cp >= 'A') && (*cp <= 'Z'))) {
            return -1;
        }
        return (cp - oldcp);
    }

    /* octal */
    if ((*cp == '0') && (cp[1] >= '0') && (cp[1] <= '9')) {
        while ((*cp >= '0') && (*cp <= '7')) {
            cp++;
        }
        if (((*cp == 'i') || (*cp == 'I')) && (flags & QPF_IMAG)) {
            cp++;
        }
        if ((*cp == '.') || ((*cp == '/') && (flags & QPF_SLASH)) || ((*cp >= '0') && (*cp <= '9')) ||
            ((*cp >= 'a') && (*cp <= 'z')) || ((*cp >= 'A') && (*cp <= 'Z'))) {
            return -1;
        }
        return (cp - oldcp);
    }

    /*
     * Number is decimal but can still be a fraction or real or exponential
     */
    while ((*cp >= '0') && (*cp <= '9')) {
        cp++;
    }
    if (*cp == '/' && flags & QPF_SLASH) { /* fraction */
        cp++;
        while ((*cp >= '0') && (*cp <= '9')) {
            cp++;
        }
        if (((*cp == 'i') || (*cp == 'I')) && (flags & QPF_IMAG)) {
            cp++;
        }
        if ((*cp == '.') || ((*cp == '/') && (flags & QPF_SLASH)) || ((*cp >= '0') && (*cp <= '9')) ||
            ((*cp >= 'a') && (*cp <= 'z')) || ((*cp >= 'A') && (*cp <= 'Z'))) {
            return -1;
        }
        return (cp - oldcp);
    }
    if (*cp == '.') { /* floating point */
        cp++;
        while ((*cp >= '0') && (*cp <= '9')) {
            cp++;
        }
    }
    if ((*cp == 'e') || (*cp == 'E')) { /* exponential */
        cp++;
        if ((*cp == '+') || (*cp == '-')) {
            cp++;
        }
        if ((*cp == '+') || (*cp == '-')) {
            return -1;
        }
        while ((*cp >= '0') && (*cp <= '9')) {
            cp++;
        }
    }

    if (((*cp == 'i') || (*cp == 'I')) && (flags & QPF_IMAG)) {
        cp++;
    }
    if ((*cp == '.') || ((*cp == '/') && (flags & QPF_SLASH)) || ((*cp >= '0') && (*cp <= '9')) ||
        ((*cp >= 'a') && (*cp <= 'z')) || ((*cp >= 'A') && (*cp <= 'Z'))) {
        return -1;
    }
    return (cp - oldcp);
}

/*
 * Print an integer which is guaranteed to fit in the specified number
 * of columns, using embedded '...' characters if numerator and/or
 * denominator is too large.
 */
void
fitprint(NUMBER *q, long width)
{
    long numdigits, dendigits, digits;
    long width1, width2;
    long n, k;

    if (width < 8) {
        width = 8;
    }
    numdigits = zdigits(q->num);
    n = numdigits;
    k = 0;
    while (++k, n) {
        n /= 10;
    }
    if (qisint(q)) {
        width -= k;
        k = 16 - k;
        if (k < 2) {
            k = 2;
        }
        PRINTF1("(%ld)", numdigits);
        while (k-- > 0) {
            PUTCHAR(' ');
        }
        fitzprint(q->num, numdigits, width);
        return;
    }
    dendigits = zdigits(q->den);
    PRINTF2("(%ld/%ld)", numdigits, dendigits);
    digits = numdigits + dendigits;
    n = dendigits;
    while (++k, n) {
        n /= 10;
    }
    width -= k;
    k = 16 - k;
    if (k < 2) {
        k = 2;
    }
    while (k-- > 0) {
        PUTCHAR(' ');
    }
    if (digits <= width) {
        qprintf("%r", q);
        return;
    }
    width1 = (width * numdigits) / digits;
    if (width1 < 8) {
        width1 = 8;
    }
    width2 = width - width1;
    if (width2 < 8) {
        width2 = 8;
        width1 = width - width2;
    }
    fitzprint(q->num, numdigits, width1);
    if (conf->fraction_space) {
        PUTCHAR(' ');
    }
    PUTCHAR('/');
    if (conf->fraction_space) {
        PUTCHAR(' ');
    }
    fitzprint(q->den, dendigits, width2);
}
