#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define VERSION "4.14"

#if defined _AMIGA || defined AMIGA
	#include <dos/dos.h>
	#if defined __SASC
		#include <proto/dos.h>
	#else
		#include <clib/dos_protos.h>
	#endif
#else
	#include <time.h>
#endif

#include "LeoLib.h"

#define DIRECTORY    "Fortunes:"
#define FORTUNEBASE  "LeoTunes"
#define INDEX         ".idx"
#define INDEX4        ".idx4"

#define	TRUE	1
#define FALSE	0

#define WW_DEFWIDTH	78



/* The following definitions are for ANSI C compilers with bad stdio.h */
#if !defined SEEK_SET
	#define SEEK_SET 0
#endif

#if !defined _IOFBF
	#define _IOFBF 0
#endif



/* AMIGA VERSION STRING */

#if defined _AMIGA || defined AMIGA
	static char *Version = "$VER:LeoTune " VERSION
	#if defined __SASC
		" " __AMIGADATE__ " ©1992-96 Leopold-Soft, leopold@cs.tut.fi"
	#endif
	;
#endif


/* MY OWN TYPE DEFINITIONS */

#if !defined _AMIGA && !defined AMIGA
	typedef short BOOL;
#endif
typedef unsigned char uchar;


/* PROTOTYPES */

static void CreateIndexFile(void);
static void ShowFortune(int Number);
static int ShowFortuneFromMem(FILE* OutFile, uchar *Text, long Length);
static uchar ToIso11(uchar c);
static void GrepFortunes(uchar *GrepStr, BOOL CaseSensitive);
#if defined _AMIGA || defined AMIGA
	static ULONG GetAmigaTicks(void);
#endif
static void Bufferize(FILE *File);	/* Only one file may be bufferized at a time */

static void PutInit(FILE *OutFile);
static void PutWord(uchar *Str);
static void PutEOL(void);
static int PutEnd(void);			/* returns lines handled */
static void PutTab(void);
static void PutSpace(void);
static void PutSetWidth(int Width);
static void PutPurgeSpace(void);
static void PutSetIndent(BOOL On);
static void PutDoIndent(void);

/* VARIABLES */

static char	FFile[1024], FIndex[1024], FIndex4[1024];
static int	CurrArg = 0, WordWrapWidth = WW_DEFWIDTH;
static BOOL	WantedIndexRecount = FALSE;
static BOOL	LongOutputMode = TRUE;
static BOOL	SignatureMode = FALSE;
static BOOL	Iso11 = FALSE, Grep = FALSE, GrepCaseSensitivity;
static int WantedFortune = 0, GrepArg;
static char *Directory;
static void *InBuff = NULL;

static unsigned long FEnd0 =
		(('\n' << 24) | ('%' << 16) | ('%' << 8) | '\n');
static unsigned long FEnd1 =
		(('\n' << 24) | ('#' << 16) | ('#' << 8) | '\n');



/* MAIN PROGRAM */

