/*
 * dml - Dialog Markup Language
 *
 * $Id: menu.c,v 1.12 2003/04/04 10:08:27 malekith Exp $
 * Author: Michal Moskal <malekith@pld-linux.org>
 * include COPYING-GNU
 */

#include "pi.h"
#include "ml.h"
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

void pi_menu_draw(struct pi_menu *self)
{
	int c1, c2;
	int x, y, i, end;

	pi_locate((struct pi_object*)self, &x, &y);

	if (self->top < 0)
		self->top = 0;

	end = self->top + self->pos.h;

	if (self->search_buffer[0])
		end--;
		
	for (i = self->top; i < end; i++) {
		pi_goto(x, y++);
		if (i == self->hl) {
			if (pi_is_active((struct pi_object*)self)) {
				c1 = pi_color_menu_hl_active_1;
				c2 = pi_color_menu_hl_active_2;
			} else {
				c1 = pi_color_menu_hl_1;
				c2 = pi_color_menu_hl_2;
			}
		} else {
			c1 = pi_color_menu_1;
			c2 = pi_color_menu_2;
		}
	
		if (i >= self->nlines)
			pi_addstr_fill_alt("~", self->pos.w - 2, c1, c2);
		else if (self->items[i]->state == pi_menu_item_normal)
			pi_addstr_fill_alt(self->items[i]->caption,
					   self->pos.w - 2, c1, c2);
		else {
			if (self->items[i]->group) {
				pi_color(c1);
				pi_putch('(');
				pi_color(c2);
				pi_putch(self->items[i]->state 
					 == pi_menu_item_checked ?  '*' : ' ');
				pi_color(c1);
				pi_putch(')');
			} else {
				pi_color(c1);
				pi_putch('[');
				pi_color(c2);
				pi_putch(self->items[i]->state 
					 == pi_menu_item_checked ?  'X' : ' ');
				pi_color(c1);
				pi_putch(']');
			}
			pi_putch(' ');
			pi_addstr_fill_alt(self->items[i]->caption,
					   self->pos.w - 6, c1, c2);
		}

		if (i == self->hl)
			pi_putch('<');
		else
			pi_putch(' ');
			
		if (self->nlines <= self->pos.h)
			continue;

		pi_scrollbar(i, self->top, self->pos.h, self->nlines, 
			pi_is_active((struct pi_object*)self) ?
					pi_color_amenu_scrollbar :
					pi_color_menu_scrollbar,
			pi_is_active((struct pi_object*)self) ?
					pi_color_amenu_scrollbar_marked :
					pi_color_menu_scrollbar_marked);
	}

	if (self->search_buffer[0]) {
		pi_goto(x, y);
		pi_color(pi_color_menu_hl_active_2);
		pi_addstrn_fill(self->search_buffer, self->pos.w - 2);
	}
}

static void free_items(struct pi_menu *self)
{
	int i;

	for (i = 0; i < self->nlines; i++) {
		xfree(self->items[i]->caption);
		xfree(self->items[i]->id);
		xfree(self->items[i]);
	}

	xfree(self->items);
	self->items = 0;
	self->nlines = 0;
}

void pi_menu_add(struct pi_menu *self, const char *caption, 
		 const char *id, const char *checked, const char *group,
		 int ghosted, int nohl)
{
	struct pi_menu_item *i;
	
	if (self->items) 
		free_items(self);

	if (self->items_db == 0)
		self->items_db = db_new();

	i = xmalloc(sizeof(struct pi_menu_item));
	i->caption = i->id = i->group = NULL;
	if (nohl) {
		int n = 0, k;
		
		for (k = 0; caption[k]; k++, n++)
			if (caption[k] == '_')
				n++;
		n++;
		i->caption = xmalloc(n);
		for (k = 0, n = 0; caption[k]; k++, n++) {
			if (caption[k] == '_')
				i->caption[n++] = '_';
			i->caption[n] = caption[k];
		}
		i->caption[n] = 0;
	} else
		pi_setstr(&i->caption, caption);
	
	pi_setstr(&i->id, id);
	if (group)
		pi_setstr(&i->group, group);

	db_addp(self->items_db, i);

	i->state = pi_menu_item_normal;
	if (checked) {
		i->state = atoi(checked) ? 
			pi_menu_item_checked :
			pi_menu_item_unchecked;
		self->type = pi_menu_checkable;
	}
	i->ghosted = ghosted;
}

void pi_menu_finish_adding(struct pi_menu *self)
{
	int i;
	
	if (self->items_db == 0)
		pi_menu_add(self, ">   -   ", "", NULL, NULL, 0, 0);
		
	self->items = db_finish(self->items_db, &self->nlines);
	self->nlines /= sizeof(void*);
	self->top = self->hl = 0;
	self->items_db = 0;
	
	for (i = 0; i < self->nlines; i++)
		if (!self->items[i]->ghosted) {
			self->hl = i;
			break;
		}
}

