package stringutil

import (
	"fmt"
	"math"
	"math/rand"
	"sync"
	"time"
)

const (
	_ = 1 << (10 * iota)
	// KiB is bytes per Kibibyte
	KiB
	// MiB is bytes per Mebibyte
	MiB
	// GiB is bytes per Gibibyte
	GiB
	// TiB is bytes per Tebibyte
	TiB
)

var alpha = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
var alphanumeric = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")

// Any strings created at runtime, duplicate or not, are copied, even though by specification,
// a go string is immutable. This utility allows us to cache runtime strings and retrieve them
// when we expect heavy duplicates.
var strings sync.Map

func init() {
	rand.Seed(time.Now().UnixNano())
}

// Bank will return a non-copy of a string if it has been used before. Otherwise, it will store
// the string as the unique instance.
func Bank(s string) string {
	ss, _ := strings.LoadOrStore(s, s)
	return ss.(string)
}

// RandSeq generates a pseudo-random alphabetic string of the given length
func RandSeq(n int) string {
	b := make([]rune, n)
	for i := range b {
		b[i] = alpha[rand.Intn(len(alpha))] // #nosec No need for a cryptographic strength random here
	}
	return string(b)
}

// FormatBytes takes a number of bytes and formats it as a string
func FormatBytes(numBytes int64) string {
	if numBytes > TiB {
		return fmt.Sprintf("%.2fTiB", float64(numBytes)/TiB)
	}
	if numBytes > GiB {
		return fmt.Sprintf("%.2fGiB", float64(numBytes)/GiB)
	}
	if numBytes > MiB {
		return fmt.Sprintf("%.2fMiB", float64(numBytes)/MiB)
	}
	if numBytes > KiB {
		return fmt.Sprintf("%.2fKiB", float64(numBytes)/KiB)
	}
	return fmt.Sprintf("%dB", numBytes)
}

// FormatUTCOffset converts a duration to a string of format "-07:00"
func FormatUTCOffset(dur time.Duration) string {
	utcOffSig := "+"
	if dur.Hours() < 0 {
		utcOffSig = "-"
	}
	utcOffHrs := int(math.Trunc(math.Abs(dur.Hours())))
	utcOffMin := int(math.Abs(dur.Minutes())) - (utcOffHrs * 60)

	return fmt.Sprintf("%s%02d:%02d", utcOffSig, utcOffHrs, utcOffMin)
}
