/*
 *	language.c - Foreign language translation for PGP
 *	Finds foreign language "subtitles" for English phrases 
 *	in external foriegn language text file.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "usuals.h"
#include "fileio.h"
#include "language.h"
#include "pgp.h"
#include "charset.h"
#include "armor.h"

#define SUBTITLES_FILE	"language.txt"
#define LANG_INDEXFILE	"language.idx"

#define	STRBUFSIZE		2048

char language[16] = "en";	/* The language code, defaults to English */
static char	*strbuf;
static char	lang[16];	/* readstr sets this to the language id of the msg it last read */
static int	subtitles_available = 0;
static int line = 0;
/*	subtitles_available is used to determine if we know whether the special
	subtitles_file exists.  subtitles_available has the following values:
	0  = first time thru, we don't yet know if subtitles_file exists.
	1  = we have already determined that subtitles_file exists.
	-1 = we have already determined that subtitles_file does not exist.
*/

#define	NEWLINE		0
#define	COMMENT		1
#define	INSTRING	2
#define	ESCAPE		3
#define	IDENT		4
#define	DONE		5
#define	ERROR		6
#define	ERR1		7

/* Look for and return a quoted string from the file.
 * If nlabort is true, return failure if we find a blank line
 * before we find the opening quote.
 */
static char	*
readstr (FILE *f, char *buf, int nlabort)
{	int		c, d;
	char *p = buf;
	int state = NEWLINE;
	int i = 0;
	
	while ((c = getc(f)) != EOF)
	{
		if (c == '\r')
			continue;
		/* line numbers are only incremented when creating index file */
		if (line && c == '\n')
			++line;
		switch (state)
		{
			case NEWLINE:
				switch(c)
				{
					case '#': state = COMMENT; break;
					case '"': state = INSTRING; break;
					case '\n':
						if (nlabort)
						{	*buf = '\0';
							return(buf);
						}
					default:
						if (i == 0 && isalnum(c))
						{
							state = IDENT;
							lang[i++] = c;
							break;
						}
						if (!isspace(c))
						{
							fprintf(stderr, "language.txt:%d: syntax error\n", line);
							state = ERROR;
						}
				}
				break;
			case COMMENT:
				if (c == '\n')
					state = NEWLINE;
				break;
			case INSTRING:
				switch(c)
				{
					case '\\': state = ESCAPE; break;
					case '"': state = DONE; break;
					default: *p++ = c;
				}
				break;
			case ESCAPE:
				switch (c)
				{
					case 'n':	*p++ = '\n';	break;
					case 'r':	*p++ = '\r';	break;
					case 't':	*p++ = '\t';	break;
					case 'e':	*p++ = '\033';	break;
					case 'a':	*p++ = '\007';	break;
					case '#':
					case '"':
					case '\\':	*p++ = c; break;
					case '\n':	break;
					case '0':
					case '1':
					case '2':
					case '3':
					case '4':
					case '5':
					case '6':
					case '7':
							d = c - '0';
							while ((c = fgetc(f)) >= '0' && c <= '7')
								d = 8 * d + c - '0';
							*p++ = d;
							ungetc(c, f);
							break;
					default:
							fprintf(stderr, "language.txt:%d: illegal escape sequence: '\\%c'\n", line, c);
							break;
				}
				state = INSTRING;
				break;
			case IDENT:		/* language identifier */
				if (c == ':') {
					state = NEWLINE;
					break;
				}
				if (c == '\n' && strncmp(lang, "No translation", 14) == 0)
				{
					i = 0;
					state = NEWLINE;
					break;
				}
				lang[i++] = c;
				if (i == 15 || !isalnum(c) && !isspace(c))
				{
					lang[i] = '\0';
					fprintf(stderr, "language.txt:%d: bad language identifier: '%s'\n", line, lang);
					state = ERROR;
					i = 0;
				}
				break;
			case DONE:
				if (c == '\n')
				{
					lang[i] = '\0';
					*p = '\0';
					return(buf);
				}
				if (!isspace(c))
				{
					fprintf(stderr, "language.txt:%d: extra characters after '\"'\n", line);
					state = ERROR;
				}
				break;
			case ERROR:
				if (c == '\n')
					state = ERR1;
				break;
			case ERR1:
				state = (c == '\n' ? NEWLINE : ERROR);
				break;
		}
	}
	if (state != NEWLINE)
		fprintf(stderr, "language.txt: unexpected EOF\n");
	return(NULL);
}

#ifdef TEST
main()
{
	char buf[2048];

	line = 1;
	while (readstr(stdin, buf, 0)) {
		printf("\nen: <%s>\n", buf);
		while (readstr(stdin, buf, 1) && *buf != '\0')
			printf("%s: <%s>\n", lang, buf);
	}
	exit(0);
}
#else

static struct indx_ent {
	word32	crc;
	long	offset;
} *indx_tbl = NULL;

static int max_msgs = 0;
static int nmsg = 0;

static FILE *langf;

static void init_lang(void);

static int make_indexfile(char *);

/*
 * uses 24-bit CRC function from armor.c
 */
static word32
message_crc(char *s)
{
	return crcbytes((byte *)s, strlen(s), (word32)0);
}

/*
 * lookup file offset in indx_tbl
 */
static long
lookup_offset(word32 crc)
{
	int i;
	
	for (i = 0; i < nmsg; ++i)
		if (indx_tbl[i].crc == crc)
			return(indx_tbl[i].offset);
	return(-1);
}


/*
 * return foreign translation of s
 */