void pi_menu_kill(struct pi_menu *self)
{
	if (self->items)
		free_items(self);
	if (self->items_db)
		db_kill(self->items_db);
}

static void enter(struct pi_menu *self, int key)
{
	struct pi_menu_item *i = self->items[self->hl];
	int k;
	
	if (i->state != pi_menu_item_normal) {
		self->type = pi_menu_checkable;

		if (i->state == pi_menu_item_checked) {
			if (i->group == NULL)
				self->items[self->hl]->state =
					pi_menu_item_unchecked;
		} else {
			i->state = pi_menu_item_checked;
		}

		if (i->group == NULL)
			return;
			
		for (k = 0; k < self->nlines; k++)
			if (k != self->hl &&
			    self->items[k]->group &&
			    self->items[k]->state == pi_menu_item_checked &&
			    strcmp(self->items[k]->group, i->group) == 0)
				self->items[k]->state = 
					pi_menu_item_unchecked;
	} else if (key == '\n' && self->cc) {
		self->type = pi_menu_checkable_signaled;
		self->parent->f->handle_cc(self->parent, self->cc);
	}
}

static int matches(const char *pat, const char *str)
{
	while (*pat) {
		while (*str && !isalnum(*str))
			str++;
		if (*str != *pat)
			return 0;
		str++;
		pat++;
	}

	return 1;
}

static void search_key(struct pi_menu *self, int key)
{
	int n = strlen(self->search_buffer);
	int i;
	
	if (key == '\b' && n) {
		self->search_buffer[n - 1] = 0;
		return;
	}
	
	if (n > (int)(sizeof(self->search_buffer) / sizeof(char)) - 3)
		return;		/* No more space in buffer left. */
	
	self->search_buffer[n++] = key;
	self->search_buffer[n] = '\0';

	for (i = self->hl; i < self->nlines; i++)
		if (matches(self->search_buffer, self->items[i]->caption))
			break;
			
	if (i < self->nlines) {
		self->hl = i;
		self->top = self->hl - self->pos.h / 2;
		if (self->top < 0)
			self->top = 0;
	}
}

void pi_menu_key(struct pi_menu *self, int key)
{
	int n, i;
	
	switch (key) {
	case pi_key_up:
	case '-':
		self->search_buffer[0] = '\0';
		for (i = self->hl - 1; i >= 0; i--)
			if (!self->items[i]->ghosted)
				break;
		if (i >= 0)
			self->hl = i;
		else
			self->top = 0;
			
		if (self->hl < self->top)
			self->top = self->hl;
		break;
	case pi_key_down:
	case '+':
		self->search_buffer[0] = '\0';
		for (i = self->hl + 1; i < self->nlines; i++)
			if (!self->items[i]->ghosted)
				break;
		if (i < self->nlines)
			self->hl = i;
		else
			self->top = self->nlines - self->pos.h;
		
		if (self->top < 0)
			self->top = 0;

		if (self->hl - self->top >= self->pos.h)
			self->top = self->hl - self->pos.h + 1;
		break;

	case pi_key_pgup:
		self->search_buffer[0] = '\0';
		for (n = self->pos.h;;) {
			for (i = self->hl - 1; i >= 0; i--)
				if (!self->items[i]->ghosted)
					break;
			if ((n -= (self->hl - i)) < 0)
				break;
			if (i >= 0)
				self->hl = i;
			else
				self->top = 0;
			
			if (self->hl < self->top)
				self->top = self->hl;
		}
		break;
		
	case pi_key_pgdn:
		self->search_buffer[0] = '\0';
		for (n = self->pos.h;;) {
			for (i = self->hl + 1; i < self->nlines; i++)
				if (!self->items[i]->ghosted)
					break;
			if ((n -= (i - self->hl)) < 0)
				break;
			if (i < self->nlines)
				self->hl = i;
			else
				self->top = self->nlines - self->pos.h;
				
			if (self->hl - self->top >= self->pos.h)
				self->top = self->hl - self->pos.h + 1;
		}
		break;
	case ' ':
	case '\n':
		self->search_buffer[0] = '\0';
		enter(self, key);
		break;
	default:
		if (isalnum(key & 0xff) || key == '\b')
			search_key(self, key);
		else {
			self->search_buffer[0] = '\0';
			pi_object_key((struct pi_object*)self, key);
		}
		break;
	}
	
	pi_draw((struct pi_object*)self);
}

