#include <IV-look/kit.h>
#include <OS/string.h>
#include <InterViews/event.h>
#include <InterViews/action.h>
#include <InterViews/background.h>
#include <InterViews/layout.h>
#include <InterViews/session.h>
#include <InterViews/color.h>
#include <InterViews/border.h>
#include <InterViews/style.h>
#include <InterViews/window.h>
#include <X11/cursorfont.h>
#include <X11/keysym.h>
#include <string.h>
#include "iv_graph.h"
#include "dsp_app.h"
#include "genmenu.h"
#include "cgidbg.h"
#include "plotdatg.h"
#include "mkstr.h"
#include "portable.h"
#include "xk_tab.h"
#include "playback.h"
#include <InterViews/enter-scope.h>
#include <OS/leave-scope.h>
#include <OS/enter-scope.h>
#include <stdlib.h>

// void TestAlloc(const char *msg=0);

class KeyMenu : public ivMenu {
	MenuKeyboard * the_keyboard ;
public:
	KeyMenu(Glyph* g, Style* s, float x1, float y1, float x2, float y2):
		ivMenu(g,s,x1,y1,x2,y2),the_keyboard(0){}
	void keyboard(MenuKeyboard * key){the_keyboard = key;}
	MenuKeyboard * keyboard() const {return the_keyboard;}
	virtual void keystroke(const ivEvent& event) ;
};

static char * find_name(Style *s)
{
	const String name("name");
	String temp ;
	if (!s->find_attribute(name,temp)) DbgError("MenuKeyboard::ctor",
		"no name");
	return Concatenate(temp.string());
}

RecAction::RecAction(MenuKeyboard * key,PullDownEntry* desc):
	the_keyboard(key),
	entry(desc)
{
}

uint32 RecAction::index()
{
	return entry->get_key_code() ;
}

void RecAction::record()
{
	the_keyboard->action_record(index());
}

int RecAction::code_execute(uint32 code, int playback_flag)
{
	if (index() != code) return 0 ;
/*
 *	LogOut << "RecAction::code_execute(" << code << "). index = " <<
 *		index() << "\n" ;
 */
	if (!playback_flag) if (!DspApplication::user_input_allowed())
		return 1 ;
	force_execute();
	// LogOut << "RecAction::code_execute - execution complete\n" ;
	return 1 ;
}

MenuKeyboard::MenuKeyboard(ivWidgetKit& kit, const ivLayoutKit& layout,
	Style *style, WindowMenu * menu, PullDownDescribe&  menu_tree):
	the_top(0),
	DspInputHandler(0,style),
	window_menu(menu),
	mouse_table(0),
	the_next_action(0),
	selected(0)
{
	// LogOut << "MenuKeyboard::ctor\n" ;
	the_name = ManagedKeyboards::add(this);
	the_top = new PullDownEntry(menu_tree);
	// LogOut << "MenuKeyboard::ctor, this = " << (void *) this << "\n" ;
	body(kit.inset_frame(menu->menubar(the_top->submenus(),kit,layout,this)));
	mouse_table = the_top->build_mouse_table() ;
	// LogOut << "mouse_table = " << (void *) mouse_table << "\n" ;
	// LogOut << "mouse_table[0] = " << (void *) mouse_table[0] << "\n" ;
}

MenuKeyboard::~MenuKeyboard()
{
	// clear();
}

void MenuKeyboard::clear()
{
	ManagedKeyboards::manager()->remove(this);
	delete the_top ;
}


void MenuKeyboard::check_release()
{
	// LogOut << "MenuKeyboard::check_release\n" ;
	if (selected) if (window_menu) if (window_menu->menu()) {
		window_menu->menu()->unselect();
	}
	selected = 0 ;
	// LogOut << "MenuKeyboard::check_release exit\n" ;
}

MenuKeyboard * WindowMenu::keyboard()
{
	if (!the_menubar) return 0 ;
	return the_menubar->keyboard();
}

