package msgp

import (
	"math"
	"math/bits"
	"strconv"
)

// The portable parts of the Number implementation

// Number can be
// an int64, uint64, float32,
// or float64 internally.
// It can decode itself
// from any of the native
// messagepack number types.
// The zero-value of Number
// is Int(0). Using the equality
// operator with Number compares
// both the type and the value
// of the number.
type Number struct {
	// internally, this
	// is just a tagged union.
	// the raw bits of the number
	// are stored the same way regardless.
	bits uint64
	typ  Type
}

// AsInt sets the number to an int64.
func (n *Number) AsInt(i int64) {
	// we always store int(0)
	// as {0, InvalidType} in
	// order to preserve
	// the behavior of the == operator
	if i == 0 {
		n.typ = InvalidType
		n.bits = 0
		return
	}

	n.typ = IntType
	n.bits = uint64(i)
}

// AsUint sets the number to a uint64.
func (n *Number) AsUint(u uint64) {
	n.typ = UintType
	n.bits = u
}

// AsFloat32 sets the value of the number
// to a float32.
func (n *Number) AsFloat32(f float32) {
	n.typ = Float32Type
	n.bits = uint64(math.Float32bits(f))
}

// AsFloat64 sets the value of the
// number to a float64.
func (n *Number) AsFloat64(f float64) {
	n.typ = Float64Type
	n.bits = math.Float64bits(f)
}

// Int casts the number as an int64, and
// returns whether or not that was the
// underlying type.
func (n *Number) Int() (int64, bool) {
	return int64(n.bits), n.typ == IntType || n.typ == InvalidType
}

// Uint casts the number as a uint64, and returns
// whether or not that was the underlying type.
func (n *Number) Uint() (uint64, bool) {
	return n.bits, n.typ == UintType
}

// Float casts the number to a float64, and
// returns whether that was the underlying
// type (either a float64 or a float32).
func (n *Number) Float() (float64, bool) {
	switch n.typ {
	case Float32Type:
		return float64(math.Float32frombits(uint32(n.bits))), true
	case Float64Type:
		return math.Float64frombits(n.bits), true
	default:
		return 0.0, false
	}
}

// Type will return one of:
// Float64Type, Float32Type, UintType, or IntType.
func (n *Number) Type() Type {
	if n.typ == InvalidType {
		return IntType
	}
	return n.typ
}

// DecodeMsg implements msgp.Decodable
func (n *Number) DecodeMsg(r *Reader) error {
	typ, err := r.NextType()
	if err != nil {
		return err
	}
	switch typ {
	case Float32Type:
		f, err := r.ReadFloat32()
		if err != nil {
			return err
		}
		n.AsFloat32(f)
		return nil
	case Float64Type:
		f, err := r.ReadFloat64()
		if err != nil {
			return err
		}
		n.AsFloat64(f)
		return nil
	case IntType:
		i, err := r.ReadInt64()
		if err != nil {
			return err
		}
		n.AsInt(i)
		return nil
	case UintType:
		u, err := r.ReadUint64()
		if err != nil {
			return err
		}
		n.AsUint(u)
		return nil
	default:
		return TypeError{Encoded: typ, Method: IntType}
	}
}

// UnmarshalMsg implements msgp.Unmarshaler
func (n *Number) UnmarshalMsg(b []byte) ([]byte, error) {
	typ := NextType(b)
	switch typ {
	case IntType:
		i, o, err := ReadInt64Bytes(b)
		if err != nil {
			return b, err
		}
		n.AsInt(i)
		return o, nil
	case UintType:
		u, o, err := ReadUint64Bytes(b)
		if err != nil {
			return b, err
		}
		n.AsUint(u)
		return o, nil
	case Float64Type:
		f, o, err := ReadFloat64Bytes(b)
		if err != nil {
			return b, err
		}
		n.AsFloat64(f)
		return o, nil
	case Float32Type:
		f, o, err := ReadFloat32Bytes(b)
		if err != nil {
			return b, err
		}
		n.AsFloat32(f)
		return o, nil
	default:
		return b, TypeError{Method: IntType, Encoded: typ}
	}
}

// MarshalMsg implements msgp.Marshaler
func (n *Number) MarshalMsg(b []byte) ([]byte, error) {
	switch n.typ {
	case IntType:
		return AppendInt64(b, int64(n.bits)), nil
	case UintType:
		return AppendUint64(b, n.bits), nil
	case Float64Type:
		return AppendFloat64(b, math.Float64frombits(n.bits)), nil
	case Float32Type:
		return AppendFloat32(b, math.Float32frombits(uint32(n.bits))), nil
	default:
		return AppendInt64(b, 0), nil
	}
}

// EncodeMsg implements msgp.Encodable
func (n *Number) EncodeMsg(w *Writer) error {
	switch n.typ {
	case IntType:
		return w.WriteInt64(int64(n.bits))
	case UintType:
		return w.WriteUint64(n.bits)
	case Float64Type:
		return w.WriteFloat64(math.Float64frombits(n.bits))
	case Float32Type:
		return w.WriteFloat32(math.Float32frombits(uint32(n.bits)))
	default:
		return w.WriteInt64(0)
	}
}

