char VersionString[100]="0.1.0";

// Das Programm ist lange nicht fertig, tut aber, was es soll und emuliert zurzeit die M3, die an der DASA in Dortmund steht
// Dieses Programm gibt Umlaute auf der Konsole aus und der Quelltext verwendet den UTF8-Zeichensatz mit Umlauten
// Konfigurieren Sie ihre Locales, Ihre Linux-Konsole und Ihr Desktop-Terminal bitte entsprechend.
// Sonst kann es geschehen, dass Umlaute und das ß-Zeichen nicht richtig angezeigt werden

#include<stdio.h>
#include<string.h>
#include<termios.h> // für getch()
#include<unistd.h> // für getch()
#include<ctype.h> // für toupper()
#include<stdlib.h>

bool _DBG=false;

int _getch(void) // Kapsel für getch()
{
    struct termios oldattr, newattr;
    int ch;
    tcgetattr( STDIN_FILENO, &oldattr );
    newattr = oldattr;
    newattr.c_lflag &= ~( ICANON | ECHO );
    tcsetattr( STDIN_FILENO, TCSANOW, &newattr );
    ch = getchar();
    tcsetattr( STDIN_FILENO, TCSANOW, &oldattr );
    return ch;
}

int getch() // getch() wartet stets auf das nächste Zeichen, das auf der Tastatur eingegeben wird
{
	int C=0;
	while (C==0) { C=_getch(); } 
	return C;
}

bool kbhit() // kbhit() gibt true zurück, wenn eine Taste gedrückt wurde
{
	if (_getch()==0) { return false; }
	else { return true; }
}

void clrscr() // clrscr() löscht den Bildschirm
{
	system("clear");
}

void input(char *S, int MaxLen) // Pendant der BASIC-Funktion INPUT A$
{
	int C=0;
	int Pos=0;
	while ((C!='\n')&&(Pos<MaxLen))
	{
		C=getch();
		if (C!='\n')
		{
			if (C==0x7f) // KB-Backspace=0x7f, ASCII-Backspace=0x08
			{
				printf("%c",0x08); printf(" "); printf("%c",0x08);
				Pos--; if (Pos<0) { Pos=0; }
			}
			else
			{
				printf("%c",C); S[Pos]=C; Pos++;
			}
		}
	}
	S[Pos]=0; printf("\n");
}

void GetToken(char *T, char *S, int &Pos) // Hilfsfunktion, um die Konfigurationsdatei Walzen.dat zu verarbeiten
{
	int TPos=0;
	while (S[Pos]<=32) { Pos++; }
	while ((S[Pos]>31)&&(S[Pos]!=0)) { T[TPos]=S[Pos]; TPos++; Pos++; }
	T[TPos]=0;
}

bool WalzeSuchen(char *SuchTyp, char *SuchName, char *Daten) // Durchsucht die Konfigurationsdatei Walzen.dat nach Typ und Name einer Walze
{
	// Gibt true zurück, wenn die Walze gefunden wurde.
	// Ansonsten wird false und ein leerer String Daten zurückgegeben
	int Pos;
	char L[256];
	char Typ[256];
	char Name[256];
	Daten[0]=0;
	FILE *F=fopen("Walzen.dat","rb");
	fgets(L,255,F); // Header lesen und ignorieren
	do
	{
		fgets(L,255,F); Pos=0; // fgets() liest hier die einzelnen Zeilen in der Konfigurationsdatei
		if (!feof(F))
		{
			if (L[0]==10) // leere Zeilen ignorieren (dient der besseren Formatierung von Wallzen.dat)
			{
				Typ[0]=0; Name[0]=0;
			}
			else
			{
				GetToken(Typ,L,Pos); GetToken(Name,L,Pos); // Hier muss stets zunächst der Typ, und dann der Name stehen
				if ((strcmp(SuchTyp,Typ)==0)&&(strcmp(SuchName,Name)==0))
				{
					GetToken(Daten,L,Pos);
					return true;
				}
			}
		}
	}
	while (!feof(F));
	fclose(F); return false; // An dieser Stelle wird false zurückgegeben, da die gesamte Datei erfolglos durchsucht wurde
}

class Walze
{
	private:
	char *S;
	int *Hin;
	int *Rueck;
	int *USP;
	int L;
	int Ring;
	int Rotor;
	public:
	Walze(char *S);
	void Anzeigen();
	void SetRing(char V);
	void SetRotor(char V);
	void Rotiere();
	int Hinweg(int C);
	int Rueckweg(int C);
	bool GetUSP();
};

Walze::Walze(char *Conf)
{
	int i,j,k,Pos;
	Hin=new int[50];
	Rueck=new int[50];
	S=new char[50]; strcpy(S,Conf);
	USP=new int[50];
	L=0; Ring=0; Rotor=0;
	while (S[L]!=',') { L++; } // Länge der Walze bestimmen, bis zu Definition der USP (durch Komma getrennt)
	for (i=0; i<L; i++)
	{
		j=S[i]-i-'A'; k=-j; // Hin enthält nur relative Angaben, wie z.B. "1 vor" oder "4 zurück"
		Hin[i]=j; Rueck[S[i]-'A']=k; // Rueck enthält nur relative Angaben, wie z.B. "4 vor" oder "1 zurück"
	}
	Pos=L; while (S[Pos]<'A') { Pos++; }
	i=0;
	while (S[Pos]!=0) { USP[i]=S[Pos]-'A'; i++; Pos++; } // Umschaltpunkte erzeugen (bis zu 50 möglich)
	USP[i]=-1; // -1=Ende der Liste
}
	
void Walze::Anzeigen()
{
	printf("%s\n",S);
}
	
void Walze::SetRing(char V) // Ringstellung=negativer Offset (das Vorzeichen wird nicht mit gespeichert)
{
	Ring=V-'A';
}

