//    This is part of the iostream library, providing input/output for C++.
//    Copyright (C) 1992 Per Bothner.
//
//    This library is free software; you can redistribute it and/or
//    modify it under the terms of the GNU Library General Public
//    License as published by the Free Software Foundation; either
//    version 2 of the License, or (at your option) any later version.
//
//    This library 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
//    Library General Public License for more details.
//
//    You should have received a copy of the GNU Library General Public
//    License along with this library; if not, write to the Free
//    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

#include "ioprivate.h"

// Format floating-point number and print them.
// Return number of chars printed, or EOF on error.

// sign_mode == '+' : print "-" or "+"
// sign_mode == ' ' : print "-" or " "
// sign_mode == '\0' : print "-' or ""

int __outfloat(double value, streambuf *sb, char type,
	       int width, int precision, ios::fmtflags flags,
	       char sign_mode, char fill)
{
    int count = 0;
#define PUT(x) do {if (sb->sputc(x) < 0) goto error; count++;} while (0)
#define PUTN(p, n) \
  do {int _n=n; count+=_n; if (sb->sputn(p,_n) != _n) goto error;} while(0)
#define PADN(fill, n) \
  do {int _n = n; count+=_n; if (sb->padn(fill, _n) < 0) goto error;} while (0)
    ios::fmtflags pad_kind = flags & (ios::left|ios::right|ios::internal);
    int skip_zeroes = 0;
    int show_dot = (flags & ios::showpoint) != 0;
    int decpt;
    int sign;
    int mode;
#define EBUF_SIZE 12
#define EBUF_END &ebuf[EBUF_SIZE]
    char ebuf[EBUF_SIZE];
    char *end;
    int exp = 0;
    switch (type) {
      case 'f':
	mode = 3;
	break;
      case 'F':
	exp = 'e';
	mode = 0;
	skip_zeroes = 1;
	type = 'g';
	break;
      case 'e':
	exp = 'e';
	mode = 2;
	precision++;  // Add one to include digit before decimal point.
	break;
      case 'E':
	exp = 'E';
	mode = 2;
	precision++;  // Add one to include digit before decimal point.
	break;
      case 'g':
      case 'G':
	exp = type == 'g' ? 'e' : 'E';
	if (precision == 0) precision = 1;
	if (!(flags & ios::showpoint))
	    skip_zeroes = 1;
	type = 'g';
	mode = 2;
	break;
    }
    /* Do the actual convension */
    char *p = dtoa(value, mode, precision, &decpt, &sign, &end);
    register int i;
    int useful_digits = end-p;
    char *exponent_start = EBUF_END;
    // Check if we need to emit an exponent.
    if (mode != 3 && decpt != 9999) {
	i = decpt - 1;
	if ((type != 'g' && type != 'F') || i < -4 || i >= precision) {
	    // Print the exponent into ebuf.
	    // We write ebuf in reverse order (right-to-left).
	    char sign;
	    if (i >= 0)
		sign = '+';
	    else
		sign = '-', i = -i;
	    /* Note: ANSI requires at least 2 exponent digits. */
	    do {
		*--exponent_start = (i % 10) + '0';
		i /= 10;
	    } while (i >= 10);
	    *--exponent_start = i + '0';
	    *--exponent_start = sign;
	    *--exponent_start = exp;
	}
    }
    int exponent_size = EBUF_END - exponent_start;
    if (mode == 1)
	precision = 1;
    /* If we print an exponent, always show just one digit before point. */
    if (exponent_size)
	decpt = 1;
    if (decpt == 9999) { // Infinity or NaN
	decpt = useful_digits;
	precision = 0;
	show_dot = 0;
    }

   // dtoa truncates trailing zeroes.  Set the variable trailing_zeroes to
   // the number of 0's we have to add (after the decimal point).
   int trailing_zeroes = 0;
   if (skip_zeroes)
       trailing_zeroes = 0;
   else if (type == 'f')
       trailing_zeroes = useful_digits <= decpt ? precision
	   : precision-(useful_digits-decpt);
   else if (exponent_size) // 'e' 'E' or 'g' format using exponential notation.
       trailing_zeroes = precision - useful_digits;
   else // 'g' format not using exponential notation.
       trailing_zeroes = useful_digits <= decpt ? precision - decpt
	   : precision-useful_digits;
    if (trailing_zeroes < 0) trailing_zeroes = 0;

    if (trailing_zeroes != 0 || useful_digits > decpt)
	show_dot = 1;
    int print_sign;
    if (sign_mode == 0)
	print_sign = sign ? '-' : 0;
    else if (sign_mode == '+')
	print_sign = sign ? '-' : '+';
    else /* if (sign_mode == ' ') */
	print_sign = sign ? '-' : ' ';
    
    // Calculate the width (before padding).
    int unpadded_width =
	(print_sign != 0) + trailing_zeroes + exponent_size + show_dot
	    + useful_digits
	    + (decpt > useful_digits ? decpt - useful_digits
	       : decpt > 0 ? 0 : 1 - decpt);

    int padding = width > unpadded_width ? width - unpadded_width : 0;
    if (padding > 0
	&& pad_kind != (ios::fmtflags)ios::left
	&& pad_kind != (ios::fmtflags)ios::internal) // Default (right) adjust.
	PADN(fill, padding);
    if (print_sign)
	PUT(print_sign);
    if (pad_kind == (ios::fmtflags)ios::internal && padding > 0)
	PADN(fill, padding);
    if (decpt > 0) {
	if (useful_digits >= decpt)
	    PUTN(p, decpt);
	else {
	    PUTN(p, useful_digits);
	    PADN('0', decpt-useful_digits);
	}
	if (show_dot) {
	    PUT('.');
	    // Print digits after the decimal point.
	    if (useful_digits > decpt)
		PUTN(p + decpt, useful_digits-decpt);
	}
    }
    else {
	PUT('0');
	if (show_dot) {
	    PUT('.');
	    PADN('0', -decpt);
	    // Print digits after the decimal point.
	    PUTN(p, useful_digits);
	}
    }
    PADN('0', trailing_zeroes);
    if (exponent_size)
	PUTN(exponent_start, exponent_size);
    if (pad_kind == (ios::fmtflags)ios::left && padding > 0) // Left adjustment
	PADN(fill, padding);
    return count;
  error:
    return EOF;
}
