/* $Id: manager.c,v 1.6 2005/06/20 06:49:34 cegger Exp $
******************************************************************************

   LibGIC - Config Manager

   Copyright (C) 1999 Andrew Apted    <andrew@ggi-project.org>

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
   THE AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
   IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

******************************************************************************
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <ggi/internal/gic.h>
#include <ggi/gic_confmgr.h>


#ifndef MAX
#define MAX(a, b)  ((a) > (b) ? (a) : (b))
#endif
#ifndef MIN
#define MIN(a, b)  ((a) < (b) ? (a) : (b))
#endif

typedef enum refresh_mode {
	REFRESH_NONE = 0,
	REFRESH_CUR_BIND = 1,	/* redraw current feature's bindings */
	REFRESH_BINDINGS = 2,	/* redraw all bindings */
	REFRESH_ITEMS = 3,	/* redraw items (& bindings) */
	REFRESH_HEADING = 4,	/* redraw heading & items */
	REFRESH_SECTION = 5	/* redraw whole section */
} RefreshMode;

typedef struct little_window {
	int x, y, w, h;		/* bounding box */

	int total;		/* total number of whatsits */
	int current;		/* current one */
	int start;		/* first one currently on screen */
	int room;		/* how many will fit on screen */

	RefreshMode refresh;	/* need refresh ? */

} LittleWin;

#define SECTIONKIND_NR  3

typedef enum section_kind {
	CM_CONTEXT = 0,
	CM_CONTROL,
	CM_FEATURE
} SectionKind;

typedef int ActionInfo;

#define ACTINFO_NONE   0x00
#define ACTINFO_STATE  0x01
#define ACTINFO_PULSE  0x02

typedef struct manager_priv {
	/* Head used for controlling the config manager.
	 */
	gic_head *head;

	/* Contexts used by the config manager.
	 */
	gic_context *browsing;
	gic_context *training;
	gic_context *testing;

	SectionKind section;

	LittleWin context_w;
	gic_context *context_p;
	LittleWin control_w;
	gic_control *control_p;
	LittleWin feature_w;
	gic_feature *feature_p;
	LittleWin binding_w;

	ActionInfo do_quit, do_train, do_test, do_cancel, do_delete,
	    move_up, move_down, move_left, move_right,
	    next_section, prev_section;

	/* Stuff needed for TRAIN mode.
	 */
	gic_recognizer *train_rec;

	/* Stuff needed for TEST mode.
	 */
	gic_feature *test_feature;

	gic_state test_pulse;
	gic_state test_state;

} ManagerPriv;

static void CM_action(gic_handle_t hand, gic_actionlist * action,
		      gic_feature * feature, gic_state newstate,
		      gic_flag flag, int recnum)
{
	ActionInfo *info = (ActionInfo *) action->privdata;

	if (flag & GIC_FLAG_PULSE) {
		if (newstate >= GIC_STATE_MIDDLE) {
			*info |= ACTINFO_PULSE;
		}
		return;
	}

	if (flag & GIC_FLAG_MUSTKNOWMASK) {
		/* something we don't know how to handle... */
		return;
	}

	*info &= ~ACTINFO_STATE;
	*info |= (newstate >= GIC_STATE_MIDDLE) ? ACTINFO_STATE : 0;
}

static void TEST_action(gic_handle_t hand, gic_actionlist * action,
			gic_feature * feature, gic_state newstate,
			gic_flag flag, int recnum)
{
	confmgr_info *info = (confmgr_info *) action->privdata;

	ManagerPriv *priv = (ManagerPriv *) info->manager_priv;

	if (flag & GIC_FLAG_PULSE) {
		priv->test_pulse = newstate;
		return;
	}

	if (flag & GIC_FLAG_MUSTKNOWMASK) {
		/* something we don't know how to handle... */
		return;
	}

	priv->test_state = newstate;
}


/* ---------------------------------------------------------------------- */


static gic_state test_feature(confmgr_info * info, gic_feature * f,
			      gii_event * ev)
{
	ManagerPriv *priv = (ManagerPriv *) info->manager_priv;

	gic_recognizer *rec;
	int number;

	gic_state cur = GIC_NOACTION;

	for (rec = f->recognizers, number = 0;
	     rec != NULL; rec = rec->next, number++) {

		priv->test_pulse = GIC_NOACTION;
		priv->test_state = GIC_NOACTION;

		rec->driver->check(info->handle, rec, ev,
				   priv->test_feature, number);

		if ((priv->test_pulse != GIC_NOACTION) &&
		    ((cur == GIC_NOACTION) || (priv->test_pulse > cur))) {
			cur = priv->test_pulse;
		}

		if ((priv->test_state != GIC_NOACTION) &&
		    ((cur == GIC_NOACTION) || (priv->test_state > cur))) {
			cur = priv->test_state;
		}
	}

	return cur;
}

