package decimal

import (
	"math"
	"strconv"
	"strings"
)

// GASPrecision contains precision for NEO Gas token.
const GASPrecision = 8

// Zero is empty Decimal value.
var Zero = &Decimal{}

// New returns new Decimal (in satoshi).
func New(v int64) *Decimal {
	return NewWithPrecision(v, GASPrecision)
}

// NewGAS returns new Decimal * 1e8 (in GAS).
func NewGAS(v int64) *Decimal {
	v *= int64(math.Pow10(GASPrecision))
	return NewWithPrecision(v, GASPrecision)
}

// NewWithPrecision returns new Decimal with custom precision.
func NewWithPrecision(v int64, p uint32) *Decimal {
	return &Decimal{Value: v, Precision: p}
}

// ParseFloat return new Decimal parsed from float64 * 1e8 (in GAS).
func ParseFloat(v float64) *Decimal {
	return new(Decimal).Parse(v, GASPrecision)
}

// ParseFloatWithPrecision returns new Decimal parsed from float64 * 1^p.
func ParseFloatWithPrecision(v float64, p int) *Decimal {
	return new(Decimal).Parse(v, p)
}

// Copy returns copy of current Decimal.
func (m *Decimal) Copy() *Decimal { return &Decimal{Value: m.Value, Precision: m.Precision} }

// Parse returns parsed Decimal from float64 * 1^p.
func (m *Decimal) Parse(v float64, p int) *Decimal {
	m.Value = int64(v * math.Pow10(p))
	m.Precision = uint32(p)
	return m
}

// String returns string representation of Decimal.
func (m Decimal) String() string {
	buf := new(strings.Builder)
	val := m.Value
	dec := int64(math.Pow10(int(m.Precision)))
	if val < 0 {
		buf.WriteRune('-')
		val = -val
	}
	str := strconv.FormatInt(val/dec, 10)
	buf.WriteString(str)
	val %= dec
	if val > 0 {
		buf.WriteRune('.')
		str = strconv.FormatInt(val, 10)
		for i := len(str); i < int(m.Precision); i++ {
			buf.WriteRune('0')
		}
		buf.WriteString(strings.TrimRight(str, "0"))
	}
	return buf.String()
}

// Add returns d + m.
func (m Decimal) Add(d *Decimal) *Decimal {
	precision := m.Precision
	if precision < d.Precision {
		precision = d.Precision
	}
	return &Decimal{
		Value:     m.Value + d.Value,
		Precision: precision,
	}
}

// Zero checks that Decimal is empty.
func (m Decimal) Zero() bool { return m.Value == 0 }

// Equal checks that current Decimal is equal to passed Decimal.
func (m Decimal) Equal(v *Decimal) bool { return m.Value == v.Value && m.Precision == v.Precision }

// GT checks that m > v.
func (m Decimal) GT(v *Decimal) bool { return m.Value > v.Value }

// GTE checks that m >= v.
func (m Decimal) GTE(v *Decimal) bool { return m.Value >= v.Value }

// LT checks that m < v.
func (m Decimal) LT(v *Decimal) bool { return m.Value < v.Value }

// LTE checks that m <= v.
func (m Decimal) LTE(v *Decimal) bool { return m.Value <= v.Value }

// Neg returns negative representation of current Decimal (m * -1).
func (m Decimal) Neg() *Decimal {
	return &Decimal{
		Value:     m.Value * -1,
		Precision: m.Precision,
	}
}