#include "lib9.h"
#include "draw.h"
#include "tk.h"
#include "label.h"

#define	O(t, e)		((long)(&((t*)0)->e))

/* Widget Commands (+ means implemented)
	+cget
	+configure
	+invoke
	+select
	+deselect
	+toggle
 */

enum {
	/* other constants */
	InvokePause	= 200,	/* delay showing button in down state when invoked */
};

TkOption tkbutopts[] =
{
	"text",		OPTtext,	O(TkLabel, text),	nil,
	"label",	OPTtext,	O(TkLabel, text),	nil,
	"underline",	OPTdist,	O(TkLabel, ul),		nil,
	"justify",	OPTstab,	O(TkLabel, justify),	tkjustify,
	"anchor",	OPTflag,	O(TkLabel, anchor),	tkanchor,
	"command",	OPTtext,	O(TkLabel, command),	nil,
	"bitmap",	OPTbmap,	O(TkLabel, bitmap),	nil,
	"image",	OPTimag,	O(TkLabel, img),	nil,
	nil
};

TkOption tkcbopts[] =
{
	"variable",	OPTtext,	O(TkLabel, variable),	nil,
	"indicatoron",	OPTstab,	O(TkLabel, indicator),	tkbool,
	"onvalue",	OPTtext,	O(TkLabel, value),	nil,
	"offvalue",	OPTtext,	O(TkLabel, offvalue), nil,
	nil,
};

TkOption tkradopts[] =
{
	"variable",	OPTtext,	O(TkLabel, variable),	nil,
	"value",	OPTtext,	O(TkLabel, value), nil,
	"indicatoron",	OPTstab,	O(TkLabel, indicator),	tkbool,
	nil,
};

static
TkEbind bb[] =
{
	{TkEnter,	"%W configure -state active"},
	{TkLeave,	"%W configure -state normal"},
	{TkButton1P,	"%W tkButton1P"},
	{TkButton1R,	"%W tkButton1R %x %y"},
	{TkMotion|TkButton1P, 	"" },
	{TkKey,	"%W tkButtonKey 0x%K"},
};

static
TkEbind cb[] = 
{
	{TkEnter,		"%W configure -state active"},
	{TkLeave,		"%W configure -state normal"},
	{TkButton1P,		"%W invoke"},
	{TkMotion|TkButton1P, 	"" },
	{TkKey,	"%W tkButtonKey 0x%K"},
};


static char	tkselbut[] = "selectedButton";

static char*	newbutton(TkTop*, int, char*, char**);
static int	istransparent(Tk*);
static void tkvarchanged(Tk*, char*, char*);

char*
tkbutton(TkTop *t, char *arg, char **ret)
{
	return newbutton(t, TKbutton, arg, ret);
}

char*
tkcheckbutton(TkTop *t, char *arg, char **ret)
{
	return newbutton(t, TKcheckbutton, arg, ret);
}

char*
tkradiobutton(TkTop *t, char *arg, char **ret)
{
	return newbutton(t, TKradiobutton, arg, ret);
}