static void empty_box(confmgr_info * info, LittleWin * lw)
{
	info->draw_box(info, CONFMGR_STYLE_BACKGROUND,
		       lw->x, lw->y, lw->w, lw->h);
}

static void clear_box(confmgr_info * info, LittleWin * lw, int cur)
{
	info->draw_box(info, (cur && (info->flags &
				      CONFMGR_FLAG_HIGHLIGHT_SECTION)) ?
		       CONFMGR_STYLE_SECTION_HIGHLIGHT :
		       CONFMGR_STYLE_SECTION_BACKGROUND,
		       lw->x, lw->y, lw->w, lw->h);
}

static int how_many(size_t space, size_t item, int gap)
{
	int result = 0;

	result = space / (item + gap);
	space = space % (item + gap);

	if (item <= space) {
		result++;
	}

	return result;
}

static void space_out_str(char *str, size_t max, size_t space)
{
	size_t i;

	if (space >= max) {
		space = max - 1;
	}

	for (i = strlen(str); i < space; i++) {
		str[i] = ' ';
	}

	str[space] = 0;
}

static void flush_input(confmgr_info * info)
{
	for (;;) {
		gii_event ev;

		struct timeval t = { 0, 0 };

		if ((*info->read_event) (info, &ev, &t) == 0) {
			return;
		}
	}
}

static int move_in_window(LittleWin * w, int amount)
{
	int new_cur = w->current + amount;
	int changed = 0;

	if (new_cur < 0) {
		new_cur = 0;

	} else if (new_cur >= w->total) {
		new_cur = w->total - 1;
	}

	if (new_cur != w->current) {
		changed |= 1;
	}

	w->current = new_cur;

	if (new_cur < w->start) {
		w->start = new_cur;
		changed |= 2;

	} else if (new_cur >= (w->start + w->room)) {
		w->start = new_cur - w->room + 1;
		changed |= 2;
	}

	return changed;
}


/* ---------------------------------------------------------------------- */


static void initial_context(confmgr_info * info)
{
	ManagerPriv *priv = (ManagerPriv *) info->manager_priv;

	priv->context_w.x = 0;
	priv->context_w.y = 0;
	priv->context_w.w = info->screen_size.x;
	priv->context_w.h = info->big_size.y * 4;

	priv->context_w.refresh = REFRESH_SECTION;

	priv->context_w.total =
	    gicHeadNumContexts(info->handle, info->head);
	priv->context_w.current = 0;
	priv->context_w.start = 0;

	priv->context_w.room = how_many((size_t)
					(priv->context_w.w -
					 info->section_border.left -
					 info->section_border.right),
					info->small_size.x *
					info->context_max,
					info->item_gap.x);

	priv->context_p = gicHeadGetContext(info->handle, info->head,
					    priv->context_w.current);
}

static void initial_control(confmgr_info * info)
{
	ManagerPriv *priv = (ManagerPriv *) info->manager_priv;

	priv->control_w.x = 0;
	priv->control_w.y = priv->context_w.y + priv->context_w.h +
	    info->section_gap.y;
	priv->control_w.w = info->screen_size.x;
	priv->control_w.h = info->big_size.y * 4;

	priv->control_w.refresh = REFRESH_SECTION;

	if (priv->context_w.total == 0) {
		priv->control_w.total = 0;
		return;
	}

	priv->control_w.total =
	    gicContextNumControls(info->handle, priv->context_p);
	priv->control_w.current = 0;
	priv->control_w.start = 0;

	priv->control_w.room = how_many((size_t) (priv->control_w.w -
						  info->section_border.
						  left -
						  info->section_border.
						  right),
					info->small_size.x *
					info->control_max,
					info->item_gap.x);

	priv->control_p =
	    gicContextGetControl(info->handle, priv->context_p,
				 priv->control_w.current);
}

