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

#define istring u.string
#define iwin u.win
#define imark u.mark
#define iline u.line

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

static char* tktwincget(Tk*, char*, char**);
static char* tktwinconfigure(Tk*, char*, char**);
static char* tktwincreate(Tk*, char*, char**);
static char* tktwinnames(Tk*, char*, char**);
static int winowned(Tk *tk, Tk *sub);

static
TkStab tkalign[] =
{
	"top",		Tktop,
	"bottom",	Tkbottom,
	"center",	Tkcenter,
	"baseline",	Tkbaseline,
	nil
};

static
TkOption twinopts[] =
{
	"align",	OPTstab,	O(TkTwind, align),	tkalign,
	"create",	OPTtext,	O(TkTwind, create),	nil,
	"padx",		OPTnndist,	O(TkTwind, padx),	nil,
	"pady",		OPTnndist,	O(TkTwind, pady),	nil,
	"stretch",	OPTstab,	O(TkTwind, stretch),	tkbool,
	"window",	OPTwinp,	O(TkTwind, sub),	nil,
	"ascent",	OPTdist,	O(TkTwind, ascent), nil,
	nil
};

TkCmdtab
tktwincmd[] =
{
	"cget",		tktwincget,
	"configure",	tktwinconfigure,
	"create",	tktwincreate,
	"names",	tktwinnames,
	nil
};

int
tktfindsubitem(Tk *sub, TkTindex *ix)
{
	Tk *tk, *isub;
	TkText *tkt;

	tk = sub->parent;
	if(tk != nil) {
		tkt = TKobj(TkText, tk);
		tktstartind(tkt, ix);
		do {
			if(ix->item->kind == TkTwin) {
				isub = ix->item->iwin->sub;
				if(isub != nil && 
				   isub->name != nil && 
				   strcmp(isub->name->name, sub->name->name) == 0)
				return 1;
			}
		} while(tktadjustind(tkt, TkTbyitem, ix));
	}
	return 0;
}

static void
tktwindsize(Tk *tk, TkTindex *ix)
{
	Tk *s;
	TkTitem *i;
	TkTwind *w;


	i = ix->item;
	/* assert(i->kind == TkTwin); */

	w = i->iwin;
	s = w->sub;
	if(s == nil)
		return;

	if(w->width != s->act.width || w->height != s->act.height) {
		s->act.width = w->width;
		s->act.height = w->height;
		if(s->slave) {
			tkpackqit(s);
			tkrunpack(tk->env->top);
		}
	}

	tktfixgeom(tk, tktprevwrapline(tk, ix->line), ix->line, 0);
	tktextsize(tk, 1);
}

/*
 * check that w->focus is a window packed under tk.
 * XXX couldn't this be done more simply by traversing
 * directly upwards from w->focus and seeing whether
 * it hits tk? (same applies to tkcvschkwfocus in cwind.c)
 */
static int
tktchkwfocus(TkTwind *w, Tk *tk)
{
	if(w->focus == tk)
		return 1;
	for(tk = tk->slave; tk; tk = tk->next)
		if(tktchkwfocus(w, tk))
			return 1;
	return 0;
}

static void
tktwingeom(Tk *sub, int x, int y, int w, int h)
{
	TkTindex ix;
	Tk *tk;
	TkTwind *win;

	USED(x);
	USED(y);

	tk = sub->parent;
	if(!tktfindsubitem(sub, &ix)) {
		print("tktwingeom: %s not found\n", sub->name->name);
		return;
	}

	win = ix.item->iwin;

	if(win->focus != nil) {
		if(tktchkwfocus(win, sub) == 0)
			win->focus = nil;
	}

	win->width = w;
	win->height = h;

	sub->req.width = w;
	sub->req.height = h;
	tktwindsize(tk, &ix);
}

static void
tktdestroyed(Tk *sub)
{
	TkTindex ix;
	Tk *tk;

	if(tktfindsubitem(sub, &ix)) {
		ix.item->iwin->sub = nil;
		ix.item->iwin->focus = nil;
		if((tk = sub->parent) != nil) {
			tktfixgeom(tk, tktprevwrapline(tk, ix.line), ix.line, 0);
			tktextsize(tk, 1);
			sub->parent = nil;
		}
	}
}

void
tktdirty(Tk *sub)
{
	Tk *tk, *parent, *isub;
	TkText *tkt;
	TkTindex ix;

	parent = nil;
	for(tk = sub; tk && parent == nil; tk = tk->master)
		parent = tk->parent;
	if(tk == nil)
		return;

	tkt = TKobj(TkText, parent);
	tktstartind(tkt, &ix);
	do {
		if(ix.item->kind == TkTwin) {
			isub = ix.item->iwin->sub;
			if(isub != nil) {
				tktfixgeom(parent, tktprevwrapline(parent, ix.line), ix.line, 0);
				if (sub->flag & Tktransparent)
					parent->flag |= Tkrefresh;	/* XXX could be more efficient, by drawing the background locally? */
				return;
			}
		}
	} while(tktadjustind(tkt, TkTbyitem, &ix));
	tktextsize(parent, 1);
}

