/* $Id: main.c,v 1.6 2005/09/19 21:41:56 cegger Exp $
******************************************************************************

   The Snazzy 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 <math.h>

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

#include "snazzy.h"

#include <ggi/internal/gg_replace.h>	/* for snprintf() */


#define SNAZ0  "Snaz Man"		/* len = 08 */
#define SNAZ1  "Snazzy Manager"		/* len = 14 */
#define SNAZ2  "Snazzy Config Manager"  /* len = 22 */


static unsigned char *smooth_tex = NULL;
static unsigned char *rough_tex  = NULL;
static unsigned char *ggi_tex    = NULL;
static unsigned char *big_font   = NULL;
static unsigned char *small_font = NULL;


static ggi_mode mode;
static ggi_visual_t vis;

static ggi_color palette[256];
static ggi_pixel lookup [256];

static confmgr_border T;  /* Title    */
static confmgr_border F;  /* Filename */
static confmgr_border M;  /* Middle   */


#define BLACK       0
#define BLUE        1
#define GREEN       2
#define CYAN        3
#define RED         4
#define PURPLE      5
#define YELLOW      6
#define WHITE       7

#define DK_GREY     8
#define BR_BLUE     9
#define BR_GREEN    10
#define BR_CYAN     11
#define BR_RED      12
#define BR_PURPLE   13
#define BR_YELLOW   14
#define BR_WHITE    15

#define BLUE_IMG     20  /* to  59 inclusive */
#define BROWN_IMG    60  /* to  99 inclusive */
#define PURPLE_IMG  100  /* to 139 inclusive */
#define BAR_IMG     140  /* to 179 inclusive */
#define UNUSED_IMG  180  /* to 219 inclusive */
#define TITLE_IMG   220  /* to 247 inclusive */


static int my_read_event(confmgr_info *info, gii_event *event, 
			 struct timeval *timeout)
{
	if (ggiEventPoll(vis, emAll, timeout)) {
		ggiEventRead(vis, event, emAll);
		return 1;
	}

	return 0;
}

static void my_draw_box(confmgr_info *info,
                        confmgr_style style,
			int x, int y, int w, int h)
{
	x += M.left;
	y += M.top;

	switch (style) {

	    case CONFMGR_STYLE_SECTION_BACKGROUND:
	    case CONFMGR_STYLE_TEST_BACKGROUND:
		draw_image(vis, x, y, w, h,
			   ggi_tex, x, y, 256, 256, lookup + BLUE_IMG + 0);
		break;

	    case CONFMGR_STYLE_SECTION_HIGHLIGHT:
		draw_image(vis, x, y, w, h,
			   ggi_tex, x, y, 256, 256, lookup + BLUE_IMG + 8);
		break;

	    case CONFMGR_STYLE_HEADING_HIGHLIGHT:
		draw_image(vis, x, y, w, h,
			   ggi_tex, x, y, 256, 256, lookup + BLUE_IMG + 8);
		break;

	    case CONFMGR_STYLE_ITEM_TEXT:
		draw_image(vis, x, y, w, h,
			   smooth_tex, x, y, 128, 128, lookup + BROWN_IMG + 0);
		break;

	    case CONFMGR_STYLE_ITEM_CURRENT:
		draw_image(vis, x, y, w, h,
			   smooth_tex, x, y, 128, 128, lookup + BROWN_IMG + 13);
		break;

	    case CONFMGR_STYLE_ITEM_HIGHLIGHT:
		draw_image(vis, x, y, w, h,
			   smooth_tex, x, y, 128, 128, lookup + BROWN_IMG + 13);
		break;

	    case CONFMGR_STYLE_BINDING_TEXT:
		draw_image(vis, x, y, w, h,
			   smooth_tex, x, y, 128, 128, lookup + PURPLE_IMG + 0);
		break;

	    case CONFMGR_STYLE_BINDING_CURRENT:
		draw_image(vis, x, y, w, h,
			   smooth_tex, x, y, 128, 128, lookup + PURPLE_IMG + 13);
		break;

	    case CONFMGR_STYLE_BINDING_HIGHLIGHT:
		draw_image(vis, x, y, w, h,
			   smooth_tex, x, y, 128, 128, lookup + PURPLE_IMG + 13);
		break;

	    case CONFMGR_STYLE_BACKGROUND:
    
	    default:
	        ggiSetGCForeground(vis, lookup[BLACK]);
		ggiDrawBox(vis, x, y, w, h);
		break;
	}
}

