package utils

import (
	"fmt"
	"regexp"
	"strconv"
	"strings"
)

type StorageCapacityUnit uint64

const (
	Byte StorageCapacityUnit = 1
	KB   StorageCapacityUnit = Byte * 1000
	MB   StorageCapacityUnit = KB * 1000
	GB   StorageCapacityUnit = MB * 1000
	TB   StorageCapacityUnit = GB * 1000
	PB   StorageCapacityUnit = TB * 1000

	KiB StorageCapacityUnit = 1 << 10
	MiB StorageCapacityUnit = 1 << 20
	GiB StorageCapacityUnit = 1 << 30
	TiB StorageCapacityUnit = 1 << 40
	PiB StorageCapacityUnit = 1 << 50
)

var (
	ReStorageSize = regexp.MustCompile(`^([1-9][0-9]*)((?:[KMGTPkmgtp]|)i?[bB])$`)
	ReUnit        = regexp.MustCompile(`^([kmgtpKMGTP]|)(|i)([bB])$`)
)

// MemorySize 内存大小，Num代表数字，Unit代表单位
type MemorySize struct {
	Num  uint64
	Unit StorageCapacityUnit
}

func ParseUnit(s string) (StorageCapacityUnit, error) {
	s = strings.Trim(strings.TrimSpace(s), "\n")
	s = strings.ReplaceAll(s, " ", "")
	if !ReUnit.MatchString(s) {
		return 0, fmt.Errorf("invalid storage size unit: %s", s)
	}
	s = strings.ToLower(s)
	// [MiB M i B]
	// [KB K '' B]
	// [B '' '' B]
	matchs := ReUnit.FindStringSubmatch(s)
	if matchs == nil || len(matchs) < 4 {
		return 0, fmt.Errorf("invalid storage size unit: %s", s)
	}
	isI := matchs[2] == "i"
	switch matchs[1] {
	case "":
		return Byte, nil
	case "k":
		if isI {
			return KiB, nil
		}
		return KB, nil
	case "m":
		if isI {
			return MiB, nil
		}
		return MB, nil
	case "g":
		if isI {
			return GiB, nil
		}
		return GB, nil
	case "t":
		if isI {
			return TiB, nil
		}
		return TB, nil
	case "p":
		if isI {
			return PiB, nil
		}
		return PB, nil
	default:
		return 0, fmt.Errorf("invalid storage size unit: %s", s)
	}
}

// ParseMemorySize 解析容量字符串，支持的格式有：123MiB 123MB "123 MiB" "123 MB"
func ParseMemorySize(s string) (*MemorySize, error) {
	s = strings.TrimSpace(strings.Trim(s, " \n"))
	s = strings.ReplaceAll(s, " ", "")
	if !ReStorageSize.MatchString(s) {
		return nil, fmt.Errorf("invalid memory size format: %s", s)
	}
	matchs := ReStorageSize.FindStringSubmatch(s)
	if matchs == nil || len(matchs) < 3 {
		return nil, fmt.Errorf("invalid memory size format: %s", s)
	}

	num, err := strconv.ParseUint(matchs[1], 10, 64)
	if err != nil {
		return nil, err
	}
	result := MemorySize{}
	result.Num = num
	unit, err := ParseUnit(matchs[2])
	if err != nil {
		return nil, err
	}
	result.Unit = unit
	return &result, nil
}