static void initial_feature(confmgr_info * info)
{
	ManagerPriv *priv = (ManagerPriv *) info->manager_priv;

	priv->feature_w.x = 0;
	priv->feature_w.y = priv->control_w.y + priv->control_w.h +
	    info->section_gap.y;
	priv->feature_w.w = info->screen_size.x - priv->feature_w.x;
	priv->feature_w.h = info->screen_size.y - priv->feature_w.y;

	priv->feature_w.refresh = REFRESH_SECTION;

	if (priv->control_w.total == 0) {
		priv->feature_w.total = 0;
		return;
	}

	priv->feature_w.total =
	    gicControlNumFeatures(info->handle, priv->control_p);
	priv->feature_w.current = 0;
	priv->feature_w.start = 0;

	priv->feature_w.room = how_many((size_t) (priv->feature_w.h -
						  info->big_size.y * 3 /
						  2 -
						  info->section_border.
						  top -
						  info->section_border.
						  bottom),
					(size_t) (info->small_size.y),
					info->binding_gap.y);

	if (priv->feature_w.room > priv->feature_w.total) {
		int diff = priv->feature_w.room - priv->feature_w.total;
		priv->feature_w.h -= MAX(0,
					 diff * info->small_size.y +
					 (diff - 1) * info->binding_gap.y -
					 info->big_size.y);
	}

	priv->feature_p =
	    gicControlGetFeature(info->handle, priv->control_p,
				 priv->feature_w.current);
}

static void initial_binding(confmgr_info * info)
{
	ManagerPriv *priv = (ManagerPriv *) info->manager_priv;

	if (priv->feature_w.total == 0) {
		priv->binding_w.total = 0;
		return;
	}

	/* NOTE: y, w, h, & refresh in binding_w are never used.
	 */

	priv->binding_w.x = priv->feature_w.x +
	    info->small_size.x * info->feature_max + info->item_gap.x;

	priv->binding_w.total =
	    1 + gicFeatureNumRecognizers(info->handle, priv->feature_p);
	priv->binding_w.current = 0;
	priv->binding_w.start = 0;

	priv->binding_w.room = how_many((size_t) (priv->feature_w.w -
						  priv->binding_w.x -
						  info->section_border.
						  right),
					info->small_size.x *
					info->binding_max,
					info->binding_gap.x);
}


/* ---------------------------------------------------------------------- */


static void draw_contexts(confmgr_info * info)
{
	ManagerPriv *priv = (ManagerPriv *) info->manager_priv;

	int i;
	int x = priv->context_w.x + info->section_border.left;
	int y = priv->context_w.y + info->section_border.top;
	int sect = (priv->section == CM_CONTEXT);

	if (priv->context_w.refresh >= REFRESH_SECTION) {

		clear_box(info, &priv->context_w,
			  priv->section == CM_CONTEXT);
	}

	if (priv->context_w.refresh >= REFRESH_HEADING) {

		info->draw_text(info, (priv->section == CM_CONTEXT) ?
				CONFMGR_STYLE_HEADING_HIGHLIGHT :
				CONFMGR_STYLE_HEADING_TEXT,
				CONFMGR_FONT_BIG, x, y, "CONTEXTS");
	}

	y += info->big_size.y * 3 / 2;

	if (priv->context_w.refresh >= REFRESH_ITEMS)

		for (i = 0; i < priv->context_w.total; i++) {

			char buf[100];

			gic_context *cur =
			    gicHeadGetContext(info->handle, info->head, i);

			if ((i < priv->context_w.start) ||
			    (i >= priv->context_w.start +
			     priv->context_w.room)) {
				continue;
			}

			gicContextGetName(info->handle, cur, buf, (size_t)(100));

			space_out_str(buf, (size_t)(100), info->context_max);

			info->draw_text(info,
					(i ==
					 priv->context_w.
					 current) ? (sect ?
						     CONFMGR_STYLE_ITEM_HIGHLIGHT
						     :
						     CONFMGR_STYLE_ITEM_CURRENT)
					: CONFMGR_STYLE_ITEM_TEXT,
					CONFMGR_FONT_SMALL, x, y, buf);

			x += info->small_size.x * info->context_max +
			    info->item_gap.x;
		}

	priv->context_w.refresh = REFRESH_NONE;
}

