/*
 * comma - convert numbers into strings with 3-digit group and integer-fraction separators
 *
 * Convert numbers into strings with 3-digit group and integer-fraction separators.
 *
 * If the value is an integer, the integer-fraction separator is not used.
 *
 * str_comma(x, [group, [decimal]])
 *
 *      Convert x into a string.
 *
 *      If group is given and is a string, group will be used as
 *      the 3-digit group separator, otherwise the default 3-digit
 *      group separator will be used.
 *
 *      If decimal is given and is a string, group will be used as
 *      the integer-fraction separator, otherwise the default
 *      integer-fraction separator will be used.
 *
 *      The decimal and group arguments are optional.
 *
 * set_default_group_separator(group)
 *
 *      Change the default 3-digit group separator if group is a string,
 *      otherwise the default 3-digit group separator will not be
 *      changed.  Return the old 3-digit group separator.
 *
 * set_default_decimal_separator(decimal)
 *
 *      Change the default 3-digit group separator if decimal is a
 *      string, otherwise the default integer-fraction separator
 *      will not be changed.  Return the old integer-fraction separator.
 *
 * print_comma(x, [group, [decimal]])
 *
 *      Print the value produced by str_comma(x, [group, [decimal]])
 *      followed by a newline.
 *
 *      If the str_comma() does not return a string, nothing is printed.
 *
 *      The decimal and group arguments are optional.
 *
 *      The value produced by str_comma() is returned.
 *
 * fprint_comma(fd, x, [group, [decimal]])
 *
 *      Print the value produced by str_comma(x, [group, [decimal]]),
 *      without a trailing newline, on file fd.
 *
 *      If the str_comma() does not return a string, nothing is printed.
 *
 *      If fd is not an open file, nothing is printed.
 *
 *      The decimal and group arguments are optional.
 *
 *      The value produced by str_comma() is returned.
 *
 * Copyright (C) 2022  Landon Curt Noll
 *
 * 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:   2022/06/20 15:51:49
 * File existed as early as:    2022
 *
 * Share and enjoy!  :-)        http://www.isthe.com/chongo/tech/comp/calc/
 */


static default_group_separator = ",";   /* default 3-digit group separator */
static default_decimal_separator = "."; /* default integer-fraction separator */


/*
 * str_comma - convert number into base 10 string with 3-digit groups and integer-fraction separator
 *
 *
 * This function converts a real number into a base 10 string, where
 * groups of 3 digits are separated by a 3-digit group separator and
 * a integer-fraction separator is printed between integer and decimal fraction.
 *
 * For example:
 *
 *      string = str_comma(x);
 *      string = str_comma(x), " ", ".");
 *      string = str_comma(x), ".", ",");
 *
 * Internally the function calls:
 *
 *     strprintf("%f", x);
 *
 * and thus the number of decimal fraction digits is subject to
 * the display() or config("display") value.  See:
 *
 *     man display
 *
 * for details.
 *
 * given:
 *
 *      x               number to convert
 *
 * optional args:
 *
 *      group           use this 3-digit group separator
 *      decimal         use this integer-fraction separator
 *
 * returns:
 *
 *      string containing the base 10 digits with group and decimal separators, OR
 *      null() if x is not a number, OR
 *      null() if group is neither null() (not given) nor a string, OR
 *      null() if group is null() (not given) AND default_group_separator is not a string, OR
 *      null() if decimal is neither null() (not given) nor a string, OR
 *      null() if decimal is null() (not given) AND default_decimal_separator is not a string.
 */
define str_comma(x, group, decimal)
{
    local group_separator;      /* 3-digit group separator */
    local decimal_separator;    /* separator between decimal integer and decimal fraction */
    local sign_str;             /* leading - if x < 0 or empty if x >= 0 */
    local integer;              /* integer part of absolute value of x */
    local int_str;              /* integer as a string */
    local int_len;              /* number of digits in int_str */
    local first_group_len;      /* length of 1st group before the 1st 3-digit group separator */
    local fraction;             /* factional part of absolute value of x */
    local frac_str;             /* fraction as a string */
    local frac_len;             /* number of digits in frac_str including leading 0. */
    local ret;                  /* string to return */
    local config_leadzero;      /* config("leadzero") to restore */
    local config_tilde;         /* config("tilde") to restore */
    local i;

    /*
     * parse args - return null if args are bogus
     *
     * Return null() if args or conditions are bogus.
     */
    if (!isreal(x)) {
        return null();
    }
    group_separator = isnull(group) ? default_group_separator : group;
    decimal_separator = isnull(decimal) ? default_decimal_separator : decimal;
    if (!isstr(group_separator)) {
        return null();
    }
    if (!isstr(decimal_separator)) {
        return null();
    }

    /*
     * split number
     */
    if (x < 0) {
        sign_str = "-";
        integer = int(-x);
        fraction = frac(-x);
    } else {
        sign_str = "";
        integer = int(x);
        fraction = frac(x);
    }
    ret = sign_str;

    /*
     * convert digits
     *
     * Avoid printing ~ and leading zeros for factional part.
     */
    int_str = strprintf("%d", integer);
    config_leadzero = config("leadzero", 0);
    config_tilde = config("tilde", 0);
    frac_str = strprintf("%d", fraction);
    config("leadzero", config_leadzero),;
    config("tilde", config_tilde),;

    /*
     * determine number of digits in the integer part
     */
    int_len = strlen(int_str);
    frac_len = strlen(frac_str);

    /*
     * form integer part with group separators as needed
     */

    /*
     * case: integer is 3 or fewer digits
     */
    if (integer < 1000) {
        ret += int_str;

    /*
     * case: integer is 4 or more digits
     */
    } else {

        /*
         * form a decimal string using group separators
         */

        /*
         * form the initial leading digits before 1st group separator
         */
        first_group_len = int_len % 3;
        if (first_group_len == 0) {
            first_group_len = 3;
        }
        ret += substr(int_str, 1, first_group_len);

        /*
         * until end of digits, print group separator followed by 3 more digits
         */
        for (i = first_group_len+1; i < int_len; i += 3) {
            ret += group_separator + substr(int_str, i, 3);
        }
    }

    /*
     * form fractional part using decimal separator as needed
     */

    /*
     * case: x is an integer
     */
    if (fraction == 0) {

        /* no fraction, nothing more to do */

    /*
     * case: x is not an integer
     */
    } else {

        /*
         * add integer-fraction separator
         */
        ret += decimal_separator;

        /*
         * add remaining digits
         *
         * Skip over the leading 0. in frac_str
         */
        ret += substr(frac_str, 2, frac_len-1);
    }

    /*
     * All Done!!! -- Jessica Noll, Age 2
     */
    return ret;
}