char *
PSTR (char *s)
{
	long filepos;

	if (subtitles_available == 0)
		init_lang();
	if (subtitles_available < 0)
		return(s);

	filepos = lookup_offset(message_crc(s));
	if (filepos == -1)
		return(s);
	else
	{
		fseek(langf, filepos, SEEK_SET);
		readstr(langf, strbuf, 1);
	}

	if (strbuf[0] == '\0')
		return(s);

	for (s = strbuf; *s; ++s)
		*s = EXT_C(*s);
	return(strbuf);
}


static struct {
	long lang_fsize;	/* size of language.txt */
	char lang[16];		/* language identifier */
	int nmsg;			/* number of messages */
} indx_hdr;


/*
 * initialize the index table: read it from language.idx or create
 * a new one and write it to the index file. A new index file is
 * created if the language set in config.pgp doesn't match the one
 * in language.idx or if the size of language.txt has changed.
 */
static void
init_lang()
{
	char indexfile[MAX_PATH];
	char subtitles_file[MAX_PATH];
	FILE *indexf;

	if (strcmp(language, "en") == 0)
	{	subtitles_available = -1;
		return;		/* use default messages */
	}

	buildfilename (subtitles_file, SUBTITLES_FILE);
	if ((langf = fopen(subtitles_file, FOPRTXT)) == NULL)
	{
		subtitles_available = -1;
		return;
	}
	init_crc();
	if ((strbuf = (char *) malloc(STRBUFSIZE)) == NULL)
	{
		fprintf(stderr, "Not enough memory for foreign subtitles\n");
		fclose(langf);
		subtitles_available = -1;
		return;
	}
	buildfilename(indexfile, LANG_INDEXFILE);
	if ((indexf = fopen(indexfile, FOPRBIN)) != NULL)
	{
		if (fread(&indx_hdr, 1, sizeof(indx_hdr), indexf) == sizeof(indx_hdr) &&
			indx_hdr.lang_fsize == fsize(langf) &&
			strcmp(indx_hdr.lang, language) == 0)
		{
			nmsg = indx_hdr.nmsg;
			indx_tbl = (struct indx_ent *) malloc(nmsg * sizeof(struct indx_ent));
			if (indx_tbl == NULL)
			{
				fprintf(stderr, "Not enough memory for foreign subtitles\n");
				fclose(indexf);
				fclose(langf);
				subtitles_available = -1;
				return;
			}
			if (fread(indx_tbl, sizeof(struct indx_ent), nmsg, indexf) != nmsg)
			{
				free(indx_tbl);	/* create a new one */
				indx_tbl = NULL;
			}
		}
		fclose(indexf);
	}
	if (indx_tbl == NULL && make_indexfile(indexfile) < 0)
	{
		fclose(langf);
		subtitles_available = -1;
	}
	else
		subtitles_available = 1;
}


static int
make_indexfile(char *indexfile)
{
	FILE *indexf;
	long filepos;
	int total_msgs = 0;
	char *res;

	if (verbose)	/* must be set in config.pgp */
		fprintf(stderr, "Creating language index file '%s' for language \"%s\"\n",
				indexfile, language);
	rewind(langf);
	indx_hdr.lang_fsize = fsize(langf);
	strncpy(indx_hdr.lang, language, 15);
	init_crc();
	line = 1;
	nmsg = 0;
	while (readstr(langf, strbuf, 0))
	{
		if (nmsg == max_msgs)
		{
			if (max_msgs)
			{	max_msgs *= 2;
				indx_tbl = (struct indx_ent *) realloc(indx_tbl, max_msgs *
							sizeof(struct indx_ent));
			}
			else
			{	max_msgs = 400;
				indx_tbl = (struct indx_ent *) malloc(max_msgs *
							sizeof(struct indx_ent));
			}
			if (indx_tbl == NULL)
			{
				fprintf(stderr, "Not enough memory for foreign subtitles\n");
				return(-1);
			}
		}
		++total_msgs;
		indx_tbl[nmsg].crc = message_crc(strbuf);
		if (lookup_offset(indx_tbl[nmsg].crc) != -1)
			fprintf(stderr, "language.txt:%d: Message CRC not unique: \"%s\"\n",
					line, strbuf);
		do
		{
			filepos = ftell(langf);
			res = readstr (langf, strbuf, 1);		/* Abort if find newline first */
		} while (res && strbuf[0] != '\0' && strcmp(language, lang) != 0);

		if (res == NULL)
			break;
		if (strbuf[0] == '\0')	/* No translation */
			continue;

		indx_tbl[nmsg].offset = filepos;
		++nmsg;
		do
			res = readstr (langf, strbuf, 1);		/* Abort if find newline first */
		while (res && strbuf[0] != '\0');
	}
	line = 0;
	indx_hdr.nmsg = nmsg;
	if (nmsg == 0)
	{	fprintf(stderr, "No translations available for language \"%s\"\n\n",
				language);
		return(-1);
	}
	if (verbose || total_msgs != nmsg)
		fprintf(stderr, "%d messages, %d translations\n\n", total_msgs, nmsg);

	if ((indexf = fopen(indexfile, FOPWBIN)) == NULL)
		fprintf(stderr, "Cannot create %s\n", indexfile);
	else
	{
		fwrite(&indx_hdr, 1, sizeof(indx_hdr), indexf);
		fwrite(indx_tbl, sizeof(struct indx_ent), nmsg, indexf);
		if (ferror(indexf) || fclose(indexf))
			fprintf(stderr, "error writing %s\n", indexfile);
	}
	return(0);
}
#endif /* TEST */
