/*

Lorenz SZ 40 Emulator

based on the Jack Good's description in "Code Breakers" (Hinsley, Stripp editors)

Syntax: tunny [-k#] "<string-to-encrypt>"
        -k#      Key Setting (random number seed)
        
In Output: _ = word space
           . = all space
           * = Carriage Return
           > = Line Feed
           & = Letters
           # = Figures

- Copyright 1997 Jim Bumgardner jbum@thePalace.com
-
- Permission granted to reuse as you please...

Version 1.0.1  3/19/97
	Modified wheel order of Chi wheels.
	Changed Wheels to a single array.
	Added enums for clarity.
	Simplified calling conventions for wheel routines.
*/

#include "tunny.h"

TunnyMachine	gTM;
int				gWheelLengths[NbrWheels] = 
							{	41,31,29,26,23,	// CHI Wheels
								43,47,51,53,59,	// PSI Wheels
								37,61 };		// Motor Wheels

// Main Entry Point
//
// Initialze a Tunny Machine and run it thru its paces...
//
void main (int argc, char **argv)

{
	int		textLength;
	char	plainText[MaxTextLength],
			cipherText[MaxTextLength],
			decodeText[MaxTextLength];
	int		i;
	char	*p,*plainAscii="This is a test string for encryption\n";
	long	tunnyKey = 666L;

#if MACINTOSH	
	  argc = ccommand(&argv);
#endif

	InitTunnyMachine();

	for (i = 1; i < argc; ++i) {
		if (argv[i][0] == '-') {
			switch (argv[i][1]) {
			case 'k':
			case 'K':
				p = &argv[i][2];
				if (&p == 0) {
					++i;
					p = argv[i];
				}
				tunnyKey = atol(p);
				break;
			default:
				fprintf(stderr,"Syntax: tunny [-k#] \"<string-to-encrypt>\"\n");
				fprintf(stderr,"   -k#   Key Setting (random number seed)\n");
				exit(0);
			}
		}
		else {
			plainAscii = argv[i];
		}
	}
		

	// Set the wheels using a psuedo-random key (666 is the default)
	SetTunnyKey(tunnyKey);

	// Convert ASCII test-string to Baudot Code...
	ConvertStringToPlainText(plainAscii,plainText,&textLength);
	DisplayTunnyString("Plain : ",plainText,textLength);

	// Encipher it and display the result
	EncodeCipher(plainText,cipherText,textLength);
	DisplayTunnyString("Cipher: ",cipherText,textLength);

	// Decipher it and display the result
	EncodeCipher(cipherText,decodeText,textLength);
	DisplayTunnyString("Decode: ",decodeText,textLength);
}


// A pseudo-random number generator which is used to
// initialize the machine.
//

#define	R_A	16807L
#define	R_M	2147483647.0
#define R_Q	127773L
#define R_R	2836L

long 	gSeed = 1;

void MySRand(long s)
{
	gSeed = s;
	if (gSeed == 0)
		gSeed = 1;
}

long LongRandom(void)
{
	long	hi,lo,test;

	hi   = gSeed / R_Q;
	lo   = gSeed % R_Q;
	test = R_A * lo - R_R * hi;
	if (test > 0)
    gSeed = test;
	else
	  gSeed = test + R_M;
	return gSeed;
}

double DoubleRandom(void)
{
	return LongRandom() / (double) R_M;
}

int MyRandom(short max)
{
	return (int) (DoubleRandom() * max);
}

// Reset all wheels to their starting positions
//
void ResetTunnyMachine()
{
	int	i;
	for (i = 0; i < NbrWheels; ++i)
		ResetWheel(i);
}

// Initialize a wheel to a specific length
//
void InitWheel(int wheelNbr, int length)
{
	Wheel	*wheel = &gTM.wheels[wheelNbr];
	wheel->length = length;
	wheel->startingPosition = 0;
	wheel->position = 0;
}

// Reset a wheel to its starting position
//
void ResetWheel(int wheelNbr)
{
	Wheel	*wheel = &gTM.wheels[wheelNbr];
	wheel->position = wheel->startingPosition;
}

// Get current pin for a wheel (returns 1 or 0)
//
int GetWheelPin(int wheelNbr)
{
	Wheel	*wheel = &gTM.wheels[wheelNbr];
	return wheel->pins[wheel->position];
}

// Rotate a wheel
//
void RotateWheel(int wheelNbr)
{
	Wheel	*wheel = &gTM.wheels[wheelNbr];
	wheel->position = (wheel->position + 1) % wheel->length;
}

// Initialize a wheel using random numbers
//
void RandomizeWheel(int wheelNbr)
{
	int	i;
	Wheel	*wheel = &gTM.wheels[wheelNbr];
	for (i = 0; i < wheel->length; ++i)
		wheel->pins[i] = ((MyRandom(2) == 0)? 0 : 1);
	wheel->startingPosition = MyRandom(wheel->length);
	wheel->position = wheel->startingPosition;
}

