package ber

import (
	"bytes"
	"errors"
	"fmt"
	"math"
	"strconv"
	"strings"
)

func encodeFloat(v float64) []byte {
	switch {
	case math.IsInf(v, 1):
		return []byte{0x40}
	case math.IsInf(v, -1):
		return []byte{0x41}
	case math.IsNaN(v):
		return []byte{0x42}
	case v == 0.0:
		if math.Signbit(v) {
			return []byte{0x43}
		}
		return []byte{}
	default:
		// we take the easy part ;-)
		value := []byte(strconv.FormatFloat(v, 'G', -1, 64))
		var ret []byte
		if bytes.Contains(value, []byte{'E'}) {
			ret = []byte{0x03}
		} else {
			ret = []byte{0x02}
		}
		ret = append(ret, value...)
		return ret
	}
}

func ParseReal(v []byte) (val float64, err error) {
	if len(v) == 0 {
		return 0.0, nil
	}
	switch {
	case v[0]&0x80 == 0x80:
		val, err = parseBinaryFloat(v)
	case v[0]&0xC0 == 0x40:
		val, err = parseSpecialFloat(v)
	case v[0]&0xC0 == 0x0:
		val, err = parseDecimalFloat(v)
	default:
		return 0.0, fmt.Errorf("invalid info block")
	}
	if err != nil {
		return 0.0, err
	}

	if val == 0.0 && !math.Signbit(val) {
		return 0.0, errors.New("REAL value +0 must be encoded with zero-length value block")
	}
	return val, nil
}

func parseBinaryFloat(v []byte) (float64, error) {
	var info byte
	var buf []byte

	info, v = v[0], v[1:]

	var base int
	switch info & 0x30 {
	case 0x00:
		base = 2
	case 0x10:
		base = 8
	case 0x20:
		base = 16
	case 0x30:
		return 0.0, errors.New("bits 6 and 5 of information octet for REAL are equal to 11")
	}

	scale := uint((info & 0x0c) >> 2)

	var expLen int
	switch info & 0x03 {
	case 0x00:
		expLen = 1
	case 0x01:
		expLen = 2
	case 0x02:
		expLen = 3
	case 0x03:
		if len(v) < 2 {
			return 0.0, errors.New("invalid data")
		}
		expLen = int(v[0])
		if expLen > 8 {
			return 0.0, errors.New("too big value of exponent")
		}
		v = v[1:]
	}
	if expLen > len(v) {
		return 0.0, errors.New("too big value of exponent")
	}
	buf, v = v[:expLen], v[expLen:]
	exponent, err := ParseInt64(buf)
	if err != nil {
		return 0.0, err
	}

	if len(v) > 8 {
		return 0.0, errors.New("too big value of mantissa")
	}

	mant, err := ParseInt64(v)
	if err != nil {
		return 0.0, err
	}
	mantissa := mant << scale

	if info&0x40 == 0x40 {
		mantissa = -mantissa
	}

	return float64(mantissa) * math.Pow(float64(base), float64(exponent)), nil
}

func parseDecimalFloat(v []byte) (val float64, err error) {
	switch v[0] & 0x3F {
	case 0x01: // NR form 1
		var iVal int64
		iVal, err = strconv.ParseInt(strings.TrimLeft(string(v[1:]), " "), 10, 64)
		val = float64(iVal)
	case 0x02, 0x03: // NR form 2, 3
		val, err = strconv.ParseFloat(strings.Replace(strings.TrimLeft(string(v[1:]), " "), ",", ".", -1), 64)
	default:
		err = errors.New("incorrect NR form")
	}
	if err != nil {
		return 0.0, err
	}

	if val == 0.0 && math.Signbit(val) {
		return 0.0, errors.New("REAL value -0 must be encoded as a special value")
	}
	return val, nil
}

func parseSpecialFloat(v []byte) (float64, error) {
	if len(v) != 1 {
		return 0.0, errors.New(`encoding of "special value" must not contain exponent and mantissa`)
	}
	switch v[0] {
	case 0x40:
		return math.Inf(1), nil
	case 0x41:
		return math.Inf(-1), nil
	case 0x42:
		return math.NaN(), nil
	case 0x43:
		return math.Copysign(0, -1), nil
	}
	return 0.0, errors.New(`encoding of "special value" not from ASN.1 standard`)
}
