// Filename:   lang.C
// Contents:   the language object methods
// Author: Gregory Shaw
// Created:    11/7/94

#ifndef _LANG_C_
#define _LANG_C_
/*
This file is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.

In addition to the permissions in the GNU General Public License, the
Free Software Foundation gives you unlimited permission to link the
compiled version of this file with other programs, and to distribute
those programs without any restriction coming from the use of this
file.  (The General Public License restrictions do apply in other
respects; for example, they cover modification of the file, and
distribution when not linked into another program.)

This file is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */


#include "bbshdr.h"

// static variables
static int    selected = 0;     //  has a language been selected?
static lang_hash  *hash_list[HASH_TABLE_SIZE];
// head of the identifier list
static char current_language[MAX_LANGUAGE_FILENAME];


// Method: constructor
// Purpose:    initialize the object
// Input:  none
// Output: (object is initialized)
// Author: Gregory Shaw
// Created:    11/7/94

language::language()
{
	int    x;

	// init list but only if not already initialzed
	if (selected != 1)
	{
		for (x=0; x< HASH_TABLE_SIZE; x++)
			hash_list[x] = NULL;
		selected = 1;
		// configure with default language
		l_open(DEFAULT_LANGUAGE);
	}
}


// Method: destructor
// Purpose:    clean up the object
// Input:  none
// Output: all data storage is freed
// Author: Gregory Shaw
// Created:    11/7/94

language::~language()
{
	l_close();                  // de-allocate storage
}


// Method: add_node
// Purpose:    add a new string to the hash table
// Input:  ident - the identity of the string
//     string - the string to print/send
// Output: none
// Author: Gregory Shaw
// Created:    12/3/94
int language::add_node(char *ident, char *string)
{
	lang_hash  *new_node;       // new node
	unsigned long  hash;        // hash for ident

	// malloc new node
	if (new_node = (lang_hash *)malloc(sizeof(lang_hash)), new_node == NULL)
	{
		ap_log("Unable to allocate space for new language hash");
		exit(0);
	}
	if (new_node->l_text = (char *)malloc(strlen(string)+1), new_node->l_text == NULL)
	{
		ap_log("Unable to allocate space for string in language module");
		exit(0);
	}
	if (new_node->l_ident = (char *)malloc(strlen(ident)+1), new_node->l_ident == NULL)
	{
		ap_log("Unable to allocate space for identity in language module");
		exit(0);
	}
	strcpy(new_node->l_text,string);
	strcpy(new_node->l_ident, ident);
	new_node->h_next = NULL;
	hash = hashpjw(ident);      // get hash
	if (hash_list[hash] == NULL)// no hash here?
	{
		// set hash
		hash_list[hash] = new_node;
	}
	else                        // node exists  -- insert at start of list
	{
		new_node->h_next = hash_list[hash];
		hash_list[hash] = new_node;
	}
	return(0);
}


// Method: current_lang
// Purpose:    return the current language
// Input:  none
// Output: a string with the filename of the default language
// Author: Gregory Shaw
// Created:    12/5/94

char *language::current_lang(void)
{
	return(current_language);
}


// Method: default_language
// Purpose:    return the default language
// Input:  none
// Output: a string with the filename of the default language
// Author: Gregory Shaw
// Created:    12/5/94

char *language::default_language(void)
{
	return(DEFAULT_LANGUAGE);
}


// Method: dump
// Purpose:  dump contents of object
// Input:  none
// Output: all strings are dumped
// Author: Gregory Shaw
// Created:    12/12/94

void language::dump(void)
{
	int x;
	int y;
	lang_hash *tmp;

	for (x = 0; x < HASH_TABLE_SIZE; x++)
		if (hash_list[x] == NULL)
			printf("hash %d NULL\n",x);
	else
	{
		y = 1;
		tmp = hash_list[x];
		while (tmp != NULL)
		{

			printf("hash %d %s\n",x,tmp->l_ident);
			tmp = tmp->h_next;
			if (!(y++%21))
			{
				waitcr();
			}
		}
	}
}


