/* This file is part of
* ======================================================
* 
*           LyX, the High Level Word Processor
* 	 
*	    Copyright (C) 1995 1996 Matthias Ettrich
*           and the LyX Team
*======================================================*/

/*
 *	International support for LyX
 */

/*
 * This file screems to be written as a class.
 * I will do that as soon as we start the 0.11.x series. [Lgb]
 */

#include "config.h"

#include <unistd.h>
#include "forms.h"
#include <stdlib.h>

#if defined(sgi) && !defined(__GNUC__)
#include <bstring.h>
#endif

#include "form1.h"
#include "tex-defs.h"
#include "inset.h"
#include "intl.h"
#include "paths.h"
#include "combox.h"
#include "lyx_cb.h"

bool keymapon = false;	/* enabled by default */
static bool primarykeymap = true; /* primary key mappings */
char *chsetcode = NULL;	/* default to 7-bit ascii */
static int curkeymap = 0;	/* current keymap */
static int otherkeymap;	/* use other keymap */

static FD_KeyMap *fd_form_keymap = NULL;	/* keymap form */


static Combox *Language;
static Combox *Language2;
//static bool combox_down = false;

static void ParseKeymapFile(FILE *f, int depth);
static void FreeKeymap();

struct Keyexc {
	char c;		/* character to make exception */
	char *data;	/* exception data */
	Keyexc *next;
};

typedef Keyexc *keyexc;

char* keymap[256];	/* keymap for all 256 characters */
/* points to character definition or NULL if no translation defined */
/* character definition is what to return, if begins with 0 - then it's */
/* the dead key - found in next byte */

char *cdefmap[256];	/* character definition map */
/* if NULL then we do not know TeX code that makes this char; if non-null */
/* then we do know */

char kallowlist[TEX_MAX_ACCENT][32];	/* characters, that are allowed
					 * after a particular deadkey */
#define allowlistsz 32*sizeof(char)

keyexc exclist[TEX_MAX_ACCENT+1];	/* exception list for each deadkey */

static int deadkey = 0;	/* no dead key pending */

extern void InsertCorrectQuote();
static char *prim_lang = "default";
static char *sec_lang = "default";

void setPrimary(char *lang)
{
	if (lyx_debug_level & (DEBUG_KBMAP | DEBUG_LYXRC))
		fprintf(stderr, "Primary: `%s'\n", lang);
	prim_lang = StringCopy(lang);
}


void setSecondary(char *lang)
{
	if (lyx_debug_level & (DEBUG_KBMAP | DEBUG_LYXRC))
		fprintf(stderr, "Secondary: `%s'\n", lang);
	sec_lang = StringCopy(lang);
}


static
void InitKeymap()
	/* low-level init */
{
	int i;
	for (i = 0; i < 256; ++i) keymap[i] = NULL;
	for (i = 0; i <= TEX_MAX_ACCENT; ++i) exclist[i] = NULL;

	/* now init allow lists */
	for (i = 0; i < 32; ++i) kallowlist[0][i] = 0;
	for (i = 'A'; i <= 'Z'; ++i) kallowlist[0][i / 8] |= 1 << (i & 7);
	for (i = 'a'; i <= 'z'; ++i) kallowlist[0][i / 8] |= 1 << (i & 7);

	for (i = 1; i < TEX_MAX_ACCENT; ++i) memcpy(kallowlist[i], kallowlist[0],
		allowlistsz);
}

static
void freeexc(keyexc exclist)
{
	keyexc p;

	p = exclist;
	while (p) {
		p = exclist->next;
		free(exclist->data);
		free(exclist);
		exclist = p;
	}
}

static
void FreeKeymap()
	/* free key map description */
{
	int i;

	for (i = 0; i < 256; ++i)
		if (keymap[i]) free(keymap[i]);
	for (i = 0; i <= TEX_MAX_ACCENT; ++i)
		freeexc(exclist[i]);

	InitKeymap();
}