static void draw_controls(confmgr_info * info)
{
	ManagerPriv *priv = (ManagerPriv *) info->manager_priv;

	int i;
	int x = priv->control_w.x + info->section_border.left;
	int y = priv->control_w.y + info->section_border.top;
	int sect = (priv->section == CM_CONTROL);

	if (priv->control_w.refresh >= REFRESH_SECTION) {

		if (priv->control_w.total == 0) {

			empty_box(info, &priv->control_w);
		} else {
			clear_box(info, &priv->control_w,
				  priv->section == CM_CONTROL);
		}
	}

	if (priv->control_w.total == 0) {
		priv->control_w.refresh = REFRESH_NONE;
		return;
	}

	if (priv->control_w.refresh >= REFRESH_HEADING) {

		info->draw_text(info, (priv->section == CM_CONTROL) ?
				CONFMGR_STYLE_HEADING_HIGHLIGHT :
				CONFMGR_STYLE_HEADING_TEXT,
				CONFMGR_FONT_BIG, x, y, "CONTROLS");
	}

	y += info->big_size.y * 3 / 2;

	if (priv->control_w.refresh >= REFRESH_ITEMS)

		for (i = 0; i < priv->control_w.total; i++) {

			char buf[100];

			gic_control *cur =
			    gicContextGetControl(info->handle,
						 priv->context_p, i);

			if ((i < priv->control_w.start) ||
			    (i >= priv->control_w.start +
			     priv->control_w.room)) {
				continue;
			}

			gicControlGetName(info->handle, cur, buf, (size_t)(100));

			space_out_str(buf, (size_t)(100), info->control_max);

			info->draw_text(info,
					(i ==
					 priv->control_w.
					 current) ? (sect ?
						     CONFMGR_STYLE_ITEM_HIGHLIGHT
						     :
						     CONFMGR_STYLE_ITEM_CURRENT)
					: CONFMGR_STYLE_ITEM_TEXT,
					CONFMGR_FONT_SMALL, x, y, buf);

			x += info->small_size.x * info->control_max +
			    info->item_gap.x;
		}

	priv->control_w.refresh = REFRESH_NONE;
}

static void draw_bindings(confmgr_info * info, gic_feature * f,
			  int y, int cur_feature)
{
	ManagerPriv *priv = (ManagerPriv *) info->manager_priv;

	int i;
	int total_b = gicFeatureNumRecognizers(info->handle, f);
	int is_sect = (priv->section == CM_FEATURE);
	int x = priv->binding_w.x;

	for (i = 0; i <= total_b; i++) {

		int is_cur = cur_feature && (priv->binding_w.current == i);

		char buf[100];

		gic_recognizer *cur =
		    gicFeatureGetRecognizer(info->handle, f, i);

		if ((i < priv->binding_w.start) ||
		    (i >= priv->binding_w.start + priv->binding_w.room)) {
			continue;
		}

		if (i > total_b) {
			break;
		}

		if (is_cur && (priv->train_rec != NULL)) {
			gicRecognizerGetName(info->handle, priv->train_rec,
					     buf, info->binding_max);
		} else if (cur == NULL) {
			buf[0] = 0;
		} else {
			gicRecognizerGetName(info->handle, cur, buf,
					     info->binding_max);
		}
		buf[99] = 0;

		space_out_str(buf, (size_t)(100), info->binding_max);

		info->draw_text(info, is_cur ?
				(is_sect ? CONFMGR_STYLE_BINDING_HIGHLIGHT
				 : CONFMGR_STYLE_BINDING_CURRENT) :
				CONFMGR_STYLE_BINDING_TEXT,
				CONFMGR_FONT_SMALL, x, y, buf);

		x += info->small_size.x * info->binding_max +
		    info->binding_gap.x;
	}
}