// Method: efficiency
// Purpose:    dump the efficiency of the language hash
// Input:  none
// Output: a string dumping the efficiency of the hash
// Author: Gregory Shaw
// Created:    12/5/94

void language::efficiency(void)
{
	int    used;
	int    x;
	char   tmpstr[60];

	used = 0;
	for (x = 0; x < HASH_TABLE_SIZE; x++)
		if (hash_list[x] != NULL)
			used++;
	sprintf(tmpstr,"language hash efficiency: %f%%",(float)used/HASH_TABLE_SIZE);
	sstrcr_c(tmpstr);
	waitcr();
}


// Method: hashpjw
// Purpose:    generate a hash from a string
// Input:  character string
// Output: hash of character string
// Author: Gregory Shaw
// Notes:  this function has been shamelessly copied from libg++.
// Created:    11/10/94

// From Dragon book, p436
unsigned long language::hashpjw(const char* x)
{
	register unsigned int h = 0;
	register unsigned int g;

	while (*x != 0)
	{
		h = (h << 4) + *x++;
		if ((g = h & 0xf0000000) != 0)
			h = (h ^ (g >> 24)) ^ g;
	}
	return h % HASH_TABLE_SIZE; // translate to array size
}


// Method: lclose
// Purpose:    de-allocate the storage used by the object
// Input:  none
// Output: all data storage is freed
// Author: Gregory Shaw
// Created:    11/7/94

int language::l_close(void)
{
	int    x;
	lang_hash  *tmp;

	for (x=0; x< HASH_TABLE_SIZE; x++)
	{
		while (hash_list[x] != NULL)
		{
			tmp = hash_list[x];
			hash_list[x] = hash_list[x]->h_next;
			free(tmp->l_text);  // get rid of text
			free(tmp->l_ident); // get rid of identity
			free(tmp);          // get rid of node
		}
	}
	return(0);
}


// Method: l_open
// Purpose:    open a language file, read into memory
// Input:  lang_name - the filename of the language file
// Output: non-zero if error
// Author: Gregory Shaw
// Created:    11/7/94

int language::l_open(char *lang_name)
{
	FILE       *langfile;
	lang_hash  *tmp = NULL;     // temporary node
	char       *tmpstr;
	char       tmpident[MAX_IDENT];
	char       *tmpptr;
	char       errstr[100];     // string for error building
	char       c;
	// where white space starts
	char       *white_space = NULL;

	// already using language?
	if (!strcmp(current_language,lang_name))
		return(0);
	// save for posterity
	strcpy(current_language,lang_name);
	// malloc buffer of empty space for temporary storage
	if (tmpstr =(char *) malloc(LANGUAGE_TEMP_BUF), tmpstr == NULL)
	{
		ap_log("Unable to allocate temporary language buffer space");
	}
	sprintf(tmpstr,"%s/lang/%s",getenv("BBSDIR"),lang_name);
	if (langfile = bopen(tmpstr, "r"), langfile == NULL)
	{
		sprintf(tmpstr,"Unable to open language file %s",lang_name);
		ap_log(tmpstr);
		// errored off.  let's try english
		sprintf(tmpstr,"%s/lang/english",getenv("BBSDIR"));
		if (langfile = bopen(tmpstr, "r"), langfile == NULL)
		{
			ap_log("Unable to open language file english.");
			ap_log("Program aborted.");
			exit(0);
		}
	}
	while (!feof(langfile))
	{
		// skip spaces
		while (c = fgetc(langfile), isspace(c) && !feof(langfile));
		if (feof(langfile))
			continue;
		// comment?
		if (c == '#')
		{                       // skip rest of line
			while (c = fgetc(langfile), c != '\n' && c != '\r' && !feof(langfile));
			continue;
		}
		// get identity
		tmpptr = tmpident;
		*tmpptr++ = c;
		while (c = fgetc(langfile), !isspace(c) && !feof(langfile))
		{
			*tmpptr++ = c;
			if (tmpptr - tmpident >= MAX_IDENT)
			{
				tmpptr--;
				*tmpptr = 0;
				sprintf(errstr,"lang: ident too long: %s",tmpident);
				ap_log(errstr);
				break;
			}
		}
		*tmpptr = 0;
		// skip whitespace
		// search for starting double-quote
		while (c = fgetc(langfile), c != '"' && !feof(langfile));
		if (feof(langfile))
			continue;
		// now get string
		tmpptr = tmpstr;
		white_space = NULL;
		while (c = fgetc(langfile), c != '"' && !feof(langfile))
		{

			// try to skip whitespace around a CR or LF
			if (isspace(c))     // white space?
			{
				// first white space
				if (white_space == NULL)
					// save spot
					white_space = tmpptr;

				// NOTE: the below will skip whitespace with a
				//return in the middle.  It ignores whitespace at the
				//start of a line by itself.  This may not be what is
				//desired.

				// found return embbeded in string
				if (c == '\n' || c == '\r')
				{
					tmpptr = white_space;
					// set to return (save)
					*tmpptr++ = '\n';
					// skip rest of whitespace.
					while (c = fgetc(langfile), isspace(c) && !feof(langfile));
					white_space = NULL;
				}
			}
			else
				white_space = NULL;



			*tmpptr++ = c;
			if (tmpptr - tmpstr == LANGUAGE_TEMP_BUF)
			{
				sprintf(errstr,"lang: string to large for identifier %s",tmp->l_ident);
				ap_log(errstr);
				// free space
				free(tmp->l_ident);
				free(tmp);
			}
		}
		*tmpptr = 0;
		// now add to list
		add_node(tmpident, tmpstr);
	}
	selected=1;
	bclose(langfile);
	return(0);
}