static char*
newbutton(TkTop *t, int btype, char *arg, char **ret)
{
	Tk *tk;
	char *e;
	TkLabel *tkl;
	TkName *names;
	TkOptab tko[4];
	TkVar *v;

	tk = tkmkbutton(t, btype);
	if(tk == nil)
		return TkNomem;

	tkl = TKobj(TkLabel, tk);

	tko[0].ptr = tk;
	tko[0].optab = tkgeneric;
	tko[1].ptr = tkl;
	tko[1].optab = tkbutopts;
	switch(btype){
	case TKcheckbutton:
		tko[2].ptr = tkl;
		tko[2].optab = tkcbopts;
		break;
	case TKradiobutton:
		tko[2].ptr = tkl;
		tko[2].optab = tkradopts;
		break;
	default:
		tk->relief = TKraised;
		tk->borderwidth = 1;
		tko[2].ptr = nil;
		break;
	}
	tko[3].ptr = nil;

	names = nil;
	e = tkparse(t, arg, tko, &names);
	if(e != nil) {
		tkfreeobj(tk);
		return e;
	}

	tksettransparent(tk, istransparent(tk));
	tksizebutton(tk);

	e = tkaddchild(t, tk, &names);
	tkfreename(names);
	if(e != nil) {
		tkfreeobj(tk);
		return e;
	}
	tk->name->link = nil;

	if (btype == TKradiobutton &&
			tkl->variable != nil &&
			strcmp(tkl->variable, tkselbut) == 0 &&
			tkl->value == nil &&
			tk->name != nil)
		tkl->value = strdup(tk->name->name);

	if (tkl->variable != nil) {
		v = tkmkvar(t, tkl->variable, 0);
		if (v == nil){
			if(btype == TKcheckbutton){
				e = tksetvar(t, tkl->variable, tkl->offvalue ? tkl->offvalue : "0");
				if (e != nil)
					goto err;
			}
		} else if(v->type != TkVstring){
			e = TkNotvt;
			goto err;
		} else
			tkvarchanged(tk, tkl->variable, v->value);
	}

	return tkvalue(ret, "%s", tk->name->name);

err:
	tkfreeobj(tk);
	return e;
}

Tk*
tkmkbutton(TkTop *t, int btype)
{
	Tk *tk;
	TkLabel *tkl;
	char *e;

	tk = tknewobj(t, btype, sizeof(Tk)+sizeof(TkLabel));
	if (tk == nil)
		return nil;

	e = nil;
	tk->relief = TKraised;
	tk->borderwidth = 0;
	tk->highlightwidth = 1;
	tk->flag |= Tktakefocus;
	tkl = TKobj(TkLabel, tk);
	tkl->ul = -1;
	tkl->justify = Tkleft;

	switch (btype) {
	case TKbutton:
		e = tkbindings(t, tk, bb, nelem(bb));
		break;
	case TKcheckbutton:
		e = tkbindings(t, tk, cb, nelem(cb));
		break;
	case TKradiobutton:
		tkl->variable = strdup(tkselbut);
		e = tkbindings(t, tk, cb, nelem(cb));
		break;
	}

	if (e != nil) {
		print("tkmkbutton internal error: %s\n", e);
		tkfreeobj(tk);
		return nil;
	}
	return tk;
}

/*
 * draw TKbutton, TKcheckbutton, TKradiobutton
 */
