#if HAVE_CONFIG_H
# include "config.h"
#endif

#if ! defined(HAVE_CONFIG_H) || HAVE_STRING_H
# include <string.h>
#endif
#include <stdio.h>		       /* for snprintf() */
#include <stdlib.h>		       /* for calloc() */

#include <gmp.h>
#include <mpfr.h>

#include "calculator.h"
#include "string_manip.h"
#include "number_formatting.h"
#ifdef MEMWATCH
#include "memwatch.h"
#endif

static size_t zero_strip(char *num);
static void add_prefix(char *num, size_t length, int base);
static char *engineering_formatted_number(char *digits, mp_exp_t exp,
					  const int precision, const int base,
					  const int prefix);
static char *full_precision_formatted_number(char *digits, mp_exp_t exp,
					     const int base,
					     const int prefix);
static char *automatically_formatted_number(char *digits, mp_exp_t exp,
					    const int base, const int prefix,
					    char *truncated_flag);
static char *precision_formatted_number(char *digits, mp_exp_t exp,
					const int precision, const int base,
					const int prefix);

/* this function takes a number (mpfr_t) and prints it.
 * This is a blatant ripoff of mpfr's mpfr_out_str(), because it formats things
 * (sorta) the way I want them formatted, though this prints things out to a
 * string, and does all the fancy presentation stuff we've come to expect from
 * wcalc.
 */
char *num_to_str_complex(const mpfr_t num, const int base, const int engr,
			 const int prec, const int prefix,
			 char *truncated_flag)
{
    char *s, *retstr;
    mp_exp_t e;

    Dprintf("num_to_str_complex: base: %i, engr: %i, prec: %i, prefix: %i\n",
	    base, engr, prec, prefix);
    if (mpfr_nan_p(num)) {
	return (char *)strdup("@NaN@");
    }
    if (mpfr_inf_p(num)) {
	if (mpfr_sgn(num) > 0) {
	    return (char *)strdup("@Inf@");
	} else {
	    return (char *)strdup("-@Inf@");
	}
    }
    if (mpfr_zero_p(num)) {
	if (mpfr_sgn(num) >= 0) {
	    return (char *)strdup("0");
	} else {
	    return (char *)strdup("-0");
	}
    }

    s = mpfr_get_str(NULL, &e, base, 0, num, GMP_RNDN);
    /* s is the string
     * e is the number of integers (the exponent) if positive
     *
     * Now, if there's odd formatting involved, make mpfr do the rounding,
     * so we know it's "correct":
     */
    if (prec > -1) {
	if (engr == 0) {
	    size_t significant_figures = 0;

	    Dprintf("prec > -1 && engr == 0\n");
	    /*printf("e: %li\n", (long)e);
	     * printf("s: %s\n", s);
	     * printf("prec: %i\n", prec); */
	    significant_figures = ((e > 0) ? e : 0) + prec;
	    if (significant_figures < 2) {	/* why is this the minimum? */
		s = mpfr_get_str(s, &e, base, 2, num, GMP_RNDN);
		if (s[1] > '4') {      /* XXX: LAME! */
		    unsigned foo;

		    foo = s[0] - '0';
		    foo++;
		    snprintf(s, 3, "%u", foo);
		    e++;
		}
	    } else {
		mpfr_free_str(s);
		s = mpfr_get_str(NULL, &e, base, significant_figures, num,
				 GMP_RNDN);
	    }
	} else {
	    s = mpfr_get_str(NULL, &e, base, 1 + prec, num, GMP_RNDN);
	}
    }
    Dprintf("post-mpfr e: %li s: %s\n", (long int)e, s);
    *truncated_flag = 0;
    if (-2 == prec) {
	retstr = full_precision_formatted_number(s, e, base, prefix);
    } else if (engr != 0) {
	retstr = engineering_formatted_number(s, e, prec, base, prefix);
    } else if (-1 == prec) {
	retstr =
	    automatically_formatted_number(s, e, base, prefix,
					   truncated_flag);
    } else {
	retstr = precision_formatted_number(s, e, prec, base, prefix);
    }
    mpfr_free_str(s);
    Dprintf("return string: %s\n", retstr);
    return retstr;
}