static
FILE *kfopen(char *name, char *ext)
	/* open key definition file */
{
	char *fpath;
	FILE *f;
	// syntax is slightly weird 
        // $$. indicates we should prepend a . to the filename 
	// to make it a hidden file
	const char searchpath [] = "$$LyX/kbd:$$.~/:./:./kbd";
	fpath = FileOpenSearch ("./", searchpath, name, ext);
        if (!fpath) fpath = FileOpenSearch ("./", searchpath, name, NULL);
   	if (!fpath) return NULL;
	f = fopen(fpath, "r");
	delete fpath;
        if (f) return f;
	else return NULL;
}


static
char getkeychar(char **p)
	/* get next char from *p, update *p */
{
	char c;

	if (!**p) return 0;
	c = *((*p)++);

	if (c == '\\') {
		if (!**p) return 0;
		c = *((*p)++);
	} else if (c == '\"') c = 0x80;

	if (lyx_debug_level & DEBUG_KBMAP)
		fprintf(stderr, "c =%c\n", c);
	return c;
}


static
int getkeymod(char **p)
	/* return modifier - decoded from p and update p */
{
	for (int i = 1; i <= TEX_MAX_ACCENT; i++) {
		if (lyx_debug_level & DEBUG_KBMAP) {
			if (lyx_accent_table[i].name)
				fprintf(stderr,
					"p = %s, lyx_accent_table[%d].name = %s\n",
					*p, i, lyx_accent_table[i].name);
		}
	
		if (lyx_accent_table[i].name &&
		    (strncmp(*p, lyx_accent_table[i].name, strlen(lyx_accent_table[i].name)) == 0)) {
			if (lyx_debug_level & DEBUG_KBMAP)
				fprintf(stderr, "Found it! Update *p to next space or so.\n");
			
			while (**p && **p != ' ' &&
			       **p != '\t' && **p != '\n') (*p)++;
			
			if (lyx_debug_level & DEBUG_KBMAP)
				fprintf(stderr, "Updated p = %s\n", *p);
			
			return i;
		}
	}
	return 0;
}


static
char *getkeydata(char *p)
	/* translate until end of string p */
{
	char buf[128], *p2;

	p2 = buf;

	while ((*(p2++) = getkeychar(&p)));

	p2 = (char *) malloc(p2 - buf);
	strcpy(p2, buf);
	return p2;
}


static
void insexc(keyexc *exclist, char c, char *data)
{
	keyexc p;

	p = (keyexc) malloc(sizeof(Keyexc));
	p -> next = *exclist;
	p -> c = c;
	p -> data = getkeydata(data);

	*exclist = p;
}


static
void ParseKeymapFile(FILE *f, int depth)
	/* primitive for parsing keymap files */
{
	char bf[128], *p, c, m;
	int action;

	if (depth > 8) return;	/* recursive ??? */

	/* read string */
	while (fgets(bf, 128, f)) {
		if (bf[0] != '\\') continue;	/* not a control keyword */

		/* first remove comment, beginning by unescaped # */
		p = bf + 1;
		while (*p && (*p != '#' || *(p-1) == '\\')) ++p;

		if (!*p) {
			/* haven't found '#' - remove whitespaces at the end */
			p = bf + strlen(bf);
		}
		do {
			*(p--) = 0;
		} while (*p == ' ' || *p == '\t' || *p == '\n');
		/* now process the entry */
		p = bf;
		while (*p && *p != ' ' && *p != '\t' && *p != '\n') ++p;
		action = 0;
		switch (p - bf) {
			case 5:
				if (strncmp(bf, "\\kmod", 5) == 0) action = 1;
				else if (strncmp(bf, "\\kmap", 5) == 0) action = 2;
				break;
			case 6:
				if (strncmp(bf, "\\kxmod", 6) == 0) action = 3;
				break;
		}
		/* do the action - define a key */

		if ((lyx_debug_level & DEBUG_KBMAP) && action)
			fprintf(stderr,
				"Key definition: [%d;%s]\n", action, p);

		switch (action) {
		    case 1:	/* define a modifier */
			if (!*(p++)) goto error;
			if (!(c = getkeychar(&p))) goto error;
			if (!*(p++)) goto error;
			if (!(m = getkeymod(&p))) goto error;
			/* define a modifier */
			if (keymap[(unsigned char) c]) free(keymap[(unsigned char) c]);	/* free old def */
			keymap[(unsigned char) c] = (char *) malloc(2);
			keymap[(unsigned char) c][0] = 0;
			keymap[(unsigned char) c][1] = m;
			if (!*(p++)) break;
			BZERO(kallowlist[--m], allowlistsz);
			while (*p) {
				kallowlist[m][*p / 8] |= 1 << (*p & 7);
				++p;
			}
			break;
		    case 2:	/* define a key mapping */
			if (!*(p++)) goto error;
			if (!(c = getkeychar(&p))) goto error;
			if (!*(p++)) goto error;
			/* define mapping */
			if (keymap[(unsigned char) c])
				free(keymap[(unsigned char) c]);	/* free old def */
			keymap[(unsigned char) c] = getkeydata(p);
			break;
		    case 3:	/* define an exception */
			if (!*(p++)) goto error;
			if (!(m = getkeymod(&p))) goto error;
			if (!*(p++)) goto error;
			if (!(c = getkeychar(&p))) goto error;
			if (!*(p++)) goto error;
			/* define an exception for modifier m/char c */
			insexc(&exclist[m], c, p);
			break;
		}
		continue;
error:
		fprintf(stderr, "Error in keymap definition: [%s]\n", bf);
	}

}