void Walze::SetRotor(char V) // Rotorstellung=positiver Offset (das Vorzeichen wird nicht mit gespeichert)
{
	Rotor=V-'A';
}

void Walze::Rotiere()
{
	Rotor=(Rotor+1)%L; // L ist die Anzahl der Eingangs-Pins an der Walze
}
	
int Walze::Hinweg(int C) // Durch die realtiven Sprünge zu den Zielpins ist diese Funktion relativ einfach zu implementieren
{
	int i;
	C-='A'; // Dies ist die Pinnummer ohne Offset (so, als wären Rotor und der Ring auf A)
	i=C-Ring; if (i<0) { i+=L; } // negativen Ring-Offset und evtl. negativen Offset auf positiven abbilden
	i=(i+Rotor)%L; // Rotorposition addieren (für positiven Offset braucht man nur einen Modulo)
	C=C+Hin[i]; if (C<0) { C+=L; }
	C%=L; return C+'A';
}

int Walze::Rueckweg(int C) // Durch die realtiven Sprünge zu den Zielpins ist diese Funktion relativ einfach zu implementieren
{
	int i;
	C-='A'; // Dies ist die Pinnummer ohne Offset (so, als wären Rotor und der Ring auf A)
	i=C-Ring; if (i<0) { i+=L; } // negativen Ring-Offset und evtl. negativen Offset auf positiven abbilden
	i=(i+Rotor)%L; // Rotorposition addieren (für positiven Offset braucht man nur einen Modulo)
	C=C+Rueck[i]; if (C<0) { C+=L; }
	C%=L; return C+'A';
}

bool Walze::GetUSP() // TODO:Mehr als einen Umschaltpunkt unterstützen, zurzeit ist nur USP[0] gültig
{
	int U=(USP[0]+Ring)%L; // Der Ring verstellt auch den Umschaltpunkt nach vorn
	U-=6; if (U<0) { U+=L; } // Die Kerbe für die Umschaltung hat einen negativen Offset von -7
	// Aber warum ist dann U=U-6? Ganz einfach: Der Umschaltpunkt muss ÜBERSCHRITTEN werden, damit diese Funktion true zurück gibt
	if (Rotor==U) { return true; }
	else { return false; }
}

void StringTausch(char *S, int A, int B)
{
	int i=S[A],j=S[B];
	S[A]=j; S[B]=i;
}

void SetSteckbrett(char *STB, char *S)
{
	int i=0;
	while (S[i]!=0)
	{
		StringTausch(STB,S[i]-'A',S[i+1]-'A');
		i+=2;
		while (S[i]==' ') { i++; }
	}
}

int main(void)
{
	bool gefunden;
	int i=0;
	int C=0;
	int WZ=0;
	char A[100];
	char Daten[100];
	char SuchName[100];
	char STB[27]="DBNATLIHGVZFMCOUYRSEPJXWQK";
	Walze *UKW;
	Walze *W[50];
	clrscr();
	printf("Steckbrett-Konfiguration (Standard:AD CN ET FL GI JV KZ PU QY WX)\n");
	printf("ENTER ohne Eingabe übernimmt die Standardkonfiguration\n");
	printf("Stecker->"); input(A,100);
	if (strlen(A)>0)
	{
		strcpy(STB,(char *)"ABCDEFGHIJKLMNOPQRSTUVWXYZ");
		SetSteckbrett(STB,A);
	}
	printf("Walzenkonfiguration (Standard:'I','IV','III')\n");
	WZ=0;
	for (i=0; i<3; i++)
	{
		gefunden=false;
		while (gefunden==false)
		{
			printf("%d.Rotorwalze:",WZ+1); input(SuchName,100);
			gefunden=WalzeSuchen((char *)"RW",SuchName,Daten);
			if (gefunden==false) { printf("Die Walze '%s' ist nicht in der Datenbank.\n",SuchName); }
			else { W[WZ]=new Walze(Daten); WZ++; }
		}
	}
	gefunden=false;
	while (gefunden==false)
	{
		printf("Umkehrwalze (Standard='UKW-B'):"); input(SuchName,100);
		gefunden=WalzeSuchen((char *)"UKW",SuchName,Daten);
		if (gefunden==false) { printf("Die Walze '%s' ist nicht in der Datenbank.\n",SuchName); }
		else { UKW=new Walze(Daten); }
	}
	printf("Ringstellung (Tagesschlüssel,Standard='PZH'):"); input(A,100);
	W[0]->SetRing(A[0]); W[1]->SetRing(A[1]); W[2]->SetRing(A[2]);
	printf("Rotorstellung (Spruchschlüssel,Standard='RTZ'):"); input(A,100);
	W[0]->SetRotor(A[0]); W[1]->SetRotor(A[1]); W[2]->SetRotor(A[2]);
	do
	{

		if (C!='\n')
		{
			C=getch();
			if (C!='\n')
			{
				if (C==' ') { printf(" "); } // Leertaste nicht verschlüsseln
				else
				{
					W[WZ-1]->Rotiere();
					for (i=WZ-1; i>0; i--)
					{
						if (W[i]->GetUSP()==true) { W[i-1]->Rotiere(); }
					}
					C=toupper(C);
					C=STB[C-'A'];
					C=W[2]->Hinweg(C);
					C=W[1]->Hinweg(C);
					C=W[0]->Hinweg(C);
					C=UKW->Hinweg(C);
					C=W[0]->Rueckweg(C);
					C=W[1]->Rueckweg(C);
					C=W[2]->Rueckweg(C);
					C=STB[C-'A'];
					printf("%c",C);
				}
			}
		}
	}
	while (C!='\n');
	printf("\nDas Programm wurde beendet.\n");
	return 0;
}