int main(int argc, char **argv) {
	static char *ColumnsStr;
	static int Columns = 0;

	if ((ColumnsStr = getenv("COLUMNS")) && (Columns = atoi(ColumnsStr)))
		WordWrapWidth = Columns;

	FFile[0] = '\000';
	if (!(Directory = getenv("LEOTUNEDIR"))) {
		if (Directory = getenv("HOME")) {
			strcpy(FFile, Directory);
			if (FFile[strlen(FFile)-1] != '/'
#if defined _AMIGA || defined AMIGA
					&& FFile[strlen(FFile)-1] != ':'
#endif
									)
				strcat(FFile, "/");
			strcat(FFile, "text/");
		} else {
			strcpy(FFile, DIRECTORY);
		}
	} else {
		strcpy(FFile, Directory);
		if (FFile[strlen(FFile)-1] != '/'
#if defined _AMIGA || defined AMIGA
				&& FFile[strlen(FFile)-1] != ':'
#endif
								)
			strcat(FFile, "/");
	}
	strcat(FFile, FORTUNEBASE);
	strcpy(FIndex, FFile);
	strcat(FIndex, INDEX);
	strcpy(FIndex4, FFile);
	strcat(FIndex4, INDEX4);

	while(++CurrArg < argc) {
		if (!strcmp(argv[CurrArg],"-i") || !strcmp(argv[CurrArg],"index")) {
			WantedIndexRecount = TRUE;
		} else if (!strcmp(argv[CurrArg],"-s") || !strcmp(argv[CurrArg],"short")) {
			LongOutputMode = FALSE;
		} else if (!strcmp(argv[CurrArg],"-4") || !strcmp(argv[CurrArg],"sig")) {
			SignatureMode = TRUE;
		} else if (!strcmp(argv[CurrArg],"-7") || !strcmp(argv[CurrArg],"iso11")) {
			Iso11 = TRUE;
		} else if ((!strcmp(argv[CurrArg],"-t") || !strcmp(argv[CurrArg],"tune")) && argc > CurrArg) {
				WantedFortune = atoi(argv[++CurrArg]);
				if (WantedFortune < 1) {
					fprintf(stderr, "\nWarning: %d is not a valid fortune number, ignoring...\n",
							WantedFortune);
					WantedFortune = 0;
				}
		} else if ((!strcmp(argv[CurrArg],"-g") || !strcmp(argv[CurrArg],"grep")) && argc > CurrArg) {
			GrepArg = ++CurrArg;
			Grep = TRUE;
			GrepCaseSensitivity = FALSE;
		} else if ((!strcmp(argv[CurrArg],"-G") || !strcmp(argv[CurrArg],"GREP")) && argc > CurrArg) {
			GrepArg = ++CurrArg;
			Grep = TRUE;
			GrepCaseSensitivity = TRUE;
		} else if ((!strcmp(argv[CurrArg],"-w") || !strcmp(argv[CurrArg],"width")) && argc > CurrArg) {
				WordWrapWidth = atoi(argv[++CurrArg]);
		} else if (!strcmp(argv[CurrArg],"-h") || !strcmp(argv[CurrArg],"?")) {
			printf("\nLeofortune " VERSION " by Henrik Herranen " __DATE__ "\n\n"
				"Usage: %s [?] | [-h] | [-i] | [-s] | [-4] | [filename]\n\n"
				"-h or ?:     Show this help page.\n"
				"-i or index: Recreate the fortune index file. This option must\n"
				"             be used every time the fortune database is updated.\n"
				"-s or short: Don't insert any empty lines.\n"
				"-4 or sig  : Use an index file with only the fortunes with\n"
				"             mostly 4 lines of less than 80 characters each.\n"
				"-7 or iso11: Convert some 8-bit ANSI characters to their ISO 11\n"
				"             (Swedish translation table) equivalents. Replace\n"
				"             rest of the 8-bit characters with '?'\n"
				"-t x or      Show a specific fortune from the database. The\n"
				"     tune x: first fortune is numbered 1.\n"
				"-g x or      Displays all fortunes that have the string x in\n"
				"     grep x: themselves (case-insensitive).\n"
				"-G x or      Displays all fortunes that have the string x in\n"
				"     GREP x: themselves (case-sensitive).\n"
				"-w x or      Displays all word-wrapped in x columns. The default\n"
				"    width x: is 79 or whatever is in $COLUMNS\n"
				"filename:    the named file will be used as the fortune file.\n\n"
				,argv[0]);
				exit(EXIT_SUCCESS);
		} else {
			strcpy(FFile,argv[CurrArg]);
			strcpy(FIndex,argv[CurrArg]);
			strcat(FIndex,INDEX);
			strcpy(FIndex4,argv[CurrArg]);
			strcat(FIndex4,INDEX4);
		}
	}

	if (WantedIndexRecount) CreateIndexFile();
	else if (Grep) GrepFortunes((uchar *) argv[GrepArg], GrepCaseSensitivity);
	else ShowFortune(WantedFortune);

	return EXIT_SUCCESS;
}



/* CREATES INDEX FILES */

