/*
 * dml - Dialog Markup Language
 *
 * $Id: ml.c,v 1.10 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 <stdlib.h>
#include <string.h>

/* actual placement logic */

struct hidden_menu {
	struct hidden_menu *next;
	struct pi_menu *menu;
	const char *name;
};

struct parser_state {
	struct phash *tag_args;
	int tag;
	char *inside;

	struct pi_window *win;
	int x, y, max_w;
	int req_width, req_height;

	char *active;

	struct hidden_menu *hidden_menus;
};

static void save_hidden_menu(struct parser_state *ctx, struct pi_menu *m)
{
	struct hidden_menu *h = xmalloc(sizeof(*h));
	
	h->menu = m;
	h->name = strdup(pi_get_string(ctx->tag_args, "id", "undef"));
	
	h->next = ctx->hidden_menus;
	ctx->hidden_menus = h;
}

char *pi_get_string(struct phash *tag_args, const char *name, char *def)
{
	struct pi_tag_arg *a;
	
	if (tag_args && 
	    (a = ph_find(tag_args, name)) && 
	    a->val)
	    	return a->val;
	return def;
}

int pi_get_int(struct phash *tag_args, const char *name, int def)
{
	struct pi_tag_arg *a;
	
	if (tag_args && 
	    (a = ph_find(tag_args, name)) && 
	    a->val &&
	    atoi(a->val))
		return atoi(a->val);
	return def;
}

#define MARGIN 2

static void link_id_info(struct parser_state *ctx, struct pi_object *o)
{
	struct pi_id_info *p, *n;

	p = xmalloc(sizeof(struct pi_id_info));
	p->name = strdup(o ? pi_get_string(ctx->tag_args, "id", "undef") : 
		"text_res");
	
	if (o && ctx->active && p->name && strcmp(p->name, ctx->active) == 0)
		ctx->win->active = o;
		
	if (o == NULL) {
		o = pi_new_text_res(0);
		o->parent = (struct pi_object*)ctx->win;
	}
	p->obj = o;
	p->next = 0;

	if (ctx->win->id_info == 0)
		ctx->win->id_info = p;
	else 
		for (n = ctx->win->id_info; ; n = n->next)
			if (n->next == 0) {
				n->next = p;
				break;
			}
}

static void link_side_button(struct parser_state *ctx, struct pi_button *f,
			     struct pi_input *input)
{
	pi_setstr(&f->caption,
		  pi_get_string(ctx->tag_args, "label", "_..."));
	f->pos.w = pi_strlen_alt(f->caption) + 4;
	f->pos.h = 1;
	f->pos.x = input->pos.x + input->pos.w - f->pos.w;
	f->pos.y = input->pos.y;
	input->pos.w -= f->pos.w + 1;

	pi_add_child((struct pi_object*)ctx->win, (struct pi_object*)f);
}

static void link_menubutton(struct parser_state *ctx, struct pi_input *o)
{
	char *menu_id, *title;
	struct pi_menubutton *mb;
	struct hidden_menu *h;

	menu_id = pi_get_string(ctx->tag_args, "menu", 0);
	if (menu_id == NULL)
		return;
	
	mb = pi_new_menubutton(0);
	mb->input = o;

	for (h = ctx->hidden_menus; h; h = h->next)
		if (strcmp(h->name, menu_id) == 0)
			break;
	if (h)
		mb->menu = h->menu;
		
	title = pi_get_string(ctx->tag_args, "title", 0);
	if (title)
		pi_setstr(&mb->title, title);
	link_side_button(ctx, &mb->super, o);
}
		
static void link_fsbutton(struct parser_state *ctx, struct pi_input *o)
{
	char *fs, *title;
	struct pi_fsbutton *f;
	int flags;

	fs = pi_get_string(ctx->tag_args, "fs", 0);
	if (fs == 0)
		return;

	flags = 0;
	if (strchr(fs, 'b'))
		flags |= PI_FSF_ALLOW_BROKEN;
	if (strchr(fs, 'd'))
		flags |= PI_FSF_ALLOW_DIR;
	if (strchr(fs, 'r'))
		flags |= PI_FSF_ALLOW_REG;
	if (strchr(fs, 'v'))
		flags |= PI_FSF_ALLOW_DEV;
	if (strchr(fs, 'o'))
		flags |= PI_FSF_ALLOW_OTHER;
	if (strchr(fs, 'a'))
		flags |= PI_FSF_ALLOW_ALL;

	if (flags == 0)
		return;
		
	f = pi_new_fsbutton(0);
	f->flags = flags;
	f->input = o;

	title = pi_get_string(ctx->tag_args, "title", 0);
	if (title)
		pi_setstr(&f->title, title);
	link_side_button(ctx, &f->super, o);
}