// Method: lookup
// Purpose:    find an identifier in the language object
// Input:  ident - the identity to look up
// Output: NULL if identifier not found.  String if found.
// Author: Gregory Shaw
// Created:    12/4/94

char *language::lookup(char *ident)
{
	unsigned long  hash;
	lang_hash  *ptr;
	char       tmpstr[255];

	hash = hashpjw(ident);
	if (hash_list[hash] != NULL)
	{
		ptr = hash_list[hash];
		while (ptr != NULL && strcmp(ptr->l_ident, ident))
			ptr = ptr->h_next;
		if (ptr != NULL)
			return(ptr->l_text);

	}
	sprintf(tmpstr,"language: identifier %s not found",ident);
	ap_log(tmpstr);
	#ifdef DEBUG
	fprintf(stderr,tmpstr);
	fflush(stderr);
	#endif
	return(NULL);
}


// Method: gstrl
// Purpose:    do a string lookup and return the string found
// Input:  ident - the identity to lookup
// Output: NULL if error. string if found.
// Author: Gregory Shaw
// Created:    12/4/94
char *language::gstrl(char *ident)
{
	char   *tmp;
	char   tmpstr[255];

	if (tmp = lookup(ident), tmp == NULL)
	{
		sprintf(tmpstr,"Unable to find language identity %s",ident);
		ap_log(tmpstr);
		return("");
	}
	return(tmp);
}


// Method: select_lang
// Purpose:    show the user the languages available and allow him to choose
// Input:  none
// Output: the language the user has chosen
// Author: Gregory Shaw
// Created:    12/5/94