int WindowMenu::exclude_this(PullDownDescribe::Exclude check)
{
	switch (check) {
case PullDownDescribe::none:
		return 0 ;
case PullDownDescribe::eye_plot:
		return is_eye_plot();
default:
		DbgError("WindowMenu::exclude_this","bad type");
		return 0;
	}
}

int MenuKeyboard::object_press(const Event& e)
{
	// LogOut << "MenuKeyboard::object_press\n" ;
	int index = X_Character::mouse_code(e,1);
	// LogOut << "object_press, index = " << index << "\n" ;
	if (index > -1) if (mouse_table[index]) {
		mouse_record(index);
		((RecAction *) mouse_table[index])->execute();
		return 1 ;
	}
	return 0 ;
}

int MenuKeyboard::object_release(const Event& e)
{
	// LogOut << "MenuKeyboard::object_release, this = " << (void *) this << "\n" ;
	int index = X_Character::mouse_code(e);
	// LogOut << "object_release, index = " << index << "\n" ;
/*
 *	if (index > -1) LogOut << "mouse_table[" << index << "] = " <<
 *		(void *) mouse_table[index] << "\n" ;
 *	LogOut << "mouse_table = " << (void *) mouse_table << "\n" ;
 */
	if (index > -1) if(mouse_table[index]) {
		mouse_record(index);
		((RecAction *) mouse_table[index])->execute();
		check_release();
		return 1 ;
	}
	return 0;
}

void MenuKeyboard::press (const Event& e)
{
	window_menu->menu()->press(e);
	
}

void MenuKeyboard::release(const Event& e)
{
	window_menu->menu()->release(e);
}

int MenuKeyboard::do_keystroke(const Event& e)
{
	if (DspApplication::prompt_active()) return 1 ;
	if (DspApplication::user_input_allowed(0))
		if (accept_keystroke(e)) return 1 ;
	return 0 ;
}

void MenuKeyboard::keystroke(const Event& e)
{
	if (DspApplication::prompt_active()) return ;
	if (do_keystroke(e)) return ;
	DspInputHandler::keystroke(e);
}

int MenuKeyboard::do_act(int32 act, const char * select)
{
/*
 *	LogOut << "MenuKeyboard::do_act(" << hex << act <<
 *		dec << ", " << select << ")\n" ;
 */
	window_menu->act_select(select);
	DspApplication::check_processing();
	ManagedKeyboards * manager = ManagedKeyboards::manager();
	manager->clear_action_recorded() ;
	int did = the_top->execute(act,1);
	int rec = manager->is_action_recorded();
	manager->clear_action_recorded();
	window_menu->act_deselect();
	if (!did || !rec) {
		TheLog << "Failed to do action: 0x" << hex <<  act << dec <<
			" for `" << select << "' in MenuKeyboard `" << the_name <<
			"', did = " << did << ", rec = " << rec ;
		ManagedKeyboards::manager()->log_current_line() ; 
		TheLog << ".\n" ;
		did = window_menu->direct_act_failed(act,select);
		rec =  manager->is_action_recorded();
		manager->clear_action_recorded();
		if (!did || !rec) TheLog << "Indirect execution failed, did = " << did
			<< ", rec = " << rec << ".\n" ;
	}
	return did ;
}

#define view_menu_string "View"


void KeyMenu::keystroke(const ivEvent& event)
{
	if (DspApplication::prompt_active()) return ;
	if (the_keyboard) the_keyboard->keystroke(event);
	ivMenu::keystroke(event);
}

KeyMenu* WindowMenu::make_key_menu(WidgetKit& kit)
{
	kit.begin_style("MenuBar", "Menu");
	KeyMenu* m =
		new KeyMenu(kit.menubar_look(), kit.style(), 0.0, 0.0, 0.0, 1.0);
	kit.end_style();
	return m;
}