static
int SelectKeyMap(char *keyfile)
	/* load keyboard map from keyfile, return 0 on success, -1 else */
{
	FILE *f;

	// This means that no default.kmap is allowed
	//if (strcmp(keyfile, tex_babel[0]) == 0) {
	//	FreeKeymap();
	//	return 0;
	//}

	f = kfopen(keyfile, "kmap");
	if (!f) {
		FreeKeymap();
		return -1;
	}

	/* free old data and initialize new keyboard mapping */
	FreeKeymap();

	/* now parse the file & it's includes */
	ParseKeymapFile(f, 0);

	fclose(f);

	return 0;
}


static
int SelectCharset(char *code)
	/* select charset to convert symbols to: NULL-7-bit ascii (default),
	   other: iso8859-xx */
{
	FILE *f;
	char buf[128], tmp[128], *p, *p2;
	int n;

	if (chsetcode) {
		// free up the old code
		free(chsetcode);
		for (n = 0; n < 256; ++n)
			if (cdefmap[n]) {
				free(cdefmap[n]);
				cdefmap[n] = NULL;
			}
	}

	chsetcode = NULL;
	if (!code || strcmp(code, "ascii") == 0) return false;	// ascii 7-bit

	// open definition file
	f = kfopen(code, "cdef");
	if (!f) return true;		// no definition, use 7-bit ascii

	// alloc and copy the code string
	chsetcode = strcpy((char*)malloc(strlen(code)+1), code);

	// now read the file
	while (fgets(buf, 128, f)) if ((n = atoi(buf))) {
		// got character definition for "n"
		p = buf;
		while (*p && *p != '\"') ++p;
		if (!p) continue;
		++p;	// point to first char after quote
		p2 = tmp;
		while (*p && *p != '\"')
			if (*p == '\\') {
				// escaped character
				++p;
				*(p2++) = *(p++);
			} else *(p2++) = *(p++);
		*p2 = 0;
		// fine, now enter it in character definition
		if (!cdefmap[n]) cdefmap[n] = strcpy((char*)
			malloc(p2 - tmp + 1), tmp);

		if (lyx_debug_level & DEBUG_KBMAP)
			fprintf(stderr, "Chardef: %d to [%s]\n", n, tmp);
	}

	fclose(f);	// definition read

	return false;
}


void KeyMapOn(bool on)
	/* turn on/off key mappings, status in keymapon */
{
	keymapon = on;

	if (!fd_form_keymap) return;
	
	fl_set_button(fd_form_keymap->KeyOffBtn, 0);
	fl_set_button(fd_form_keymap->KeyOnBtn, 0);
	fl_set_button(fd_form_keymap->KeyOnBtn2, 0);

	if (on) {
		if (primarykeymap) fl_set_button(fd_form_keymap->KeyOnBtn, 1);
			else fl_set_button(fd_form_keymap->KeyOnBtn2, 1);
	} else {
		fl_set_button(fd_form_keymap->KeyOffBtn, 1);
		fl_hide_object(fd_form_keymap->KeymapErr);
	}
}


