// C-Programmierung Beleg 6
// Copyright: Matthias Jauernig, 2005
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

// WICHTIG!: laut C9X-Standard ist fflush() nur auf Output-Streams anwendbar,
// fflush(stdin) z.B. undefiniert - dass dies bei Solaris funktioniert, ist reiner Zufall,
// bei Linux und anderen Systemen kann das schon ganz anders aussehen...
// => daher: eigenes Fflush() als Makro realisieren, welches stdin flush'ed
#define Fflush() while(getchar() != '\n')

/* --- Datenstruktur --------------------------------------------------------------------------	*/
typedef struct {
	char name[30], vorname[20];
	char adresse[50];
} datensatz;

typedef struct {						//Datenstruktur für Indexfile
	char name[30];
	long position;
} iindex;

typedef struct ELEMENT {					//Datenstruktur für verkettete Liste
	struct ELEMENT *next;
	iindex *ind;
} element;

/* --- Funktionsdeklarationen -----------------------------------------------------------------	*/
// Return-Codes der Funktionen, die char zurück geben
//  retval=0: kein Fehler
//  retval=1: Rückgabe mit Fehler ohne Fehlermeldung oder Abbruch durch Benutzer
//  retval=2: Rückgabe mit Fehler, zu dem Fehlermeldung ausgegeben werden kann
char eingabeDatenfile(char*);
char ausgabeDatenfile(char*);
char aendernDatenfile(char*);
element *sorteinfuegen(element*, element*);		//Einfügen in sort. verk. Liste
void sequAusgabeListe(element*);			//verk. Liste sequentiell ausgeben
void loescheListe(element*);				//verk. Liste löschen
char erstelleIndexfile(char*, char**);			//Indexfile erstellen
char sortAusgabeDatenfile(char*, char*);		//sortierte Ausgabe des Datenfiles mit Hilfe des Indexfiles
char pruefeExistenzDatenfile(int,char**,char**);	//prüfe ob Datenfile vorhanden ist
char pruefeExistenzIndexfile(char*, char);		//prüfe ob Indexfile vorhanden ist

/* --- main() ---------------------------------------------------------------------------------	*/
int main(int argc, char **argv){					//argv[1] ist evtl. filename
	char abfrage, *datenfile, *indexfile=NULL;
	char retval;

	if(pruefeExistenzDatenfile(argc,argv,&datenfile)!=0)
		return 1;
	
	do{
		printf("\n-------------\n"
		       "| Hauptmenu |\n"
		       "-------------\n");
		printf("(1) Eingabe eines Datensatzes\n"
		       "(2) Sequentielle Ausgabe des Datenfiles\n"
		       "(3) Datensatz aendern\n"
		       "(4) Indexfile erstellen\n"
		       "(5) Sortierte Ausgabe mit Indexfile\n"
		       "(6) Verlassen\n"
		       "> ");
		abfrage=getchar();  Fflush();
		
		retval=0;
		switch(abfrage){
			case '1':	retval=eingabeDatenfile(datenfile);			break;
			case '2':	retval=ausgabeDatenfile(datenfile);			break;
			case '3':	retval=aendernDatenfile(datenfile);			break;
			case '4':	retval=erstelleIndexfile(datenfile,&indexfile);		break;
 			case '5':	retval=sortAusgabeDatenfile(datenfile,indexfile);	break;
			case '6':	break;
			default:	printf("Ungueltige Nummer!\n");
		}
		if(indexfile && retval==0 && (abfrage=='1' || abfrage=='3'))
			retval=erstelleIndexfile(datenfile,&indexfile);
		if(retval==2)
			perror("Fehler");
	}while(abfrage!='6');

	return 0;
}

/* --- Funktionsdefinitionen ------------------------------------------------------------------	*/
char eingabeDatenfile(char *filename){
	datensatz neu;
	FILE *fp=fopen(filename, "ab");					//Schreiben im Append-Modus
	if(!fp) return 2;
	printf("Name eingeben:     ");
	fgets(neu.name,30,stdin); neu.name[strlen(neu.name)-1]='\0';
	printf("Vorname eingeben:  ");
	fgets(neu.vorname,20,stdin); neu.vorname[strlen(neu.vorname)-1]='\0';
	printf("Adresse eingeben:  ");
	fgets(neu.adresse,50,stdin); neu.adresse[strlen(neu.adresse)-1]='\0';
	if(!fwrite(&neu,sizeof(neu),1,fp)){
		fclose(fp);
		return 2;
	}
	fclose(fp);
	return 0;
}

char ausgabeDatenfile(char *filename){
	int i=1;
	datensatz neu;
	FILE *fp=fopen(filename, "rb");					//Lesen im Lese-Modus
	if(!fp) return 2;
	while(fread(&neu,sizeof(neu),1,fp)){
		printf("\nDatensatz Nr. %d:\n",i++);
		printf("Name:     %s\n",neu.name);
		printf("Vorname:  %s\n",neu.vorname);
		printf("Adresse:  %s\n",neu.adresse);
	}
	if(ferror(fp)){
	 	fclose(fp);
		return 2;
	}
	fclose(fp);
	return 0;
}

