#include "dat.h"
#include "fn.h"

static Im im;
static int popup = 0;
static void dictqmap(Im*);

static void
backmap(Im *im)
{
	spopr(&im->pre);
}

Lang langs[] = {
	{LangEN,    "english", nil,      transmap, backmap, dictqmap, nil, nil},
	{LangJP,    "hira",    "kanji",  transmap, backmap, dictqmap, nil, nil},
	{LangJPK,   "kata",    "kanji",  transmap, backmap, dictqmap, nil, nil},
	{LangKO,    "hangul",  nil,      transko,  backko,  dictqmap, nil, nil},
	{LangEMOJI, "emoji",   "emoji",  transmap, backmap, dictqmap, nil, nil},
	{LangVI,    "telex",   nil,      transvi,  backmap, dictqmap, nil, nil},
};
int nlang = nelem(langs);

static void
clearkouho(void)
{
	im.nkouho = 0;
	im.sel = -1;
}

static void
setkouho(Dictres *res)
{
	int i;

	clearkouho();
	for(i = 0; i < res->nkouho; i++){
		im.kouho[i] = res->kouho[i];
		im.nkouho++;
	}
}

static void
show(void)
{
	Drawcmd dc;
	int i, first, n;

	if(!popup)
		return;
	sclear(&dc.pre);
	if(!mapget(im.l->map, &im.pre, &dc.pre))
		dc.pre = im.pre;
	first = im.sel >= Maxdisp ? im.sel - Maxdisp + 1 : 0;
	n = im.nkouho - first;
	if(n > Maxdisp) n = Maxdisp;
	dc.nkouho = n;
	dc.sel = im.sel >= 0 ? im.sel - first : -1;
	for(i = 0; i < n; i++)
		dc.kouho[i] = im.kouho[first + i];
	chansend(drawc, &dc);
}

static void
reset(void)
{
	sclear(&im.pre);
	clearkouho();
	show();
}

void
dictsend(Im *im, Str *key)
{
	Dictreq req;

	req.key = *key;
	req.lang = im->l->lang;
	req.pre = im->pre;
	channbsend(dictreqc, &req);
}

static void
dictqmap(Im *im)
{
	Str dict;

	if(!mapget(im->l->map, &im->pre, &dict)){
		clearkouho();
		show();
		return;
	}
	dictsend(im, &dict);
}

static int
setlang(int c)
{
	Lang *l;

	l = getlang(c);
	if(l == nil)
		return 0;
	im.l = l;
	return 1;
}

static void
commit(Str *com)
{
	Str val;

	if(mapget(im.l->map, &im.pre, &val))
		sappend(com, &val);
	else
		sappend(com, &im.pre);
	sclear(&im.pre);
}

static int
dotrans(Rune c, Str *com)
{
	Emit e;
	Dictreq req;

	e = im.l->trans(&im, c);
	if(e.s.n > 0)
		sappend(com, &e.s);
	sclear(&im.pre);
	sappend(&im.pre, &e.next);
	if(e.eat && e.dict.n > 0){
		req.key = e.dict;
		req.lang = im.l->lang;
		req.pre = im.pre;
		channbsend(dictreqc, &req);
	}
	return e.eat;
}

Lang*
getlang(int lang)
{
	int i;

	for(i = 0; i < nelem(langs); i++)
		if(langs[i].lang == lang)
			return &langs[i];
	return nil;
}

static int
maplookup(Trie *t, Str *key, Str *out)
{
	char buf[256], *v;
	int klen, vlen;

	if(key->n == 0)
		return 0;
	v = nil;
	vlen = 0;
	klen = stoutf(key, buf, sizeof(buf));
	if(!trielookup(t, buf, klen, &v, &vlen))
		return 0;
	if(out != nil && v != nil)
		sinit(out, v, vlen);
	return 1;
}

Emit
transmap(Im *im, Rune c)
{
	Emit e = {0};
	Str key;
	Trie *t;

	t = im->l->map;
	key = im->pre;
	sputr(&key, c);
	if(maplookup(t, &key, &e.dict)){
		e.eat = 1;
		e.next = key;
		return e;
	}
	if(!mapget(t, &im->pre, &e.s))
		e.s = im->pre;
	sclear(&key);
	sputr(&key, c);
	if(!maplookup(t, &key, &e.dict))
		return e;
	e.eat = 1;
	sputr(&e.next, c);
	return e;
}