// CoerceInt attempts to coerce the value of
// the number into a signed integer and returns
// whether it was successful.
// "Success" implies that no precision in the value of
// the number was lost, which means that the number was an integer or
// a floating point that mapped exactly to an integer without rounding.
func (n *Number) CoerceInt() (int64, bool) {
	switch n.typ {
	case InvalidType, IntType:
		// InvalidType just means un-initialized.
		return int64(n.bits), true
	case UintType:
		return int64(n.bits), n.bits <= math.MaxInt64
	case Float32Type:
		f := math.Float32frombits(uint32(n.bits))
		if n.isExactInt() && f <= math.MaxInt64 && f >= math.MinInt64 {
			return int64(f), true
		}
		if n.bits == 0 || n.bits == 1<<31 {
			return 0, true
		}
	case Float64Type:
		f := math.Float64frombits(n.bits)
		if n.isExactInt() && f <= math.MaxInt64 && f >= math.MinInt64 {
			return int64(f), true
		}
		return 0, n.bits == 0 || n.bits == 1<<63
	}
	return 0, false
}

// CoerceUInt attempts to coerce the value of
// the number into an unsigned integer and returns
// whether it was successful.
// "Success" implies that no precision in the value of
// the number was lost, which means that the number was an integer or
// a floating point that mapped exactly to an integer without rounding.
func (n *Number) CoerceUInt() (uint64, bool) {
	switch n.typ {
	case InvalidType, IntType:
		// InvalidType just means un-initialized.
		if int64(n.bits) >= 0 {
			return n.bits, true
		}
	case UintType:
		return n.bits, true
	case Float32Type:
		f := math.Float32frombits(uint32(n.bits))
		if f >= 0 && f <= math.MaxUint64 && n.isExactInt() {
			return uint64(f), true
		}
		if n.bits == 0 || n.bits == 1<<31 {
			return 0, true
		}
	case Float64Type:
		f := math.Float64frombits(n.bits)
		if f >= 0 && f <= math.MaxUint64 && n.isExactInt() {
			return uint64(f), true
		}
		return 0, n.bits == 0 || n.bits == 1<<63
	}
	return 0, false
}

// isExactInt will return true if the number represents an integer value.
// NaN, Inf returns false.
func (n *Number) isExactInt() bool {
	var eBits int // Exponent bits
	var mBits int // Mantissa bits

	switch n.typ {
	case InvalidType, IntType, UintType:
		return true
	case Float32Type:
		eBits = 8
		mBits = 23
	case Float64Type:
		eBits = 11
		mBits = 52
	default:
		return false
	}
	// Calculate float parts
	exp := int(n.bits>>mBits) & ((1 << eBits) - 1)
	mant := n.bits & ((1 << mBits) - 1)
	if exp == 0 && mant == 0 {
		// Handle zero value.
		return true
	}

	exp -= (1 << (eBits - 1)) - 1
	if exp < 0 || exp == 1<<(eBits-1) {
		// Negative exponent is never integer (except zero handled above)
		// Handles NaN (exp all 1s)
		return false
	}

	if exp >= mBits {
		// If we have more exponent than mantissa bits it is always an integer.
		return true
	}
	// Check if all bits below the exponent are zero.
	return bits.TrailingZeros64(mant) >= mBits-exp
}

// CoerceFloat returns the number as a float64.
// If the number is an integer, it will be
// converted to a float64 with the closest representation.
func (n *Number) CoerceFloat() float64 {
	switch n.typ {
	case IntType:
		return float64(int64(n.bits))
	case UintType:
		return float64(n.bits)
	case Float32Type:
		return float64(math.Float32frombits(uint32(n.bits)))
	case Float64Type:
		return math.Float64frombits(n.bits)
	default:
		return 0.0
	}
}

// Msgsize implements msgp.Sizer
func (n *Number) Msgsize() int {
	switch n.typ {
	case Float32Type:
		return Float32Size
	case Float64Type:
		return Float64Size
	case IntType:
		return Int64Size
	case UintType:
		return Uint64Size
	default:
		return 1 // fixint(0)
	}
}

// MarshalJSON implements json.Marshaler
func (n *Number) MarshalJSON() ([]byte, error) {
	t := n.Type()
	if t == InvalidType {
		return []byte{'0'}, nil
	}
	out := make([]byte, 0, 32)
	switch t {
	case Float32Type, Float64Type:
		f, _ := n.Float()
		return strconv.AppendFloat(out, f, 'f', -1, 64), nil
	case IntType:
		i, _ := n.Int()
		return strconv.AppendInt(out, i, 10), nil
	case UintType:
		u, _ := n.Uint()
		return strconv.AppendUint(out, u, 10), nil
	default:
		panic("(*Number).typ is invalid")
	}
}

// String implements fmt.Stringer
func (n *Number) String() string {
	switch n.typ {
	case InvalidType:
		return "0"
	case Float32Type, Float64Type:
		f, _ := n.Float()
		return strconv.FormatFloat(f, 'f', -1, 64)
	case IntType:
		i, _ := n.Int()
		return strconv.FormatInt(i, 10)
	case UintType:
		u, _ := n.Uint()
		return strconv.FormatUint(u, 10)
	default:
		panic("(*Number).typ is invalid")
	}
}
