/**********************************************************************
 
	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/VMenu.h"
#include "v/VWindow.h"
#include "v/VEditable.h"
extern "C" {
#include "memory_debug.h"
}

int	VMenuItem::cnt = 0;

void
VMenuItem::set_member_vars(
		int id,
		short type,
		short flag,
		char *name,
		LC_WRITING_STYLE *ws,
		short modifier,
		char shortcut_key,
		char access_key,
		V_CALLBACK(func),
		VMenuBar *menu_bar) {
	this->id = id;
	this->name = name ? nl_copy_str(std_cm, name) : 0;
	this->ws = ws;
	this->flag = flag;
	this->type = type;
	this->modifier = modifier;
	this->shortcut_key = shortcut_key;
	this->access_key = access_key;
	this->func = func;
	this->menu_bar = menu_bar;
	this->submenu = 0;
	this->next = 0;
	memset(&info, 0, sizeof(VMenuItemInfo));
	cnt++;
}

VMenuItem::VMenuItem(VMenuItem& menu)
{
	set_member_vars(
		menu.id, menu.type, menu.flag,
		0, menu.ws,
		menu.modifier,
		menu.shortcut_key,
		menu.access_key,
		menu.func, 0);
	name = menu.name ? ll_copy_str(menu.name) : 0;
}


VMenuItem::VMenuItem(VMenuItem& menu, VMenuBar *menu_bar)
{
	set_member_vars(
		menu.id, menu.type, menu.flag,
		0, menu.ws,
		menu.modifier,
		menu.shortcut_key,
		menu.access_key,
		menu.func, menu_bar);
	name = menu.name ? ll_copy_str(menu.name) : 0;
	VMenuItem *last = 0, *m, *item;
	if ( menu.submenu )
		submenu = new VMenuItem(*menu.submenu, menu_bar);
	for ( m = menu.next ; m ; m = m->next ) {
		item = new VMenuItem(*m);
		item->menu_bar = menu_bar;
		if ( last )
			last->next = item;
		else
			next = item;
		last = item;
		if ( m->submenu )
			item->submenu = new VMenuItem(*m->submenu, menu_bar);
	}
}

VMenuItem::~VMenuItem()
{
	if ( name )
		d_f_ree(name);
	if ( next )
		delete next;
	if ( submenu )
		delete submenu;
	cnt--;
}

void
VMenuItem::set_submenu(VMenuItem *menu)
{
	if ( submenu )
		delete submenu;
	submenu = menu;
}

void
VMenuItem::append(VMenuItem *menu, int where)
{
	VMenuItem *prev = 0;
	int cnt = 1;
	if ( where != 1 && submenu ) {
		for ( prev = submenu ; prev->next ; prev = prev->next ) {
			if ( ++cnt == where )
				break;
		}
		menu->next = prev->next;
		prev->next = menu;
	}
	else {
		menu->next = submenu;
		submenu = menu;
	}
}

void
VMenuItem::append_to_list(VMenuItem *menu)
{
	VMenuItem *item;
	for ( item = this ; item->next ; item = item->next ) {}
	item->next = menu;
	menu->next = 0;
}

void
VMenuItem::merge(VMenuItem *from)
{
	int name_match;
	VMenuItem *m;
	append(new VMenuItem(VMT_SEPARATOR,0,0,0,0,0,0,0));
	for ( from = from->submenu ; from ; from = from->next ) {
		if ( from->type != VMT_SEPARATOR ) {
			for ( m = submenu ; m ; m = m->next ) {
				if ( m->type == VMT_SEPARATOR )
					continue;
				name_match = from->name && m->name && l_strcmp(from->name, m->name)==0;
				if ( name_match || m->id == from->id ) {
					if ( from->submenu ) {
						if ( m->submenu )
							m->merge(from);
						else
							m->submenu = new VMenuItem(*from->submenu, from->menu_bar);
					}
					if ( ! name_match ) {
						d_f_ree(m->name);
						m->name = ll_copy_str(from->name);
					}
					m->flag = from->flag;
					m->modifier = from->modifier;
					m->shortcut_key = from->shortcut_key;
					m->access_key = from->access_key;
					m->func = from->func;
					break;
				}
			}
		}
		if ( m == 0 ) {
			append(new VMenuItem(*from, from->menu_bar));
		}
	}
}

void
VMenuItem::remove(VMenuItem *menu)
{
	VMenuItem *prev = 0, *m;
	for ( m = submenu ; m && m != menu ; prev = m, m = m->next ) {}
	if ( m ) {
		if ( prev )
			prev->next = m->next;
		else
			submenu = m->next;
		m->next = 0;
	}
}

void
VMenuItem::remove_from_list(VMenuItem *menu, VMenuItem **from_list)
{
	VMenuItem *prev = 0, *m;
	for ( m = *from_list ; m && m != menu ; prev = m, m = m->next ) {}
	if ( m ) {
		if ( prev )
			prev->next = m->next;
		else
			*from_list = m->next;
	}
	m->next = 0;
}

VMenuBar*	VMenuBar::v_menu_bars = 0;

VMenuBar::VMenuBar(VMenuItem *items, int category, bool base)
	: items(items), category(category), base(base)
{
	next = v_menu_bars;
	v_menu_bars = this;
	
	if ( category == 0 )
		init();
}

VMenuBar::~VMenuBar()
{
	VMenuBar *m, *prev = 0;
	delete items;
	for ( m = v_menu_bars ; m && m != this ; prev = m, m = m->next ) {}
	if ( m == 0 )
		return;	// sub class is not in the list
	if ( prev )
		prev->next = next;
	else
		v_menu_bars = next;
}


VMenuBar *
VMenuBar::get_menu_bar(int category)
{
	for ( VMenuBar *m = v_menu_bars ; m ; m = m->next )
		if ( m->category == category )
			return m;
	return 0;
}

VCustomizedMenuBar *
VCustomizedMenuBar::create(int category, VWindow *window)
{
	VMenuBar *bar = VMenuBar::get_menu_bar(category);
	if ( bar )
		return new VCustomizedMenuBar(bar, window);
	return 0;
}

VCustomizedMenuBar::VCustomizedMenuBar(VMenuBar *bar, VWindow *window)
	: window(window)
{
	this->category = bar->get_category();
	base = bar->is_base();
	make_customized_menu(bar);
	hash = new VMenuItemHash(this);
}

void
VCustomizedMenuBar::update(VMenuItem *menus)
{
	if ( menus == 0 )
		menus = items;
	VEditable *focused = VEditable::get_focused_object();
	for ( ; menus ; menus = menus->next ) {
		short flag = menus->flag;
		if ( focused && 
				menus->type >= VMT_EDIT_START  && menus->type <= VMT_EDIT_END ) {
			if ( focused->command_status(menus->type) )
				flag |= VMF_ENABLED;
			else
				flag &= ~VMF_ENABLED;
		}
		set_menu_flag_do(menus, flag);
		if ( menus->submenu )
			update(menus->submenu);
	}
}

bool
VCustomizedMenuBar::set_menu_func(int id, V_CALLBACK(func))
{
	bool ret = false;
	VMenuItem *item = hash->search(id);
	if ( item ) {
		item->func = func;
		ret = true;
	}
	return ret;
}

bool
VCustomizedMenuBar::get_menu_name(int id, const L_CHAR **name)
{
	bool ret = false;
	VMenuItem *item = hash->search(id);
	if ( item ) {
		*name = item->name;
		ret = true;
	}
	return ret;
}

bool
VCustomizedMenuBar::set_menu_name(int id, const L_CHAR *name)
{
	bool ret = false;
	VMenuItem *item = hash->search(id);
	if ( item ) {
		if ( item->name )
			d_f_ree(item->name);
		item->name = ll_copy_str(const_cast<L_CHAR*>(name));
		set_menu_name_do(item, name);
		ret = true;
	}
	return ret;
}

bool
VCustomizedMenuBar::get_menu_flag(int id, short *flag)
{
	bool ret = false;
	VMenuItem *item = hash->search(id);
	if ( item ) {
		*flag = item->flag;
		ret = true;
	}
	return ret;
}

bool
VCustomizedMenuBar::set_menu_flag(int id, short flag)
{
	bool ret = false;
	VMenuItem *item = hash->search(id);
	if ( item ) {
		item->flag = flag;
		set_menu_flag_do(item, flag);
		ret = true;
	}
	return ret;
}

bool
VCustomizedMenuBar::menu_choosed(VMenuItem *item)
{
	VEditable *focused = VEditable::get_focused_object();
	if ( focused && item->type != VMT_OTHER && item->type != VMT_SEPARATOR )
		if ( focused->obey_command(item->type) )
			return true;
	
	if ( item && item->func ) {
		vq_insert_callback(window, item->func, item, 0, 0);
		return true;
	}
	return false;
}

void
VCustomizedMenuBar::add_flag_hierarchic(VMenuItem *items, short flag)
{
	for ( ; items ; items = items->next ) {
		items->flag |= flag;
		
		if ( items->submenu )
			add_flag_hierarchic(items->submenu, flag);
	}
}

void
VMenuItemHash::add_menu_items(VMenuItem *menu)
{
	VMenuList *list;
	for ( ; menu ; menu = menu->next ) {
		int key = (unsigned)menu->id % v_menu_item_hash_size;
		list = new VMenuList;
		list->item = menu;
		list->next = hash[key];
		hash[key] = list;
		if ( menu->submenu )
			add_menu_items(menu->submenu);
	}
}

VMenuItemHash::VMenuItemHash(VMenuBar *bar)
{
	for ( int i = 0 ; i < v_menu_item_hash_size ; i++ )
		hash[i] = 0;
	add_menu_items(bar->get_items());
}

VMenuItem *
VMenuItemHash::search(int id) const
{
	int key = (unsigned)id % v_menu_item_hash_size;
	for ( VMenuHashNode node = hash[key] ; node ; node = node->next )
		if ( node->item->id == id )
			return node->item;
	return 0;
}

VMenuItemHash::~VMenuItemHash()
{
	VMenuHashNode node, next;
	for ( int i = 0 ; i < v_menu_item_hash_size ; i++ ) {
		for ( node = hash[i] ; node ; node = next ) {
			next = node->next;
			delete node;
		}
	}
}


void
VMenuItem::debug_print(int indent) {
	for ( int i = 0 ; i < indent ; i++ )
		putchar(' ');
	printf("[%x]", flag);
	const char *cname = name ? n_string(std_cm, name) : "-";
	puts(cname);
	if ( submenu )
		submenu->debug_print(indent+strlen(cname));
	if ( next )
		next->debug_print(indent);
}