Menu* WindowMenu::menubar(PullDownEntry** info, WidgetKit& kit,
	const LayoutKit& layout, MenuKeyboard * key)
{
	KeyMenu* m = make_key_menu(kit); // kit.menubar();
	Resource::ref(m);
	if (!the_menubar) the_menubar = m ;
	if (key) the_menubar->keyboard(key);
	for (PullDownEntry** ii = info; *ii; ii++) {
		PullDownEntry* i=*ii ;
		// LogOut << "`" << i->string() << "'\n" ;
		if (exclude_this(i->exclude())) {
			if (!strcmp(i->string(),view_menu_string)) clear_view_allowed();
			continue ;
		}
		MenuItem* mi = kit.menubar_item(kit.fancy_label(i->display_line()));
		if (i->submenus()) mi->menu(pulldown(i->submenus(),  kit, layout));
		i->index(m->count());
		i->menu(m) ;
		m->append_item(mi);
	}
	return m;
}

Menu* WindowMenu::menu() const
{
	return the_menubar ;
}

Menu* WindowMenu::pulldown( PullDownEntry** info, WidgetKit& k,
	const LayoutKit& layout)
{
	if (!info) return  0 ;
	Menu* m = k.pulldown();
	Resource::ref(m);
	for (PullDownEntry** ii = info; *ii; ii++) {
		PullDownEntry* i = *ii;
		// LogOut << "`" << i->string() << "'\n" ;
		if (exclude_this(i->exclude())) continue ;
		if (i->string()[0] == '\0') {
			m->append_item(k.menu_item_separator());
		} else {
			Glyph* g = layout.r_margin( k.fancy_label(i->display_line()),
				0.0, fil, 0.0);
			MenuItem* mi;
			mi = k.menu_item(g);
			if (i->action() == nil && i->submenus()) {
				mi->menu(pulldown(i->submenus(),  k, layout));
			}
			else mi->action(set_action(i));
			i->menu(m) ;
			i->index(m->count());
			m->append_item(mi);
		}
	}
	return m;
}


static PullDownDescribe null_describe ;

PullDownEntry::PullDownEntry():
	pull_down(null_describe),
	key_code(0),
	the_menu(0),
	the_index(0),
	the_submenu(0),
	the_action(0)
{
}

const RecAction ** PullDownEntry::build_mouse_table(RecAction ** table)
{
	// LogOut<< "PullDownEntry::build_mouse_table(" << (void *) table << ")\n" ;
	if (!table) {
		int n ;
		table = new RecAction * [n = X_Character::mouse_table_size()] ;
		for (int i = 0 ; i < n ; i++) table[i] = 0 ;
	}
	if (pull_down.mouse_code) {
		// LogOut << "code = " << pull_down.mouse_code << "\n" ;
		const * index = X_Character::mouse_code_to_index_array(
			pull_down.mouse_code);
		if (!index) DbgError("PullDownEntry::build_mouse_table","no index");
		for (const int * ix = index; *ix > -1; ix++) {
/*
 *			LogOut << "*ix = " << *ix << ", the_action = " <<
 *				(void *) the_action << "\n" ;
 */
			if (!the_action) DbgError("PullDownEntry::build_mouse_table",
				"no action");
			if (table[*ix]) DbgError("PullDownEntry::build_mouse_table",
				"two mouse commands");
			table[*ix] = the_action ;
		}
	}
	if (the_submenu)
		for (PullDownEntry ** entry = the_submenu; *entry; entry++)
			(*entry)->build_mouse_table(table);
	return table ;
}

PullDownEntry::PullDownEntry(PullDownDescribe& desc):
	pull_down(desc),
	key_code(desc.key_code),
	the_menu(0),
	the_index(0),
	the_submenu(0),
	the_action(0)
{
	// LogOut << "PullDownEntry::PullDownEntry\n" ;
	// if (desc.str) LogOut << "`" << desc.str << "'\n" ;
	if (desc.submenu) {
		int count = 0 ;
		for (PullDownDescribe * subitem = desc.submenu; subitem->key_code;
			subitem++,count++);
		the_submenu = new PullDownEntry * [count+1] ;
		int index = 0 ;
		for (subitem = desc.submenu; subitem->key_code;subitem++,index++)
			the_submenu[index]= new PullDownEntry(*subitem);
		the_submenu[index] = 0 ;
	}
}

PullDownEntry::~PullDownEntry()
{
	if (!the_submenu) return ;
	for (PullDownEntry ** entry  = the_submenu; *entry ; entry++)
		delete *entry ;
	delete the_submenu ;
}