void language::select_lang(char *lang)
{
	FILE   *langfile;
	char   tmpstr[200];
	char   filenames[MAX_LANGUAGES][MAX_LANGUAGE_FILENAME];
	char   selection_keys[MAX_LANGUAGES];
	char   *tmpptr;
	char   *cptr;
	char   c;
	int    done;
	int    x;
	int    languages;           // number of languages

	// open languages selection file

	languages = 0;
	sprintf(tmpstr,"%s/config/languages",getenv("BBSDIR"));
	if (langfile = bopen(tmpstr, "r"), langfile == NULL)
	{
		ap_log("language: Unable to open languages selection file");
	}
	// loop through file
	sstrcrl("PICK_A_LANGUAGE");
	cr();
	while (!feof(langfile))
	{
		tmpptr = tmpstr;
		while (c = fgetc(langfile), c != '\n' && c != '\r' && !feof(langfile))
			*tmpptr++ = c;
		*tmpptr = 0;
		if (tmpstr[0] == 'A')   // available
		{
			tmpptr = tmpstr;
			// skip to next field
			while (*tmpptr != '|')
				tmpptr++;
			// now get name of file
			tmpptr++;           // skip |
			cptr = filenames[languages];
			// get filename
			while (*tmpptr != '|')
				*cptr++ = *tmpptr++;
			*cptr = 0;

			// now get key
			tmpptr++;           // skip |
			selection_keys[languages] = *tmpptr;
			tmpptr++;           // skip |

			// now dump rest to user
			tmpptr++;
			sstrcr_c(tmpptr);
			languages++;
			if (languages > MAX_LANGUAGES)
			{
				ap_log("lang: too many languages in language file");
			}
		}
	}
	// have him pick a language
	cr();
	sstrl("YOURCHOICE");
	// report it back
	done = 0;
	while (!done)
	{
		c = gch(1);
		for (x=0; x<languages; x++)
			if (selection_keys[x] == c)
		{
			done++;
			// open language
			l_open(filenames[x]);
			// save for later
			strcpy(lang, filenames[x]);
		}
	}
	cr();
	cr();
}


// Method: sstrcrl
// Purpose:    send a string from the language file, identified by ident
//     followed by a CR.
// Input:  ident - the identity of the string to send
// Output: non-zero if error
// Author: Gregory Shaw
// Created:    11/8/94
// Notes:  I should really check the return value of lookup for NULL

int language::sstrcrl( char *ident)
{
	char   *tmp;
	char   tmpstr[255];

	if (tmp = lookup(ident), tmp == NULL)
	{
		sprintf(tmpstr,"Unable to find language identity %s",ident);
		ap_log(tmpstr);
	}
	sstrcr_c(tmp);
	return(0);
}


// Method: sstrl
// Purpose:    send a string from the language file, identified by ident
// Input:  ident - the identity of the string to send
// Output: non-zero if error
// Author: Gregory Shaw
// Created:    11/8/94

int language::sstrl(char *ident)
{
	char   *tmp;
	char   tmpstr[255];

	if (tmp = lookup(ident), tmp == NULL)
	{
		sprintf(tmpstr,"Unable to find language identity %s",ident);
		ap_log(tmpstr);
	}
	sstr_c(tmp);
	return(0);
}


// Method: yesno
// Purpose:    get 'y' or 'n' from user.
// Input:  none
// Output: true for yes, false for no
// Author: Greg Shaw
// Created:    7/13/93
// Modified: Kim-Minh Kaplan, Feb 1995

int language::yesno(void)
{
	struct fd_set fds[2];
	char   found=0;
	char   c;
	struct timeval waittime;
	char   bsstr[] =            // backspace
	{
		0x8,0x20,0x8, 0
	};
	static char *stryes;
	static char cyes = 0;
	static char *strno;
	static char cno = 0;
	static char strprompt[] = "[?,?] ";

	if (cyes == 0)
	{
		if ((stryes = lookup("YES")) == NULL)
		{
			ap_log("Unable to find language YES");
			stryes = "Yes";
		}
		cyes = toupper(*stryes);
		if ((strno = lookup("NO")) == NULL)
		{
			ap_log("Unable to find language NO");
			strno = "No";
		}
		while ((cno = toupper(*strno++)) != '\0' && cno == cyes)
			;                   /* NOP */
		sprintf(strprompt, "[%c,%c] ", cyes, cno);
	}
	sstr(strprompt);

	waittime.tv_sec = 0;
	waittime.tv_usec = 100;
	FD_SET(fileno(stdin), fds);
	while (!found)
		while (!found)
	{
		c = toupper(gch(1));
		if (c != 0)
		{
			if (c == cyes)
				found = 1;
			else if (c == cno)
				found = 2;
			else
				sstr(bsstr);
		}
		select(FD_SETSIZE,fds,NULL,NULL,&waittime);
	}
	fflush(stdin);
	cr();
	return(found==1);
};


#endif                          // _LANG_C_