static void link_token(struct parser_state *ctx)
{
	int w, multiline = 0;
	char *caption = 0;
	struct pi_object *o = 0;
	
	w = 0;
	caption = pi_get_string(ctx->tag_args, "caption", 
				pi_get_string(ctx->tag_args, "text", 0));
				
	switch (ctx->tag) {
	case pi_tag_check:
		w = 3;
		break;
	case pi_tag_input:
		if (caption == 0)
			caption = "";
		w = pi_get_int(ctx->tag_args, "width", 30);
		if (pi_get_string(ctx->tag_args, "fs", 0))
			w += pi_strlen_alt(pi_get_string(
				ctx->tag_args, "label", "_...")) + 5;
		if (pi_get_string(ctx->tag_args, "menu", 0))
			w += pi_strlen_alt(pi_get_string(
				ctx->tag_args, "label", "_...")) + 5;
		break;
	case pi_tag_button:
		if (caption == 0)
			caption = "_Ok";
		w = pi_get_int(ctx->tag_args, "width", 
				pi_strlen_alt(caption) + 4);
		break;
	case pi_tag_label:
		caption = ctx->inside;
		if (caption == 0)
			caption = "";
		w = strlen(caption);
		break;
	case pi_tag_meta:
		if (pi_get_string(ctx->tag_args, "title", 0))
			pi_setstr(&ctx->win->title, 
				  pi_get_string(ctx->tag_args, "title", 0));
		if (pi_get_int(ctx->tag_args, "res", 0))
			ctx->win->modal_result = 
				pi_get_int(ctx->tag_args, "res", 0);
		if (pi_get_int(ctx->tag_args, "width", 0))
			ctx->req_width = 
				pi_get_int(ctx->tag_args, "width", 0);
		if (pi_get_int(ctx->tag_args, "height", 0))
			ctx->req_height = 
				pi_get_int(ctx->tag_args, "height", 0);
		if (pi_get_string(ctx->tag_args, "active", 0))
			pi_setstr(&ctx->active,
				  pi_get_string(ctx->tag_args, "active", 0));
		return;
	case pi_tag_br:
		ctx->y++;
		ctx->x = MARGIN;
		return;
	case pi_tag_menu:
	case pi_tag_text:
		multiline = 1;
		w = pi_get_int(ctx->tag_args, "width", 40);
		caption = ctx->inside;
		if (caption == 0)
			caption = "";
		if (!(ctx->tag == pi_tag_menu &&
		      pi_get_int(ctx->tag_args, "hidden", 0)) &&
		    ctx->x != MARGIN) {
			ctx->y++;
			ctx->x = MARGIN;
		}
		break;
	}

	if (w == 0)
		return;
		
	switch (ctx->tag) {
	case pi_tag_label:
		/* FIXME: optimize for "      " labels, not to be added */
		o = (struct pi_object*)pi_new_label(0);
		pi_setstr(&((struct pi_label*)o)->caption, caption);
		break;
	case pi_tag_button:
		o = (struct pi_object*)pi_new_button(0);
		((struct pi_button*)o)->cc = pi_get_int(ctx->tag_args, 
							"res", 1);
		((struct pi_button*)o)->text_res = 
				strdup(pi_get_string(ctx->tag_args, 
							"text_res", "none"));
		pi_setstr(&((struct pi_button*)o)->caption, caption);
		link_id_info(ctx, o);
		break;
	case pi_tag_check:
		o = (struct pi_object*)pi_new_checkbox(0);
		((struct pi_checkbox*)o)->checked = 
			pi_get_int(ctx->tag_args, "checked", 0);
		link_id_info(ctx, o);
		break;
	case pi_tag_input:
		o = (struct pi_object*)pi_new_input(0);
		pi_input_set_caption((struct pi_input*)o, caption);
		((struct pi_input*)o)->pass = 
			pi_get_int(ctx->tag_args, "pass", 0);
		((struct pi_input*)o)->res =
		        pi_get_int(ctx->tag_args, "res", 0);
		link_id_info(ctx, o);
		break;
	case pi_tag_menu:
		o = (struct pi_object*)pi_new_menu(0);
		pi_menu_set_caption((struct pi_menu*)o, caption);
		pi_menu_set_default_size((struct pi_menu*)o);
		w = o->pos.w = pi_get_int(ctx->tag_args, "width", o->pos.w);
		o->pos.h = pi_get_int(ctx->tag_args, "height", o->pos.h);
		((struct pi_menu*)o)->cc = pi_get_int(ctx->tag_args, "res", 0);
		if ((caption = pi_get_string(ctx->tag_args, "selected", 0)))
			pi_menu_set_hl((struct pi_menu*)o, caption);
		if ((caption = pi_get_string(ctx->tag_args, "checked", 0)))
			pi_menu_set_checked((struct pi_menu*)o, caption);
		if (pi_get_int(ctx->tag_args, "hidden", 0)) {
			save_hidden_menu(ctx, (struct pi_menu*)o);
			return;	/* Don't add it to window. */
		} else
			link_id_info(ctx, o);
		break;
	case pi_tag_text:
		o = (struct pi_object*)pi_new_textarea(0);
		o->pos.w = w;
		o->pos.h = pi_get_int(ctx->tag_args, "height", 8);
		pi_textarea_set_caption((struct pi_textarea*)o, caption);
		break;
	}

	if (w + ctx->x + MARGIN > ctx->max_w)
		ctx->max_w = w + ctx->x + MARGIN;
		
	o->pos.x = ctx->x;
	o->pos.y = ctx->y;
	o->pos.w = w;
	
	if (o->pos.h == 0)
		o->pos.h = 1;

	if (pi_get_int(ctx->tag_args, "ghosted", 0))
		o->ghosted = 1;

	pi_add_child((struct pi_object*)ctx->win, o);
	
	if (multiline) {
		ctx->x = MARGIN;
		ctx->y += o->pos.h;
	} else 
		ctx->x += w;

	if (ctx->tag == pi_tag_input) {
		link_fsbutton(ctx, (struct pi_input*)o);
		link_menubutton(ctx, (struct pi_input*)o);
	}
}

