// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file implements the 'e', 'f', 'g' floating-point formats.
// It is closely following the corresponding implementation in
// strconv/ftoa.go, but modified and simplified for big.Float.

// Algorithm:
//   1) convert Float to multiprecision decimal
//   2) round to desired precision
//   3) read digits out and format

package big

import "strconv"

// TODO(gri) Consider moving sign into decimal - could make the signatures below cleaner.

// bigFtoa formats a float for the %e, %E, %f, %g, and %G formats.
func (f *Float) bigFtoa(buf []byte, fmt byte, prec int) []byte {
	if debugFloat && f.IsInf() {
		panic("non-finite float")
	}

	// 1) convert Float to multiprecision decimal
	var mant nat
	if f.form == finite {
		mant = f.mant
	}
	var d decimal
	d.init(mant, int(f.exp)-f.mant.bitLen())

	// 2) round to desired precision
	shortest := false
	if prec < 0 {
		shortest = true
		panic("unimplemented")
		// TODO(gri) complete this
		// roundShortest(&d, f.mant, int(f.exp))
		// Precision for shortest representation mode.
		switch fmt {
		case 'e', 'E':
			prec = len(d.mant) - 1
		case 'f':
			prec = max(len(d.mant)-d.exp, 0)
		case 'g', 'G':
			prec = len(d.mant)
		}
	} else {
		// round appropriately
		switch fmt {
		case 'e', 'E':
			// one digit before and number of digits after decimal point
			d.round(1 + prec)
		case 'f':
			// number of digits before and after decimal point
			d.round(d.exp + prec)
		case 'g', 'G':
			if prec == 0 {
				prec = 1
			}
			d.round(prec)
		}
	}

	// 3) read digits out and format
	switch fmt {
	case 'e', 'E':
		return fmtE(buf, fmt, prec, f.neg, d)
	case 'f':
		return fmtF(buf, prec, f.neg, d)
	case 'g', 'G':
		// trim trailing fractional zeros in %e format
		eprec := prec
		if eprec > len(d.mant) && len(d.mant) >= d.exp {
			eprec = len(d.mant)
		}
		// %e is used if the exponent from the conversion
		// is less than -4 or greater than or equal to the precision.
		// If precision was the shortest possible, use eprec = 6 for
		// this decision.
		if shortest {
			eprec = 6
		}
		exp := d.exp - 1
		if exp < -4 || exp >= eprec {
			if prec > len(d.mant) {
				prec = len(d.mant)
			}
			return fmtE(buf, fmt+'e'-'g', prec-1, f.neg, d)
		}
		if prec > d.exp {
			prec = len(d.mant)
		}
		return fmtF(buf, max(prec-d.exp, 0), f.neg, d)
	}

	// unknown format
	return append(buf, '%', fmt)
}

// %e: -d.ddddde±dd
func fmtE(buf []byte, fmt byte, prec int, neg bool, d decimal) []byte {
	// sign
	if neg {
		buf = append(buf, '-')
	}

	// first digit
	ch := byte('0')
	if len(d.mant) > 0 {
		ch = d.mant[0]
	}
	buf = append(buf, ch)

	// .moredigits
	if prec > 0 {
		buf = append(buf, '.')
		i := 1
		m := min(len(d.mant), prec+1)
		if i < m {
			buf = append(buf, d.mant[i:m]...)
			i = m
		}
		for ; i <= prec; i++ {
			buf = append(buf, '0')
		}
	}

	// e±
	buf = append(buf, fmt)
	var exp int64
	if len(d.mant) > 0 {
		exp = int64(d.exp) - 1 // -1 because first digit was printed before '.'
	}
	if exp < 0 {
		ch = '-'
		exp = -exp
	} else {
		ch = '+'
	}
	buf = append(buf, ch)

	// dd...d
	if exp < 10 {
		buf = append(buf, '0') // at least 2 exponent digits
	}
	return strconv.AppendInt(buf, exp, 10)
}

// %f: -ddddddd.ddddd
func fmtF(buf []byte, prec int, neg bool, d decimal) []byte {
	// sign
	if neg {
		buf = append(buf, '-')
	}

	// integer, padded with zeros as needed
	if d.exp > 0 {
		m := min(len(d.mant), d.exp)
		buf = append(buf, d.mant[:m]...)
		for ; m < d.exp; m++ {
			buf = append(buf, '0')
		}
	} else {
		buf = append(buf, '0')
	}

	// fraction
	if prec > 0 {
		buf = append(buf, '.')
		for i := 0; i < prec; i++ {
			ch := byte('0')
			if j := d.exp + i; 0 <= j && j < len(d.mant) {
				ch = d.mant[j]
			}
			buf = append(buf, ch)
		}
	}

	return buf
}

func min(x, y int) int {
	if x < y {
		return x
	}
	return y
}