const char * PullDownEntry::comment_from_index(int index) const
{
	if (the_action) if (the_action->index() == index) return pull_down.str ;
	const char * ret ;
	if (submenus()) for(PullDownEntry**entry = submenus(); *entry;entry++)
            if (ret = (*entry)->comment_from_index(index)) return ret ;
	return 0 ;
}

int PullDownEntry::execute(int32 index, int playback_flag)
{
	// LogOut << "PullDownEntry::execute(" << index << ")\n" ;
	if (the_action) if (the_action->code_execute(index,playback_flag)) {
		// LogOut << "Did action for `" << pull_down.str << "'.\n" ;
		return 1 ;
	}
	if (submenus()) for(PullDownEntry**entry=submenus(); *entry;entry++)
		if ((*entry)->execute(index,playback_flag)) return 1 ;
	return 0 ;
}


PullDownEntry * PullDownEntry::find_key_code(uint32 key, int rec_call)
{
	PullDownEntry * found = 0 ;
	if (key_code == key) {
/*
 *		LogOut << "PullDownEntry::find_key_code(" << (void *) key <<
 *			"), str is `" << string() << "'.\n" ;
 */
		if (action() || !rec_call) return this ;
		return 0 ;
	}
	if (submenus()) {
		for(PullDownEntry**entry=submenus();*entry;entry++) 
			if ((*entry)->key_code == key) {
				return (*entry)->find_key_code(key,rec_call);
			}

		for(entry=submenus();*entry;entry++) {
			found = (*entry)->find_key_code(key,1);
			if (found) return found ;
		}
	}
	return 0 ;
}

const char * PullDownDescribe::display_line()
{
	const char * mouse_string = X_Character::name_of_mouse_code(mouse_code);
	int mouse_length = 0 ;
	if (mouse_string) mouse_length = strlen(mouse_string);
	const buf_size = 96 ;
	static char buf[buf_size] ;
	static const char * pre = " (" ;
	static const char * post = ") " ;
	const char * code = DspApplication::xk_name_of_char(key_code);
	if (!code) {
		TheLog << "key code 0x" << hex <<  key_code << dec << "\n" ;
		TheLog << str << "\n" ;
		DbgError("PullDownDescribe::display_line","no name");
	}
	if (strlen(pre) + strlen(post) + strlen(str) + strlen(code) +
		mouse_length >= buf_size)
		DbgError("PullDownDescribe::display_line","too big");
	strcpy(buf,str);
	if (mouse_string) strcat(buf,mouse_string);
	strcat(buf,pre);
	strcat(buf,code);
	strcat(buf,post);
	// LogOut << "line is `" << buf << "'\n" ;
	return buf ;
}

ShiftTable X_Character::shift_table ;

ShiftTable::ShiftTable()
{
	for (int i = 0 ; i < shift_table_size; i++)table[i] = '\0' ;
	table['`'] = '~' ;
	table['1'] = '!' ;
	table['2'] = '@' ;
	table['3'] = '#' ;
	table['4'] = '$' ;
	table['5'] = '%' ;
	table['6'] = '^' ;
	table['7'] = '&' ;
	table['8'] = '*' ;
	table['9'] = '(' ;
	table['0'] = ')' ;
	table['-'] = '_' ;
	table['='] = '+' ;
	table['\\'] = '|' ;

	table['['] = '{' ;
	table[']'] = '}' ;
	
	table[';'] = ':' ;
	table['\''] = '"' ;
	
	table[','] = '<' ;
	table['.'] = '>' ;
	table['/'] = '?' ;
	char c = 'a' ;
	for (char C = 'A' ; C <= 'Z'; C++) table[c++] = C ;

	for (i = 0 ; i < shift_table_size; i++)un_table[i] = '\0' ;
	un_table['~'] = '`' ;
	un_table['!'] = '1' ;
	un_table['@'] = '2' ;
	un_table['#'] = '3' ;
	un_table['$'] = '4' ;
	un_table['%'] = '5' ;
	un_table['^'] = '6' ;
	un_table['&'] = '7' ;
	un_table['*'] = '8' ;
	un_table['('] = '9' ;
	un_table[')'] = '0' ;
	un_table['_'] = '-' ;
	un_table['+'] = '=' ;
	un_table['|'] = '\\' ;

	un_table['{'] = '[' ;
	un_table['}'] = ']' ;
	
	un_table[':'] = ';' ;
	un_table['"'] = '\'' ;
	
	un_table['<'] = ',' ;
	un_table['>'] = '.' ;
	un_table['?'] = '/' ;
	C = 'A' ;
	for (c = 'a' ; c <= 'z'; c++) un_table[C++] = c ;
	int error = 0 ;
	for (i = 0 ; i < shift_table_size;i++) {
		if (table[i]) if (table[un_table[table[i]]] != table[i]) {
			cerr << "ShiftTable::ShiftTable bad_table 1 :" << i << "\n" ;
			error = 1;
		}
		if (un_table[i]) if (un_table[table[un_table[i]]] != un_table[i]) {
			cerr << "ShiftTable::ShiftTable bad_table 2 :" << i << "\n" ;
			error = 1 ;
        }
	}
	if (error) exit(1);
}