static void my_draw_text(confmgr_info *info,
                         confmgr_style style,
                         confmgr_font font,
			 int x, int y, const char *text)
{
	ggi_pixel colors[2];

	unsigned char *font_tex = small_font;

	int font_w = 14, font_h = 20, font_step = 0;
	
	
	colors[0] = lookup[BLACK];
	colors[1] = lookup[WHITE];

	if (font == CONFMGR_FONT_BIG) {
			font_tex = big_font;
			font_w = 17;
			font_h = 26;
			font_step = 1;
	}
	
	switch (style) {

	    case CONFMGR_STYLE_HEADING_TEXT:
	    case CONFMGR_STYLE_HEADING_HIGHLIGHT:

		colors[1] = lookup[BR_YELLOW];
		font_step = 1;
		break;

	    case CONFMGR_STYLE_ITEM_CURRENT:
		/* ... */
		break;

	    case CONFMGR_STYLE_ITEM_HIGHLIGHT:
		colors[1] = lookup[BR_WHITE];
		break;

	    case CONFMGR_STYLE_BINDING_TEXT:
		/* ... */
		break;

	    case CONFMGR_STYLE_BINDING_CURRENT:
	    case CONFMGR_STYLE_BINDING_HIGHLIGHT:
		colors[1] = lookup[BR_WHITE];
		break;

	    default:
	        break;
	}
	
	my_draw_box(info, style, x, y,
		    (int)((font_w+font_step) * strlen(text)), 
		    font_h);

	x += M.left;
	y += M.top;

	draw_string(vis, x, y, (const unsigned char *) text, 
		    font_tex, font_w, font_h, font_step, colors);
}

static void my_draw_bar(confmgr_info *info,
			gic_state state,
			int x, int y, int w, int h)
{
	float frac;
	int part;

	x += M.left;
	y += M.top;

	if (state == GIC_NOACTION) {
		state = GIC_STATE_MIN;
	}

	frac = (float) (state - GIC_STATE_MIN) /
	       (float) (GIC_STATE_MAX - GIC_STATE_MIN);
	
	part = (int) (frac * (float) w);
	
	draw_image(vis, x, y, w, h, rough_tex, x, y, 128, 128, 
		   lookup + BAR_IMG + 0);

	if (part > 0) {
		draw_image(vis, x, y, part, h, rough_tex, x, y, 128, 128, 
			   lookup + BAR_IMG + 13);
	}
}

static void my_flush(confmgr_info *info)
{
	ggiFlush(vis);
}

static void my_make_sound(confmgr_info *info, confmgr_sound sound)
{
	switch (sound) {

	    /* ... */

	    default:
	    	/* silence */
		break;
	}
}


/* Config Manager Info.
 *
 * This is by default optimised for 640x480 mode (the minimum mode that
 * the snazzy manager can run in).  Larger modes will be detected and
 * compensated for later on.
 */
 
static confmgr_info my_info =
{
	NULL,    /* handle: filled in later */

	NULL,    /* head: filled in later */

	NULL, NULL,  /* appl_priv & manager_priv */

	{  0,  0 },    /* screen size: filled in later */
	{ 15, 20 },    /* small char size */
	{ 18, 26 },    /* big char size   */

	11,          /* max context length */
	11,          /* max control length */
	10,          /* max feature length */
	 8,          /* max binding length */
	
	{ 16, 16, },    /* section gap */
	{  6,  8, },    /* item gap    */
	{  4,  8, },    /* binding gap */

	{ 4, 4, 4, 4 },    /* section border */
	{ 4, 4, 4, 4 },    /* menu border    */
	{ 4, 4, 4, 4 },    /* message border */

	{ 8, 8, 0, 0 },    /* heading box */
	{ 1, 1, 0, 0 },    /* item box    */
	{ 1, 1, 0, 0 },    /* binding box */

	CONFMGR_FLAG_HIGHLIGHT_SECTION,	  /* flags */

	&my_read_event,    /* callback API */
	&my_draw_box,
	&my_draw_text,
	&my_draw_bar,
	&my_flush,
	&my_make_sound
};


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


static void set_one_color(int i, int r, int g, int b)
{
	palette[i].r = r;
	palette[i].g = g;
	palette[i].b = b;
}