static void draw_features(confmgr_info * info, gic_state * states)
{
	ManagerPriv *priv = (ManagerPriv *) info->manager_priv;

	int i;
	int x = priv->feature_w.x + info->section_border.left;
	int y = priv->feature_w.y + info->section_border.top;
	int is_sect = (priv->section == CM_FEATURE);
	int ystep = info->small_size.y + info->binding_gap.y;
	int barwidth = MIN(priv->feature_w.w -
			   info->section_border.right - info->item_gap.x -
			   priv->binding_w.x, info->big_size.x * 40);

	if (priv->feature_w.refresh >= REFRESH_SECTION) {

		int bottom_y = priv->feature_w.y + priv->feature_w.h;

		if (priv->feature_w.total == 0) {

			empty_box(info, &priv->feature_w);
		} else {
			clear_box(info, &priv->feature_w, is_sect);
		}

		if (bottom_y < info->screen_size.y) {

			info->draw_box(info, CONFMGR_STYLE_BACKGROUND,
				       priv->feature_w.x, bottom_y,
				       priv->feature_w.w,
				       info->screen_size.y - bottom_y);
		}
	}

	if (priv->feature_w.total == 0) {
		priv->feature_w.refresh = REFRESH_NONE;
		return;
	}

	if (priv->feature_w.refresh >= REFRESH_HEADING) {

		info->draw_text(info, is_sect ?
				CONFMGR_STYLE_HEADING_HIGHLIGHT :
				CONFMGR_STYLE_HEADING_TEXT,
				CONFMGR_FONT_BIG, x, y, "FEATURES");
	}

	y += info->big_size.y * 3 / 2;

	if (priv->feature_w.refresh > REFRESH_NONE)

		for (i = 0; i < priv->feature_w.total; i++) {

			int is_cur = (i == priv->feature_w.current) &&
			    (states == NULL);

			gic_feature *cur =
			    gicControlGetFeature(info->handle,
						 priv->control_p, i);

			if ((i < priv->feature_w.start) ||
			    (i >=
			     priv->feature_w.start +
			     priv->feature_w.room)) {
				continue;
			}

			if (priv->feature_w.refresh >= REFRESH_ITEMS) {

				char buf[100];

				if ((i < priv->feature_w.start) ||
				    (i >= priv->feature_w.start +
				     priv->feature_w.room)) {
					continue;
				}

				gicFeatureGetName(info->handle, cur, buf,
						  (size_t)(100));

				space_out_str(buf, (size_t)(100), info->feature_max);

				info->draw_text(info, is_cur ?
						(is_sect ?
						 CONFMGR_STYLE_ITEM_HIGHLIGHT
						 :
						 CONFMGR_STYLE_ITEM_CURRENT)
						: CONFMGR_STYLE_ITEM_TEXT,
						CONFMGR_FONT_SMALL, x, y,
						buf);
			}

			if ((priv->feature_w.refresh != REFRESH_CUR_BIND)
			    || is_cur) {
				if (states) {
					if (priv->feature_w.refresh
					    >= REFRESH_SECTION) {
						(*info->draw_box) (info,
								   CONFMGR_STYLE_TEST_BACKGROUND,
								   priv->
								   binding_w.
								   x, y,
								   barwidth,
								   ystep);
					}

					(*info->draw_bar) (info, states[i],
							   priv->binding_w.
							   x, y, barwidth,
							   info->
							   small_size.y);
				} else {
					draw_bindings(info, cur, y,
						      is_cur);
				}
			}

			y += ystep;
		}

	priv->feature_w.refresh = REFRESH_NONE;
}


/* ---------------------------------------------------------------------- */


static void move_section(confmgr_info * info, int amount)
{
	ManagerPriv *priv = (ManagerPriv *) info->manager_priv;

	int new_s = priv->section + amount;

	int ref = (info->flags & CONFMGR_FLAG_HIGHLIGHT_SECTION) ?
	    REFRESH_SECTION : REFRESH_HEADING;

	if ((new_s < CM_CONTEXT) || (new_s > CM_FEATURE)) {
		return;
	}

	switch (new_s) {

	case CM_CONTEXT:
		break;

	case CM_CONTROL:
		if (priv->context_w.total == 0) {
			new_s = CM_CONTEXT;
		}
		break;

	case CM_FEATURE:
		if (priv->control_w.total == 0) {
			new_s = CM_CONTEXT;
		}
		break;
	}

	if (priv->section == (unsigned) new_s) {
		return;
	}

	switch (priv->section) {
	case CM_CONTEXT:
		priv->context_w.refresh = ref;
		break;
	case CM_CONTROL:
		priv->control_w.refresh = ref;
		break;
	case CM_FEATURE:
		priv->feature_w.refresh = ref;
		break;
	}

	switch (new_s) {
	case CM_CONTEXT:
		priv->context_w.refresh = ref;
		break;
	case CM_CONTROL:
		priv->control_w.refresh = ref;
		break;
	case CM_FEATURE:
		priv->feature_w.refresh = ref;
		break;
	}

	(*info->make_sound) (info, CONFMGR_SOUND_NEW_SECTION);

	priv->section = new_s;

	draw_contexts(info);
	draw_controls(info);
	draw_features(info, NULL);
	(*info->flush) (info);
}

static void move_context(confmgr_info * info, int amount)
{
	ManagerPriv *priv = (ManagerPriv *) info->manager_priv;

	if (priv->context_w.total == 0) {
		return;
	}

	if (move_in_window(&priv->context_w, amount) <= 0) {
		return;
	}

	priv->context_w.refresh = REFRESH_ITEMS;
	priv->context_p = gicHeadGetContext(info->handle, info->head,
					    priv->context_w.current);

	initial_control(info);
	initial_feature(info);
	initial_binding(info);

	draw_contexts(info);
	draw_controls(info);
	draw_features(info, NULL);

	(*info->make_sound) (info, CONFMGR_SOUND_NEW_ITEM);
	(*info->flush) (info);
}

