# CARROUSEL: A transposition cryptosystem to be used as paper & pencil cipher
#          for text with letters, numerals and typographic characters
versionname = "CARROUSEL 1.0"
# - 2015-08-03 by Ph. Allard
# - Implementation of the paper: "CARROUSEL - A Transposition Cipher", 2015
# - To simplify the command line interface
#   - the plaintext is always read from or written to the file plaintext.txt.
#   - the ciphertext is always read from or written to the file ciphertext.txt.
#   So, the user has to enter only the cryptographic parameters (always two):
#   Mandatory: - key for transposition or arrangement of the text.
#   Optionally: The name of the file with the working alphabet
#               (by default the given file alpha_L.txt is used which contains "ABCD...XYZ")
# Sample usage:
#   copy alpha_LNT.txt  alpha.txt
#   copy plaintext_LNT.txt  plaintext.txt
#   python Carrousel.py -e aaaaaaa -a alpha -v 

#----------------------------------------- Initialization -------------------------------

### Handle Options (command line parameters)
## Get options
from optparse import OptionParser
parser = OptionParser(usage = "usage: %prog -e OR -d key  [-a alpha]  [-v]\n" + versionname +
" -- Transposition cryptosystem for paper&pencil usage processing text with letters, numerals and typographic characters according to selected alphabet.")
parser.add_option("-e", "--encrypt", help='Encrypt plaintext.txt to ciphertext.txt.', action="store", type="str", nargs=1)
parser.add_option("-d", "--decrypt", help='Decrypt ciphertext.txt to plaintext.txt.', action="store", type="str", nargs=1)
parser.add_option("-a", "--alphabet", help='Read alphabet to use in alpha.txt', action="store", type="str", nargs=1, default="alpha_L")
parser.add_option("-v", "--verbose", help='Print and store some intermediate results.', action='store_true')
(options, args) = parser.parse_args()

## Interpret/check options, exit if wrong, set according global variables
# # (Usage is shown, if options are used incorrectly.)
if options.verbose:
    T_verbose=1
else:
    T_verbose=0
if options.encrypt and options.decrypt:
    parser.error("Options -e and -d are mutually exclusive.")
if not(options.encrypt or options.decrypt): 
    parser.error("Option (either -e or -d) is missing.")
    
# Set key for -e or -d
if options.encrypt:
  K = options.encrypt
if options.decrypt:
  K = options.decrypt
upK = K.upper()	# key in upper case
lenkey = len(upK)		# length of key and extraction list

# Get the name of the file containing the alphabet (given after -a).
# (If no option -a is set, the default is used: alpha="alpha_L".)
if options.alphabet:
  alpha = options.alphabet

  
## helper functions

def writefile(f,s):
    fp = open(f,'w')
    fp.write(s)
    fp.close()

def readfile(f):
   fp = open(f,'r')
   s = fp.read()
   fp.close()
   return s

## reading alphabet to use
alphabet = readfile(alpha+'.txt').upper()               # input of alphabet from text file
modulus=len(alphabet)

## reading text to transpose or arrange
if options.encrypt:
    text_string = readfile('plaintext.txt').upper()         # input of plaintext from text file
if options.decrypt:
    text_string = readfile('ciphertext.txt').upper()         # input of ciphertext from text file
size = len(text_string)

## helper functions (suite)

def str2list(s): return [alphabet.index(c)+1 for c in s]
def list2char(l): return [alphabet[(i-1) % modulus] for i in l]
def list2str(l): return ''.join( list2char(l) )

print(versionname+"  -- A Transposition Cipher --\n")
if options.encrypt: print("Encryption process from "+str(lenkey)+"-key ("+upK+") and "+str(modulus)+"-alphabet ["+alphabet+"]")
if options.decrypt: print("Decryption process from "+str(lenkey)+"-key ("+upK+") and "+str(modulus)+"-alphabet ["+alphabet+"]")

#-------------------------------------------- Text Encryption -------------------------------

def permutation (upkey) :
    # list of ranks of characters in upkey
    extraction_list = [alphabet.index(c)+1 for c in upkey]
    # extraction of characters from the right according to upkey
    permuted_list=[]
    pos = 0
    text_list = str2list(text_string)
    for i in range(0,len(text_list)):
        pos = (pos-extraction_list[i % lenkey]) % len(text_list)
        permuted_list.append(text_list[pos])
        del text_list[pos]
    permuted_string = list2str(permuted_list)    # conversion to string
    if T_verbose:
        print("Transposition of text according to following ranks :", extraction_list, " for "+upkey)
    return permuted_string  

if options.encrypt:
    ciphertext_string = permutation(upK)
    writefile('ciphertext.txt', ciphertext_string)
    print ("The generated ciphertext was written to 'ciphertext.txt'.\n")
    if T_verbose:
        print(str(size)+"-Plaintext:\n" + text_string+"\n")
        print(str(size)+"-Ciphertext:\n" + ciphertext_string+"\n")
 
#-------------------------------------------- Text Decryption -------------------------------

def arrangement (upkey) :
    # list of ranks of characters in upkey
    extraction_list = [alphabet.index(c)+1 for c in upkey]
    # extraction of characters from the left according to key
    arranged_list = []
    for i in range(size) : arranged_list.append ('')
    text_list = str2list(text_string)
    pos = size
    for i in range(0,len(text_list)):
        j = extraction_list[i % lenkey]
        k = 1
        while k <= j :
            pos = (pos+1) % size
            if arranged_list[-pos] == '' : k = k+1
        arranged_list[-pos] = text_list[0]
        del text_list[0]
    arranged_string = list2str(arranged_list)                # conversion to string
    if T_verbose:
        print("Arrangement of text according to following ranks :", extraction_list, " for "+upkey)
    return arranged_string  

if options.decrypt:
    plaintext_string = arrangement(upK)
    writefile('plaintext.txt', plaintext_string)
    print ("The arranged plaintext was written to 'plaintext.txt'.\n")
    if T_verbose:
        print(str(size)+"-Ciphertext:\n" + text_string+"\n")
        print(str(size)+"-Plaintext:\n" + plaintext_string+"\n")

#-------------------------------------------------------------------------------------------
