/**********************************************************************
 
	Copyright (C) 2003-2004
	Hirohisa MORI <joshua@nichibun.ac.jp>
	Tomoki SEKIYAMA <sekiyama@yahoo.co.jp>
 
	This program is free software; you can redistribute it 
	and/or modify it under the terms of the GLOBALBASE 
	Library General Public License (G-LGPL) as published by 

	http://www.globalbase.org/
 
	This program is distributed in the hope that it will be 
	useful, but WITHOUT ANY WARRANTY; without even the 
	implied warranty of MERCHANTABILITY or FITNESS FOR A 
	PARTICULAR PURPOSE.

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



#include <string.h>

#include "v/VWindow.h"
#include "v/VMenu.h"
#include "v/vobj_utils.h"

extern "C" {
#include "xl.h"
#include "xlerror.h"
#include "lock_level.h"
#include "memory_debug.h"

const int vobj_menu_func_hash_size = 128;
struct vobj_MenuFuncHash
{
	int		id;
	L_CHAR	*func;
	VWindow	*win;
	vobj_MenuFuncHash	*next;
} *vobj_menu_func_hash[vobj_menu_func_hash_size];
SEM vobj_menu_func_lock;

void vobj_init_menu_entry();
bool vobj_remove_menu_entry(int id, VWindow *win);
void vobj_add_menu_entry(int id, VWindow *win, L_CHAR *function);
const L_CHAR * vobj_search_menu_entry(int id, VWindow *win);
V_CALLBACK_D(vobj_menu_choosed);
short str_2_vmt(char *str);
short str_2_vmf_sub(char *str);
short str_2_vmf(char *str);
VMenuItem * sexp_2_menu_item(XLISP_ENV *env, XL_SEXP* item, XL_SEXP** sub);
VMenuItem * sexp2menus(XLISP_ENV *env, XL_SEXP *menus);
XL_SEXP * vobj_VMenuBar(XLISP_ENV *env, XL_SEXP *arg, XLISP_ENV *a, XL_SYM_FIELD *sf);
XL_SEXP * vobj_SetMenuStatus(XLISP_ENV *env, XL_SEXP *arg, XLISP_ENV *a, XL_SYM_FIELD *sf);


void
vobj_init_menu_entry()
{
	vobj_menu_func_lock = new_lock(LL_VOBJECT);
	for ( int i = 0 ; i < vobj_menu_func_hash_size ; i++ )
		vobj_menu_func_hash[i] = 0;
}

bool
vobj_remove_menu_entry(int id, VWindow *win)
{
	bool ret = false;
	unsigned int key = (unsigned int)id % vobj_menu_func_hash_size;
	lock_task(vobj_menu_func_lock);
	vobj_MenuFuncHash *node, *prev = 0, *prev0;
	for ( node = vobj_menu_func_hash[key] ; node ; prev = node, node = node->next ) {
		if ( node->id == id && node->win == win ) {
			prev0 = prev;
			break;
		}
	}
	if ( node ) {
		if ( prev0 )
			prev0->next = node->next;
		else
			vobj_menu_func_hash[key] = node->next;
		d_f_ree(node->func);
		delete node;
		ret = true;
	}
	unlock_task(vobj_menu_func_lock, "vobj_search_menu_entry");
	return ret;
}

void
vobj_add_menu_entry(int id, VWindow *win, L_CHAR *function)
{
	vobj_remove_menu_entry(id, win); // eliminate duplicatation
	unsigned int key = (unsigned int)id % vobj_menu_func_hash_size;
	lock_task(vobj_menu_func_lock);
	vobj_MenuFuncHash *node = new vobj_MenuFuncHash;
	node->id = id;
	node->func = ll_copy_str(function);
	node->win = win;
	node->next = vobj_menu_func_hash[key];
	vobj_menu_func_hash[key] = node;
	unlock_task(vobj_menu_func_lock, "vobj_add_menu_entry");
}

const L_CHAR *
vobj_search_menu_entry(int id, VWindow *win)
{
	L_CHAR *func = 0;
	unsigned int key = (unsigned int)id % vobj_menu_func_hash_size;
	lock_task(vobj_menu_func_lock);
	vobj_MenuFuncHash *node;
	for ( node = vobj_menu_func_hash[key] ; node ; node = node->next ) {
		if ( node->id == id ) {
			if ( func == 0 && node->win == 0 )
				func = node->func;
			if ( node->win == win ) {
				func = node->func;
				break;
			}
		}
	}
	unlock_task(vobj_menu_func_lock, "vobj_search_menu_entry");
	return func;
}


V_CALLBACK_D(vobj_menu_choosed)
{
	VMenuItem *item = (VMenuItem*)user_arg;
	int id = item->get_id();
	VCustomizedMenuBar *bar = 
		dynamic_cast<VCustomizedMenuBar*>(item->get_bar());
	VWindow *win = bar ? bar->get_window() : 0;
	
	const L_CHAR *func = vobj_search_menu_entry(id, win);
	if ( func ) {
		vobj_callback_glue(win, (void*)func, 0);
	}
}


short
str_2_vmt(char *str)
{
#define VMT_CVT(tag) if (strcmp(str,#tag)==0) return VMT_##tag

	VMT_CVT(OTHER);
	VMT_CVT(APPLICATION);
	VMT_CVT(FILE);
	VMT_CVT(EDIT);
	VMT_CVT(VIEW);
	VMT_CVT(WINDOW);
	VMT_CVT(HELP);

	VMT_CVT(APP_START);
	VMT_CVT(SETUP);
	VMT_CVT(ABOUT);
	VMT_CVT(CLOSE);
	VMT_CVT(QUIT);
	VMT_CVT(APP_END);

	VMT_CVT(EDIT_START);
	VMT_CVT(UNDO);
	VMT_CVT(REDO);
	VMT_CVT(CUT);
	VMT_CVT(COPY);
	VMT_CVT(PASTE);
	VMT_CVT(CLEAR);
	VMT_CVT(SEL_ALL);
	VMT_CVT(EDIT_END);

	VMT_CVT(SEPARATOR);
	return 0;
}

short
str_2_vmf_sub(char *str)
{
#define VMF_CVT(tag) if (strcmp(str,#tag)==0) return VMF_##tag
	
	VMF_CVT(ENABLED);
	VMF_CVT(CHECKED);
	VMF_CVT(FLAGGED);

	VMF_CVT(B_DISABLE);
	VMF_CVT(B_HIDE);
	
	return -1;
}

short
str_2_vmf(char *str)
{
	int i, mode;
	short ff, ret = 0;
	char tag[32];
	char *p = str;
	if ( str == 0 )
		return -2;	
	i = 0;
	mode = 0;
	
	for ( p = str ; mode != 2 ; p++ ) {
		switch ( mode ) {
		  case 0:
		  	if ( *p == 0 ) {
		  		mode = 2;
		  		continue;
		  	}
			if ( *p == ' ' )
				continue;
			mode = 1;
			// continue into case 1

		  case 1:
			if ( *p && *p != ',' ) {
				tag[i++] = *p;
				if ( i >= 32 )
					return -1;
				continue;
			}
			tag[i] = 0;
			mode = 0;
			i = 0;
			
			ff = str_2_vmf_sub(tag);
			if ( ff == -1 )
				return -1;
			ret |= ff;
			if ( *p == 0 )
				mode = 2;
			break;
		}
	}
	return ret;
}

VMenuItem *
sexp_2_menu_item(XLISP_ENV *env, XL_SEXP* item, XL_SEXP** sub)
{
	XL_SEXP *dat;
	VMenuItem *ret;
	
	short type;
	short flag;
	char *name;
	LC_WRITING_STYLE *ws;
	short modifier;
	char shortcut_key;
	char access_key;
	L_CHAR *function;
	L_CHAR *symbol = 0;
	
	char *tmp;

	if ( get_type(item) != XLT_PAIR )
		goto type_missmatch;
	dat = car(item);
	if ( get_type(dat) != XLT_STRING )
		goto type_missmatch;
	type = str_2_vmt(n_string(std_cm, dat->string.data));
	
	item = cdr(item);
	if ( get_type(item) != XLT_PAIR )
		goto type_missmatch;
	dat = car(item);
	if ( get_type(dat) != XLT_STRING )
		goto type_missmatch;
	flag = str_2_vmf(n_string(std_cm, dat->string.data));
	
	item = cdr(item);
	if ( get_type(item) != XLT_PAIR )
		goto type_missmatch;
	dat = car(item);
	if ( get_type(dat) != XLT_STRING )
		goto type_missmatch;
	name = n_string(std_cm, dat->string.data);
	
	item = cdr(item);
	if ( get_type(item) != XLT_PAIR )
		goto type_missmatch;
	dat = car(item);
	if ( get_type(dat) == XLT_NULL )
		ws = 0;
	else {
		if ( get_type(dat) != XLT_STRING )
			goto type_missmatch;
		ws = get_ws(dat->string.data);
	}
	
	item = cdr(item);
	if ( get_type(item) != XLT_PAIR )
		goto type_missmatch;
	dat = car(item);
	if ( get_type(dat) == XLT_NULL )
		modifier = 0;
	else {
		if ( get_type(dat) != XLT_STRING )
			goto type_missmatch;
		modifier = v_modkey_by_name(dat->string.data);
	}
	
	item = cdr(item);
	if ( get_type(item) != XLT_PAIR )
		goto type_missmatch;
	dat = car(item);
	if ( get_type(dat) == XLT_NULL )
		shortcut_key = 0;
	else {
		if ( get_type(dat) != XLT_STRING )
			goto type_missmatch;
		tmp = n_string(std_cm, dat->string.data);
		if ( tmp[0] == 0 || tmp[1] ) // not 1 char
			goto type_missmatch;
		shortcut_key = tmp[0];
	}
	item = cdr(item);
	if ( get_type(item) != XLT_PAIR )
		goto type_missmatch;
	dat = car(item);
	if ( get_type(dat) == XLT_NULL )
		access_key = 0;
	else {
		if ( get_type(dat) != XLT_STRING )
			goto type_missmatch;
		tmp = n_string(std_cm, dat->string.data);
		if ( tmp[0] == 0 || tmp[1] ) // not 1 char
			goto type_missmatch;
		access_key = tmp[0];
	}

	item = cdr(item);
	if ( get_type(item) != XLT_PAIR )
		goto type_missmatch;
	dat = car(item);
	if ( get_type(dat) == XLT_NULL )
		function = 0;
	else {
		if ( get_type(dat) != XLT_STRING )
			goto type_missmatch;
		function = dat->string.data;
	}
	
	item = cdr(item);
	if ( get_type(item) != XLT_PAIR )
		goto type_missmatch;
	dat = car(item);
	if ( get_type(dat) == XLT_NULL )
		symbol = 0;
	else {
		if ( get_type(dat) != XLT_STRING )
			goto type_missmatch;
		symbol = dat->symbol.data;
	}

	item = cdr(item);
	if ( get_type(item) != XLT_PAIR )
		goto type_missmatch;
	*sub = car(item);
	
	ret = new VMenuItem(type,flag,name,ws,
		modifier,shortcut_key,access_key,function?vobj_menu_choosed:0);
	
	if ( function )
		vobj_add_menu_entry(ret->get_id(), 0, function);
	
	if ( symbol )
		set_env(env, symbol, get_integer(ret->get_id(),0));

	return ret;
	
type_missmatch:
	return 0;
}

VMenuItem *
sexp2menus(XLISP_ENV *env, XL_SEXP *menus)
{
	XL_SEXP *list, *sub;
	VMenuItem *ret = 0, *menu;
	gc_push(0,0,"sexp2menus");

	for ( ; get_type(menus) == XLT_PAIR ; menus = cdr(menus) ) {
		list = car(menus);
		menu = sexp_2_menu_item(env, list, &sub);
		if ( menu == 0 )
			goto type_missmatch;
		if ( get_type(sub) != XLT_NULL ) {
			VMenuItem *submenu = sexp2menus(env, sub);
			if ( submenu )
				menu->set_submenu(submenu);
			else
				goto type_missmatch;
		}
		
		if ( ret )
			ret->append_to_list(menu);
		else
			ret = menu;
	}
	if ( get_type(menus) != XLT_NULL )
		goto type_missmatch;

	gc_pop(0,0);
	return ret;
	
type_missmatch:
	gc_pop(0,0);
	if ( ret )
		delete ret;
	return 0;
}


XL_SEXP *
vobj_VMenuBar(XLISP_ENV *env, XL_SEXP *arg, XLISP_ENV *a, XL_SYM_FIELD *sf)
{
	XL_SEXP *category, *base, *menus;
	VMenuItem *items;
	category = get_el(arg, 1);
	if ( get_type(category) != XLT_INTEGER )
		goto type_missmatch;
	base = get_el(arg, 2);
	if ( get_type(base) != XLT_INTEGER )
		goto type_missmatch;
	menus = get_el(arg, 3);
	if ( get_type(menus) != XLT_PAIR )
		goto type_missmatch;
	
	items = sexp2menus(env, menus);
	
	if ( items == 0 )
		goto type_missmatch;
		
	new VMenuBar(items, category->integer.data, base->integer.data);
	
	return 0;

type_missmatch:
	return get_error(
		arg->h.file,
		arg->h.line,
		XLE_SEMANTICS_TYPE_MISSMATCH,
		l_string(std_cm,"VMenuBar"),
		n_get_string("type missmatch"));
}

XL_SEXP *
vobj_SetMenuStatus(XLISP_ENV *env, XL_SEXP *arg, XLISP_ENV *a, XL_SYM_FIELD *sf)
{
	XL_SEXP *win, *id, *v;
	VObject *obj;
	VWindow *vwin;
	char *f = n_string(std_cm, car(arg)->symbol.data);
	bool exec = true;
	
	if ( get_type(car(arg)) != XLT_SYMBOL )
		er_panic("pe");

	win = get_el(arg, 1);
	if ( get_type(win) != XLT_INTEGER )
		goto type_missmatch;
	obj = VObject::get_object_by_id(win->integer.data);
	if ( obj == 0 )
		return vobj_get_error(initial_VExError(V_ER_NOT_FOUND,0,0), arg);
	vwin = dynamic_cast<VWindow*>(obj);
	if ( vwin == 0 || vwin->get_menu_bar() == 0 )
		goto object_type_missmatch;

	id = get_el(arg, 2);
	if ( get_type(id) != XLT_INTEGER )
		goto type_missmatch;
	
	v = get_el(arg, 3);
	if ( get_type(v) && get_type(v) != XLT_STRING )
		goto type_missmatch;
	
	if ( strcmp("SetMenuFlag", f) == 0 )
		exec = vwin->get_menu_bar()->set_menu_flag(
				id->integer.data,
				get_type(v) ? str_2_vmf(n_string(std_cm, v->string.data)) : 0);
	else if ( strcmp("SetMenuName", f) == 0 )
		exec = vwin->get_menu_bar()->set_menu_name(
				id->integer.data, get_type(v) ? v->string.data : 0);
	else if ( strcmp("SetMenuFunc", f) == 0 ) {
		if ( get_type(v) ) {
			vobj_add_menu_entry(id->integer.data, vwin, v->string.data);
			vwin->get_menu_bar()->set_menu_func(
				id->integer.data, vobj_menu_choosed);
		}
		else {
			vwin->get_menu_bar()->set_menu_func(
				id->integer.data, 0);
			vobj_remove_menu_entry(id->integer.data, vwin);
		}
	}
		
	if ( ! exec )
		return vobj_get_error(initial_VExError(V_ER_PARAM,0,0), arg);
	return 0;

type_missmatch:
	return get_error(
		arg->h.file,
		arg->h.line,
		XLE_SEMANTICS_TYPE_MISSMATCH,
		l_string(std_cm,"SetMenuFlag"),
		n_get_string("type missmatch"));
object_type_missmatch:
	return get_error(
		arg->h.file,
		arg->h.line,
		XLE_PROTO_INV_PARAM,
		l_string(std_cm,"SetMenuFlag"),
		n_get_string("object type missmatch"));
}

void
init_VMenu(XLISP_ENV *env)
{
	vobj_init_menu_entry();
	set_env(env,l_string(std_cm,"VMenuBar"),
		get_func_prim((XL_SEXP*(*)())vobj_VMenuBar,FO_APPLICATIVE,0,4,4));
	set_env(env,l_string(std_cm,"SetMenuFlag"),
		get_func_prim((XL_SEXP*(*)())vobj_SetMenuStatus,FO_APPLICATIVE,0,4,4));
	set_env(env,l_string(std_cm,"SetMenuName"),
		get_func_prim((XL_SEXP*(*)())vobj_SetMenuStatus,FO_APPLICATIVE,0,4,4));
	set_env(env,l_string(std_cm,"SetMenuFunc"),
		get_func_prim((XL_SEXP*(*)())vobj_SetMenuStatus,FO_APPLICATIVE,0,4,4));
}

} // extern "C"