static void setup_colors(void)
{
	int i;

	set_one_color(BLACK,     0x0000, 0x0000, 0x0000);
	set_one_color(BLUE,      0x0000, 0x0000, 0xbbbb);
	set_one_color(CYAN,      0x0000, 0xbbbb, 0xbbbb);
	set_one_color(GREEN,     0x0000, 0xbbbb, 0x0000);
	set_one_color(RED,       0xbbbb, 0x0000, 0x0000);
	set_one_color(PURPLE,    0xbbbb, 0x0000, 0xbbbb);
	set_one_color(YELLOW,    0xbbbb, 0xbbbb, 0x0000);
	set_one_color(WHITE,     0xbbbb, 0xbbbb, 0xbbbb);

	set_one_color(DK_GREY,   0x5aaa, 0x5aaa, 0x5aaa);
	set_one_color(BR_BLUE,   0x0000, 0x0000, 0xffff);
	set_one_color(BR_CYAN,   0x0000, 0xffff, 0xffff);
	set_one_color(BR_GREEN,  0x0000, 0xffff, 0x0000);
	set_one_color(BR_RED,    0xffff, 0x0000, 0x0000);
	set_one_color(BR_PURPLE, 0xffff, 0x0000, 0xffff);
	set_one_color(BR_YELLOW, 0xffff, 0xffff, 0x0000);
	set_one_color(BR_WHITE,  0xffff, 0xffff, 0xffff);

	for (i=16; i < 256; i++) {
		set_one_color(i, 0, 0, 0);
	}
	
	for (i=0; i < 40; i++) {

		int k1 = 0xffff * i / 39;
		int k2 = 0xaaaa * i / 39;
		int k3 = (int) ((float) 0xfffe * pow((float) i / 39.0, 1.5));

		set_one_color(BLUE_IMG+i,    0,  0, k1);
		set_one_color(BROWN_IMG+i,  k1, k2,  0);
		set_one_color(PURPLE_IMG+i, k1,  0, k1);
		set_one_color(BAR_IMG+i,    k3, k3, k3);
	}

	palette[TITLE_IMG] = palette[BLACK];

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

		int k1 = 0xffff * i / 26;
		int k2 = 0x7fff * i / 26 + 0x8000;

		set_one_color(TITLE_IMG+i+1, k1, k2, k2);
	}

	if (GT_SCHEME(mode.graphtype) == GT_PALETTE) {
		
		if (GT_DEPTH(mode.graphtype >= 8)) {

			ggiSetPalette(vis, GGI_PALETTE_DONTCARE, 256, palette);
		} else {
			ggiSetColorfulPalette(vis);
		}
	}

	for (i=0; i < 256; i++) {
		lookup[i] = ggiMapColor(vis, palette+i);
	}
}

static void setup_textures(void)
{
	smooth_tex = decode_image(coded_smoothtex, 128, 128);
	rough_tex  = decode_image(coded_roughtex,  128, 128);
	ggi_tex    = decode_image(coded_ggitex,    256, 256);
	big_font   = decode_image(coded_bigfont,   272, 312);
	small_font = decode_image(coded_smallfont, 224, 240);
}

static void setup_title(void)
{
	const char *title;
	float y_0, y_1, y_2;
	float f_0, f_1, f_2;
	int x_scale, y_scale;
	int max_right = mode.virt.x / 10;

	/* select the title that takes up the most room */

	y_0 = (float) mode.virt.y / (float) ((strlen(SNAZ0)+1) * 17);
	y_1 = (float) mode.virt.y / (float) ((strlen(SNAZ1)+1) * 17);
	y_2 = (float) mode.virt.y / (float) ((strlen(SNAZ2)+1) * 17);

	f_0 = y_0 - (float) (int) y_0;
	f_1 = y_1 - (float) (int) y_1;
	f_2 = y_2 - (float) (int) y_2;

	title = SNAZ0;
	if ((y_1 >= 1.0) && (f_1 < f_0)) title = SNAZ1;
	if ((y_2 >= 1.0) && (f_2 < f_0) && (f_2 < f_1)) title = SNAZ2;

	/* calculate scales */

	y_scale = mode.virt.y / (strlen(title)+1) / 17;

	if (y_scale == 0) {
		title = SNAZ0;
		y_scale = mode.virt.y / (strlen(title)+1) / 17;
	}

	if (y_scale == 0) {
		y_scale = 1;
	}

	x_scale = max_right / 30;

	if (x_scale == 0) {
		x_scale = 1;
	}
	
	T.right  = mode.virt.x;
	T.left   = T.right - MAX(x_scale*30, max_right);
	T.top    = 0;
	T.bottom = mode.virt.y;
	
	draw_image(vis, T.left, T.top, T.right-T.left, T.bottom-T.top,
		   ggi_tex, T.left, T.top, 256, 256,
		   lookup + BROWN_IMG + 0);

	draw_snazzy_string(vis, (T.left+T.right)/2 - x_scale*12,
			   (int)((T.top + T.bottom - strlen(title)*17*y_scale)/2), 
			   x_scale, y_scale, (const unsigned char *) title, 
			   big_font, 17, 26, smooth_tex, 128, 128, 
			   lookup + TITLE_IMG + 0);
}

