#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <exec/types.h>
#include <proto/dos.h>
#include <proto/utility.h>

int ReadLELong (FILE * fh, ULONG * res)
{
    UBYTE val[4];

    if (fread (val, 4, 1, fh) != 1)
	return EOF;

    *res = (val[0] << 24L) | (val[1] << 16) | (val[2] << 8) | val[3];

    return 1;
}

ULONG ConvLE (ULONG le)
{
    return ((le & 0xFF000000) >> 24)
	 | ((le & 0x00FF0000) >> 8)
	 | ((le & 0x0000FF00) << 8)
	 | ((le & 0x000000FF) << 24)
	 ;
}

typedef struct _DAT
{
    char  Word[31];
    char  Phonem[31];
    ULONG Offset;
    char  pad;
} DAT;

typedef struct _MEM
{
    int     Size;
    UBYTE * Text;
} MEM;

typedef struct _Entry
{
    DAT Dat;
    MEM Mem;
} Entry;

int ReadEntry (FILE * fhDat, FILE * fhMem, Entry * entry)
{
    static int cache = 0;
    static Entry nextEntry;

    if (cache)
    {
	memcpy (&entry->Dat, &nextEntry.Dat, 67);

	if (fseek (fhMem, entry->Dat.Offset, SEEK_SET) == EOF)
	    return EOF;
    }
    else
    {
	if (fread (&entry->Dat, 67, 1, fhDat) != 1)
	    return EOF;

	entry->Dat.Offset = ConvLE (entry->Dat.Offset);

	if (fseek (fhMem, entry->Dat.Offset, SEEK_SET) == EOF)
	    return EOF;

	cache = 1;
    }

    if (fread (&nextEntry.Dat, 67, 1, fhDat) != 1)
	return EOF;

    nextEntry.Dat.Offset = ConvLE (nextEntry.Dat.Offset);

    entry->Mem.Size = nextEntry.Dat.Offset - entry->Dat.Offset;
    entry->Mem.Text = malloc (entry->Mem.Size);

    if (fread (entry->Mem.Text, entry->Mem.Size, 1, fhMem) != 1)
	return EOF;

    return 1;
}

void FreeEntry (Entry * entry)
{
    if (entry->Mem.Text)
    {
	free (entry->Mem.Text);
	entry->Mem.Text = NULL;
    }
}

enum RenderMode { M_italics, M_itWord, M_rmSent, M_roman, M_bold };

void ChangeRenderMode (enum RenderMode mode)
{
    putchar ('\x9b');

    switch (mode)
    {
    case M_rmSent: case M_roman: fputs ("0m", stdout); break;
    case M_itWord: case M_italics: fputs ("3m", stdout); break;
    case M_bold: fputs ("1m", stdout); break;
    }
}

void ShowText (STRPTR text)
{
    STRPTR skipptr = text;
    enum RenderMode curMode = M_italics;

    ChangeRenderMode (curMode);

    for (;;)
    {
	if (text == skipptr)
	{
	    skipptr += 128;
	    text += 4;
	}

	if (*text == 0x08)
	{
	    switch (text[1])
	    {
	    case 3:
		curMode = M_rmSent;
		ChangeRenderMode (curMode);
		break;
	    case 4:
		curMode = M_itWord;
		ChangeRenderMode (curMode);
		break;
	    default:
		printf ("<<<%02x>>>", text[1]);
	    }
	    text += 2;
	}

	if (!*text)
	    break;
	else if (*text == '\n' ||
		(*text >= 32 && *text < 128) ||
		*text >= 160
	    )
	{
	    switch (curMode)
	    {
	    case M_rmSent:
		putchar (*text);

		if (strchr (",;!.:?", *text))
		{
		    ChangeRenderMode (curMode = M_italics);
		}

		break;

	    case M_itWord:
		if (*text == ' ')
		{
		    curMode = M_italics;
		    ChangeRenderMode (curMode);
		}

	    case M_italics:
	    default:
		putchar (*text);
	    }
	}

	text ++;
    }
}

void restoreRendition (void)
{
    ChangeRenderMode (M_roman);
}

const char arg_template[] = "WORDS/M,LANG/K,PATH/K";

struct Args
{
    char ** words;
    char * lang;
    char * path;
} arg_struct =
{
    NULL,
    "de",
    "COMPVOK2:"
};

FILE * OpenFile (char * name)
{
    char filename[256];
    FILE * fh;

    strcpy (filename, arg_struct.path);
    AddPart (filename, arg_struct.lang, sizeof (filename));
    strcat (filename, name);

    if (!(fh = fopen (filename, "r")) )
    {
	fprintf (stderr, "Cannot open \"%s\": %s\n"
	    , filename
	    , strerror (errno)
	    );
    }

    return fh;
}

void StrUpr (STRPTR str)
{
    while (*str)
    {
	*str = ToUpper (*str);
	str ++;
    }
}

void StrCpy (STRPTR dst, STRPTR src)
{
    while (*src)
    {
	*dst ++ = ToUpper (*src ++);
    }

    *dst = 0;
}

int main (int argc, char ** argv)
{
    struct RDArgs * rda;
    FILE * fhDat;
    FILE * fhMem;
    ULONG  dummy;
    Entry  entry;
    char ** wptr;
    char    cda_path[256];
    char    cda_lang[10];

    if (GetVar ("CDA_LANG", cda_lang, sizeof (cda_lang), 0) != -1)
	arg_struct.lang = cda_lang;

    if (GetVar ("CDA_PATH", cda_path, sizeof (cda_path), 0) != -1)
	arg_struct.path = cda_path;

    if (!(rda = ReadArgs ((char *)arg_template, (LONG *)&arg_struct, NULL)) )
	return 10;

    if (!(fhDat = OpenFile (".dat")) )
    {
	FreeArgs (rda);
	return 10;
    }

    if (!(fhMem = OpenFile (".mem")) )
    {
	FreeArgs (rda);
	fclose (fhDat);
	return 10;
    }

    atexit (restoreRendition);

    ReadLELong (fhDat, &dummy);
    ReadLELong (fhDat, &dummy);

    if (arg_struct.words)
    {
	for (wptr=arg_struct.words; *wptr; wptr++)
	    StrUpr (*wptr);
    }
    else
    {
	/* This is just to avoid another variable in a check later. */
	static char * dummy = "";
	wptr = &dummy;
    }

    while (ReadEntry (fhDat, fhMem, &entry) != EOF)
    {
	if (arg_struct.words)
	{
	    char word[32];
	    char * ptr;

	    StrCpy (word, entry.Dat.Word);

	    for (wptr=arg_struct.words; *wptr; wptr++)
	    {
		if ((ptr = strstr (word, *wptr)))
		{
		    /* printf ("--- %s\n", ptr); */
		    break;
		}
	    }
	}

	if (*wptr)
	{
	    ChangeRenderMode (M_bold);
	    printf ("%s ", entry.Dat.Word);
	    ChangeRenderMode (M_roman);
	    ShowText (entry.Mem.Text);
	    putchar ('\n');
	    ChangeRenderMode (M_roman);
	}

	FreeEntry (&entry);
    }

    fclose (fhMem);
    fclose (fhDat);
    FreeArgs (rda);

    return 0;
}
