/*
 * This file is part of the UCB release of Plan 9. It is subject to the license
 * terms in the LICENSE file found in the top-level directory of this
 * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
 * part of the UCB release of Plan 9, including this file, may be copied,
 * modified, propagated, or distributed except according to the terms contained
 * in the LICENSE file.
 */

#include <u.h>
#include <lib9.h>
#include <draw.h>
#include <thread.h>
#include <mouse.h>
#include <keyboard.h>
#include <control.h>

typedef struct Menu0 Menu0;	/* Menu is taken by mouse.h */

struct Menu0
{
	Control;
	CImage	*image;
	CImage	*bordercolor;
	CImage	*textcolor;
	CImage	*selectcolor;
	CImage	*selecttextcolor;
	CFont	*font;
	char		**line;
	int		nline;
	int		border;
	int		align;
	Image	*window;
	int		visible;	/* state of menu */
	int		selection;		/* currently selected line; -1 == none */
	int		prevsel;	/* previous selection */
	int		lastbut;	/* previous state of mouse button */
};

enum{
	EAdd,
	EAlign,
	EBorder,
	EBordercolor,
	EFocus,
	EFont,
	EFormat,
	EHide,
	EImage,
	ERect,
	EReveal,
	ESelectcolor,
	ESelecttextcolor,
	EShow,
	ESize,
	ETextcolor,
	EWindow,
};

static char *cmds[] = {
	[EAdd] = 			"add",
	[EAlign] = 			"align",
	[EBorder] = 		"border",
	[EBordercolor] =	"bordercolor",
	[EFocus] = 		"focus",
	[EFont] =			"font",
	[EFormat] = 		"format",
	[EHide] =			"hide",
	[EImage] =		"image",
	[ERect] =			"rect",
	[EReveal] =		"reveal",
	[ESelectcolor] =	"selectcolor",
	[ESelecttextcolor] =	"selecttextcolor",
	[EShow] =			"show",
	[ESize] =			"size",
	[ETextcolor] =		"textcolor",
	[EWindow] =		"window",
	nil
};

static void	menushow(Menu0*);
static void menuhide(Menu0*);

static void
menufree(Control *c)
{
	Menu0 *m;

	m = (Menu0*)c;
	_putctlfont(m->font);
	_putctlimage(m->image);
	_putctlimage(m->textcolor);
	_putctlimage(m->bordercolor);
	_putctlimage(m->selectcolor);
	_putctlimage(m->selecttextcolor);
}

static void
menushow(Menu0 *m)
{
	Rectangle r, clipr;
	int i, dx, dy, w;
	Font *f;
	Point p, q;
	Image *im, *c;

	if(m->hidden || m->window == nil)
		return;

	m->visible = 1;
	f = m->font->font;
	draw(m->window, m->rect, m->image->image, nil, m->image->image->r.min);
	if(m->border > 0)
		border(m->window, m->rect, m->border, m->bordercolor->image, ZP);
	/* text goes here */
	dx = 0;
	for(i=0; i<m->nline; i++){
		w = stringwidth(f, m->line[i]);
		if(dx < w)
			dx = w;
	}
	dy = m->nline*f->height;
	clipr = insetrect(m->rect, m->border);
	p = _ctlalignpoint(clipr, dx, dy, m->align);
	im = m->textcolor->image;
//	if(m->pressed)
//		im = m->pressedtextcolor->image;
	for(i=0; i<m->nline; i++){
		r.min = p;
		r.max.x = p.x+dx;
		r.max.y = p.y+f->height;
		c = im;
		if(i == m->selection){
			draw(m->window, r, m->selectcolor->image, nil, ZP);
			c = m->selecttextcolor->image;
		}
		q = _ctlalignpoint(r, stringwidth(f, m->line[i]), f->height, m->align%3);
		_string(m->window, q, c,
			ZP, f, m->line[i], nil, strlen(m->line[i]),
			clipr, nil, ZP, SoverD);
		p.y += f->height;
	}
//	if(m->pressed)
//		draw(m->screen, m->rect, m->lighm->image, m->mask->image, m->mask->image->r.min);
	flushimage(display, 1);
}

static Point
menusize(Menu0 *m)
{
	int x, y;
	int i;
	Point p;
	Font *f;

	x = 0;
	y = 0;
	f = m->font->font;
	for(i=0; i<m->nline; i++){
		p = stringsize(f, m->line[i]);
		if(p.x > x)
			x = p.x;
		y += f->height;
	}

	return Pt(x+2*m->border, y+2*m->border);
}

static void
menuhide(Menu0 *m)
{
	freeimage(m->window);
	m->window = nil;
	m->rect.max.y = m->rect.min.y;	/* go to zero size */
	m->lastbut = 0;
	m->visible = 0;
	if(m->selection >= 0)
		m->prevsel = m->selection;
	m->selection = -1;
	_ctlfocus(m, 0);
}

static void
menutrack(Control *c, Mouse *ms)
{
	Rectangle r;
	int s;
	Menu0 *m;

	m = (Menu0*)c;
	if(m->window == nil)
		return;
	if(m->lastbut && ms->buttons==0){	/* menu was released */
		chanprint(m->event, "%q: value %d", m->name, m->selection);
		menuhide(m);
		return;
	}
	m->lastbut = ms->buttons;
	r = insetrect(m->rect, m->border);
	if(!ptinrect(ms->xy, r))
		s = -1;
	else{
		s = (ms->xy.y - r.min.y)/m->font->font->height;
		if(s < 0 || s >= m->nline)
			s = -1;
	}
	if(m->visible== 0 || s!=m->selection){
		m->selection = s;
		menushow(m);
	}
}