void ToggleKeyMap()
{
	if (keymapon && primarykeymap) {
		KeyMapSec();
	} else if (keymapon) {
		KeyMapOn(false);
	} else	
		KeyMapPrim();
}


void KeyMapPrim()
{
	int i;
	const char *p;

	fl_set_button(fd_form_keymap->KeyOffBtn, 0);
	fl_set_button(fd_form_keymap->KeyOnBtn, 1);
	fl_set_button(fd_form_keymap->KeyOnBtn2, 0);

	keymapon = true;
	primarykeymap = true;

	/* read text from choice */
	i = Language->get();
	
	if (lyx_debug_level & DEBUG_KBMAP)
		fprintf(stderr, "Table:%s\n", tex_babel[i-1]);

	if (i == otherkeymap)
		p = fl_get_input(fd_form_keymap->OtherKeymap);
	else
		p = Language->getline();

	curkeymap = i;

	if (SelectKeyMap((char *) p)) {
		// error selecting keymap
		fl_show_object(fd_form_keymap->KeymapErr);
	} else {
		// no error
		fl_hide_object(fd_form_keymap->KeymapErr);
	}
}


void KeyMapSec()
{
	int i;
	const char *p;

	fl_set_button(fd_form_keymap->KeyOffBtn, 0);
	fl_set_button(fd_form_keymap->KeyOnBtn, 0);
	fl_set_button(fd_form_keymap->KeyOnBtn2, 1);

	keymapon = true;
	primarykeymap = false;

	/* read text from choice */
	i = Language2->get();
	
	if (lyx_debug_level & DEBUG_KBMAP)
		fprintf(stderr, "Table:%s\n", tex_babel[i-1]);

	if (i == otherkeymap)
		p = fl_get_input(fd_form_keymap->OtherKeymap2);
	else
		p = Language2->getline();
	curkeymap = i;

	if (SelectKeyMap((char *) p)) {
		// error selecting keymap
		fl_show_object(fd_form_keymap->KeymapErr);
	} else {
		// no error
		fl_hide_object(fd_form_keymap->KeymapErr);
	}
}


static
void ChangeKeymap(char *keyfile, bool on, bool warn, int lngidx)
{
	if (SelectKeyMap(keyfile) != 0) {
		if (warn) {
		}
		return;
	}

	Language->select(lngidx);
	KeyMapOn(on);
}


//static
void checkchcodebuf(char *buf)
	// check for any LaTeX commands to match a char in the character set
{
	int i;
	char *p = buf, *p2;

	if (lyx_debug_level & DEBUG_KBMAP)
		fprintf(stderr, "Charset check: [%s]; ", buf);

	while (*p) {
		if (*p == '\200') {
			++p; p2 = p;
			while (*p2 && *p2 != '\200') ++p2;
			if (!*p2) break;
			*p2 = 0;
			p2++;
			for (i = 255; i >= 0; --i) if (cdefmap[i]) {
				if (strcmp(cdefmap[i], p) == 0) break;
			}
			if (i > 0) {
				
				// We've found a meaning of this LaTeX cmd
				*(p-1) = (char) i;	// set char
				strcpy(p, p2);		// copy rest 
			} else {
				// continue after this LaTeX command
				*(p2-1) = '\200';
				p = p2;
			}
		}
		++p;
	}
	if (lyx_debug_level & DEBUG_KBMAP)
		fprintf(stderr, "Check result: [%s]\n", buf);
}