struct phash *pi_make_tag_args(char *p)
{
	struct phash *h;
	struct pi_tag_arg *a;
	char *e;

	h = ph_new();

	while (p && *p) {
		e = strchr(p, pi_tag_arg);
		if (e)
			*e++ = 0;
		a = xmalloc(sizeof(struct pi_tag_arg));
		a->name = p;
		a->val = 0;
		if (e) {
			p = e;
			e = strchr(p, pi_tag_arg);
			if (e)
				*e++ = 0;
			a->val = p;
		}
		ph_add(h, a);
		p = e;
	}

	return h;
}

struct pi_window *pi_make_ml_window(const char *ml)
{
	char *f, *p, *e;
	struct parser_state ctx;
	struct dbuf *cap;
	char skip_tags[] = { pi_tag_b, pi_tag_i, pi_tag_item, 0 },
	     end_tags[] = { pi_tag_menu, pi_tag_text, 0 }/*,
	     norm_tags[] = { pi_tag_input, pi_tag_button, pi_tag_meta, 
	     			pi_tag_br, 0 }*/;

	memset(&ctx, 0, sizeof(ctx));
	p = f = pi_parse_ml(ml, PI_TAG_ALL);
	ctx.win = pi_new_window(0);
	ctx.y = 1;
	ctx.x = MARGIN;
	ctx.max_w = 8;

	while (*p) {
		if (*p == pi_tag_start) {
			p++;
			if (strchr(skip_tags, *p)) {
				p = strchr(p, pi_tag_end) + 1;
				continue;
			}
			ctx.tag = *p++;
			e = strchr(p, pi_tag_end);
			*e++ = 0;
			if (*p)
				ctx.tag_args = pi_make_tag_args(p + 1);
			p = e;
			if (strchr(end_tags, ctx.tag)) {
				cap = db_new();
				while (*p)
					if (*p == pi_tag_start &&
					    p[1] == ctx.tag + 1)
					    	break;
					else
						db_addc(cap, *p++);
				db_addc(cap, 0);
				ctx.inside = db_finish(cap, 0);
			}
		} else {
			cap = db_new();
			while (*p && *p != pi_tag_start)
				db_addc(cap, *p++);
			db_addc(cap, 0);
			ctx.inside = db_finish(cap, 0);
			ctx.tag = pi_tag_label;
		}

		link_token(&ctx);
		
		if (ctx.tag_args) {
			ph_std_kill(ctx.tag_args);
			ctx.tag_args = 0;
		}
		if (ctx.inside) {
			xfree(ctx.inside);
			ctx.inside = 0;
		}
	}
	
	link_id_info(&ctx, NULL);
	ctx.win->pos.w = ctx.req_width ? ctx.req_width : ctx.max_w;
	ctx.win->pos.h = ctx.req_height ? ctx.req_height : ctx.y + 2;
	if (ctx.win->pos.w > pi_screen_width - 2)
		ctx.win->pos.w = pi_screen_width - 2;
	if (ctx.win->pos.h > pi_screen_height - 2)
		ctx.win->pos.h = pi_screen_height - 2;

	pi_center(ctx.win);

	xfree(f);
	
	return ctx.win;
}