char*
tkdrawbutton(Tk *tk, Point orig)
{
 	TkEnv *e;
	TkLabel *tkl;
	Rectangle r, s, mainr, focusr;
	int dx, dy, h;
	Point p, u, v, pp[4];
	Image *i, *dst, *cd, *cl, *ct, *img;
	int relief, bgnd, fgnd;

	e = tk->env;

	dst = tkimageof(tk);
	if(dst == nil)
		return nil;

	v.x = tk->act.width + 2*tk->borderwidth;
	v.y = tk->act.height + 2*tk->borderwidth;

	r.min = ZP;
	r.max = v;
	focusr = insetrect(r, tk->borderwidth);
	mainr = insetrect(focusr, tk->highlightwidth);
	relief = tk->relief;

	tkl = TKobj(TkLabel, tk);

	fgnd = TkCforegnd;
	bgnd = TkCbackgnd;
	if (tk->flag & Tkdisabled)
		fgnd = TkCdisablefgnd;
	else if ((tk->type == TKcheckbutton || tk->type == TKradiobutton) && tkl->indicator == BoolF && tkl->check)
		bgnd = TkCselect;
	else if (tk->flag & Tkactive) {
		fgnd = TkCactivefgnd;
		bgnd = TkCactivebgnd;
	}

	i = tkitmp(e, r.max, bgnd);
	if(i == nil)
		return nil;

	if(tk->flag & Tkactive)
		draw(i, r, tkgc(e, bgnd), nil, ZP);

	p = mainr.min;
	h = tkl->h - 2 * tk->highlightwidth;

	dx = tk->act.width - tkl->w - tk->ipad.x;
	dy = tk->act.height - tkl->h - tk->ipad.y;
	if((tkl->anchor & (Tknorth|Tksouth)) == 0)
		p.y += dy/2;
	else if(tkl->anchor & Tksouth)
		p.y += dy;

	if((tkl->anchor & (Tkeast|Tkwest)) == 0)
		p.x += dx/2;
	else if(tkl->anchor & Tkeast)
		p.x += dx;

	switch(tk->type) {
	case TKcheckbutton:
		if(tkl->indicator == BoolF) {
			relief = tkl->check? TKsunken: TKraised;
			break;
		}
		u.x = p.x + ButtonBorder;
		u.y = p.y + ButtonBorder + (h - CheckSpace) / 2;

		cl = tkgc(e, bgnd+TkLightshade);
		cd = tkgc(e, bgnd+TkDarkshade);
		tkbevel(i, u, CheckButton, CheckButton, CheckButtonBW, cd, cl);
		if(tkl->check) {
			u.x += CheckButtonBW+1;
			u.y += CheckButtonBW+1;
			pp[0] = u;
			pp[0].y += CheckButton/2-1;
			pp[1] = pp[0];
			pp[1].x += 2;
			pp[1].y += 2;
			pp[2] = u;
			pp[2].x += CheckButton/4;
			pp[2].y += CheckButton-2;
			pp[3] = u;
			pp[3].x += CheckButton-2;
			pp[3].y++;
			bezspline(i, pp, 4, Enddisc, Enddisc, 1, tkgc(e, TkCforegnd), ZP);
		}
		break;
	case TKradiobutton:
		if(tkl->indicator == BoolF) {
			relief = tkl->check? TKsunken: TKraised;
			break;
		}
		u.x = p.x + ButtonBorder;
		u.y = p.y + ButtonBorder + (h - CheckSpace) / 2;
		v = Pt(u.x+CheckButton/2,u.y+CheckButton/2);
		ellipse(i, v, CheckButton/2, CheckButton/2, CheckButtonBW-1, tkgc(e, bgnd+TkDarkshade), ZP);
		if(tkl->check)
			fillellipse(i, v, CheckButton/2-2, CheckButton/2-2, tkgc(e, TkCforegnd), ZP);	/* could be TkCselect */
		break;
	case TKbutton:
		if ((tk->flag & (Tkactivated|Tkactive)) == (Tkactivated|Tkactive))
			relief = TKsunken;
		break;
	}

	p.x += tk->ipad.x/2;
	p.y += tk->ipad.y/2;
	u = ZP;
	if(tk->type == TKbutton && relief == TKsunken) {
		u.x++;
		u.y++;
	}
	if((tk->type == TKcheckbutton || tk->type == TKradiobutton) && tkl->indicator != BoolF)
		u.x += CheckSpace;

	img = nil;
	if (tkl->img != nil && tkl->img->img != nil)
		img = tkl->img->img;
	else if (tkl->bitmap != nil)
		img = tkl->bitmap;
	if (img != nil) {
		s.min.x = p.x + Bitpadx;
		s.min.y = p.y + Bitpady;
		s.max.x = s.min.x + Dx(img->r);
		s.max.y = s.min.y + Dy(img->r);
		s = rectaddpt(s, u);
		if(tkchanhastype(img->chan, CGrey))
			draw(i, s, tkgc(e, fgnd), img, ZP);
		else
			draw(i, s, img, nil, ZP);
	} else if(tkl->text != nil) {
		u.x += Textpadx;
		u.y += Textpady;
		ct = tkgc(e, fgnd);
		
		p.y += (h - tkl->textheight) / 2;
		tkdrawstring(tk, i, addpt(u, p), tkl->text, tkl->ul, ct, tkl->justify);
	}

//	if(tkhaskeyfocus(tk))
//		tkbox(i, focusr, tk->highlightwidth, tkgc(e, TkChighlightfgnd));
	tkdrawrelief(i, tk, ZP, bgnd, relief);

	p.x = tk->act.x + orig.x;
	p.y = tk->act.y + orig.y;
	r = rectaddpt(r, p);
	draw(dst, r, i, nil, ZP);

	return nil;
}