static void move_control(confmgr_info * info, int amount)
{
	ManagerPriv *priv = (ManagerPriv *) info->manager_priv;

	if (priv->control_w.total == 0) {
		return;
	}

	if (move_in_window(&priv->control_w, amount) <= 0) {
		return;
	}

	priv->control_w.refresh = REFRESH_ITEMS;
	priv->control_p =
	    gicContextGetControl(info->handle, priv->context_p,
				 priv->control_w.current);

	initial_feature(info);
	initial_binding(info);

	draw_controls(info);
	draw_features(info, NULL);

	(*info->make_sound) (info, CONFMGR_SOUND_NEW_ITEM);
	(*info->flush) (info);
}

static void move_feature(confmgr_info * info, int amount)
{
	ManagerPriv *priv = (ManagerPriv *) info->manager_priv;

	if (priv->feature_w.total == 0) {
		return;
	}

	if ((priv->feature_w.current + amount) < 0) {
		move_section(info, amount);
		return;
	}

	if (move_in_window(&priv->feature_w, amount) <= 0) {
		return;
	}

	priv->feature_w.refresh = REFRESH_ITEMS;
	priv->feature_p =
	    gicControlGetFeature(info->handle, priv->control_p,
				 priv->feature_w.current);

	/* Make sure that binding_w.current is still valid:
	 */
	priv->binding_w.total =
	    1 + gicFeatureNumRecognizers(info->handle, priv->feature_p);
	move_in_window(&priv->binding_w, 0);

	draw_features(info, NULL);

	(*info->make_sound) (info, CONFMGR_SOUND_NEW_ITEM);
	(*info->flush) (info);
}

static void move_binding(confmgr_info * info, int amount)
{
	ManagerPriv *priv = (ManagerPriv *) info->manager_priv;

	switch (move_in_window(&priv->binding_w, amount)) {

	case 0:
		return;

	case 1:
		priv->feature_w.refresh = REFRESH_CUR_BIND;
		break;

	default:
		priv->feature_w.refresh = REFRESH_SECTION;
		break;
	}

	draw_features(info, NULL);

	(*info->make_sound) (info, CONFMGR_SOUND_NEW_ITEM);
	(*info->flush) (info);
}


/* ---------------------------------------------------------------------- */


static void delete_binding(confmgr_info * info)
{
	ManagerPriv *priv = (ManagerPriv *) info->manager_priv;

	gic_recognizer *rec;

	if (priv->feature_w.total == 0) {
		(*info->make_sound) (info, CONFMGR_SOUND_INVALID);
		return;
	}

	rec = gicFeatureGetRecognizer(info->handle, priv->feature_p,
				      priv->binding_w.current);

	if (rec == NULL) {
		/* don't try to delete the space */
		(*info->make_sound) (info, CONFMGR_SOUND_INVALID);
		return;
	}

	gicFeatureDetachRecognizer(info->handle, priv->feature_p, rec);

	(*info->make_sound) (info, CONFMGR_SOUND_DELETED);

	initial_binding(info);

	priv->feature_w.refresh = REFRESH_SECTION;

	draw_features(info, NULL);
	(*info->flush) (info);
}


/* ---------------------------------------------------------------------- */


static void do_training(confmgr_info * info)
{
	ManagerPriv *priv = (ManagerPriv *) info->manager_priv;

	gic_recognizer *trainresult = NULL;
	gic_recognizer *rec;

	if (priv->binding_w.total == 0) {
		(*info->make_sound) (info, CONFMGR_SOUND_INVALID);
		return;
	}

	rec = gicFeatureGetRecognizer(info->handle, priv->feature_p,
				      priv->binding_w.current);

	info->draw_text(info, CONFMGR_STYLE_HEADING_HIGHLIGHT,
			CONFMGR_FONT_BIG,
			info->big_size.x * 16, priv->feature_w.y,
			"TRAINING MODE");

	(*info->make_sound) (info, CONFMGR_SOUND_TRAIN_START);
	(*info->flush) (info);

	gicRecognizerTrainStart(info->handle, &trainresult);

	priv->do_cancel = ACTINFO_NONE;

	while (!priv->do_cancel) {

		gii_event ev;

		(*info->read_event) (info, &ev, NULL);

		if (gicContextHandleEvent
		    (info->handle, priv->training, &ev) != 0) {
			continue;
		}

		if (gicRecognizerTrain(info->handle, &trainresult, &ev) <=
		    0) {
			continue;
		}

		if (trainresult) {
			priv->train_rec = trainresult;
			priv->feature_w.refresh = REFRESH_CUR_BIND;

			draw_features(info, NULL);
			(*info->flush) (info);
		}
	}

	priv->train_rec = NULL;

	if (trainresult) {
		/* Ideally we would _replace_ the recognizer... */
		if (rec != NULL) {
			gicFeatureDetachRecognizer(info->handle,
						   priv->feature_p, rec);
		}
		gicFeatureAttachRecognizer(info->handle, priv->feature_p,
					   trainresult);
		trainresult = trainresult->next;
	}

	gicRecognizerTrainStop(info->handle, &trainresult);

	initial_binding(info);

	priv->feature_w.refresh = REFRESH_SECTION;
	draw_features(info, NULL);

	(*info->make_sound) (info, CONFMGR_SOUND_TRAIN_STOP);
	(*info->flush) (info);

	flush_input(info);
}


