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

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

/* Layout constants */
enum {
	Triangle	= 10,	/* Height of scroll bar triangle */
	Elembw	= 1,		/* border around elements (triangles etc.) */
	Scrollbw	= 1,		/* bevel border on scrollbar */
	Tribw=	1,	/* shadow border on triangle */
};

typedef struct TkScroll TkScroll;
struct TkScroll
{
	int		activer;
	int		orient;		/* Horitontal or Vertical */
	int		dragpix;	/* Scroll delta in button drag */
	int		dragtop;
	int		dragbot;
	int		jump;		/* Jump scroll enable */
	int		flag;		/* Display flags */
	int		top;		/* Top fraction */
	int		bot;		/* Bottom fraction */
	int		a1;		/* Pixel top/left arrow1 */
	int		t1;		/* Pixel top/left trough */
	int		t2;		/* Pixel top/left lower trough */
	int		a2;		/* Pixel top/left arrow2 */
	char*		cmd;
};

enum {
	ActiveA1	= (1<<0),	/* Scrollbar control */
	ActiveA2	= (1<<1),
	ActiveB1	= (1<<2),
	ButtonA1	= (1<<3),
	ButtonA2	= (1<<4),
	ButtonB1	= (1<<5),
	Autorepeat = (1<<6)
};

static
TkOption opts[] =
{
	"activerelief",	OPTstab,	O(TkScroll, activer),	tkrelief,
	"command",	OPTtext,	O(TkScroll, cmd),	nil,
	"jump",	OPTstab,	O(TkScroll, jump),	tkbool,
	"orient",	OPTstab,	O(TkScroll, orient),	tkorient,
	nil
};

static
TkEbind b[] = 
{
	{TkLeave,		"%W activate {}"},
	{TkEnter,		"%W activate [%W identify %x %y]"},
	{TkMotion,		"%W activate [%W identify %x %y]"},
	{TkButton1P|TkMotion,	"%W tkScrollDrag %x %y"},
	{TkButton1P,		"%W tkScrolBut1P %x %y"},
	{TkButton1P|TkDouble,	"%W tkScrolBut1P %x %y"},
	{TkButton1R,	"%W tkScrolBut1R; %W activate [%W identify %x %y]"},
	{TkButton2P,		"%W tkScrolBut2P [%W fraction %x %y]"},
};

static char*
tkinitscroll(Tk *tk)
{
	int gap;
	TkScroll *tks;

	tks = TKobj(TkScroll, tk);
	
	gap = 2*tk->borderwidth;
	if(tks->orient == Tkvertical) {
		if(tk->req.width == 0)
			tk->req.width = Triangle + gap;
		if(tk->req.height == 0)	
			tk->req.height = 2*Triangle + gap + 6*Elembw;
	}
	else {
		if(tk->req.width == 0)
			tk->req.width = 2*Triangle + gap + 6*Elembw;
		if(tk->req.height == 0)	
			tk->req.height = Triangle + gap;
	}


	return tkbindings(tk->env->top, tk, b, nelem(b));
}

