package strutil

import (
	"math/big"
	"strconv"
	"strings"

	"github.com/gookit/goutil/x/basefn"
)

//
// -------------------- convert base --------------------
//

const (
	Base10Chars = "0123456789"
	Base16Chars = "0123456789abcdef"
	Base32Chars = "0123456789abcdefghjkmnpqrstvwxyz"
	Base36Chars = "0123456789abcdefghijklmnopqrstuvwxyz"
	Base48Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL"
	Base62Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
	Base64Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/"
)

// Base10Conv convert base10 string to new base string.
func Base10Conv(src string, to int) string { return BaseConv(src, 10, to) }

// BaseConv convert base string by from and to base.
//
// Note: from and to base must be in [2, 64]
//
// Usage:
//
//	BaseConv("123", 10, 16) // Output: "7b"
//	BaseConv("7b", 16, 10) // Output: "123"
func BaseConv(src string, from, to int) string {
	if from > 64 || from < 2 {
		from = 10
	}
	if to > 64 || to < 2 {
		to = 16
	}
	return BaseConvByTpl(src, Base64Chars[:from], Base64Chars[:to])
}

// BaseConvInt convert base int to new base string.
//
// Usage:
//
//	BaseConv(123, 16) // Output: "7b"
func BaseConvInt(src uint64, toBase int) string {
	if toBase > 64 || toBase < 2 {
		toBase = 16
	}

	// bigInt 支持 2-62 进制转换处理 TODO
	if toBase <= 36 {
		return strconv.FormatUint(src, toBase)
	}
	if toBase <= 62 {
		bigInt := new(big.Int).SetUint64(src)
		return bigInt.Text(toBase)
	}

	return BaseConvIntByTpl(src, Base64Chars[:toBase])
}

// BaseConvByTpl convert base string by template.
//
// Usage:
//
//	BaseConvert("123", Base62Chars, Base16Chars) // Output: "1e"
//	BaseConvert("1e", Base16Chars, Base62Chars) // Output: "123"
func BaseConvByTpl(src string, fromBase, toBase string) string {
	if fromBase == toBase {
		return src
	}

	// convert to base 10
	var dec uint64
	if fromBase == Base10Chars {
		var err error
		dec, err = strconv.ParseUint(src, 10, 0)
		if err != nil {
			basefn.Panicf("input is not a valid decimal number: %s(%v)", src, err)
		}
	} else {
		fLen := uint64(len(fromBase))
		for _, c := range src {
			dec = dec*fLen + uint64(strings.IndexRune(fromBase, c))
		}
	}

	// convert to new base
	return BaseConvIntByTpl(dec, toBase)
}

// BaseConvIntByTpl convert base int to new base string.
func BaseConvIntByTpl(dec uint64, toBase string) string {
	// convert to new base
	var res string
	toLen := uint64(len(toBase))
	for dec > 0 {
		res = string(toBase[dec%toLen]) + res
		dec /= toLen
	}
	return res
}