/* ---------------------------------------------------------------------- */


static void do_testing(confmgr_info * info)
{
	ManagerPriv *priv = (ManagerPriv *) info->manager_priv;

	int i;

	gic_state *states;
	gic_feature *cur;

	if (priv->feature_w.total == 0) {
		(*info->make_sound) (info, CONFMGR_SOUND_INVALID);
		return;
	}

	states = malloc(sizeof(int) * priv->feature_w.total);

	if (states == NULL) {
		(*info->make_sound) (info, CONFMGR_SOUND_INVALID);
		return;
	}

	for (i = 0; i < priv->feature_w.total; i++) {
		states[i] = GIC_STATE_MIN;
	}

	priv->do_cancel = ACTINFO_NONE;

	priv->feature_w.refresh = REFRESH_SECTION;
	draw_features(info, states);

	info->draw_text(info, CONFMGR_STYLE_HEADING_HIGHLIGHT,
			CONFMGR_FONT_BIG,
			info->big_size.x * 16, priv->feature_w.y,
			"TESTING MODE");

	(*info->flush) (info);
	(*info->make_sound) (info, CONFMGR_SOUND_TEST_START);

	while (!priv->do_cancel) {

		gii_event ev;

		(*info->read_event) (info, &ev, NULL);

		if (gicContextHandleEvent
		    (info->handle, priv->training, &ev) != 0) {
			continue;
		}

		for (i = 0; i < priv->feature_w.total; i++) {

			gic_state tested;

			cur =
			    gicControlGetFeature(info->handle,
						 priv->control_p, i);

			tested = test_feature(info, cur, &ev);

			if (tested != GIC_NOACTION) {
				states[i] = tested;
			}
		}

		priv->feature_w.refresh = REFRESH_BINDINGS;
		draw_features(info, states);
		(*info->flush) (info);
	}

	free(states);

	initial_binding(info);

	priv->feature_w.refresh = REFRESH_SECTION;
	draw_features(info, NULL);

	(*info->make_sound) (info, CONFMGR_SOUND_TEST_STOP);
	(*info->flush) (info);

	flush_input(info);
}


/* ---------------------------------------------------------------------- */


static void setup_action(gic_actionlist * cur, const char *name, int *data)
{
	cur->next = NULL;
	cur->name = name;
	cur->action = &CM_action;
	cur->privdata = data;

	*data = ACTINFO_NONE;
}

static void setup_all_actions(confmgr_info * info)
{
	ManagerPriv *priv = (ManagerPriv *) info->manager_priv;

	gic_actionlist actionmap[12];

	setup_action(actionmap + 0, "quit", &priv->do_quit);
	setup_action(actionmap + 1, "train", &priv->do_train);
	setup_action(actionmap + 4, "test", &priv->do_test);
	setup_action(actionmap + 2, "cancel", &priv->do_cancel);
	setup_action(actionmap + 3, "delete", &priv->do_delete);

	setup_action(actionmap + 5, "move_up", &priv->move_up);
	setup_action(actionmap + 6, "move_down", &priv->move_down);
	setup_action(actionmap + 7, "move_left", &priv->move_left);
	setup_action(actionmap + 8, "move_right", &priv->move_right);
	setup_action(actionmap + 9, "next_section", &priv->next_section);
	setup_action(actionmap + 10, "prev_section", &priv->prev_section);

	actionmap[11].name = NULL;	/* terminate list */

	gicHeadMapActions(info->handle, priv->head, actionmap);
}