void
tksizebutton(Tk *tk)
{
	int w, h;
	TkLabel *tkl;
	
	tkl = TKobj(TkLabel, tk);
	if(tkl->anchor == 0)	
		tkl->anchor = Tkcenter;

	tksizelabel(tk);	/* text, bitmap or image, and highlight */
	w = tkl->w;
	h = tkl->h;

	if((tk->type == TKcheckbutton || tk->type == TKradiobutton) && tkl->indicator != BoolF) {
		w += CheckSpace;
		if(h < CheckSpace)
			h = CheckSpace;
	}
	tkl->w = w;
	tkl->h = h;
	if((tk->flag & Tksetwidth) == 0)
		tk->req.width = w;
	if((tk->flag & Tksetheight) == 0)
		tk->req.height = h;
}

int
tkbuttonmargin(Tk *tk)
{
	TkLabel *tkl;
	tkl = TKobj(TkLabel, tk);

	switch (tk->type) {
	case TKbutton:
		if (tkl->img != nil || tkl->bitmap != nil)
			return 0;
		return Textpadx+tk->highlightwidth;
	case TKcheckbutton:
	case TKradiobutton:
		return CheckButton + 2*CheckButtonBW + 2*ButtonBorder;
	}
	return tklabelmargin(tk);
}

void
tkfreebutton(Tk *tk)
{
	tkfreelabel(tk);
}

static char*
tkbuttoncget(Tk *tk, char *arg, char **val)
{
	TkOptab tko[4];
	TkLabel *tkl = TKobj(TkLabel, tk);

	tko[0].ptr = tk;
	tko[0].optab = tkgeneric;
	tko[1].ptr = tkl;
	tko[1].optab = tkbutopts;
	switch(tk->type){
	case TKcheckbutton:
		tko[2].ptr = tkl;
		tko[2].optab = tkcbopts;
		break;
	case TKradiobutton:
		tko[2].ptr = tkl;
		tko[2].optab = tkradopts;
		break;
	default:
		tko[2].ptr = nil;
		break;
	}
	tko[3].ptr = nil;
	return tkgencget(tko, arg, val, tk->env->top);
}

static char*
tkbuttonconf(Tk *tk, char *arg, char **val)
{
	char *e;
	TkGeom g;
	int bd;
	TkOptab tko[4];
	TkVar *v;
	TkLabel *tkl = TKobj(TkLabel, tk);

	tko[0].ptr = tk;
	tko[0].optab = tkgeneric;
	tko[1].ptr = tkl;
	tko[1].optab = tkbutopts;
	switch(tk->type){
	case TKcheckbutton:
		tko[2].ptr = tkl;
		tko[2].optab = tkcbopts;
		break;
	case TKradiobutton:
		tko[2].ptr = tkl;
		tko[2].optab = tkradopts;
		break;
	default:
		tko[2].ptr = nil;
		break;
	}
	tko[3].ptr = nil;

	if(*arg == '\0')
		return tkconflist(tko, val);

	g = tk->req;
	bd = tk->borderwidth;
	e = tkparse(tk->env->top, arg, tko, nil);
	tksizebutton(tk);
	tkgeomchg(tk, &g, bd);

	tk->dirty = tkrect(tk, 1);
	tksettransparent(tk, istransparent(tk));
	/*
	 * XXX what happens if we're now disabled, but we were in
	 * active state before?
	 */
	if (tkl->variable != nil) {
		v = tkmkvar(tk->env->top, tkl->variable, 0);
		if (v != nil) {
			if (v->type != TkVstring) {
				e = TkNotvt;
				free(tkl->variable);
				tkl->variable = nil;
			}
			else
				tkvarchanged(tk, tkl->variable, v->value);
		}
	}
	return e;
}