static void CreateIndexFile(void) {
	static FILE	*InFile,*InFile2,*OutFile,*OutFile4;
	static long	FortuneCount=0,FortuneCount4=0;
	static int	InC;
	static long	FortuneBeginAddress=0;
	static long	Width=0,Widest=0,Lines=0;
	static int	i;
	static long OldFortuneAmount=0,OldFortuneAmount4=0;
	static long WordWrappedFortunes, WordWrappedFortunes4;
	static unsigned long Last4 = 0;
	static long FortuneLength = 0, Loc = 0;
	static long MaxFortuneLength = 0;
	static uchar *FortuneText = NULL;

	SignatureMode = TRUE;

	if (!(InFile = fopen(FFile,"rb"))) {
		fprintf(stderr,"Couldn't open fortune file %s for reading!\n",FFile);
		exit(EXIT_FAILURE);
	}
	Bufferize(InFile);

	if (InFile2 = fopen(FIndex,"rb")) {
		fread(&OldFortuneAmount,1,sizeof(OldFortuneAmount),InFile2);
		fclose(InFile2);
	}

	if (InFile2 = fopen(FIndex4,"rb")) {
		fread(&OldFortuneAmount4,1,sizeof(OldFortuneAmount4),InFile2);
		fclose(InFile2);
	}

	if (!(OutFile = fopen(FIndex,"wb"))) {
		fprintf(stderr,"Couldn't open fortune file %s for writing!\n",FIndex);
		exit(EXIT_FAILURE);
	}

	if (!(OutFile4 = fopen(FIndex4,"wb"))) {
		fprintf(stderr,"Couldn't open fortune file %s for writing!\n",FIndex4);
		exit(EXIT_FAILURE);
	}

	/* Leave some space for the # of fortunes */
	for (i=0; i<sizeof(FortuneCount); i++) {
		fputc('?',OutFile);
		fputc('?',OutFile4);
	}

	do {
		InC = fgetc(InFile);
		Loc++;
		FortuneLength++;
		if (FortuneLength > MaxFortuneLength) {
			static uchar *Tmp;
			long OldFLen = MaxFortuneLength;
			MaxFortuneLength = MaxFortuneLength * 120 / 100 + 100;
			if (!(Tmp = malloc((size_t) MaxFortuneLength))) {
				fprintf(stderr, "Couldn't allocate %d bytes!\n", FortuneLength);
				exit(EXIT_FAILURE);
			}
			if (OldFLen) {
				memcpy(Tmp, FortuneText, (size_t) OldFLen);
				free (FortuneText);
			}
			FortuneText = Tmp;
		}
		FortuneText[FortuneLength-1] = InC;
		Last4 = (Last4 << 8) | InC;
		if (Last4 == FEnd0 || Last4 == FEnd1) {
			FortuneLength -= 3;
			FortuneCount++;
			fwrite(&FortuneBeginAddress, 1, sizeof(FortuneBeginAddress), OutFile);
			fwrite(&FortuneLength, 1, sizeof(FortuneLength), OutFile);
			if (Last4 == FEnd1) WordWrappedFortunes++;

			if (Last4 == FEnd0 && Widest < 80 && Lines < 5 ||
				Last4 == FEnd1 && ShowFortuneFromMem(NULL, FortuneText, FortuneLength) < 5) {
				FortuneCount4++;
				fwrite(&FortuneBeginAddress, 1, sizeof(FortuneBeginAddress), OutFile4);
				fwrite(&FortuneLength, 1, sizeof(FortuneLength), OutFile4);
				if (Last4 == FEnd1) WordWrappedFortunes4++;
			}
			Width = 0; Widest = 0; Lines = 0;
			FortuneBeginAddress = Loc;
			FortuneLength = 0;
		} else {
			if (InC == '\n') {
				if (Width > Widest) Widest = Width;
				Width = 0;
				Lines++;
			} else if (InC == '\t') {
				Width = (Width + 8) & ~7L;
			} else {
				Width++;
			}
		}
	} while (InC != -1);

	fseek(OutFile, 0, SEEK_SET);
	fwrite(&FortuneCount, 1, sizeof(FortuneCount), OutFile);
	fseek(OutFile4, 0, SEEK_SET);
	fwrite(&FortuneCount4, 1, sizeof(FortuneCount4), OutFile4);

	fclose(OutFile);
	fclose(InFile);

	printf("\n%d entries (%d+%d; %d new) created in %s\n"
			 "%d entries (%d+%d; %d new) created in %s\n\n",
			FortuneCount,
				WordWrappedFortunes, FortuneCount-WordWrappedFortunes,
				FortuneCount-OldFortuneAmount, FIndex,
			FortuneCount4,
				WordWrappedFortunes4, FortuneCount4-WordWrappedFortunes4,
				FortuneCount4-OldFortuneAmount4, FIndex4);

	return;
}




