package fixedn

import (
	"errors"
	"fmt"
	"math/big"
	"strconv"
	"strings"
)

const maxAllowedPrecision = 16

// ErrInvalidFormat is returned when decimal format is invalid.
var ErrInvalidFormat = errors.New("invalid decimal format")

var _pow10 []*big.Int

func init() {
	var p = int64(1)
	for range maxAllowedPrecision + 1 {
		_pow10 = append(_pow10, big.NewInt(p))
		p *= 10
	}
}

func pow10(n int) *big.Int {
	last := len(_pow10) - 1
	if n <= last {
		return _pow10[n]
	}
	p := new(big.Int)
	p.Mul(_pow10[last], _pow10[1])
	for i := last + 1; i < n; i++ {
		p.Mul(p, _pow10[1])
	}
	return p
}

// ToString converts a big decimal with the specified precision to a string.
func ToString(bi *big.Int, precision int) string {
	var dp, fp big.Int
	dp.QuoRem(bi, pow10(precision), &fp)

	var s = dp.String()
	if fp.Sign() == 0 {
		return s
	}
	frac := fp.Uint64()
	trimmed := 0
	for ; frac%10 == 0; frac /= 10 {
		trimmed++
	}
	return s + "." + fmt.Sprintf("%0"+strconv.FormatUint(uint64(precision-trimmed), 10)+"d", frac)
}

// FromString converts a string to a big decimal with the specified precision.
func FromString(s string, precision int) (*big.Int, error) {
	parts := strings.SplitN(s, ".", 2)
	bi, ok := new(big.Int).SetString(parts[0], 10)
	if !ok {
		return nil, ErrInvalidFormat
	}
	bi.Mul(bi, pow10(precision))
	if len(parts) == 1 {
		return bi, nil
	}

	if len(parts[1]) > precision {
		return nil, ErrInvalidFormat
	}
	fp, ok := new(big.Int).SetString(parts[1], 10)
	if !ok {
		return nil, ErrInvalidFormat
	}
	fp.Mul(fp, pow10(precision-len(parts[1])))
	if bi.Sign() == -1 {
		return bi.Sub(bi, fp), nil
	}
	return bi.Add(bi, fp), nil
}