static void setup_filename(const char *filename)
{
	char buf[512];

	ggi_pixel filecols[2];

	snprintf(buf, 512, "Config file: %s", filename);

	filecols[0] = lookup[BLACK];
	filecols[1] = lookup[WHITE];
	
	F.left   = 0;
	F.right  = T.left;
	F.top    = 0;
	F.bottom = MAX(30, mode.virt.y / 12);

	draw_image(vis, F.left, F.top, F.right-F.left, F.bottom-F.top,
		   ggi_tex, F.left, F.top, 256, 256,
		   lookup + BROWN_IMG + 0);

	ggiSetGCClipping(vis, F.left, F.top, F.right, F.bottom);

	draw_string(vis, F.left+12, (F.top+F.bottom)/2-14, 
		    (const unsigned char *) buf, small_font, 14, 20, 1, 
		    filecols);
}

static void setup_info(void)
{
	/* fill in confmgr_info */

	M.left   = 12;
	M.right  = T.left - 12;
	M.top    = F.bottom + 10;
	M.bottom = mode.virt.y - 10;
	
	my_info.screen_size.x = M.right  - M.left;
	my_info.screen_size.y = M.bottom - M.top;
	
	ggiSetGCClipping(vis, M.left, M.top, M.right, M.bottom);

	/* Choose wider boxes when in larger modes */

	if (mode.virt.x > 640) {
	
		int diff = mode.virt.x - 640;
		
		my_info.context_max += diff / 100;
		my_info.control_max += diff / 100;
		my_info.feature_max += diff / 150;
		my_info.binding_max += diff / 150;

		my_info.item_gap.x    += diff / 80;
		my_info.binding_gap.x += diff / 80;
	}

	if (mode.virt.y > 480) {
	
		int diff = mode.virt.x - 480;
		
		my_info.item_gap.y    += diff / 80;
		my_info.binding_gap.y += diff / 80;
	}
}

int main(int argc, char *argv[])
{
	FILE *fp;

	int result;

	const char *filename = "configmanager.gic";
	
	gic_handle_t handle;

	
	/* handle args */

	if (argc > 1) {
		filename = argv[1];
	}
	
	/* initialize */

	if (gicInit() < 0) {
		ggiPanic("Couldn't init GIC\n");
	}

	handle = gicOpen(NULL);
	if (handle == NULL) {
		ggiPanic("Couldn't open default recognizers\n");
	}

	my_info.handle=handle;
	if (ggiInit() < 0) {
		ggiPanic("Couldn't init GGI\n");
	}

	/* read in config file */

	fp = fopen(filename, "r");

	if (fp == NULL) {
		ggiPanic("Couldn't open config file.\n");
	}
	
	my_info.head = gicHeadRead(handle,fp);
	fclose(fp);
	
	if (my_info.head == NULL) {
		ggiPanic("Error reading config file.\n");
	}
	
	/* now open a LibGGI visual */

	vis = ggiOpen(NULL);
	
	ggiSetFlags(vis, GGIFLAG_ASYNC);

	ggiParseMode("", &mode);

	if (ggiSetMode(vis, &mode) != 0) {
		ggiPanic("Unable to set mode.\n");
	}
	
	gicInputRegister(handle, ggiJoinInputs(vis,NULL));

	setup_colors();
	setup_textures();
	setup_title();
	setup_filename(filename);
	setup_info();

	ggiFlush(vis);

	/* invoke the config manager */

	result = gicConfigManager(&my_info);

	if (result < 0) {
		fprintf(stderr, "ERROR (%d) from config manager.\n", result);
	}

	/* save the GIC file */

	if (result == CONFMGR_SAVE) {

		fp = fopen(filename, "w");

		if (fp == NULL) {
			ggiPanic("Couldn't write to config file.\n");
		}

		gicHeadWrite(handle, my_info.head, fp);
		fclose(fp);
	}
	
	gicClose(handle);
	gicExit();
	ggiClose(vis);
	ggiExit();

	return 0;
}