static int
keystroke(u32int ks, u32int mod, Str *com)
{
	int n, off;

	if(ks == Ksuper || ks == Kshift)
		return 1;
	if(ks == Kdown || ks == Kup){
		if(im.nkouho == 0)
			return 0;
		if(ks == Kdown && im.sel < im.nkouho - 1)
			im.sel++;
		if(ks == Kup && im.sel >= 0)
			im.sel--;
		show();
		return 1;
	}
	n = ks - '1';
	off = 0;
	if(im.sel >= Maxdisp)
		off = im.sel - Maxdisp + 1;
	if(n >= 0 && n < Maxdisp && off + n < im.nkouho){
		sappend(com, &im.kouho[off + n]);
		reset();
		return 1;
	}
	if(ks == Ktab || ks == Kret){
		if(im.sel >= 0 && im.sel < im.nkouho){
			sappend(com, &im.kouho[im.sel]);
			reset();
			return 1;
		}
		if(im.pre.n > 0){
			commit(com);
			reset();
			return 1;
		}
		return 0;
	}
	if(ks == Kback){
		if(im.pre.n == 0)
			return 0;
		im.l->back(&im);
		if(im.pre.n == 0){
			reset();
			return 1;
		}
		im.l->dictq(&im);
		return 1;
	}
	if(ks == Kesc){
		if(im.pre.n == 0)
			return 0;
		reset();
		return 1;
	}
	if(ks >= Kspec || (mod & (Malt|Msuper))){
		commit(com);
		reset();
		return 0;
	}
	if(mod & Mctrl){
		reset();
		if(ks >= 'a' && ks <= 'z')
			ks -= 'a' - 1;
		if(ks == 0x10){
			popup = !popup;
			return 1;
		}
		if(setlang(ks))
			return 1;
		return 0;
	}
	if(ks == '0' && im.nkouho > 0){
		commit(com);
		reset();
		return 1;
	}
	if(ks > 0x7f || ks == ' '){
		if(im.pre.n == 0)
			return 0;
		commit(com);
		sputr(com, ks);
		reset();
		return 1;
	}
	n = dotrans(ks, com);
	show();
	return n;
}

static void
init(void)
{
	memset(&im, 0, sizeof(im));
	im.l = getlang(LangEN);
}

void
imthread(void*)
{
	Keyreq kr;
	Dictres res;
	Str com;
	uchar out[256];
	int n, len, r;
	Alt alts[] = {
		{keyc, &kr, CHANRCV, nil},
		{dictresc, &res, CHANRCV, nil},
		{nil, nil, CHANEND, nil},
	};

	threadsetname("im");
	init();
	for(;;){
		switch(alt(alts)){
		case 0:
			sclear(&com);
			r = keystroke(kr.ks, kr.mod, &com);
			out[0] = r;
			out[1] = 0;
			n = 2;
			if(com.n > 0){
				len = stoutf(&com, (char*)(out+2), sizeof(out)-2);
				out[1] = len;
				n += len;
			}
			if(kr.want){
				len = im.pre.n > 0 ?
					stoutf(&im.pre, (char*)(out+n+1),
						sizeof(out)-n-1) : 0;
				out[n] = len;
				n += 1 + len;
			}
			write(kr.fd, out, n);
			break;
		case 1:
			if(scmp(&res.key, &im.pre) == 0){
				setkouho(&res);
				show();
			}
			break;
		}
	}
}

int
mapget(Trie *t, Str *key, Str *out)
{
	char buf[256], *v;
	int klen, vlen;

	if(key->n == 0)
		return 0;
	klen = stoutf(key, buf, sizeof(buf));
	v = trieget(t, buf, klen, &vlen);
	if(v == nil)
		return 0;
	sinit(out, v, vlen);
	return 1;
}

void
mapinit(char *dir)
{
	char path[1024];
	int i;

	for(i = 0; i < nelem(langs); i++){
		if(langs[i].mapname == nil)
			continue;
		snprint(path, sizeof(path), "%s/%s.map", dir, langs[i].mapname);
		langs[i].map = trieopen(path);
	}
}