char *precision_formatted_number(char *digits, mp_exp_t exp,
				 const int precision, const int base,
				 const int prefix)
{
    size_t length;
    size_t full_length;
    size_t decimal_count = 0;
    size_t print_limit;
    char *retstring, *curs, *dcurs = digits;

    length = strlen(digits);
    /* testing against both zero and length because length is unsigned */
    if (exp > 0 && (size_t)exp > length)
	length = exp;
    length += 3;

    if (length < (size_t) (precision + 3)) {	// leading zero, decimal, and null
	length = (size_t) (precision + 3);
    }
    Dprintf("Precision Formatted Number\n");
    Dprintf("digits: %s(%u), exp: %i, base: %i, prefix: %i, precision: %i\n",
	    digits, (unsigned)length, (int)exp, base, prefix, precision);

    // ten extra, 'cuz of the *possible* exponent
    full_length = length + 10;
    curs = retstring = (char *)calloc(full_length, sizeof(char));
    Dprintf("length: %lu, full_length: %lu\n", length, full_length);
    // now, copy the digits into the output string, carefully

    // copy over the negative sign
    if (*dcurs == '-') {
	snprintf(curs++, length--, "%c", *dcurs++);
    }
    // copy in a prefix
    if (prefix) {
	char *nc;

	add_prefix(curs, length, base);
	nc = strchr(curs, '\0');
	length -= nc - curs;
	curs = nc;
    }
    // copy in the integers
    if (exp > 0) {
	snprintf(curs++, length--, "%c", *dcurs++);
	exp--;			       // leading digit
	while (exp > 0 && *dcurs) {
	    snprintf(curs++, length--, "%c", *dcurs++);
	    exp--;
	}
	for (; exp > 0; exp--) {
	    snprintf(curs++, length--, "0");
	}
    } else {
	snprintf(curs++, length--, "0");
    }
    if (precision > 0) {
	// the decimal
	snprintf(curs++, length--, ".");
	Dprintf("the integers: %s\n", retstring);
	Dprintf("l: %lu, fl: %lu, dc: %u, p: %i, e: %i\n", length,
		full_length, (unsigned)decimal_count, (int)precision,
		(int)exp);
	// everything after this is affected by decimalcount
	// copy in the leading zeros
	while (exp < 0 && (ssize_t)decimal_count <= precision) {
	    snprintf(curs++, length--, "0");
	    exp++;
	    decimal_count++;
	}
	Dprintf("l: %lu, fl: %lu, dc: %u, p: %i, e: %i\n", length,
		full_length, (unsigned)decimal_count, (int)precision,
		(int)exp);
	// copy in the rest of the mantissa (the decimals)
	Dprintf("leading zeros: %s\n", retstring);
	// this variable exists because snprintf's return value is unreliable,
	// and can be larger than the number of digits printed
	print_limit =
	    ((length <
	      (precision - decimal_count + 1)) ? length : (precision -
							   decimal_count +
							   1));
	snprintf(curs, print_limit, "%s", dcurs);
    }

    return retstring;
}