static void
menuctl(Control *c, CParse *cp)
{
	int up, cmd, h;
	Rectangle r;
	Menu0 *m;
	Point diag;

	m = (Menu0*)c;
	cmd = _ctllookup(cp->args[0], cmds, nelem(cmds));
	switch(cmd){
	default:
		ctlerror("%q: unrecognized message '%s'", m->name, cp->str);
		break;
	case EAdd:
		_ctlargcount(m, cp, 2);
		m->line = ctlrealloc(m->line, (m->nline+1)*sizeof(char*));
		m->line[m->nline++] = ctlstrdup(cp->args[1]);
		menushow(m);
		break;
	case EAlign:
		_ctlargcount(m, cp, 2);
		m->align = _ctlalignment(cp->args[1]);
		menushow(m);
		break;
	case EBorder:
		_ctlargcount(m, cp, 2);
		m->border = cp->iargs[1];
		menushow(m);
		break;
	case EBordercolor:
		_ctlargcount(m, cp, 2);
		_setctlimage(m, &m->bordercolor, cp->args[1]);
		menushow(m);
		break;
	case EFocus:
		_ctlargcount(m, cp, 2);
		if(atoi(cp->args[1]) == 0)
			menuhide(m);
		break;
	case EFont:
		_ctlargcount(m, cp, 2);
		_setctlfont(m, &m->font, cp->args[1]);
		break;
	case EFormat:
		_ctlargcount(m, cp, 2);
		m->format = ctlstrdup(cp->args[1]);
		break;
	case EHide:
		_ctlargcount(m, cp, 1);
		m->hidden = 1;
		break;
	case EImage:
		_ctlargcount(m, cp, 2);
		_setctlimage(m, &m->image, cp->args[1]);
		menushow(m);
		break;
	case ERect:
		_ctlargcount(m, cp, 5);
		r.min.x = cp->iargs[1];
		r.min.y = cp->iargs[2];
		r.max.x = cp->iargs[3];
		r.max.y = cp->iargs[4];
		if(Dx(r)<0 || Dy(r)<0)
			ctlerror("%q: bad rectangle: %s", m->name, cp->str);
		m->rect = r;
		menushow(m);
		break;
	case EReveal:
		_ctlargcount(m, cp, 1);
		m->hidden = 0;
		menushow(m);
		break;
	case ESelectcolor:
		_ctlargcount(m, cp, 2);
		_setctlimage(m, &m->selectcolor, cp->args[1]);
		menushow(m);
		break;
	case ESelecttextcolor:
		_ctlargcount(m, cp, 2);
		_setctlimage(m, &m->selecttextcolor, cp->args[1]);
		menushow(m);
		break;
	case EShow:
		_ctlargcount(m, cp, 1);
		menushow(m);
		break;
	case ESize:
		if (cp->nargs == 3)
			r.max = Pt(0x7fffffff, 0x7fffffff);
		else{
			_ctlargcount(m, cp, 5);
			r.max.x = cp->iargs[3];
			r.max.y = cp->iargs[4];
		}
		r.min.x = cp->iargs[1];
		r.min.y = cp->iargs[2];
		if(r.min.x<=0 || r.min.y<=0 || r.max.x<=0 || r.max.y<=0 || r.max.x < r.min.x || r.max.y < r.min.y)
			ctlerror("%q: bad sizes: %s", m->name, cp->str);
		m->size.min = r.min;
		m->size.max = r.max;
		break;
	case ETextcolor:
		_ctlargcount(m, cp, 2);
		_setctlimage(m, &m->textcolor, cp->args[1]);
		menushow(m);
		break;
	case EWindow:
		/* no args == toggle; otherwise 0 or 1 for state of window */
		if(cp->nargs >= 2)
			up = cp->iargs[1];
		else
			up = (m->window == nil);
		if(!up){	/* take window down */
			if(m->window)
				menuhide(m);
			break;
		}
		if(m->window != nil)
			break;
		diag = menusize(m);
		m->rect.max.x = m->rect.min.x + diag.x;
		m->rect.max.y = m->rect.min.y + diag.y;
		m->window = allocwindow(_screen, m->rect, Refbackup, DWhite);
		if(m->window == nil)
			m->window = m->screen;
		up = m->prevsel;
		if(up<0 || up>=m->nline)
			up = 0;
		m->selection = up;
		menushow(m);
		h = m->font->font->height;
		moveto(m->controlset->mousectl,
			Pt(m->rect.min.x+Dx(m->rect)/2, m->rect.min.y+up*h+h/2));
//		_ctlfocus(m, 1);
		break;
	}
}

Control*
createmenu(Controlset *cs, char *name)
{
	Menu0 *m;

	m = (Menu0*)_createctl(cs, "menu", sizeof(Menu0), name);
	m->font = _getctlfont("font");
	m->image = _getctlimage("white");
	m->textcolor = _getctlimage("black");
	m->selectcolor = _getctlimage("yellow");
	m->selecttextcolor = _getctlimage("black");
	m->bordercolor = _getctlimage("black");
	m->format = ctlstrdup("%q: value %d");
	m->border = 0;
	m->align = Aupperleft;
	m->visible = 0;
	m->window = nil;
	m->lastbut = 0;
	m->selection = -1;
	m->mouse = menutrack;
	m->ctl = menuctl;
	m->exit = menufree;
	return (Control *)m;
}
