import string
import unidecode
import random
from sympy import *


def Normalize_Data(raw_user_input):        
	final_user_input = unidecode.unidecode(raw_user_input)
	return final_user_input

def Generate_Keys():
	'''
	Generating random prime numbers with randprime function
	from sympy library. 
	'''
	p = randprime(0,99999999999999)
	q = randprime(0,99999999999999)
	
	'''
	All necessary calculations for key generation, loop is
	used for finding greatest common divider equal to one.
	'''
	n = p * q
	fi = (p - 1) * (q - 1)
	e = random.randint(2,fi + 1)
	for _ in iter(int,1):
		if gcd(e,fi) == 1:
			break
		else:
			e += 1

	d = pow(e, -1, fi)

	return p,q,e,n,d

def to_Bin(input):
	'''
	Converting characters to their Unicode representation with
	ord() function
	'''
	my_ascii = []
	for i in range(len(input)):
		my_ascii.append(ord(input[i]))
		
	'''
	First spliting the values into parts of equal lenght, in this case
	7. Then converting the unicode values into binary using format() 
	function. Then joining the list of lists into one big list
	'''
	to_bin = []
	x = 7
	to_bin += [my_ascii[i: i + x] for i in range(0, len(my_ascii), x)]

	for i in range(len(to_bin)):
		for j in range(len(to_bin[i])):
			to_bin[i][j] = format(to_bin[i][j], "011b")

	for i in range(len(to_bin)):
		to_bin[i] = ''.join(to_bin[i])
	return to_bin

def Encrypt(input,e,n):
	'''
	First, we convert input in form of string into its binary representation.
	Then we take the binary input and turn them into Integers, using [2:] to
	slice off 0b part of the binary. Then we parse the integer number through
	the formula using public keys e and n.

	'''
	input = to_Bin(input)
	for i in range(len(input)):
		input[i] = int(input[i][2:],2)
		input[i] = pow(input[i],e,n)
	return input

def Decrypt(input,n,d):
	'''
	Fist we use the decryption formula with our public and
	private key.
	'''

	for i in range(len(input)):
		input[i] = pow(input[i],d,n)

	output = ''
	actuall_output = ''

	'''
	We start by changing the input into its binary values. Once that
	is done we reverse the binary because of the way the format returns our
	binary would break our code. Then we count until we reach the 
	eight-bit and convert it first back to Unicode and then to its 
	corresponding character. In the end, we need to reverse it once again as 
	the text is backward. The reason for all the reversing is due to how 
	python compiler works and interprets binary.  
	'''

	for i in range(len(input)):
		bin_value = format(input[i], "011b")
		reverse_input = bin_value[::-1]
		reverse_input = str(reverse_input)
		j = 0
		itter = 0
		holder = ''
		while j < len(reverse_input):
			holder += reverse_input[j]
			if len(holder) == 8:
				j = j + 4
				holder = holder[::-1]
				reverse = int(holder,2)
				output += chr(reverse)
				holder = ''
				continue
			j += 1
		holder = holder[::-1]
		reverse = int(holder,2)
		output += chr(reverse)
		actuall_output += output[::-1]
		output = ''
	return actuall_output


string = Normalize_Data('This is my testing string')

p,q,e,n,d = Generate_Keys()

encrypted_message = Encrypt(string,e,n)

decrypted_message = Decrypt(encrypted_message,n,d)

print("OUTPUT:")
print("public key e: ", e,
	"\npublic key n: ", n)
print("Testing string: ", string)
print("Encrypted string: ", encrypted_message)
print("Decrypted string: ", decrypted_message)