char aendernDatenfile(char *filename){
	int i, nr;
	char abfrage, zk[50];
	datensatz neu;
	FILE *fp=fopen(filename, "rb+");				//R/W im Update-Modus
	if(!fp) return 2;
	printf("\nNr. des zu aendernden Datensatzes?: ");
	scanf("%d",&nr); Fflush();
	if(fseek(fp,0,SEEK_END)==-1){					//Dateiende lesen
		fclose(fp);
		return 2;
	}
	i=ftell(fp);							//Bytes bis zur akt. Filepos
	if(i/sizeof(datensatz)<nr){					//wenn nr über Fileende
		printf("Soviele Datensaetze sind im File nicht vorhanden!\n");
		fclose(fp);
		return 1;
	}
	
	if(fseek(fp,(nr-1)*sizeof(neu),SEEK_SET)==-1){			//zu Datensatz nr spulen
		fclose(fp);
		return 2;
	}
	if(!fread(&neu,sizeof(neu),1,fp)){				//Datensatz lesen
		fclose(fp);
		return 2;
	}
	printf("\nDatensatz Nr. %d:\n",nr);
	printf("Name:     %s\n",neu.name);
	printf("Vorname:  %s\n",neu.vorname);
	printf("Adresse:  %s\n",neu.adresse);
	printf("=> Soll dieser Datensatz wirklich geaendert werden (j/n)?: ");
	abfrage=getchar(); Fflush();
	if(abfrage!='j' && abfrage!='J'){
		fclose(fp);
		return 1;
	}
	
	printf("Name eingeben:     ");
	fgets(zk,30,stdin); zk[strlen(zk)-1]='\0';
	if(strcmp(zk,"")!=0) strncpy(neu.name,zk,30);
	printf("Vorname eingeben:  ");
	fgets(zk,20,stdin); zk[strlen(zk)-1]='\0';
	if(strcmp(zk,"")!=0) strncpy(neu.vorname,zk,20);
	printf("Adresse eingeben:  ");
	fgets(zk,50,stdin); zk[strlen(zk)-1]='\0';
	if(strcmp(zk,"")!=0) strncpy(neu.adresse,zk,50);
	
	if(fseek(fp,-sizeof(neu),SEEK_CUR)==-1){			//einen Datensatz zurück spulen
		fclose(fp);
		return 2;
	}
	if(!fwrite(&neu,sizeof(neu),1,fp)){
		fclose(fp);
		return 2;
	}
	fclose(fp);
	return 0;
}

char pruefeExistenzDatenfile(int argc, char **argv, char **datenfile){
	FILE *fp;
	
	char abfrage='j';
	do{
		if(argc==1 || (abfrage!='j' && abfrage!='J')){
			*datenfile=(char*)malloc(100);
			printf("\nEinzulesende Datei angeben: ");
			scanf("%s", *datenfile); Fflush();
		}
		else *datenfile=argv[1];
	
		fp=fopen(*datenfile,"rb");				//File existent?
		if(!fp){						//Datei existiert nicht
			perror("Fehler");
			printf("Datei neu anlegen (j/n, q=quit)?: ");
			abfrage=getchar(); Fflush();
			if(abfrage=='J' || abfrage=='j')
				fp=fopen(*datenfile,"ab");		//File anlegen (Append-Modus)
			else if(abfrage=='Q' || abfrage=='q')
				return 1;				//Quit
			else if(*datenfile!=argv[1]) free(*datenfile);
		}
		else abfrage='j';					//Datei existiert, daher weiter
	}while(abfrage!='j' && abfrage!='J');
	fclose(fp);
	
	return 0;
}

char pruefeExistenzIndexfile(char *indexfile, char overwrite){
	FILE *fp;
	char abfrage;
	
	//prüfen ob Indexfile existiert
	fp=fopen(indexfile,"rb");				
	if(!fp){						//Datei existiert nicht
		printf("Indexfile existiert nicht. Neu anlegen (j/n)?: ");
		abfrage=getchar(); Fflush();
		if(abfrage=='J' || abfrage=='j'){
			fp=fopen(indexfile,"ab");		//File anlegen (Append-Modus)
			if(!fp)
				return 2;
			else fclose(fp);
		}else return 1;					//Rückgabe
	}else{
		if(!overwrite){
			printf("Datei existiert bereits - ueberschreiben (j/n)?: ");
			abfrage=getchar(); Fflush();
			if(abfrage=='n' || abfrage=='N')
				return 1;
		}
		fclose(fp);
		fp=fopen(indexfile,"wb");
		if(!fp)
			return 2;
		else fclose(fp);
	}
	
	return 0;
}

