package units

import (
	"fmt"
	"strconv"
)

func handleOverflow(val int64, mult int64) (int64, error) {
	result := val * mult
	if val == 0 || mult == 0 || val == 1 || mult == 1 {
		return result, nil
	}

	if val != 0 && (result/val) != mult {
		return -1, fmt.Errorf("Overflow multiplying %d with %d", val, mult)
	}

	return result, nil
}

// ParseByteSizeString parses a human representation of an amount of
// data into a number of bytes.
func ParseByteSizeString(input string) (int64, error) {
	// Empty input
	if input == "" {
		return 0, nil
	}

	// Find where the suffix begins
	suffixLen := 0
	for i, chr := range []byte(input) {
		_, err := strconv.Atoi(string([]byte{chr}))
		if err != nil {
			suffixLen = len(input) - i
			break
		}
	}

	if suffixLen == len(input) {
		return -1, fmt.Errorf("Invalid value: %s", input)
	}

	// Extract the suffix
	suffix := input[len(input)-suffixLen:]

	// Extract the value
	value := input[0 : len(input)-suffixLen]
	valueInt, err := strconv.ParseInt(value, 10, 64)
	if err != nil {
		return -1, fmt.Errorf("Invalid integer: %s", input)
	}

	// Figure out the multiplicator
	multiplicator := int64(0)
	switch suffix {
	case "", "B", " bytes":
		multiplicator = 1
	case "kB":
		multiplicator = 1000
	case "MB":
		multiplicator = 1000 * 1000
	case "GB":
		multiplicator = 1000 * 1000 * 1000
	case "TB":
		multiplicator = 1000 * 1000 * 1000 * 1000
	case "PB":
		multiplicator = 1000 * 1000 * 1000 * 1000 * 1000
	case "EB":
		multiplicator = 1000 * 1000 * 1000 * 1000 * 1000 * 1000
	case "KiB":
		multiplicator = 1024
	case "MiB":
		multiplicator = 1024 * 1024
	case "GiB":
		multiplicator = 1024 * 1024 * 1024
	case "TiB":
		multiplicator = 1024 * 1024 * 1024 * 1024
	case "PiB":
		multiplicator = 1024 * 1024 * 1024 * 1024 * 1024
	case "EiB":
		multiplicator = 1024 * 1024 * 1024 * 1024 * 1024 * 1024
	default:
		return -1, fmt.Errorf("Invalid value: %s", input)
	}

	return handleOverflow(valueInt, multiplicator)
}

// ParseBitSizeString parses a human representation of an amount of
// data into a number of bits.
func ParseBitSizeString(input string) (int64, error) {
	// Empty input
	if input == "" {
		return 0, nil
	}

	// Find where the suffix begins
	suffixLen := 0
	for i, chr := range []byte(input) {
		_, err := strconv.Atoi(string([]byte{chr}))
		if err != nil {
			suffixLen = len(input) - i
			break
		}
	}

	if suffixLen == len(input) {
		return -1, fmt.Errorf("Invalid value: %s", input)
	}

	// Extract the suffix
	suffix := input[len(input)-suffixLen:]

	// Extract the value
	value := input[0 : len(input)-suffixLen]
	valueInt, err := strconv.ParseInt(value, 10, 64)
	if err != nil {
		return -1, fmt.Errorf("Invalid integer: %s", input)
	}

	// Figure out the multiplicator
	multiplicator := int64(0)
	switch suffix {
	case "", "bit":
		multiplicator = 1
	case "kbit":
		multiplicator = 1000
	case "Mbit":
		multiplicator = 1000 * 1000
	case "Gbit":
		multiplicator = 1000 * 1000 * 1000
	case "Tbit":
		multiplicator = 1000 * 1000 * 1000 * 1000
	case "Pbit":
		multiplicator = 1000 * 1000 * 1000 * 1000 * 1000
	case "Ebit":
		multiplicator = 1000 * 1000 * 1000 * 1000 * 1000 * 1000
	case "Kibit":
		multiplicator = 1024
	case "Mibit":
		multiplicator = 1024 * 1024
	case "Gibit":
		multiplicator = 1024 * 1024 * 1024
	case "Tibit":
		multiplicator = 1024 * 1024 * 1024 * 1024
	case "Pibit":
		multiplicator = 1024 * 1024 * 1024 * 1024 * 1024
	case "Eibit":
		multiplicator = 1024 * 1024 * 1024 * 1024 * 1024 * 1024

	default:
		return -1, fmt.Errorf("Unsupported suffix: %s", suffix)
	}

	return handleOverflow(valueInt, multiplicator)
}

// GetByteSizeString takes a number of bytes and precision and returns a
// human representation of the amount of data.
func GetByteSizeString(input int64, precision uint) string {
	if input < 1000 {
		return fmt.Sprintf("%dB", input)
	}

	value := float64(input)

	for _, unit := range []string{"kB", "MB", "GB", "TB", "PB", "EB"} {
		value = value / 1000
		if value < 1000 {
			return fmt.Sprintf("%.*f%s", precision, value, unit)
		}
	}

	return fmt.Sprintf("%.*fEB", precision, value)
}

// GetByteSizeStringIEC takes a number of bytes and precision and returns a
// human representation of the amount of data using IEC units.
func GetByteSizeStringIEC(input int64, precision uint) string {
	if input < 1024 {
		return fmt.Sprintf("%dB", input)
	}

	value := float64(input)

	for _, unit := range []string{"KiB", "MiB", "GiB", "TiB", "PiB", "EiB"} {
		value = value / 1024
		if value < 1024 {
			return fmt.Sprintf("%.*f%s", precision, value, unit)
		}
	}

	return fmt.Sprintf("%.*fEB", precision, value)
}
