"Exercise: SimpleCipher"

simpleCipher: aString key: aKey decode: aBoolean
"Implement a simple shift cipher like Caesar and a more secure substitution cipher.
    1. Ciphers are very straight-forward algorithms that allow us to render text less readable while still allowing easy deciphering.
    They are vulnerable to many forms of cryptanalysis, but Caesar was lucky that his enemies were not cryptanalysts.
    The Caesar Cipher was used for some messages from Julius Caesar that were sent afield.
    Now Caesar knew that the cipher wasn't very good, but he had one ally in that respect: almost nobody could read well.
    So even being a couple letters off was sufficient so that people couldn't recognize the few words that they did know.
    Your task is to create a simple shift cipher like the Caesar Cipher.
    2. Shift ciphers quickly cease to be useful when the opposition commander figures them out.
    So instead, let's try using a substitution cipher. Try amending the code to allow us to specify a key and use that for the shift distance.
    In the example above, we've set a = 0 for the key value. So when the plaintext is added to the key, we end up with the same message coming out.
    So aaaa is not an ideal key. But if we set the key to dddd, we would get the same thing as the Caesar Cipher.
    3. The weakest link in any cipher is the human being.
    Let's make your substitution cipher a little more fault tolerant by providing a source of randomness and ensuring that the key contains only lowercase letters.
    If someone doesn't submit a key at all, generate a truly random key of at least 100 lowercase characters in length."
"(self new simpleCipher: 'iamapandabear' key: 'ddddddddddddddddd' decode: false) >>> 'ldpdsdqgdehdu'"
"(self new simpleCipher: 'ldpdsdqgdehdu' key: 'ddddddddddddddddd' decode: true) >>> 'iamapandabear'"
"(self new simpleCipher: 'iamapandabear' key: 'aaaaaaaaaaaaaaaaaa' decode: false) >>> 'iamapandabear'"
| alphabet key isRandom genKey result |
	alphabet := Character alphabet.
	isRandom := false.
	key := aKey ifNil: [
			       isRandom := true.
			       genKey := String new: 100.
			       1 to: 100 do: [ :i | genKey at: i put: alphabet atRandom ].
			       genKey ].

	result := aString withIndexCollect: [ :char :idx |
			          | p kpos kdist newIndex |
			          p := alphabet indexOf: char.
			          kpos := idx \\ key size.
			          kpos = 0 ifTrue: [ kpos := key size ].
			          kdist := (alphabet indexOf: (key at: kpos)) - 1.
			          aBoolean
				          ifTrue: [
						          newIndex := p - kdist.
						          newIndex < 1 ifTrue: [
							          newIndex := alphabet size + newIndex ] ]
				          ifFalse: [
						          newIndex := p + kdist.
						          newIndex > alphabet size ifTrue: [
							          newIndex := newIndex \\ alphabet size ] ].
			          alphabet at: newIndex ].

	^ isRandom
		  ifTrue: [ Array with: result with: key ]
		  ifFalse: [ result ]


"Tests"
testSimpleCipher
| pair result key |

  "Random key cipher can encode"
  pair := task simpleCipher: 'aaaaaaaaaa' key: nil decode: false.
  result := pair first.
  key := pair second.
  self assert: result equals: (key copyFrom: 1 to: 10).

  "Random key cipher can decode"
  self assert: (task simpleCipher: (key copyFrom: 1 to: 10) key: key decode: true) equals: 'aaaaaaaaaa'.

  "Random key cipher is reversible"
  pair := task simpleCipher: 'abcdefghij' key: nil decode: false.
  self assert: (task simpleCipher: pair first key: pair second decode: true) equals: 'abcdefghij'.

  "Random key is made only of lowercase letters"
  self assert: (pair second matchesRegex: '^[a-z]+$').

  "Substitution cipher can encode"
  self assert: (task simpleCipher: 'aaaaaaaaaa' key: 'abcdefghij' decode: false) equals: 'abcdefghij'.

  "Substitution cipher can decode"
  self assert: (task simpleCipher: 'abcdefghij' key: 'abcdefghij' decode: true) equals: 'aaaaaaaaaa'.

  "Substitution cipher is reversible"
  result := task simpleCipher: 'abcdefghij' key: 'abcdefghij' decode: false.
  self assert: (task simpleCipher: result key: 'abcdefghij' decode: true) equals: 'abcdefghij'.

  "Substitution cipher can double shift encode"
  self assert: (task simpleCipher: 'iamapandabear' key: 'iamapandabear' decode: false) equals: 'qayaeaagaciai'.

  "Substitution cipher can wrap on encode"
  self assert: (task simpleCipher: 'zzzzzzzzzz' key: 'abcdefghij' decode: false) equals: 'zabcdefghi'.

  "Substitution cipher can wrap on decode"
  self assert: (task simpleCipher: 'zabcdefghi' key: 'abcdefghij' decode: true) equals: 'zzzzzzzzzz'.

  "Substitution cipher can encode messages longer than the key"
  self assert: (task simpleCipher: 'iamapandabear' key: 'abc' decode: false) equals: 'iboaqcnecbfcr'.

  "Substitution cipher can decode messages longer than the key"
  self assert: (task simpleCipher: 'iboaqcnecbfcr' key: 'abc' decode: true) equals: 'iamapandabear'