void MenuKeyboard::clear_selected()
{
	if (window_menu) window_menu->clear_selected();
}

void MenuKeyboard::key_record_act(uint32 key)
{
	PullDownEntry * found = 0;
	if (selected) found = selected->find_key_code(key);
	if (!found) found = the_top->find_key_code(key);
	if (!found) return  ;
	RecAction* act = found->action();
	if (act) act->record();
}

void MenuKeyboard::key_record(uint32 key)
{
	const char * sel = window_menu->key_selected() ;
	if (!sel) sel = "NO SELECTION" ;
	// LogOut << the_name << ":record key " << sel << " " << key << "\n" ;
	DspApplication::record_key(key,sel,the_name);
}

void MenuKeyboard::mouse_record(uint32 push)
{
	const char * sel = window_menu->mouse_selected() ;
	if (!sel) sel = "NO SELECTION" ;
	// LogOut << the_name << ":record mouse " << sel  << " " << push << "\n" ;
	DspApplication::record_mouse(push,sel,the_name);
}

void MenuKeyboard::action_record(uint32 index)
{
	const char * sel = window_menu->key_selected() ;
	if (!sel) sel = "NO SELECTION" ;
	// LogOut << the_name << ":record action " << sel  << " " << index << "\n" ;
	DspApplication::record_action(index, sel, the_name,
		comment_from_index(index));
}

int MenuKeyboard::accept_keystroke(const Event& e)
{
	int control = e.control_is_down();
	int shift = e.shift_is_down();
	unsigned long sym = e.keysym();
	sym &= 0xffffL ;
	uint32 alias = X_Character::table().alias_of((int)sym);
/*
 *	LogOut<<"MenukeyBoard::accept_keystroke 0x" << hex << (int) sym << " (" <<
 *		alias << dec << ")\n" ;
 */
	if (alias) sym = alias ;
	switch(sym) {
case XK_Escape:
case XK_Return:
		check_release();
default:
		uint32 key = (uint32) (sym & 0xffffL) ;
		if (shift) {
			uint32 s_key = DspApplication::shift_table().shift(key);
			if (s_key) key = s_key ;
			else key |= XK_s_ ;
		}
		if (control) key |= XK_c_ ;
		key_record(key);
		PullDownEntry * menu = 0 ;
		if (selected) menu = selected->find_key_code(key);
		if (!menu) menu = the_top->find_key_code(key);
/*
 *		LogOut << "MenuKeyboard::accept_keystroke, key = " <<
 *			(void *) key << ", menu = " << (void *) menu <<  "\n" ;
 */
		if (!menu) return 0 ;
		Action * action = menu->action();
		if (action) {
			action->execute();
			check_release();
			return 1 ;
		}
		if (menu->menu()) {
			menu->menu()->select(menu->index());
			selected = menu ;
			return 1 ;
		}
		return 0 ;
	}
	return 0 ;
}