static int setup_confmgr_config(confmgr_info * info, const char *filename)
{
	ManagerPriv *priv = (ManagerPriv *) info->manager_priv;

	FILE *fp;

	/* Read in config file */

	fp = fopen(filename, "r");

	if (fp == NULL) {
		return CONFMGR_ENOFILE;
	}

	priv->head = gicHeadRead(info->handle, fp);

	fclose(fp);

	if (priv->head == NULL) {
		return CONFMGR_EBADFILE;
	}

	/* Map names to actions */

	setup_all_actions(info);

	/* Lookup all the bits we need */

	priv->browsing =
	    gicHeadLookupContext(info->handle, priv->head, "Browsing");
	priv->training =
	    gicHeadLookupContext(info->handle, priv->head, "Training");
	priv->testing =
	    gicHeadLookupContext(info->handle, priv->head, "Testing");

	if ((priv->browsing == NULL) || (priv->training == NULL) ||
	    (priv->testing == NULL)) {
		/* gicWriteDefaultConfig() ??? */
		return CONFMGR_EBADFILE;
	}

	return 0;
}

static int setup_test_feature(confmgr_info * info)
{
	ManagerPriv *priv = (ManagerPriv *) info->manager_priv;

	priv->test_feature =
	    gicFeatureAllocate(info->handle, "Test Feature", "test");

	if (priv->test_feature == NULL) {
		return CONFMGR_ENOMEM;
	}

	gicFeatureAttachAction(info->handle, priv->test_feature,
			       &TEST_action, info, "test-action");
	return 0;
}


/* ---------------------------------------------------------------------- */


int gicConfigManager(confmgr_info * info)
{
	ManagerPriv *priv;

	int err;


	/* Allocate private stuff */

	priv = info->manager_priv = malloc(sizeof(ManagerPriv));

	if (priv == NULL) {
		return CONFMGR_ENOMEM;
	}

	memset(priv, 0, sizeof(ManagerPriv));

	priv->train_rec = NULL;

	/* The configmanager also uses GIC... */

	if ((err = setup_confmgr_config(info, "configmanager.gic")) < 0) {
		free(priv);
		return err;
	}

	if ((err = setup_test_feature(info)) < 0) {
		free(priv);
		return err;
	}

	/* Draw initial screen */

	(*info->draw_box) (info, CONFMGR_STYLE_BACKGROUND,
			   0, 0, info->screen_size.x, info->screen_size.y);

	initial_context(info);
	initial_control(info);
	initial_feature(info);
	initial_binding(info);

	draw_contexts(info);
	draw_controls(info);
	draw_features(info, NULL);

	(*info->make_sound) (info, CONFMGR_SOUND_START);
	(*info->flush) (info);

	flush_input(info);

	while (!priv->do_quit) {

		gii_event ev;

		(*info->read_event) (info, &ev, NULL);

		if (gicContextHandleEvent
		    (info->handle, priv->browsing, &ev) == 0) {
			continue;
		}

		if ((priv->next_section) || (priv->prev_section)) {

			int amount = (priv->next_section ? +1 : 0) +
			    (priv->prev_section ? -1 : 0);

			move_section(info, amount);

			priv->next_section = priv->prev_section =
			    ACTINFO_NONE;
		}

		if (priv->move_left || priv->move_right) {

			int amount = (priv->move_left ? -1 : 0) +
			    (priv->move_right ? +1 : 0);

			switch (priv->section) {

			case CM_CONTEXT:
				move_context(info, amount);
				break;

			case CM_CONTROL:
				move_control(info, amount);
				break;

			case CM_FEATURE:
				move_binding(info, amount);
				break;
			}

			priv->move_left = priv->move_right = ACTINFO_NONE;
		}

		if (priv->move_up || priv->move_down) {

			int amount = (priv->move_up ? -1 : 0) +
			    (priv->move_down ? +1 : 0);

			switch (priv->section) {

			case CM_CONTEXT:
			case CM_CONTROL:
				move_section(info, amount);
				break;

			case CM_FEATURE:
				move_feature(info, amount);
				break;
			}

			priv->move_up = priv->move_down = ACTINFO_NONE;
		}

		if (priv->do_train) {

			do_training(info);
			priv->do_train = ACTINFO_NONE;
		}

		if (priv->do_test) {

			do_testing(info);
			priv->do_test = ACTINFO_NONE;
		}

		if (priv->do_delete) {

			delete_binding(info);
			priv->do_delete = ACTINFO_NONE;
		}
	}

	/* All done... */

	(*info->make_sound) (info, CONFMGR_SOUND_STOP);

	free(priv);

	/* !!! Have a nice exit menu to determine the return code */
	return CONFMGR_SAVE;
}