char *full_precision_formatted_number(char *digits, mp_exp_t exp,
				      const int base, const int prefix)
{
    size_t length;
    size_t full_length;
    size_t decimal_count = 0;
    size_t printed;
    char *retstring, *curs, *dcurs = digits;

    length = strlen(digits);
    /* testing against both zero and length because length is unsigned */
    if (exp > 0 && (size_t)exp > length)
	length = exp;
    length += 3;		       /* the null, the (possible) sign, and the decimal */

    Dprintf("Full Precision Formatted Number\n");
    Dprintf("digits: %s(%u), exp: %i, base: %i, prefix: %i\n", digits,
	    (unsigned)length, (int)exp, base, prefix);
    Dprintf("strlen(digits): %u\n", (unsigned)strlen(digits));

    // ten extra, 'cuz of the *possible* exponent
    full_length = length + 10;
    curs = retstring = (char *)calloc(sizeof(char), full_length);
    Dprintf("length: %lu, full_length: %lu\n", length, full_length);

    // now, copy the digits into the output string, carefully

    // copy over the negative sign
    if (*dcurs == '-') {
	snprintf(curs++, length--, "%c", *dcurs++);
    }
    // copy in a prefix
    if (prefix) {
	char *nc;

	add_prefix(curs, length, base);
	nc = strchr(curs, '\0');
	length -= nc - curs;
	curs = nc;
    }
    Dprintf("ready for ints: %s\n", retstring);
    // copy over the integers
    if (exp > 0) {
	snprintf(curs++, length--, "%c", *dcurs++);
	exp--;			       // leading digit
	while (exp > 0 && *dcurs) {
	    snprintf(curs++, length--, "%c", *dcurs++);
	    exp--;
	}
	for (; exp > 0; exp--) {
	    snprintf(curs++, length--, "0");
	}
    } else {
	snprintf(curs++, length--, "0");
    }
    // the decimal
    snprintf(curs++, length--, ".");
    Dprintf("the integers: %s\n", retstring);
    Dprintf("length: %lu, full_length: %lu\n", length, full_length);
    // XXX: Currently, this function is not used for decimals, so...

    // the leading decimal zeros
    while (exp < 0) {
	snprintf(curs++, length--, "0");
	exp++;
	decimal_count++;
    }
    // the rest of the mantissa (the decimals)

    // this variable exists because snprintf's return value is unreliable.
    // and can be larger than the number of digits printed
    printed = ((length - 1 < strlen(dcurs)) ? length - 1 : strlen(dcurs));
    snprintf(curs, length, "%s", dcurs);
    length -= printed;
    decimal_count += printed;

    // strip off the trailing 0's
    zero_strip(retstring);

    // copy in an exponent if necessary
    if (exp != 0) {
	curs = strchr(retstring, '\0');
	Dprintf("space left: %lu\n", full_length - (curs - retstring));
	snprintf(curs, full_length - (curs - retstring),
		 (base <= 10 ? "e%ld" : "@%ld"), (long)exp);
    }

    return retstring;
}

char *automatically_formatted_number(char *digits, mp_exp_t exp,
				     const int base, const int prefix,
				     char *truncated_flag)
{
    size_t length;
    size_t full_length;
    size_t decimal_count = 0;
    size_t printed;
    char *retstring, *curs, *dcurs = digits;

    length = strlen(digits);
    /* testing against both zero and length because length is unsigned */
    if (exp > 0 && (size_t)exp > length)
	length = exp;
    length += 3;		       /* the null, the (possible) sign, and the decimal */

    Dprintf("Automatically Formatted Number\n");
    Dprintf("digits: %s(%u), exp: %i, base: %i, prefix: %i\n", digits,
	    (unsigned)length, (int)exp, base, prefix);
    Dprintf("strlen(digits): %u\n", (unsigned)strlen(digits));

    // ten extra, 'cuz of the *possible* exponent
    full_length = length + 10;
    curs = retstring = (char *)calloc(sizeof(char), full_length);
    Dprintf("length: %lu, full_length: %lu\n", length, full_length);

    // now, copy the digits into the output string, carefully

    // copy over the negative sign
    if (*dcurs == '-') {
	snprintf(curs++, length--, "%c", *dcurs++);
    }
    // copy in a prefix
    if (prefix) {
	char *nc;

	add_prefix(curs, length, base);
	nc = strchr(curs, '\0');
	length -= nc - curs;
	curs = nc;
    }
    Dprintf("ready for ints: %s\n", retstring);
    // copy over the integers
    if (exp > 0) {
	snprintf(curs++, length--, "%c", *dcurs++);
	exp--;			       // leading digit
	while (exp > 0 && *dcurs) {
	    snprintf(curs++, length--, "%c", *dcurs++);
	    exp--;
	}
	for (; exp > 0; exp--) {
	    snprintf(curs++, length--, "0");
	}
    } else {
	snprintf(curs++, length--, "0");
    }
    // the decimal
    snprintf(curs++, length--, ".");
    Dprintf("the integers: %s\n", retstring);
    Dprintf("length: %lu, full_length: %lu\n", length, full_length);
    // XXX: Currently, this function is not used for decimals, so...

    // the leading decimal zeros
    while (exp < 0) {
	snprintf(curs++, length--, "0");
	exp++;
	decimal_count++;
    }
    // the rest of the mantissa (the decimals)

    // this variable exists because snprintf's return value is unreliable.
    // and can be larger than the number of digits printed
    printed = ((length - 1 < strlen(dcurs)) ? length - 1 : strlen(dcurs));
    snprintf(curs, length, "%s", dcurs);
    length -= printed;
    decimal_count += printed;

    // strip off the trailing 0's
    zero_strip(retstring); {	       /* XXX: This seems like a stupid hack. Is this for
				        * readability? Note to self: remove this if it
				        * ever becomes an issue again (and merge
				        * full_precision_formatted_number back with this
				        * function). */
	char *period = strchr(retstring, '.');

	Dprintf("retstring: %s\n", retstring);
	Dprintf("period: %s\n", period);
	if (period && strlen(period) > 10) {
	    period[10] = 0;
	    *truncated_flag = 1;
	    zero_strip(retstring);
	}
    }

    // copy in an exponent if necessary
    if (exp != 0) {
	curs = strchr(retstring, '\0');
	Dprintf("space left: %lu\n", full_length - (curs - retstring));
	snprintf(curs, full_length - (curs - retstring),
		 (base <= 10 ? "e%ld" : "@%ld"), (long)exp);
    }

    return retstring;
}