/* DISPLAYS A FORTUNE */

static void ShowFortune(int FNumber) {
	static long Random, i;
	static FILE *InFile;
	static long FortuneBeginAddress,FortuneLength,FortuneAmount;
	static uchar *FortuneText;

	if (SignatureMode) strcpy(FIndex, FIndex4);
	if (!(InFile = fopen(FIndex,"rb"))) {
		fprintf(stderr,"Couldn't open fortune file %s for reading!\n",FIndex);
		exit(EXIT_FAILURE);
	}

	fread(&FortuneAmount,1,sizeof(FortuneAmount),InFile);
	if (FNumber > 0) {
		if (FNumber <= FortuneAmount) {
			Random = FNumber - 1;
		} else {
			fprintf(stderr,"\nWarning: %d is not a valid fortune number (max=%d), ignoring...\n",
				FNumber, FortuneAmount);
			FNumber = 0;
		}
	}

	if (!FNumber) {
#if defined _AMIGA || defined AMIGA
		if (!(Random = (GetAmigaTicks() >> 1))) {
#else
		if ((Random = time(NULL)) < 0) {
			Random = 0;
#endif
			fprintf(stderr, "System clock not set, resetting to fortune #0!\n");
		} else {
			Random %= FortuneAmount;
		}
	}

	fseek(InFile, Random*(sizeof(FortuneBeginAddress)+sizeof(FortuneLength))+sizeof(FortuneAmount), SEEK_SET);

	fread(&FortuneBeginAddress,1,sizeof(FortuneBeginAddress),InFile);
	fread(&FortuneLength,1,sizeof(FortuneLength),InFile);

	fclose(InFile);

	if (!(InFile = fopen(FFile,"rb"))) {
		fprintf(stderr, "Couldn't open fortune file %s for reading!\n",FFile);
		exit(EXIT_FAILURE);
	}

	if(LongOutputMode) fputc('\n', stdout);
	fseek(InFile, FortuneBeginAddress, SEEK_SET);
	if (!(FortuneText = malloc((size_t) (FortuneLength+1)))) {
		fprintf(stderr, "Couldn't allocate %d bytes!\n", FortuneLength);
		exit(EXIT_FAILURE);
	}

	fread(FortuneText, 1, (size_t) (FortuneLength+1), InFile);
	if (Iso11) {
		for (i=0; i<FortuneLength; i++) {
			FortuneText[i] = ToIso11(FortuneText[i]);
		}
	}

	if (FortuneText[FortuneLength] == '%') {		/* No WordWrap required */
		fwrite(FortuneText, 1, (size_t) FortuneLength, stdout);
	} else {										/* WordWrap required	*/
		ShowFortuneFromMem(stdout, FortuneText, FortuneLength);
	}												/* End of if (WordWrap)	*/

	free(FortuneText);

	if(LongOutputMode) fputc('\n', stdout);

	fclose(InFile);

	return;
}



/* RETURNS THE ISO 11 EQUIVALENT OF AN 8-BIT UNSIGNED CHARACTER */

static uchar ToIso11(uchar c) {
	switch (c) {
		case 0304 : c = '[' ; break;	/* A" */
		case 0344 : c = '{' ; break;	/* a" */
		case 0326 : c = '|' ; break;	/* O", intentionally _NOT_ '\\' */
		case 0366 : c = '|' ; break;	/* o" */
		case 0305 : c = ']' ; break;	/* A' */
		case 0345 : c = '}' ; break;	/* a' */
		case 0334 : c = '^' ; break;	/* U" */
		case 0374 : c = '~' ; break;	/* u" */
		case 0311 : c = '@' ; break;	/* E' */
		case 0351 : c = '`' ; break;	/* e' */
		case 0253 : c = '"' ; break;	/* << */
		case 0273 : c = '"' ; break;	/* >> */
		default :   if (c > 126) c = '?'; break;
	}
	return c;
}




/* SHOWS FORTUNES THAT HAVE THE SPECIFIED STRING IN THEM */

static void GrepFortunes(uchar *FromStr, BOOL Sensitive) {
	static uchar GrepStr[80];
	static uchar Last[80];
	int Curr = 1, LastLoc = 0;
	BOOL OldSigMode = SignatureMode,
		OldLOMode = LongOutputMode,
		SkipUntilEndOfFortune = FALSE;
	static FILE *InFile;
	static int InC, GrepStrLen, i;
	unsigned long Last4 = 0, Magic = 0, MagicGrepStr = 0;
	static char Line[71] =
		"----------------------------------------------------------------------";
	SignatureMode = FALSE;
	LongOutputMode = FALSE;

	GrepStr[0] = '\000';
	for (i = 0; i < 80 && FromStr[i]; i++) {
		if (Sensitive) GrepStr[i] = FromStr[i];
		else GrepStr[i] = ToUpper(FromStr[i]);
		MagicGrepStr += GrepStr[i];
		Last[i] = 0;
	}
	GrepStr[79] = '\000';
	GrepStrLen = strlen((char *) GrepStr);
	if (!GrepStrLen) return;

	if (!(InFile = fopen(FFile,"rb"))) {
		fprintf(stderr,"Couldn't open fortune file %s for reading!\n",FFile);
		exit(EXIT_FAILURE);
	}
	Bufferize(InFile);

	i = 20 + strlen((char *) GrepStr);
	if (i > 70) i = 70;
	printf("---------- Grep: \"%s\" %s\n", GrepStr, &Line[i]);

	while ((InC = fgetc(InFile)) != -1) {
		Last4 = (Last4 << 8) | InC;
		if (!Sensitive) InC = ToUpper(InC);
		if (InC == '\n') InC = ' ';

		LastLoc++;
		if (LastLoc >= GrepStrLen) LastLoc = 0;
		Magic -= Last[LastLoc];
		Magic += InC;
		Last[LastLoc] = InC;

		if (Last4 == FEnd0 || Last4 == FEnd1) {
			Curr++;
			SkipUntilEndOfFortune = FALSE;
		}

		if (Magic == MagicGrepStr && !SkipUntilEndOfFortune) {
			int k = LastLoc, ok = TRUE;
			for (i=GrepStrLen-1; i>=0; i--) {
				if (Last[k] != GrepStr[i]) ok = FALSE;
				k--;
				if (k<0) k = GrepStrLen-1;
			}
			if (ok) {
				ShowFortune(Curr);
				printf("%s\n", Line);
				SkipUntilEndOfFortune = TRUE;
			}
		}
	}

	fclose(InFile);
	SignatureMode = OldSigMode;
	LongOutputMode = OldLOMode;
}




/* RETURNS THE CURRENT TIME IN FIFTIETH OF SECONDS, AMIGA SPECIFIC */

#if defined _AMIGA || defined AMIGA
static ULONG GetAmigaTicks(void) {
	static struct DateStamp ds;
	DateStamp(&ds);
	return (ULONG) ((ULONG) ds.ds_Tick +
			((ULONG) ds.ds_Minute +
			(ULONG) ds.ds_Days*24*60)*60*TICKS_PER_SECOND);
}
#endif




/***********************************\
*									*
*	Text displaying put-routines	*
*									*
\***********************************/

static FILE *PutFile=NULL;
static int PutPos, PutMaxColumn = 79, PutLines, PutIndent;
static BOOL PutLastSpace;

static void PutInit(FILE *OutFile) {
	PutFile = OutFile;
	PutPos = 0;
	PutLastSpace = FALSE;
	PutLines = 0;
	PutIndent = 0;
}

static void PutWord(uchar *Str) {
	static int Len;

	Len = strlen((char *) Str);
	if (PutPos + Len >= PutMaxColumn) {
		if (PutFile) fputc('\n', PutFile);
		PutPos = 0;
		PutLines++;
		PutDoIndent();
		if (PutFile) fprintf(PutFile, "%s", Str);
		PutPos += Len - 1;
	} else {
		if (PutLastSpace) {
			if (PutFile) fputc(' ', PutFile);
			PutPos++;
		}
		if (PutFile) fprintf(PutFile, "%s", Str);
		PutPos += Len;
	}
	PutLastSpace = FALSE;
}

static void PutEOL(void) {
	if (PutFile) fputc('\n', PutFile);
	PutPos = 0;
	PutLines++;
	PutLastSpace = FALSE;
	PutSetIndent(FALSE);
}

static void PutTab(void) {
	PutPurgeSpace();
	if (PutFile) fputc('\t', PutFile);
	PutPos = (PutPos + 8) & (~7);
}

static void PutSpace(void) {
	if (PutLastSpace) {
		PutLastSpace = FALSE;
		PutWord(" ");
	}
	PutLastSpace = TRUE;
}

static void PutPurgeSpace(void) {
	if (PutLastSpace) {
		if (PutPos + 1 >= PutMaxColumn) {
			if (PutFile) fputc('\n', PutFile);
			PutPos = 0;
			PutLines++;
			PutDoIndent();
		} else {
			if (PutFile) fputc(' ', PutFile);
			PutPos++;
		}
	}
	PutLastSpace = FALSE;
}

static void PutSetWidth(int Width) {
	if (Width < 20) Width = 20;

	PutMaxColumn = Width - 1;
}

static int PutEnd(void) {
	return PutLines;
}

static void PutSetIndent(BOOL On) {
	PutPurgeSpace();
	if (On) PutIndent = PutPos;
	else PutIndent = 0;
}

static void PutDoIndent(void) {
	if (PutIndent <= PutMaxColumn-20) {
		while (PutPos < PutIndent) {
			if (PutFile) fputc(' ', PutFile);
			PutPos++;
		}
	}
}




/* SHOWS A FORTUNE FILE FROM MEMORY */

static int ShowFortuneFromMem(FILE* OutFile, uchar *FortuneText, long FortuneLength) {
	static uchar Word[80];
	int WordLen = 0;
	BOOL LastNL = TRUE, LastEscape = FALSE;
	static int InC;
	static long i;

	if (SignatureMode) {
		PutSetWidth(WW_DEFWIDTH);
	} else {
		PutSetWidth(WordWrapWidth);
	}

	PutInit(OutFile);

	for (i=0; i<FortuneLength; i++) {
		InC = FortuneText[i];
		if (InC == '\\' && i<FortuneLength-1 && !LastEscape) {
			switch (FortuneText[i+1]) {
				case '<':
					PutSetIndent(FALSE);
					i++;
					break;
				case '>':
					if (WordLen) {
						PutWord(Word);
						WordLen = 0;
					}
					PutSetIndent(TRUE);
					i++;
					break;
				default:
					LastEscape = 2;
					break;
			}
		} else if (InC == '\n') {
			if (LastNL) {
				if (WordLen) {
					PutWord(Word);
					WordLen = 0;
				}
				PutEOL();
			} else {
				if (WordLen) {
					PutWord(Word);
					WordLen = 0;
				}
				PutSpace();
			}
		} else if (InC == '\t') {
			if (WordLen) {
				PutWord(Word);
				WordLen = 0;
			}
			PutTab();
		} else {
			if (InC == ' ') {
				if (WordLen) {
					PutWord(Word);
					WordLen = 0;
				}
				PutSpace();
			} else {
				if (WordLen < 78) {
					Word[WordLen] = InC;
					Word[++WordLen] = '\000';
				}
			}
		}
		LastNL = (InC == '\n');
		if (LastEscape) LastEscape--;
	}
	PutEOL();
	return PutEnd();
}



/* CONNECT A FILE TO A 16 KB IO-BUFFER TO ENHANCE FILE HANDLING.	*/
/* NOTE: ONLY ONE FILE AT A TIME MAY HAVE THIS BUFFER!				*/
/*		 THIS FUNCTION MUST BE CALLED IMMEDIATELY AFTER FOPEN()!	*/

static void Bufferize(FILE *File) {
	if (!InBuff) InBuff = malloc(16384);
	if (InBuff) setvbuf(File, InBuff, _IOFBF, (size_t) 16384);
}