/*
 * set_default_group_separator - change the default 3-digit group separator
 *
 * If group is not a string, then the default 3-digit group separator
 * is not changed.  Thus, this will only return the default 3-digit group separator:
 *
 *      set_default_group_separator(null());
 *
 * given:
 *
 *      group           3-digit group separator
 *
 * returns:
 *
 *      previous 3-digit group separator value
 */
define set_default_group_separator(group)
{
    local old_default_group_separator;          /* previous default 3-digit group separator to return */

    /*
     * save current 3-digit group separator
     */
    old_default_group_separator = default_group_separator;

    /*
     * change 3-digit group separator if group is a string
     */
    if (isstr(group)) {
        default_group_separator = group;
    }
    return old_default_group_separator;
}


/*
 * set_default_decimal_separator - change the default integer-fraction separator
 *
 * If decimal is not a string, then the default integer-fraction separator
 * is not changed.  Thus, this will only return the integer-fraction separator:
 *
 *      set_default_decimal_separator(null());
 *
 * given:
 *
 *      decimal         separator between decimal integer and decimal fraction (def: ".")
 *
 * returns:
 *
 *      previous integer-fraction separator value
 */
define set_default_decimal_separator(decimal)
{
    local old_default_decimal_separator;        /* previous default integer-fraction separator */

    /*
     * save current integer-fraction separator
     */
    old_default_decimal_separator = default_decimal_separator;

    /*
     * change 3-digit decimal integer-fraction if decimal is a string
     */
    if (isstr(decimal)) {
        default_decimal_separator = decimal;
    }
    return old_default_decimal_separator;
}


/*
 * print_comma - print base 10 string with 3-digit group separators & integer-fraction separator + newline
 *
 * This function prints the result of str_comma(x, group, decimal) followed by a newline.
 * For example:
 *
 *      print_comma(x);
 *      print_comma(x), " ", ".");
 *      print_comma(x), ".", ",");
 *
 * If str_comma() does not return a string, this function prints nothing.
 *
 * NOTE: To print without a newline, use fprint_comma(fd, x, group, decimal).
 *
 * given:
 *      x               number to convert
 *
 * optional args:
 *
 *      group           use this 3-digit group separator
 *      decimal         use this integer-fraction separator
 *
 * returns:
 *
 *      string containing the base 10 digits with group and decimal separators, OR
 *      null() if x is not a number, OR
 *      null() if group is neither null() (not given) nor a string, OR
 *      null() if group is null() (not given) AND default_group_separator is not a string, OR
 *      null() if decimal is neither null() (not given) nor a string, OR
 *      null() if decimal is null() (not given) AND default_decimal_separator is not a string.
 */
define print_comma(x, group, decimal)
{
    local ret;          /* base 10 string with 3-digit group and integer-fraction separators */

    /*
     * convert to string
     */
    ret = str_comma(x, group, decimal);

    /*
     * print converted string
     */
    if (isstr(ret)) {
        printf("%s\n", ret);
    }
    return ret;
}


/*
 * fprint_comma - print base 10 string with 3-digit group separators & integer-fraction separator w/o newline
 *
 * This function prints the result of str_comma(x, group, decimal) on an open file, without a trailing newline.
 * For example:
 *
 *      fprint_comma(files(1), x);
 *      fprint_comma(fd, x), " ", ".");
 *      fprint_comma(files(2), x), ".", ",");
 *
 * If str_comma() does not return a string, this function prints nothing.
 *
 * This function flushes output to the open file before returning.
 *
 * NOTE: To print with a newline, use print_comma(x, group, decimal).
 *
 * given:
 *      fd              open file
 *      x               number to convert
 *
 * optional args:
 *
 *      group           use this 3-digit group separator
 *      decimal         use this integer-fraction separator
 *
 * returns:
 *
 *      string containing the base 10 digits with group and integer-fraction separators, OR
 *      null() if x is not a number, OR
 *      null() if group is neither null() (not given) nor a string, OR
 *      null() if group is null() (not given) AND default_group_separator is not a string, OR
 *      null() if decimal is neither null() (not given) nor a string, OR
 *      null() if decimal is null() (not given) AND default_decimal_separator is not a string.
 */
define fprint_comma(fd, x, group, decimal)
{
    local ret;          /* base 10 string with 3-digit group and integer-fraction separators */

    /*
     * convert to string
     */
    ret = str_comma(x, group, decimal);

    /*
     * print converted string
     */
    if (isstr(ret) && isfile(fd)) {
        fprintf(fd, "%s", ret);
        fflush(fd);
    }
    return ret;
}