char erstelleIndexfile(char *datenfile, char **indexfile){
	FILE *fp;						//Filepointer
	iindex *ind;						//Index für Indexfile
	datensatz neu;						//Datensatz aus Datenfile
	element *akt, *kopf=NULL;				//verkettete Liste
	char overwrite=0, retval;
	
	if(*indexfile==NULL){
		*indexfile=(char*)malloc(100);
		printf("Indexfile angeben: ");
		scanf("%s",*indexfile); Fflush();
	}else overwrite=1;
	
	if((retval=pruefeExistenzIndexfile(*indexfile, overwrite))!=0){
		free(*indexfile); *indexfile=NULL;
		return retval;
	}
	
	//Daten aus Datenfile in verkettete Liste einlesen
	fp=fopen(datenfile, "rb");				//Öffnen des Datenfiles
	if(!fp){
		fclose(fp);
		return 2;
	}
	while(fread(&neu,sizeof(neu),1,fp)){			//Lesen des Datenfiles
		ind=(iindex*)malloc(sizeof(iindex));		//Platz für ein Indexelement reservieren
		akt=(element*)malloc(sizeof(element));		//neues Listenelement erzeugen
		akt->ind=ind;					//neues Indexelement mit Liste verketten
		strncpy(ind->name,neu.name,30);			//Name auf Zk kopieren
		ind->position=ftell(fp)-sizeof(neu);		//Position in Element für verkettete Liste schreiben
		kopf=sorteinfuegen(akt,kopf);			//sortiertes Einfügen des gelesenen Elements in die Liste
	}
	if(ferror(fp)){						//Lesefehler aufgetreten
		fclose(fp);
		loescheListe(kopf);
		return 2;
	}
	fclose(fp);
	printf("\nIndexfile erstellt:\n");
	sequAusgabeListe(kopf);
	
	//Daten aus verketteter Liste in Indexfile schreiben
	fp=fopen(*indexfile,"wb");
	akt=kopf;
	while(akt){						//lesen bis Ende der verk. Liste erreicht
		fwrite(akt->ind,sizeof(iindex),1,fp);		//Schreibe in Indexfile
		akt=akt->next;					//Element der verk. Liste weitersetzen
	}
	loescheListe(kopf);
	if(ferror(fp)){						//Schreibfehler aufgetreten
		fclose(fp);
		return 2;
	}
	fclose(fp);
	
	return 0;
}

char sortAusgabeDatenfile(char *datenfile, char *indexfile){
	iindex ind;						//Index für Indexfile
	datensatz neu;						//Datensatz aus Datenfile
	FILE *fp, *fp1;						//Filepointer
	
	if(indexfile==NULL){
		printf("Noch kein Indexfile erstellt!\n");
		return 1;
	}
	
	//nun werden die Datensätze aus dem Datenfile mit Hilfe des Indexfiles ausgegeben
	printf("Ausgabe sortiert nach Name:\n"
	       "---------------------------\n");
	fp=fopen(datenfile,"rb");
	if(!fp)
		return 2;
	fp1=fopen(indexfile,"rb");
	if(!fp1){
		fclose(fp);
		return 2;
	}
	while(fread(&ind,sizeof(ind),1,fp1)){
		fseek(fp,ind.position,SEEK_SET);
		fread(&neu,sizeof(neu),1,fp);
		if(ferror(fp)){					//Schreibfehler aufgetreten
			fclose(fp); fclose(fp1);
			return 2;
		}
		printf("\nDatensatz Nr. %d:\n",(int)(ind.position/sizeof(datensatz))+1);
		printf("Name:     %s\n",neu.name);
		printf("Vorname:  %s\n",neu.vorname);
		printf("Adresse:  %s\n",neu.adresse);
	}
	if(ferror(fp1)){					//Schreibfehler aufgetreten
		fclose(fp); fclose(fp1);
		return 2;
	}
	fclose(fp);
	fclose(fp1);
	
	return 0;
}

element *sorteinfuegen(element *tmp, element *kopf){
	element *retp=kopf;			//rück zu gebenden Zeiger auf Kopf setzen
	
	//kein Element oder das erste größer dem einzufügenden Element -> neuer Kopf
	if(kopf==NULL || strcmp(tmp->ind->name,kopf->ind->name)<0){
		tmp->next=kopf;			//next-Zeiger aus bisher erstes bzw. NULL
		retp=tmp;			//rück zu gebender Zeiger=neuer Kopf
	}else{
		//wenn noch Elemente vorh. oder kleiner einzufügendem
		while(kopf->next && strcmp(tmp->ind->name,kopf->next->ind->name)>0)
			kopf=kopf->next;		//setze Zeiger weiter
		
		tmp->next=kopf->next;		//zeige auf nächstes Element der Liste
		kopf->next=tmp;			//nächstes Listenelement=neues Element
	}
	return retp;				//neuen/alten Kopf zurückgeben
}

void sequAusgabeListe(element *akt){
	while(akt){					//solange noch Elemente auszugeben
		printf(" %s, %ld\n",akt->ind->name,akt->ind->position);
		akt=akt->next;				//setze Zeiger weiter
	}
	printf("\n");   
}

void loescheListe(element *akt){
	element *tmp;
	while(akt){
		tmp=akt;
		akt=akt->next;
		free(tmp->ind);
		free(tmp);
	}
}