static char*
tktwinchk(Tk *tk, TkTwind *w, Tk *oldsub)
{
	Tk *sub;

	sub = w->sub;
	if (sub != oldsub) {
		w->sub = oldsub;
		if(sub == nil)
			return nil;

		if(sub->flag & Tkwindow)
			return TkIstop;
	
		if(sub->master != nil || sub->parent != nil)
			return TkWpack;

		if (oldsub != nil) {
			oldsub->parent = nil;
			oldsub->geom = nil;
			oldsub->destroyed = nil;
		}
		w->sub = sub;
		w->focus = nil;

		sub->parent = tk;
		tksetbits(sub, Tksubsub);
		sub->geom = tktwingeom;
		sub->destroyed = tktdestroyed;

		w->width = sub->req.width;
		w->height = sub->req.height;
		w->owned = winowned(tk, sub);
	}

	return nil;
}


/* Text Window Command (+ means implemented)
	+cget
	+configure
	+create
	+names
*/

static char*
tktwincget(Tk *tk, char *arg, char **val)
{
	char *e;
	TkTindex ix;
	TkOptab tko[2];

	e = tktindparse(tk, &arg, &ix);
	if(e != nil)
		return e;
	if(ix.item->kind != TkTwin)
		return TkBadwp;

	tko[0].ptr = ix.item->iwin;
	tko[0].optab = twinopts;
	tko[1].ptr = nil;

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

static char*
tktwinconfigure(Tk *tk, char *arg, char **val)
{
	char *e;
	TkTindex ix;
	TkOptab tko[2];
	Tk *oldsub;

	USED(val);

	e = tktindparse(tk, &arg, &ix);
	if(e != nil)
		return e;
	if(ix.item->kind != TkTwin)
		return TkBadwp;

	oldsub = ix.item->iwin->sub;

	tko[0].ptr = ix.item->iwin;
	tko[0].optab = twinopts;
	tko[1].ptr = nil;

	e = tkparse(tk->env->top, arg, tko, nil);
	if(e != nil)
		return e;

	e = tktwinchk(tk, ix.item->iwin, oldsub);
	if(e != nil)
		return e;

	tktwindsize(tk, &ix);
	return nil;
}

/*
 * return true if tk is an ancestor of sub
 */
static int
winowned(Tk *tk, Tk *sub)
{
	int len;
	if (tk->name == nil || sub->name == nil)
		return 0;
	len = strlen(tk->name->name);
	if (strncmp(tk->name->name, sub->name->name, len) == 0 &&
			sub->name->name[len] == '.')
		return 1;
	return 0;
}

static char*
tktwincreate(Tk *tk, char *arg, char **val)
{
	char *e;
	TkTindex ix;
	TkTitem *i;
	TkText *tkt;
	TkOptab tko[2];

	USED(val);

	tkt = TKobj(TkText, tk);

	e = tktindparse(tk, &arg, &ix);
	if(e != nil)
		return e;

	e = tktnewitem(TkTwin, 0, &i);
	if(e != nil)
		return e;

	i->iwin = malloc(sizeof(TkTwind));
	if(i->iwin == nil) {
		tktfreeitems(tkt, i, 1);
		return TkNomem;
	}

	memset(i->iwin, 0, sizeof(TkTwind));
	i->iwin->align = Tkcenter;
	i->iwin->ascent = -1;

	tko[0].ptr = i->iwin;
	tko[0].optab = twinopts;
	tko[1].ptr = nil;

	e = tkparse(tk->env->top, arg, tko, nil);
	if(e != nil) {
    err1:
		tktfreeitems(tkt, i, 1);
		return e;
	}

	e = tktwinchk(tk, i->iwin, nil);
	if(e != nil)
		goto err1;

	e = tktsplititem(&ix);
	if(e != nil)
		goto err1;

	tktiteminsert(tkt, &ix, i);
	if(e != nil)
		goto err1;

	tktadjustind(tkt, TkTbyitemback, &ix);
	tktwindsize(tk, &ix);

	return nil;
}

static char*
tktwinnames(Tk *tk, char *arg, char **val)
{
	char *e, *fmt;
	TkTindex ix;
	TkText *tkt = TKobj(TkText, tk);

	USED(arg);

	tktstartind(tkt, &ix);
	fmt = "%s";
	do {
		if(ix.item->kind == TkTwin 
		   && ix.item->iwin->sub != nil
                   && (ix.item->iwin->sub->name != nil)) {
			e = tkvalue(val, fmt, ix.item->iwin->sub->name->name);
			if(e != nil)
				return e;
			fmt = " %s";
		}
	} while(tktadjustind(tkt, TkTbyitem, &ix));
	return nil;
}