char *engineering_formatted_number(char *digits, mp_exp_t exp,
				   const int precision, const int base,
				   const int prefix)
{
    size_t length;
    size_t full_length;
    char *retstring, *curs, *dcurs = digits;

    length = strlen(digits);
    /* testing against both zero and length because length is unsigned */
    if (exp > 0 && (size_t)exp > length)
	length = exp;
    length += 3;		       /* the null, the (possible) sign, and the decimal */

    Dprintf("Engineering Formatted Number\n");
    Dprintf("digits: %s(%u), exp: %i, prec: %i, prefix: %i\n", digits,
	    (unsigned)length, (int)exp, precision, prefix);

    // ten extra, 'cuz of the exponent
    full_length = length + 10;
    curs = retstring = (char *)calloc(sizeof(char), full_length);
    Dprintf("length: %lu, full_length: %lu\n", length, full_length);

    // now, copy the digits into the output string, carefully

    // copy over the negative sign
    if (*dcurs == '-') {
	snprintf(curs++, length--, "%c", *dcurs++);
    }
    // copy in a prefix
    if (prefix) {
	char *nc;

	add_prefix(curs, length, base);
	nc = strchr(curs, '\0');
	length -= nc - curs;
	curs = nc;
    }
    // copy over the integer
    snprintf(curs++, length--, "%c", *dcurs++);
    exp--;
    // the decimal
    snprintf(curs++, length--, ".");
    Dprintf("the integers: %s\n", retstring);
    Dprintf("length: %lu, full_length: %lu\n", length, full_length);

    // now, add in the rest of the digits
    // note that the digits are already correctly rounded and I've already
    // allocated enough space, because of how I asked mpfr for the original
    // digit string.
    snprintf(curs, length, "%s", dcurs);
    Dprintf("the decimals: %s\n", retstring);

    // strip off the trailing 0's
    if (-1 == precision) {
	zero_strip(retstring);
    }
    // copy in an exponent
    curs = strchr(retstring, '\0');
    Dprintf("space left: %lu\n", full_length - (curs - retstring));
    snprintf(curs, full_length - (curs - retstring),
	     (base <= 10 ? "e%ld" : "@%ld"), (long)exp);

    return retstring;
}

/* this function removes zeros and decimals from the back end of a number,
 * and returns how many characters it stripped */
size_t zero_strip(char *num)
{
    size_t curs = strlen(num) - 1;
    size_t count = 0;

    while ('0' == num[curs]) {
	num[curs--] = '\0';
	count++;
    }
    if ('.' == num[curs]) {
	num[curs] = '\0';
	count++;
    }
    return count;
}

/* this function prints a prefix for the specified base into the specified
 * string */
void add_prefix(char *num, size_t length, int base)
{
    switch (base) {
	case 16:
	    snprintf(num, length, "0x");
	    return;
	case 10:
	    return;
	case 8:
	    snprintf(num, length, "0");
	    return;
	case 2:
	    snprintf(num, length, "0b");
	    return;
    }
}


syntax highlighted by Code2HTML, v. 0.9.1