// Rotate the Tunny Machine (done once for each letter)
//
void RotateTunnyMachine()
{
	// Psi wheels are driven by M37
 	/* changing next line to ! to make Frode's correction to
 	   discription given by Goode, -JPG  - e.g. "inactive" pins
 	   drive the psi Wheels... */

	if ( ! GetWheelPin(M37)) {
		RotateWheel(Psi43);
		RotateWheel(Psi47);
		RotateWheel(Psi51);
		RotateWheel(Psi53);
		RotateWheel(Psi59);
	}

	// Motor Wheels - M37 is driven by M61
		// Is this also driven on an inactive pin??
	if (GetWheelPin(M61))
		RotateWheel(M37);

	// M61 is driven regularly
	RotateWheel(M61);

	// Chi Wheels are driven Regularly
	RotateWheel(Chi41);
	RotateWheel(Chi31);
	RotateWheel(Chi29);
	RotateWheel(Chi26);
	RotateWheel(Chi23);
}

// Initialize the wheel lengths of the Tunny Machine
//
void InitTunnyMachine()
{
	int	i;
	// Chi Wheels - move regularly
	for (i = 0; i < NbrWheels; ++i)
		InitWheel(i,gWheelLengths[i]);
}

// Set the wheel settings randomly using a single number as a "key"
//
// In reality, this isn't suitably random, but is good enough for our
// purposes...
//
void SetTunnyKey(long key)
{
	int	i;
	MySRand(key);
	for (i = 0; i < NbrWheels; ++i)
		RandomizeWheel(i);
}

// Encode a string of text
// (this routine also used for decoding)
//
void EncodeCipher(char *inStr,char *outStr,int len)
{
	int	outChar;

	ResetTunnyMachine();

	while (len--)
	{
		outChar = 0;

		// Note "^" is XOR (or Modulo-2 Addition) operator
		// << is Bit Shift
		outChar |= (*inStr & 0x01) ^ ((GetWheelPin(Chi41) ^ GetWheelPin(Psi43)) << 0);
		outChar |= (*inStr & 0x02) ^ ((GetWheelPin(Chi31) ^ GetWheelPin(Psi47)) << 1);
		outChar |= (*inStr & 0x04) ^ ((GetWheelPin(Chi29) ^ GetWheelPin(Psi51)) << 2);
		outChar |= (*inStr & 0x08) ^ ((GetWheelPin(Chi26) ^ GetWheelPin(Psi53)) << 3);
		outChar |= (*inStr & 0x10) ^ ((GetWheelPin(Chi23) ^ GetWheelPin(Psi59)) << 4);

		*outStr = outChar;
		++outStr;
		++inStr;

		RotateTunnyMachine();
	}
}

// Table for converting from Ascii to International Teleprinter (Baudot) Code
//
char	letterCode[] = {
//	 0    1    2    3    4    5    6    7  
	0x18,0x13,0x0e,0x12,0x10,0x16,0x0b,0x05,
	0x0c,0x1a,0x1e,0x09,0x07,0x06,0x03,0x0d,
	0x1d,0x0a,0x14,0x01,0x1c,0x0f,0x19,0x17,
	0x15,0x11,0x1f,0x1b,0x08,0x02,0x04,0x00};

// Table for converting  from International Teleprinter (Baudot) Code to Ascii
//
/**
In Output: _ = word space
           . = all space
           * = Carriage Return
           > = Line Feed
           & = Letter Shift
           # = Figure Shift
**/

char	tunnyChar[] = {
//	0   1    2    3   4   5   6   7  
	'.','T','*','O','_','H','N','M',
	'>','L','R','G','I','P','C','V',
	'E','Z','D','B','S','Y','F','X',
	'A','W','J','#','U','Q','K','&'};

// Display Tunny Output by converting to Ascii...
// Uses a space every 5 characters...
//
void DisplayTunnyString(char *prefix, char *tunnyStr, int len)
{
	int	n = 0;
	int	outChar;
	printf("%s",prefix);
	while (len--) {
		outChar = tunnyChar[(int) tunnyStr[n]];
		printf("%c",outChar);
		if (n && n % 5 == 0)
			printf("  ");
		++n;
	}
	printf("\n");
}

// Convert an Ascii String to Baudot code for Tunny operation...
//
void ConvertStringToPlainText(char *str, char *tunnyStr, int *len)
{
	short	n = 0;
	int		outChar;
	while (*str) {
		outChar = 0;
		if (isalpha(*str)) {
			// Switch to letter mode if need be...
			outChar = letterCode[toupper(*str) - 'A'];
		}
		else if (isdigit(*str)) {
			// Switch to digit mode if need be...
		}
		else if (*str == '\n')
			outChar = 8;
		else if (*str == '\r')
			outChar = 2;
		else if (*str == ' ')
			outChar = 4;
		*tunnyStr = outChar;
		++n;
		++str;
		++tunnyStr;
	}
	*len = n;
}