static int
istransparent(Tk *tk)
{
	TkEnv *e = tk->env;
	return (tkhasalpha(e, TkCbackgnd) || tkhasalpha(e, TkCselectbgnd) || tkhasalpha(e, TkCactivebgnd));
}

static void
tkvarchanged(Tk *tk, char *var, char *val)
{
	TkLabel *tkl;
	char *sval;

	tkl = TKobj(TkLabel, tk);
	if (tkl->variable != nil && strcmp(tkl->variable, var) == 0) {
		sval = tkl->value;
		if (sval == nil)
			sval = tk->type == TKcheckbutton ? "1" : "";
		tkl->check = (strcmp(val, sval) == 0);
		tk->dirty = tkrect(tk, 1);
		tkdirty(tk);
	}
}

static char*
tkbutton1p(Tk *tk, char *arg, char **val)
{
	USED(arg);
	USED(val);
	if(tk->flag & Tkdisabled)
		return nil;
	tk->flag |= Tkactivated;
	tk->dirty = tkrect(tk, 1);
	tkdirty(tk);
	return nil;
}

static char*
tkbutton1r(Tk *tk, char *arg, char **val)
{
	char *e;
	Point p;
	Rectangle hitr;

	USED(arg);

	if(tk->flag & Tkdisabled)
		return nil;
	e = tkxyparse(tk, &arg, &p);
	if (e == nil) {
		hitr.min = ZP;
		hitr.max.x = tk->act.width + tk->borderwidth*2;
		hitr.max.y = tk->act.height + tk->borderwidth*2;
		if(ptinrect(p, hitr) && (tk->flag & Tkactivated))
			e = tkbuttoninvoke(tk, nil, val);
	}
	tk->flag &= ~Tkactivated;
	tk->dirty = tkrect(tk, 1);
	tkdirty(tk);
	return e;
}

static char*
tkbuttonkey(Tk *tk, char *arg, char **val)
{
	int key;

	if(tk->flag & Tkdisabled)
		return nil;

	key = strtol(arg, nil, 0);
	if (key == '\n' || key ==' ')
		return tkbuttoninvoke(tk, nil, val);
	return nil;
}

static char*
tkbuttontoggle(Tk *tk, char *arg, char **val)
{
	char *e;
	TkLabel *tkl = TKobj(TkLabel, tk);
	char *v;

	USED(arg);
	USED(val);
	if(tk->flag & Tkdisabled)
		return nil;
	tkl->check = !tkl->check;
	if (tkl->check)
		v = tkl->value ? tkl->value : "1";
	else
		v = tkl->offvalue ? tkl->offvalue : "0";
	e = tksetvar(tk->env->top, tkl->variable, v);
	tk->dirty = tkrect(tk, 0);
	return e;
}

static char*
buttoninvoke(Tk *tk, char **val)
{
	char *e = nil;
	TkTop *top;
	TkLabel *tkl = TKobj(TkLabel, tk);

	top = tk->env->top;
	if (tk->type == TKcheckbutton)
		e = tkbuttontoggle(tk, "", val);
	else if (tk->type == TKradiobutton)
		e = tksetvar(top, tkl->variable, tkl->value);
	if(e != nil)
		return e;
	if(tkl->command != nil)
		return tkexec(tk->env->top, tkl->command, val);
	return nil;
}

static void
cancelinvoke(Tk *tk, void *v, int cancelled)
{
	int unset;
	USED(cancelled);
	USED(v);

	/* if it was active before then leave it active unless cleared since */
	if (v)
		unset = 0;
	else
		unset = Tkactive;
	unset &= (tk->flag & Tkactive);
	unset |= Tkactivated;
	tk->flag &= ~unset;
	tksettransparent(tk, istransparent(tk));
	tk->dirty = tkrect(tk, 1);
	tkdirty(tk);
	tkupdate(tk->env->top);
}

