// 网上导入有问题所有直接把代码复制过来
// https://github.com/xxtea/xxtea-go.git
package otherutils

import (
	"encoding/base64"
	"strings"
)

const delta = 0x9E3779B9

func toBytes(v []uint32, includeLength bool) []byte {
	length := uint32(len(v))
	n := length << 2
	if includeLength {
		m := v[length-1]
		n -= 4
		if (m < n-3) || (m > n) {
			return nil
		}
		n = m
	}
	bytes := make([]byte, n)
	for i := uint32(0); i < n; i++ {
		bytes[i] = byte(v[i>>2] >> ((i & 3) << 3))
	}
	return bytes
}

func toUint32s(bytes []byte, includeLength bool) (v []uint32) {
	length := uint32(len(bytes))
	n := length >> 2
	if length&3 != 0 {
		n++
	}
	if includeLength {
		v = make([]uint32, n+1)
		v[n] = length
	} else {
		v = make([]uint32, n)
	}
	for i := uint32(0); i < length; i++ {
		v[i>>2] |= uint32(bytes[i]) << ((i & 3) << 3)
	}
	return v
}

func mx(sum uint32, y uint32, z uint32, p uint32, e uint32, k []uint32) uint32 {
	return ((z>>5 ^ y<<2) + (y>>3 ^ z<<4)) ^ ((sum ^ y) + (k[p&3^e] ^ z))
}

func fixk(k []uint32) []uint32 {
	if len(k) < 4 {
		key := make([]uint32, 4)
		copy(key, k)
		return key
	}
	return k
}

func encrypt(v []uint32, k []uint32) []uint32 {
	length := uint32(len(v))
	n := length - 1
	k = fixk(k)
	var y, z, sum, e, p, q uint32
	z = v[n]
	sum = 0
	for q = 6 + 52/length; q > 0; q-- {
		sum += delta
		e = sum >> 2 & 3
		for p = 0; p < n; p++ {
			y = v[p+1]
			v[p] += mx(sum, y, z, p, e, k)
			z = v[p]
		}
		y = v[0]
		v[n] += mx(sum, y, z, p, e, k)
		z = v[n]
	}
	return v
}

func decrypt(v []uint32, k []uint32) []uint32 {
	length := uint32(len(v))
	n := length - 1
	k = fixk(k)
	var y, z, sum, e, p, q uint32
	y = v[0]
	q = 6 + 52/length
	for sum = q * delta; sum != 0; sum -= delta {
		e = sum >> 2 & 3
		for p = n; p > 0; p-- {
			z = v[p-1]
			v[p] -= mx(sum, y, z, p, e, k)
			y = v[p]
		}
		z = v[n]
		v[0] -= mx(sum, y, z, p, e, k)
		y = v[0]
	}
	return v
}

// Encrypt the data with key.
// data is the bytes to be encrypted.
// key is the encrypt key. It is the same as the decrypt key.
func Encrypt(data []byte, key []byte) []byte {
	if data == nil || len(data) == 0 {
		return data
	}
	return toBytes(encrypt(toUint32s(data, true), toUint32s(key, false)), false)
}

// Decrypt the data with key.
// data is the bytes to be decrypted.
// key is the decrypted key. It is the same as the encrypt key.
func Decrypt(data []byte, key []byte) []byte {
	if data == nil || len(data) == 0 {
		return data
	}
	return toBytes(decrypt(toUint32s(data, false), toUint32s(key, false)), true)
}

// Encrypt the data with key.
// data is the string to be encrypted.
// key is the string of encrypt key.
func EncryptString(str, key string) string {
	s := []byte(str)
	k := []byte(key)
	b64 := base64.StdEncoding
	return b64.EncodeToString(Encrypt(s, k))
}

// Decrypt the data with key.
// data is the string to be decrypted.
// key is the decrypted key. It is the same as the encrypt key.
func DecryptString(str, key string) (string, error) {
	k := []byte(key)
	b64 := base64.StdEncoding
	decodeStr, err := b64.DecodeString(str)
	if err != nil {
		return "", err
	}
	result := Decrypt([]byte(decodeStr), k)
	return string(result), nil
}

// Encrypt the string with key and convert the string to URL format
func EncryptStdToURLString(str, key string) string {
	return encryptBase64ToUrlFormat(EncryptString(str, key))
}

// Decrypt the URL string with key and convert the URL string to the origin string
func DecryptURLToStdString(str, key string) (string, error) {
	return DecryptString(decryptBase64ToStdFormat(str), key)
}

// Replace std character to URL character in base64 string
func encryptBase64ToUrlFormat(str string) string {
	str = strings.Replace(str, "+", "-", -1)
	str = strings.Replace(str, "/", "_", -1)
	str = strings.Replace(str, "=", "~", -1)
	return str
}

// Replace URL character to origin character in base64 string
func decryptBase64ToStdFormat(str string) string {
	str = strings.Replace(str, "-", "+", -1)
	str = strings.Replace(str, "_", "/", -1)
	str = strings.Replace(str, "~", "=", -1)
	return str
}