static
void Translate(char *instr, char *outstr)
	/* translate instr and write to outstr, if translations are on */
	/* provide big enough buffer !!! Ie, 128 bytes */
{
	if (!keymapon) {
		strcpy(outstr, instr);
		return;
	}

  cont:
	while (*instr) {
		char buf[32];
		
		if (keymap[(unsigned char) *instr]) {
			/* found keymap entry */
			if (!keymap[(unsigned char) *instr][0]) {
				/* dead-key */
				if (deadkey) {
					keyexc p = exclist[deadkey];
					while (p) {
						if (p->c == *instr) {
							goto hated_goto;
							// but it's the best way
							// to do it here... :-(
						}
						p = p->next;
					}
				}
				deadkey = keymap[(unsigned char) *instr][1];
			} else {
				/* mapped character */
				strcpy(buf, keymap[(unsigned char) *instr]);
				checkchcodebuf(buf);
				strcpy(outstr, buf);
				outstr += strlen(outstr);
				deadkey = 0;
			}
			++instr;
		} else if (deadkey) {
			/* map character from instr */
		  hated_goto:
			keyexc p = exclist[deadkey];
			char *p2;
			while (p) {
				if (p->c == *instr) {
					deadkey = 0;
					strcpy(buf, p->data);
					checkchcodebuf(buf);
					strcpy(outstr, buf);
					outstr += strlen(outstr);
					++instr;
					goto cont; // I hate this
				}
				p = p->next;
			}
			
			/* now compose the latex char */
			buf[0] = 0x80;
			p2 = buf+1;
			strcpy(p2, lyx_accent_table[deadkey].cmd);
			p2 += strlen(p2);
			if (*instr == ' ') {
				strcpy(p2, "{ }\200");
				p2 += strlen(p2);
			} else if (kallowlist[deadkey-1][(*instr)/8] &
				   (1 << (*instr & 7))) {
				*(p2++) = *instr;
				*(p2++) = 0x80;
			} else {
                                /* incorrect stuff to put deadkey on */
				// maybe we should also print deadkey
				//char first
				LyXBell();
				
                                *(outstr++) = *(instr++);
                                deadkey = 0;
                                goto cont; // sorry
			}
			*p2 = 0;
			deadkey = 0;
			instr++;
			// now check for &buf[1] being in cdefmap
			checkchcodebuf(buf);
			strcpy(outstr, buf);
			outstr += strlen(outstr);
		} else *(outstr++) = *(instr++);
	}
	*outstr = 0;
}


static
void LCombo(int i)
{
	const char *p;
	if (!primarykeymap) return;

	if (i == otherkeymap)
		p = fl_get_input(fd_form_keymap->OtherKeymap);
	else 
		p = Language->getline();
	
	curkeymap = i;
	
	if (SelectKeyMap((char *) p)) {
		// error selecting keymap
		fl_show_object(fd_form_keymap->KeymapErr);
	} else {
		// no error
		fl_hide_object(fd_form_keymap->KeymapErr);
	}
}

static
void LCombo2(int i)
{
	const char *p;
	if (primarykeymap) return;
	
	if (i == otherkeymap)
		p = fl_get_input(fd_form_keymap->OtherKeymap2);
	else
		p = Language2->getline();
	
	curkeymap = i;
	
	if (SelectKeyMap((char *) p)) {
		// error selecting keymap
		fl_show_object(fd_form_keymap->KeymapErr);
	} else {
		// no error
		fl_hide_object(fd_form_keymap->KeymapErr);
	}
}


void InitKeyMapper()
	/* initialize key mapper */
{
	if (lyx_debug_level & DEBUG_KBMAP)
		fprintf(stderr,"Initializing key mappings...\n");

	Language = new Combox(FL_COMBOX_DROPLIST);
	Language2 = new Combox(FL_COMBOX_DROPLIST);
	Language->setcallback(LCombo);
	Language2->setcallback(LCombo2);

	InitKeymap();

	fd_form_keymap = create_form_KeyMap();

	fl_hide_object(fd_form_keymap->KeymapErr);
	fl_hide_object(fd_form_keymap->ChsetErr);
	fl_set_input(fd_form_keymap->Charset, DEFCHSET);

	fl_addto_form(fd_form_keymap->KeyMap);
	FL_OBJECT *tobj;
	Language->add(110,70,170,30,300);
	Language2->add(110,150,170,30,300);
	// this is what it should be, but combox doesn't support labels yet.
	//Language->setLabel("Primary");
	//Language2->setLabel("Secondary");
	tobj = fl_add_text(FL_NORMAL_TEXT,50,70,60,30,"Primary");
	fl_set_object_lsize(tobj, FL_MEDIUM_SIZE);
	tobj = fl_add_text(FL_NORMAL_TEXT,30,150,80,30,"Secondary");
	fl_set_object_lsize(tobj, FL_MEDIUM_SIZE);
	fl_end_form();

	int n=0;
 	while (true)
		if (strcmp(tex_babel[n++], "last_item")==0)
			break;
		else {
			Language->addto(tex_babel[n-1]);
			Language2->addto(tex_babel[n-1]);
		}
	
	Language->addto("other...");
	Language2->addto("other...");
	otherkeymap = n;
	if (!Language->select_text(prim_lang)) {
		Language->select(n);
		fl_set_input(fd_form_keymap->OtherKeymap, prim_lang);
	}
	if (!Language2->select_text(sec_lang)) {
		Language2->select(n);
		fl_set_input(fd_form_keymap->OtherKeymap2, sec_lang);
	}
	KeyMapOn(keymapon);
	if (keymapon)
		Keymap(NULL,23); // turn primary on

	for (int i = 0; i < 256; ++i) cdefmap[i] = NULL;

	SelectCharset(DEFCHSET);
}