char*
tkbuttoninvoke(Tk *tk, char *arg, char **val)
{
	char *e;
	USED(arg);

	if(tk->flag & Tkdisabled)
		return nil;
	e = buttoninvoke(tk, val);
	if (e == nil && tk->type == TKbutton && !(tk->flag & Tkactivated)) {
		tkrepeat(tk, cancelinvoke, (void*)(tk->flag&Tkactive), InvokePause, 0);
		tk->flag |= Tkactivated | Tkactive;
		tksettransparent(tk, istransparent(tk));
		tk->dirty = tkrect(tk, 1);
		tkdirty(tk);
		tkupdate(tk->env->top);
	}
	return e;
}

static char*
tkbuttonselect(Tk *tk, char *arg, char **val)
{
	char *e, *v;
	TkLabel *tkl = TKobj(TkLabel, tk);

	USED(arg);
	USED(val);
	if (tk->type == TKradiobutton)
		v = tkl->value;
	else if (tk->type == TKcheckbutton) {
		v = tkl->value ? tkl->value : "1";
		tkl->check = 1;
		tk->dirty = tkrect(tk, 0);
	} else
		v = nil;
	e = tksetvar(tk->env->top, tkl->variable, v);
	if(e != nil)
		return e;
	return nil;
}

static char*
tkbuttondeselect(Tk *tk, char *arg, char **val)
{
	char *e, *v;
	TkLabel *tkl = TKobj(TkLabel, tk);

	USED(arg);
	USED(val);

	if (tk->type == TKcheckbutton) {
		v = tkl->offvalue ? tkl->offvalue : "0";
		tkl->check = 0;
		tk->dirty = tkrect(tk, 0);
	} else
		v = nil;

	e = tksetvar(tk->env->top, tkl->variable, v);
	if(e != nil)
		return e;
	return nil;
}

static
TkCmdtab tkbuttoncmd[] =
{
	"cget",			tkbuttoncget,
	"configure",		tkbuttonconf,
	"invoke",		tkbuttoninvoke,
	"tkButton1P",		tkbutton1p,
	"tkButton1R",		tkbutton1r,
	"tkButtonKey",		tkbuttonkey,
	nil
};

static
TkCmdtab tkchkbuttoncmd[] =
{
	"cget",			tkbuttoncget,
	"configure",		tkbuttonconf,
	"invoke",		tkbuttoninvoke,
	"select",		tkbuttonselect,
	"deselect",		tkbuttondeselect,
	"toggle",		tkbuttontoggle,
	"tkButtonKey",		tkbuttonkey,
	nil
};

static
TkCmdtab tkradbuttoncmd[] =
{
	"cget",			tkbuttoncget,
	"configure",		tkbuttonconf,
	"invoke",		tkbuttoninvoke,
	"select",		tkbuttonselect,
	"deselect",		tkbuttondeselect,
	"tkButtonKey",		tkbuttonkey,
	nil
};

TkMethod buttonmethod = {
	"button",
	tkbuttoncmd,
	tkfreebutton,
	tkdrawbutton,
	nil,
	tklabelgetimgs
};

TkMethod checkbuttonmethod = {
	"checkbutton",
	tkchkbuttoncmd,
	tkfreebutton,
	tkdrawbutton,
	nil,
	tklabelgetimgs,
	nil,
	nil,
	nil,
	nil,
	nil,
	nil,
	tkvarchanged
};

TkMethod radiobuttonmethod = {
	"radiobutton",
	tkradbuttoncmd,
	tkfreebutton,
	tkdrawbutton,
	nil,
	nil,
	nil,
	nil,
	nil,
	nil,
	nil,
	nil,
	tkvarchanged
};