const char *pi_menu_fetch(struct pi_menu *self)
{
	static char *buf;
	struct dbuf *db;
	int i;
	
	if (self->type != pi_menu_checkable)
		return self->items[self->hl]->id;
	
	xfree(buf);
	db = db_new();

	for (i = 0; i < self->nlines; i++)
		if (self->items[i]->state == pi_menu_item_checked) {
			db_add(db, self->items[i]->id, 
				strlen(self->items[i]->id));
			db_addc(db, ' ');
		}
	
	db_addc(db, 0);
	buf = db_finish(db, NULL);

	return buf;
}

static struct pi_funcs menu_funcs = {
	1,
	(pi_handler_void)pi_menu_draw,
	(pi_handler_int)pi_object_cc,
	(pi_handler_int)pi_menu_key,
	(pi_handler_void)pi_menu_kill,
	(pi_handler_rstring)pi_menu_fetch,
};

struct pi_menu *pi_new_menu(int size)
{
	struct pi_menu *self;

	if (size == 0)
		size = sizeof(struct pi_menu);
	self = (struct pi_menu*)pi_new_object(size);
	self->f = &menu_funcs;
	self->search_buffer[0] = '\0';

	return self;
}

void pi_menu_set_caption(struct pi_menu *self, char *p)
{
	char *x;
	struct phash *ph = 0;
	struct dbuf *db;
	
	while (*p && (*p != pi_tag_start || p[1] != pi_tag_item))
		p++;
		
	while (*p) {
		p += 2;
		x = p;
		p = strchr(p, pi_tag_end);
		*p++ = 0;
		if (*x)
			ph = pi_make_tag_args(x+1);
		db = db_new();
		while (*p) {
			if (*p == pi_tag_start && p[1] == pi_tag_item)
				break;
			if (*p == pi_tag_start)
				p = strchr(p, pi_tag_end) + 1;
			if (*p)
				db_addc(db, *p++);
		}
		db_addc(db, 0);
		x = db_finish(db, 0);
		pi_menu_add(self, x, 
				pi_get_string(ph, "id", x), 
				pi_get_string(ph, "checked", NULL),
				pi_get_string(ph, "group", NULL),
				pi_get_int(ph, "ghosted", 0),
				pi_get_int(ph, "nohl", 0));
		xfree(x);
		if (ph) {
			ph_std_kill(ph);
			ph = 0;
		}
	}

	pi_menu_finish_adding(self);
}

void pi_menu_set_default_size(struct pi_menu *self)
{
	int i, max_w = 0, w;

	for (i = 0; i < self->nlines; i++) {
		w = pi_strlen_alt(self->items[i]->caption);
		if (self->items[i]->state != pi_menu_item_normal)
			w += 4;
		if (w > max_w)
			max_w = w;
	}
	self->pos.w = max_w + 2;
	self->pos.h = i > 8 ? 8 : i;
}

void pi_menu_set_hl(struct pi_menu *self, const char *id)
{
	int i;

	for (i = 0; i < self->nlines; i++)
		if (strcmp(id, self->items[i]->id) == 0)
			break;
	if (i != self->nlines) {
		self->hl = i;
		self->top = self->hl - self->pos.h / 2;
		if (self->nlines - self->top < self->pos.h)
			self->top = self->hl - self->pos.h + 1;
		if (self->top < 0)
			self->top = 0;
	}
}

void pi_menu_set_checked(struct pi_menu *self, const char *ids)
{
	int i;
	char *p = NULL, *id;

	pi_setstr(&p, ids);

	for (id = strtok(p, " \t\n"); id; id = strtok(NULL, " \t\n")) {
		for (i = 0; i < self->nlines; i++)
			if (strcmp(id, self->items[i]->id) == 0)
				break;
		if (i != self->nlines)
			self->items[i]->state = pi_menu_item_checked;
	}
	xfree(p);
}

char *pi_popup_menu(struct pi_menu *menu, const char *title)
{
	struct pi_window *win;
	struct pi_object *back;
	char *ret;

	if (menu == NULL)
		return strdup("No such menu");

	back = _pi_root_object->active;
	
	win = pi_new_window(0);

	menu->pos.x = 2;
	menu->pos.y = 1;
	menu->cc = 1;

	pi_add_child((struct pi_object*)win, (struct pi_object*)menu);
	pi_setpos((struct pi_object*)win, 0, 0, 
		  menu->pos.w + 4, menu->pos.h + 2);
	pi_center(win);
	if (title)
		pi_setstr(&win->title, title);

	win->modal_result = 0;
	pi_exec(win);
	if (win->modal_result != 1)
		ret = NULL;
	else
		ret = strdup(menu->f->fetch((struct pi_object*)menu));

	/* Don't free menu instance, we can need it again! */
	win->children = NULL;

	pi_kill((struct pi_object*)win);
	
	_pi_root_object->active = back;
	pi_repaint();

	return ret;
}
