package bigrsacipher

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

/*
Care has been taken to uses cryptographic secure functions
The primes numbers are 1024 bits which is as secure as u can get really
crypto/rand library has been imported as crypto and not rand
This import style will make it easier to spot all the cryptographic secure functions
*/
// func main() {
// 	p, _ := crypto.Prime(crypto.Reader, 1024)
// 	q, _ := crypto.Prime(crypto.Reader, 1024)
// 	if !(primeCheck(p) || primeCheck(q)) {
// 		//they are always prime, no worries
// 		fmt.Println("These numbers ain't prime")
// 	}
// 	n := new(big.Int).Mul(p, q)

// 	one := big.NewInt(1)

// 	delta := lcmBig(p.Sub(p, one), q.Sub(q, one))

// 	e, _ := crypto.Prime(crypto.Reader, delta.BitLen())
// 	d := big.NewInt(0)
// 	d.ModInverse(e, delta)

// 	cleartext := "Black Lives Matter, all lives can't matter until Black lives matter"
// 	runes := []rune(cleartext)
// 	ASCIIs := toASCII(runes)
// 	stringEncoded := stringEncode(ASCIIs)
// 	bigNum, _ := new(big.Int).SetString(stringEncoded, 0)
// 	/*
// 		TODO: check that bigNum is not larger than N if larger break
// 		into two or more strings and encrypt separately
// 	*/
// 	fmt.Printf("Message to be encrypted: %v \n", cleartext)
// 	fmt.Printf("ASCII encoded: %v\n", bigNum)
// 	encrypted := encryptBig(bigNum, e, n)
// 	fmt.Printf("ciphertext: %v \n", encrypted)
// 	decrypted := decryptBig(encrypted, d, n)
// 	fmt.Printf("Decrypted but still ASCII encoded: %v \n", decrypted)
// 	decryptASCIIs := stringDecode(decrypted)
// 	fmt.Printf("Plaintext (original message) :%v", toRune(decryptASCIIs))
// }

// EncryptBig Main encryption function
func EncryptBig(num *big.Int, privateExponent *big.Int, modulus *big.Int) *big.Int {
	//encrypts by modular exponentiation
	encrypted := new(big.Int).Exp(num, privateExponent, modulus)
	return encrypted
}

// DecryptBig Main decryption function
func DecryptBig(num *big.Int, publicExponent *big.Int, modulus *big.Int) *big.Int {
	//decrypts by modular exponentiation
	decrypted := new(big.Int).Exp(num, publicExponent, modulus)
	return decrypted
}

func lcmBig(x *big.Int, y *big.Int) *big.Int {
	//an lcm implementation for big.Int numbers
	gcd := new(big.Int).GCD(nil, nil, x, y)
	temp := new(big.Int).Mul(x, y)
	lcm := new(big.Int).Div(temp, gcd)
	return lcm
}

// PrimeCheck primality test
func PrimeCheck(prime *big.Int) bool {
	//primality test
	return prime.ProbablyPrime(256)
}

func toASCII(slice []rune) []int {
	//runs in O(n) where n = len(slice)
	var converted []int
	for _, v := range slice {
		converted = append(converted, int(v))
	}
	return converted
}

// ToRune convert a string to a rune
func ToRune(slice []int) string {
	//runs in O(n) where n = len(slice)
	// var str string
	var str strings.Builder
	for _, v := range slice {
		str.WriteString(fmt.Sprint(v))
	}
	return str.String()
}

func stringEncode(slice []int) string {
	//encodes the ASCII to a string
	var out []string
	for _, v := range slice {
		if v < 100 {
			out = append(out, "0"+strconv.Itoa(v))
			continue
		}
		out = append(out, strconv.Itoa(v))
	}
	var str string
	for _, v := range out {
		str += v
	}
	//strips leading 0 if present to avoid conversion errors
	if str[0] == '0' {
		str = str[1:]
	}
	return str
}

func stringDecode(decryptedBig *big.Int) []int {
	//decodes the number to string then ASCII values
	str := decryptedBig.String()
	if len(str)%3 != 0 {
		str = "0" + str
	}
	var ASCII []int
	for i := 0; i < len(str); i += 3 {
		temp, _ := strconv.Atoi(str[i : i+3])
		ASCII = append(ASCII, temp)
	}
	return ASCII
}

Rsacipher(Big)