void Keymap(FL_OBJECT *, long code)
{
	int i;
	const char *p;

	if (lyx_debug_level & DEBUG_KBMAP)
		fprintf(stderr, "KeyMap callback: %ld\n", code);

	switch (code) {
	    case 0:
		/* cancel/hide */
		fl_hide_form(fd_form_keymap->KeyMap);
		break;
	    case 3:
	    case 23:
	    case 43:
		/* on/off */
		keymapon = (code != 3);
		if (lyx_debug_level & DEBUG_KBMAP)
			fprintf(stderr, "KeyMapOn: %d\n", keymapon);

		primarykeymap = (code == 23);
		if (!keymapon) {
			fl_hide_object(fd_form_keymap->KeymapErr);
			break;
		}
		code -= 19;	// change to language change type code
		
	  case 4: // 4 and 24 will never be called directly, they will onlu
	  case 24: // called through 3,23,43 (Lgb)
		/* change keymap */
		/* read text from choice */
		if ((code == 24 && primarykeymap) || (code == 4 &&
			!primarykeymap)) return;
		if (primarykeymap) {
			i = Language->get();
		} else {
			i = Language2->get();
		}

		if (lyx_debug_level & DEBUG_KBMAP)
			printf("Table:%s\n", tex_babel[i]);

		if (i == otherkeymap)
			if (primarykeymap)
				p = fl_get_input(fd_form_keymap->OtherKeymap);
			else
				p = fl_get_input(fd_form_keymap->OtherKeymap2);
		else
			if (primarykeymap)
				p = Language->getline();
			else
				p = Language2->getline();

		curkeymap = i;

		if (SelectKeyMap((char *) p)) {
			// error selecting keymap
			fl_show_object(fd_form_keymap->KeymapErr);
		} else {
			// no error
			fl_hide_object(fd_form_keymap->KeymapErr);
		}
		break;
	    case 27:	/* set new font norm */
		p = fl_get_input(fd_form_keymap->Charset);
		if (SelectCharset((char *) p))
			fl_show_object(fd_form_keymap->ChsetErr);
		else
			fl_hide_object(fd_form_keymap->ChsetErr);
		break;
	}
}


void MenuKeymap()
{
	if (fd_form_keymap->KeyMap->visible) {
		fl_raise_form(fd_form_keymap->KeyMap);
	} else fl_show_form(fd_form_keymap->KeyMap, FL_PLACE_CENTER,
		FL_FULLBORDER, "Key Mappings");
}


void TranslateAndInsert(char c, LyXText *text)
	/* insert correct stuff into paragraph */
{
	char buf[2], obuf[128], tbuf[128];
	unsigned char *p, *p2;

	/* fill in instr */
	buf[0] = c;
	buf[1] = 0;

	/* translate the sequence */
	Translate(buf, obuf);

	if (lyx_debug_level & DEBUG_KBMAP)
		fprintf(stderr, "Translated string: %s to %s\n", buf, obuf);

	/* insert into buffer for text */
	p = (unsigned char *) obuf;

	while (*p) {
		if (*p == 0x80) {
			/* start Latex style */
			++p;
			p2 = (unsigned char *) tbuf;
			while (*p && *p != 0x80) *(p2++) = *(p++);
			*p2 = 0;
			Inset *new_inset = new InsetLatexAccent(tbuf);
			text->InsertInset(new_inset);
			if (!*p) break;
		} else if (*p == '\"')
			InsertCorrectQuote();
		else text->InsertChar(*p);
		++p;
	}
}