char*
tkscrollbar(TkTop *t, char *arg, char **ret)
{
	Tk *tk;
	char *e;
	TkName *names;
	TkScroll *tks;
	TkOptab tko[3];

	tk = tknewobj(t, TKscrollbar, sizeof(Tk)+sizeof(TkScroll));
	if(tk == nil)
		return TkNomem;

	tks = TKobj(TkScroll, tk);

	tk->relief = TKflat;
	tk->borderwidth = 1;
	tks->activer = TKraised;
	tks->orient = Tkvertical;

	tko[0].ptr = tk;
	tko[0].optab = tkgeneric;
	tko[1].ptr = tks;
	tko[1].optab = opts;
	tko[2].ptr = nil;

	names = nil;
	e = tkparse(t, arg, tko, &names);
	if(e != nil) {
		tkfreeobj(tk);
		return e;
	}
	tksettransparent(tk, tkhasalpha(tk->env, TkCbackgnd));

	e = tkinitscroll(tk);
	if(e != nil) {
		tkfreeobj(tk);
		return e;
	}

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

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

static char*
tkscrollcget(Tk *tk, char *arg, char **val)
{
	TkOptab tko[3];
	TkScroll *tks = TKobj(TkScroll, tk);

	tko[0].ptr = tk;
	tko[0].optab = tkgeneric;
	tko[1].ptr = tks;
	tko[1].optab = opts;
	tko[2].ptr = nil;

	return tkgencget(tko, arg, val, tk->env->top);
}

void
tkfreescrlb(Tk *tk)
{
	TkScroll *tks = TKobj(TkScroll, tk);

	if(tks->cmd != nil)
		free(tks->cmd);
}

static void
drawarrow(TkScroll *tks, Image *i, Point p[3], TkEnv *e, int activef, int buttonf)
{
	Image *l, *d, *t;
	int bgnd;

	bgnd = TkCbackgnd;
	if(tks->flag & (activef|buttonf)) {
		bgnd = TkCactivebgnd;
		fillpoly(i, p, 3, ~0, tkgc(e, bgnd), p[0]);
	}

	l = tkgc(e, bgnd+TkLightshade);
	d = tkgc(e, bgnd+TkDarkshade);
	if(tks->flag & buttonf) {
		t = d;
		d = l;
		l = t;
	}
	line(i, p[1], p[2], 0, 0, Tribw-1, d, p[1]);
	line(i, p[2], p[0], 0, 0, Tribw-1, d, p[2]);
	line(i, p[0], p[1], 0, 0, Tribw-1, l, p[0]);
}

static void
drawslider(TkScroll *tks, Image *i, Point o, int w, int h, TkEnv *e)
{
	Image *l, *d;
	Rectangle r;
	int bgnd;

	bgnd = TkCbackgnd;
	if(tks->flag & (ActiveB1|ButtonB1)) {
		r.min = o;
		r.max.x = o.x + w + Elembw*2;
		r.max.y = o.y + h + Elembw*2;
		bgnd = TkCactivebgnd;
		draw(i, r, tkgc(e, bgnd), nil, ZP);
	}

	l = tkgc(e, bgnd+TkLightshade);
	d = tkgc(e, bgnd+TkDarkshade);
	if(tks->flag & ButtonB1)
		tkbevel(i, o, w, h, Scrollbw, d, l);
	else
		tkbevel(i, o, w, h, Scrollbw, l, d);
}

static void
tkvscroll(Tk *tk, TkScroll *tks, Image *i, Point size)
{
	TkEnv *e;
	Point p[3], o;
	int bo, w, h, triangle;

	e = tk->env;

	triangle = tk->act.width - Elembw;

	bo = tk->borderwidth + Elembw;
	p[0].x = size.x/2;
	p[0].y = bo;
	p[1].x = p[0].x - triangle/2;
	p[1].y = p[0].y + triangle;
	p[2].x = p[0].x + triangle/2;
	p[2].y = p[0].y + triangle;
	drawarrow(tks, i, p, e, ActiveA1, ButtonA1);

	tks->a1 = p[2].y;
	h = p[2].y + Elembw;

	p[0].y = size.y - bo - 1;
	p[1].y = p[0].y - triangle;
	p[2].y = p[0].y - triangle;
	drawarrow(tks, i, p, e, ActiveA2, ButtonA2);

	tks->a2 = p[2].y;

	o.x = tk->borderwidth ;
	o.y = bo + triangle + 2*Elembw;
	w = size.x - 2*bo;
	h = p[2].y - 2*Elembw - h - 2*tk->borderwidth;

	o.y += TKF2I(tks->top*h);
	h *= tks->bot - tks->top;
	h = TKF2I(h);

	tks->t1 = o.y - Elembw;
	tks->t2 = o.y + h + Elembw;

	drawslider(tks, i, o, w, h, e);
}

static void
tkhscroll(Tk *tk, TkScroll *tks, Image *i, Point size)
{
	TkEnv *e;
	Point p[3], o;
	int bo, w, h, triangle;

	e = tk->env;

	triangle = tk->act.height - Elembw;

	bo = tk->borderwidth + Elembw;
	p[0].x = bo;
	p[0].y = size.y/2;
	p[1].x = p[0].x + triangle;
	p[1].y = p[0].y - triangle/2 + 1;
	p[2].x = p[0].x + triangle;
	p[2].y = p[0].y + triangle/2 - 2;
	drawarrow(tks, i, p, e, ActiveA1, ButtonA1);

	tks->a1 = p[2].x;
	w = p[2].x + Elembw;

	p[0].x = size.x - bo - 1;
	p[1].x = p[0].x - triangle;
	p[2].x = p[0].x - triangle;
	drawarrow(tks, i, p, e, ActiveA2, ButtonA2);

	tks->a2 = p[2].x;

	o.x = bo + triangle + 2*Elembw;
	o.y = tk->borderwidth;
	w = p[2].x - 2*Elembw - w - 2*tk->borderwidth;
	h = size.y - 2*bo;

	o.x += TKF2I(tks->top*w);
	w *= tks->bot - tks->top;
	w = TKF2I(w);

	tks->t1 = o.x - Elembw;
	tks->t2 = o.x + w + Elembw;

	drawslider(tks, i, o, w, h, e);
}

char*
tkdrawscrlb(Tk *tk, Point orig)
{
	Point p;
	TkEnv *e;
	Rectangle r;
	Image *i, *dst;
	TkScroll *tks = TKobj(TkScroll, tk);

	e = tk->env;

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

	r.min = ZP;
	r.max.x = tk->act.width + 2*tk->borderwidth;
	r.max.y = tk->act.height + 2*tk->borderwidth;

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

	if(tks->orient == Tkvertical)
		tkvscroll(tk, tks, i, r.max);
	else
		tkhscroll(tk, tks, i, r.max);

	tkdrawrelief(i, tk, ZP, TkCbackgnd, tk->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;
}

/* Widget Commands (+ means implemented)	
	+activate
	+cget
	+configure
	+delta
	+fraction
	+get
	+identify
	+set
*/

static char*
tkscrollconf(Tk *tk, char *arg, char **val)
{
	char *e;
	TkGeom g;
	int bd;
	TkOptab tko[3];
	TkScroll *tks = TKobj(TkScroll, tk);

	tko[0].ptr = tk;
	tko[0].optab = tkgeneric;
	tko[1].ptr = tks;
	tko[1].optab = opts;
	tko[2].ptr = nil;

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

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

	tk->dirty = tkrect(tk, 1);
	return e;
}

static char*
tkscrollactivate(Tk *tk, char *arg, char **val)
{
	int s, gotarg;
	char buf[Tkmaxitem];
	TkScroll *tks = TKobj(TkScroll, tk);

	USED(val);
	tkword(tk->env->top, arg, buf, buf+sizeof(buf), &gotarg);
	s = tks->flag;
	if (!gotarg) {
		char *a;
		if (s & ActiveA1)
			a = "arrow1";
		else if (s & ActiveA2)
			a = "arrow2";
		else if (s & ActiveB1)
			a = "slider";
		else
			a = "";
		return tkvalue(val, a);
	}
	tks->flag &= ~(ActiveA1 | ActiveA2 | ActiveB1);
	if(strcmp(buf, "arrow1") == 0)
		tks->flag |= ActiveA1;
	else
	if(strcmp(buf, "arrow2") == 0)
		tks->flag |= ActiveA2;
	else
	if(strcmp(buf, "slider") == 0)
		tks->flag |= ActiveB1;

	if(s ^ tks->flag)
		tk->dirty = tkrect(tk, 1);
	return nil;
}

static char*
tkscrollset(Tk *tk, char *arg, char **val)
{
	TkTop *t;
	char *e;
	TkScroll *tks = TKobj(TkScroll, tk);

	USED(val);
	t = tk->env->top;
	e = tkfracword(t, &arg, &tks->top, nil);
	if (e != nil)
		return e;
	e = tkfracword(t, &arg, &tks->bot, nil);
	if (e != nil)
		return e;
	if(tks->top < 0)
		tks->top = 0;
	if(tks->top > TKI2F(1))
		tks->top = TKI2F(1);
	if(tks->bot < 0)
		tks->bot = 0;
	if(tks->bot > TKI2F(1))
		tks->bot = TKI2F(1);

	tk->dirty = tkrect(tk, 1);
	return nil;
}

static char*
tkscrolldelta(Tk *tk, char *arg, char **val)
{
	int l, delta;
	char buf[Tkmaxitem];
	TkScroll *tks = TKobj(TkScroll, tk);

	arg = tkitem(buf, arg);
	if(tks->orient == Tkvertical)
		tkitem(buf, arg);
	if(*arg == '\0' || *buf == '\0')
		return TkBadvl;

	l = tks->a2-tks->a1-4*Elembw;
	delta = TKI2F(1);
	if(l != 0)
		delta = TKI2F(atoi(buf)) / l;
	tkfprint(buf, delta);

	return tkvalue(val, "%s", buf);	
}

static char*
tkscrollget(Tk *tk, char *arg, char **val)
{
	char *v, buf[Tkmaxitem];
	TkScroll *tks = TKobj(TkScroll, tk);

	USED(arg);
	v = tkfprint(buf, tks->top);
	*v++ = ' ';
	tkfprint(v, tks->bot);

	return tkvalue(val, "%s", buf);	
}

static char*
tkscrollidentify(Tk *tk, char *arg, char **val)
{
	int gotarg;
	TkTop *t;
	char *v, buf[Tkmaxitem];
	Point p;
	TkScroll *tks = TKobj(TkScroll, tk);

	t = tk->env->top;
	arg = tkword(t, arg, buf, buf+sizeof(buf), &gotarg);
	if (!gotarg)
		return TkBadvl;
	p.x = atoi(buf);
	tkword(t, arg, buf, buf+sizeof(buf), &gotarg);
	if (!gotarg)
		return TkBadvl;
	p.y = atoi(buf);
	if (!ptinrect(p, tkrect(tk, 0)))
		return nil;
	if (tks->orient == Tkvertical)
		p.x = p.y;
	p.x += tk->borderwidth;

	v = "";
	if(p.x <= tks->a1)
		v = "arrow1";
	if(p.x > tks->a1 && p.x <= tks->t1)
		v = "trough1";
	if(p.x > tks->t1 && p.x < tks->t2)
		v = "slider";
	if(p.x >= tks->t2 && p.x < tks->a2)
		v = "trough2";
	if(p.x >= tks->a2)
		v = "arrow2";
	return tkvalue(val, "%s", v);
}

static char*
tkscrollfraction(Tk *tk, char *arg, char **val)
{
	int len, frac, pos;
	char buf[Tkmaxitem];
	TkScroll *tks = TKobj(TkScroll, tk);

	arg = tkitem(buf, arg);
	if(tks->orient == Tkvertical)
		tkitem(buf, arg);
	if(*arg == '\0' || *buf == '\0')
		return TkBadvl;

	pos = atoi(buf);
	if(pos < tks->a1)
		pos = tks->a1;
	if(pos > tks->a2)
		pos = tks->a2;
	len = tks->a2 - tks->a1 - 4*Elembw;
	frac = TKI2F(1);
	if(len != 0)
		frac = TKI2F(pos-tks->a1)/len;
	tkfprint(buf, frac);
	return tkvalue(val, "%s", buf);
}

static char*
tkScrolBut1R(Tk *tk, char *arg, char **val)
{
	TkScroll *tks = TKobj(TkScroll, tk);

	USED(val);
	USED(arg);
	tkcancelrepeat(tk);
	tks->flag &= ~(ActiveA1|ActiveA2|ActiveB1|ButtonA1|ButtonA2|ButtonB1|Autorepeat);
	tk->dirty = tkrect(tk, 1);
	return nil;
}

/* tkScrolBut2P fraction */
static char*
tkScrolBut2P(Tk *tk, char *arg, char **val)
{
	TkTop *t;
	char *e, buf[Tkmaxitem], fracbuf[Tkmaxitem];
	TkScroll *tks = TKobj(TkScroll, tk);
	

	USED(val);
	t = tk->env->top;

	if(arg[0] == '\0')
		return TkBadvl;

	tkword(t, arg, fracbuf, fracbuf+sizeof(fracbuf), nil);

	e = nil;
	if(tks->cmd != nil) {
		snprint(buf, sizeof(buf), "%s moveto %s", tks->cmd, fracbuf);
		e = tkexec(t, buf, nil);
	}
	return e;
}

static void
sbrepeat(Tk *tk, void *v, int cancelled)
{
	char *e, buf[Tkmaxitem];
	TkScroll *tks = TKobj(TkScroll, tk);
	char *fmt = (char *)v;

	if (cancelled) {
		tks->flag &= ~Autorepeat;
		return;
	}
		
	if(tks->cmd != nil && fmt != nil) {
		snprint(buf, sizeof(buf), fmt, tks->cmd);
		e = tkexec(tk->env->top, buf, nil);
		if (e != nil) {
			tks->flag &= ~Autorepeat;
			tkcancelrepeat(tk);
		} else
			tkupdate(tk->env->top);
	}
}

/* tkScrolBut1P %x %y */
static char*
tkScrolBut1P(Tk *tk, char *arg, char **val)
{
	int pix;
	TkTop *t;
	char *e, *fmt, buf[Tkmaxitem];
	TkScroll *tks = TKobj(TkScroll, tk);

	USED(val);
	t = tk->env->top;

	if (tks->flag & Autorepeat)
		return nil;
	arg = tkword(t, arg, buf, buf+sizeof(buf), nil);
	if(tks->orient == Tkvertical)
		tkword(t, arg, buf, buf+sizeof(buf), nil);
	if(buf[0] == '\0')
		return TkBadvl;

	pix = atoi(buf);
	
	tks->dragpix = pix;
	tks->dragtop = tks->top;
	tks->dragbot = tks->bot;

	pix += tk->borderwidth;

	fmt = nil;
	e = nil;
	if(pix <= tks->a1) {
		fmt = "%s scroll -1 unit";
		tks->flag |= ButtonA1;
	}
	if(pix > tks->a1 && pix <= tks->t1)
		fmt = "%s scroll -1 page";
	if(pix > tks->t1 && pix < tks->t2)
		tks->flag |= ButtonB1;
	if(pix >= tks->t2 && pix < tks->a2)
		fmt = "%s scroll 1 page";
	if(pix >= tks->a2) {
		fmt = "%s scroll 1 unit";
		tks->flag |= ButtonA2;
	}
	if(tks->cmd != nil && fmt != nil) {
		snprint(buf, sizeof(buf), fmt, tks->cmd);
		e = tkexec(t, buf, nil);
		tks->flag |= Autorepeat;
		tkrepeat(tk, sbrepeat, fmt, TkRptpause, TkRptinterval);
	}
	tk->dirty = tkrect(tk, 1);
	return e;
}

/* tkScrolDrag %x %y */
static char*
tkScrollDrag(Tk *tk, char *arg, char **val)
{
	TkTop *t;
	int pix, delta;
	char frac[32], buf[Tkmaxitem];
	TkScroll *tks = TKobj(TkScroll, tk);

	USED(val);
	t = tk->env->top;

	if (tks->flag & Autorepeat)
		return nil;
	if((tks->flag & ButtonB1) == 0)
		return nil;

	arg = tkword(t, arg, buf, buf+sizeof(buf), nil);
	if(tks->orient == Tkvertical)
		tkword(t, arg, buf, buf+sizeof(buf), nil);
	if(buf[0] == '\0')
		return TkBadvl;

	pix = atoi(buf);

	delta = TKI2F(pix-tks->dragpix);
	if ( tks->a2 == tks->a1 )
		return TkBadvl;
	delta = delta/(tks->a2-tks->a1-4*Elembw);
	if(tks->jump == BoolT) {
		if(tks->dragtop+delta >= 0 &&
		   tks->dragbot+delta <= TKI2F(1)) {
			tks->top = tks->dragtop+delta;
			tks->bot = tks->dragbot+delta;
		}
		return nil;
	}
	if(tks->cmd != nil) {
		delta += tks->dragtop;
		if(delta < 0)
			delta = 0;
		if(delta > TKI2F(1))
			delta = TKI2F(1);
		tkfprint(frac, delta);
		snprint(buf, sizeof(buf), "%s moveto %s", tks->cmd, frac);
		return tkexec(t, buf, nil);
	}
	return nil;
}

TkCmdtab tkscrlbcmd[] =
{
	"activate",		tkscrollactivate,
	"cget",			tkscrollcget,
	"configure",		tkscrollconf,
	"delta",		tkscrolldelta,
	"fraction",		tkscrollfraction,
	"get",			tkscrollget,
	"identify",		tkscrollidentify,
	"set",			tkscrollset,
	"tkScrollDrag",		tkScrollDrag,
	"tkScrolBut1P",		tkScrolBut1P,
	"tkScrolBut1R",		tkScrolBut1R,
	"tkScrolBut2P",		tkScrolBut2P,
	nil
};

TkMethod scrollbarmethod = {
	"scrollbar",
	tkscrlbcmd,
	tkfreescrlb,
	tkdrawscrlb
};
