#include <string.h>
#include <OS/string.h>
#include <fstream.h>
#include <strstream.h>
#include <stream.h>
#include <unistd.h>
#include <ctype.h>
#include <InterViews/window.h>
#include <InterViews/style.h>
#include "help.h"
#include "helpwin.h"
#include <OS/leave-scope.h>
#include "remcom.h"
#include "genmenu.h"
#include "playback.h"
#include "cgidbg.h"
#include "mkstr.h"
#include "dsp_app.h"
#include "xgui.h"
#include "y.tab.h"

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

void DeferredAct::dump()
{
	TheLog << "Act: " << hex <<  act << dec  ;
	if (window) TheLog << " win `" << window << "'" ;
	if (select) TheLog << " sel `" << select << "'" ;
	if (prompt) TheLog << " prompt `" << prompt << "'" ;
	TheLog << "\n" ;
}


char * IndexedName::new_version()
{
	index++;
    return Concatenate(base,"##",DspApplication::int_string(index));
}

char * IndexedNameList::is_new_name(const char * name)
{
	IndexedNameListIterator next(*this);
	IndexedName * entry ;
	while (entry = next()) if (!strcmp(name,entry->base_name()))
		return entry->new_version();
	append(new IndexedName(name));
	return Concatenate(name) ;
}

WindowOrKeyboardList::WindowOrKeyboardList(const char * name_main_pre,
	const char *name_info):
	main_window_prefix(name_main_pre),
	info_window_name(name_info),
	length(strlen(main_window_prefix)),
	warn_check(0)
{
}

const char * WindowOrKeyboardList::add(WindowOrKeyboard * obj)
{
	Append(obj);
	return obj->name();
}

const char * WindowOrKeyboardList::add(MenuKeyboard *key)
{
	return add(new WindowOrKeyboard(key));
}

const char * WindowOrKeyboardList::add(FocusSelector * win)
{
	return add(new WindowOrKeyboard(win));
}


MenuKeyboard * WindowOrKeyboardList::find_keyboard(const char * name)
{
	WindowOrKeyboard * found = find(name);
	if (!found) return 0 ;
	return found->keyboard();
}

WindowOrKeyboard * WindowOrKeyboardList::find(const char * name)
{
	WindowOrKeyboard * entry ;
	WindowOrKeyboard * first = 0 ;  
	WindowOrKeyboardListIterator next(*this);
	static WindowOrKeyboard * help_win = 0 ;
	
	if (!strcmp(name,info_window_name)) {
		FocusSelector * sel = DspApplication::help_display()->view();
		if (!help_win) help_win = new WindowOrKeyboard(sel);
		else help_win->set(sel);
		return help_win ;
	}
	while (entry = next()) {
		if (!first) first = entry ;
		// LogOut << "checking `" << entry->name() << "'\n" ;
		if (!strcmp(name,entry->name())) return entry ;
	}

	// main window will always be first
	if (!strncmp(name,main_window_prefix,length)) {
		if (!warn_check) {
			if (strcmp(name,first->name())) {
				char * message[2] ;
				ManagedKeyboards * the_managed_keyboards =
					ManagedKeyboards::manager();
				const char * file_name = "NONE" ;
				const char * temp = 0 ;
				if (the_managed_keyboards)
					if (the_managed_keyboards->active_file())
						temp = the_managed_keyboards->active_file()->name();
				if (temp) file_name = temp ;
				char * x = Concatenate("Warning validation file `", file_name,
					"' was created by `");
				message[0] = Concatenate(x, name,"', you are running `",
					first->name(),".");
				delete x ;
				message[1] = 0 ;
				DspApplication::warn_info_window(1.5*4*72.0,72.,message);
				delete message[0];
			}
			warn_check = 1 ;
		}
		return first ;
	}
	return 0 ;
}

IndexedNameList WindowOrKeyboard::the_names ;

WindowOrKeyboard::WindowOrKeyboard(FocusSelector *win):
	the_window(win),
	the_keyboard(0)
{
	make_name();
}

WindowOrKeyboard::WindowOrKeyboard(MenuKeyboard *key):
	the_window(0),
	the_keyboard(key)
{
	make_name();
}

WindowOrKeyboard::~WindowOrKeyboard()
{
	delete the_name ;
}

void WindowOrKeyboard::make_name()
{
	ivStyle * sty = 0 ;
	osString temp ;
	const char * name ;

	if (the_keyboard) sty = the_keyboard->style();
	else if (the_window) {
		ivWindow * window = the_window->window();
		if (!window) DbgError("WindowOrKeyboard::make_name","no window");
		sty = window->style();
	}
	else DbgError("WindowOrKeyboard::make_name","null object");

	if (!sty) DbgError("WindowOrKeyboard::make_name","no style");

	if (sty->find_attribute("name",temp)) name=temp.string();
	else DbgError("WindowOrKeyboard::make_name","no style name");

	the_name = the_names.is_new_name(name);
}

const int ManagedKeyboards::max_menu_history = 5 ;

ManagedKeyboards ManagedKeyboards::the_manager ;

ManagedKeyboards::ManagedKeyboards():
	the_active_file(0),
	abort_flag(0),
  	file_state(0),
	continue_after_error(0),
	last_char('\n'),
	delay(0),
    yywrap_called(0),
	menu_history(new char *[ max_menu_history +1])
{
	for (int i = 0 ; i < max_menu_history+1;i++) menu_history[i] = 0 ;
	deferred_act.init();
	manager();
}

void ManagedKeyboards::add_to_menu_state(char * str)
{
	for (int i = 0 ; i < max_menu_history;i++) if (!menu_history[i]) {
		menu_history[i] = str ;
		return ;
	}
	DbgError("ManagedKeyboards::add_to_menu_state","history too long");
}

void ManagedKeyboards::log_current_line()
{
	if (the_active_file) the_active_file->log_current_line();
}

void ManagedKeyboards::record_menu_state()
{
	if (!menu_history[0]) return ;
	DspApplication::record_menu_state(menu_history);
}

void ManagedKeyboards::start_menu_state()
{
	for (int i = 0 ; i < max_menu_history+1;i++) {
		delete menu_history[i] ;
		menu_history[i] = 0 ;
	}
}


WindowOrKeyboard * ManagedKeyboards::find(const char * name)
{
	return the_managed_keyboards.find(name);
}

FocusSelector * ManagedKeyboards::find_focus(const char * name)
{
	WindowOrKeyboard * obj = find(name);
	if (!obj) return 0 ;
	if (obj->window()) return obj->window();
	MenuKeyboard * board = obj->keyboard();
	if (!board) DbgError("ManagedKeyboards::find_focus","bad object");
	return board ;
}
ivWindow * ManagedKeyboards::find_window(const char * name)
{
	WindowOrKeyboard * obj = find(name);
	if (!obj) return 0 ;
	if (obj->window()) return obj->window()->window();
	MenuKeyboard * board = obj->keyboard();
	if (!board) DbgError("ManagedKeyboards::find","bad object");
	return board->window();
}




WindowOrKeyboardList ManagedKeyboards::the_managed_keyboards(
	DspApplication::main_window_prefix, DspApplication::info_window) ;

ManagedKeyboards * ManagedKeyboards::manager()
{
	return &the_manager ;
}


const char * ManagedKeyboards::add(FocusSelector * win)
{
	return the_managed_keyboards.add(win);
}

const char * ManagedKeyboards::add(MenuKeyboard * key)
{
	return the_managed_keyboards.add(key);
}

void ManagedKeyboards::remove(const char * name)
{
	WindowOrKeyboard * obj = find(name);
	if (!obj) DbgError("ManagedKeyboards::remove","does not exist");
	if (the_managed_keyboards.remove(obj) != OK)
		DbgError("ManagedKeyboards::remove","cannot remove");
}

void ManagedKeyboards::remove(MenuKeyboard *key)
{
	const char * name = key->name();
	remove(name);
}


void ManagedKeyboards::remove(FocusSelector * win)
{
	const char * name = win->name();
	remove(name);
}

extern int yyparse();
extern int yylex();

int ManagedKeyboards::parse_execute()
{
	while(FocusSelector::currently_printing(0))
		while(DspApplication::event_check()) ;
	do_deferred();
	int ret = yyparse() ;
	if (!ret || continue_after_error) do_deferred();
	return ret ;
}

int ManagedKeyboards::read_line()
{
	if (abort_flag) return 0 ;
	// LogOut << "ManagedKeyboards::read_line\n" ;
	if (!the_active_file) return 0 ;
	if (file_state == EOF) return 0 ;
	int ret =  !parse_execute();
	if (abort_flag) return 0 ;
	return ret ;
}

void ManagedKeyboards::abort_play()
{
	if (!the_active_file) {
		CppOut << "Cannot abort playback. Playback is not active.\n" ;
		return ;
	}
	DspApplication::action_read_off();
	abort_flag = 1 ;
	defer_act(-1,0,0); // clear any deferred action
	
	CppOut << "Action file read aborted by user" ;
	the_active_file->error_at();	
	CppOut << "\n" ;

	if (!DspApplication::is_busy()) return ;
	StringList list ;
	list.Append(Concatenate("Action file read aborted."));
	list.Append(Concatenate(
		"The DSP process is still executing a command or reading a state file."));
	list.Append(Concatenate(
        "To abort the DSP process select `abort dsp' from the `state' menu."));
	list.Append(Concatenate(
		"(You may need to select `other' in the `state' menu to see the"));
	list.Append(Concatenate(
		"`abort dsp' option.)") );
	DspApplication::information(list,DspApplication::menu_window());
}

static void pause_callback(const char * name, InputType, void*)
{
    ManagedKeyboards::manager()->pause_call_back(name);
}


void ManagedKeyboards::pause_call_back(const char * str)
{
	if (!str)  return ;
	if (!*str) return ;
	if (*str == '\03') return ;
	istrstream value(str);
	int val = -1 ;
	value >> val ;
	if (val < 0 || val > 30) 
		delay_err_msg = Concatenate("Please specifiy an integer value between 0 and 60. `",
			str, "' is invalid or out of range.\n");
	else delay = val ;
}

void ManagedKeyboards::play_pause()
{
	int first_time = 1 ;
	delay_err_msg = 0 ;
	while (first_time || delay_err_msg) {
		first_time = 0 ;
		StringList lst ;
		if (delay_err_msg) lst.Append(delay_err_msg);
		delay_err_msg = 0 ;
		lst.Append(Concatenate("Specify integer delay in seconds between recorded actions:"));
		DspApplication::prompt(lst, ::pause_callback,"1");
	}
}

void ManagedKeyboards::playback(const char * file_name)
{
	FileListIterator next(the_file_list);
	File * f ;
	int error = 0 ;
	if (the_active_file)
		if (!strcmp(file_name,the_active_file->name())) error = 1 ;
	while (f = next()) if (!strcmp(file_name,f->name())) error = 1 ;
	if (error) {
		StringList list ;
		list.Append(Concatenate("Attempt to play back file"));
		list.Append(Concatenate("`", file_name,"' recursively."));
		list.Append(Concatenate("Second playback of this file aborted."));
		DspApplication::information(list,DspApplication::menu_window(),1);
		return ;
	}
	File * file = new File(file_name);
	if (!file->good()) {
		CppOut << "Cannot open file `" << file_name <<
			"'. Action playback aborted.\n" ;
		delete file ;
		return ;
	}
	file_state = 0 ;

	DspApplication::command_record_state();

	if (the_active_file) the_file_list.append(the_active_file);	
	the_active_file = file ;
	DspApplication::action_read_on();
	the_managed_keyboards.new_file();
	do {
		continue_after_error = 1 ;
		file_state = 0 ;
		while (continue_after_error && file_state != EOF ) {
			parse_execute() ;
			if (abort_flag) break ;
			if (!continue_after_error) {
				parse_execute();
				break ;
			}
/*
 *			if (the_active_file) {
 *				LogOut << "Parsed to line " << the_active_file->line() << "\n" ; ;
 *			}
 */
		}
		// LogOut << "did one file, con = " << continue_after_error << "\n" ;
		delete the_active_file ;
		the_active_file = 0 ;
		if (!continue_after_error || abort_flag) {
			the_file_list.clear();
			break ;
		}
	} while (the_active_file = the_file_list.pop());
	if (!abort_flag && continue_after_error) while(DspApplication::is_busy()) {
		do_deferred();
		while(ReadSeg->CheckPacketRead());
		while (DspApplication::event_check());
		if (abort_flag) break ;
	}
	DspApplication::action_read_off();
	abort_flag = 0 ;
}

void ManagedKeyboards::play_actions()
{
	// LogOut << "ManagedKeyboards::play_actions\n" ;
	const char * file = DspApplication::get_file_name(
		"Select an action file to read:",0,"*.rec");
	if (!file) return ;
	// LogOut << "file is `" << file << "': " << (int) *file << "\n" ;
	if (!*file) return ;
	if (*file == '\03') return ;
	playback(file);
}

static void the_callback(const char * name, InputType, void*)
{
	ManagedKeyboards::manager()->record_actions(name);
}

void ManagedKeyboards::record_actions(const char * file_name)
{
	if (file_name) {
		if (!*file_name) return ;
		if (*file_name == '\03') return ;
		DspApplication::record_on(file_name);
		return ;
	}
	if (DspApplication::record_file_name()) {
		DspApplication::record_on();
		return ;
	}
	DspApplication::prompt("Specify record file name:", the_callback,
		"objpro.rec");
}

void ManagedKeyboards::record_off()
{
	DspApplication::record_off();
}

void ManagedKeyboards::record_flush()
{
	DspApplication::record_close();
}

void ManagedKeyboards::check_wait_for_dsp()
{
	DspApplication::check_wait_for_dsp();
	DspApplication::cond_check_packet_read();
	while (DspApplication::is_busy() || FocusSelector::currently_printing(0)) {
		while (DspApplication::check_packet_read());
		while (DspApplication::event_check());
		if (abort_flag) return ;
	}
}

void ManagedKeyboards::do_act(int32 act, const char * select,
	const char * window)
{
/*
 *	LogOut << "ManagedKeyboards::do_act(0x" << hex << act << dec <<
 *		", " << select << ", " << window << ")\n" ;
 */
 
	DspApplication::check_wait_for_dsp();
	if (!strcmp(select,FocusSelector::generic_action_name)) {
		if (delay) sleep(delay);
		if (abort_flag) return ;
		FocusSelector::generic_window_action(act,window);
		check_wait_for_dsp();
		return ;
	}
	MenuKeyboard * board = 0 ;
	int limit = 100 ;
	while (!board && limit--) {
		while(DspApplication::event_check() || ReadSeg->CheckPacketRead());
		board = the_managed_keyboards.find_keyboard(window);
	}
	if (abort_flag) return ;
	if(board) {
		if (delay) sleep(delay);
		if (abort_flag) return ;
		board->do_act(act,select);
		check_wait_for_dsp();
	}  // else LogOut << "No keyboard for `" << window << "'\n" ; 
}



void ManagedKeyboards::defer_act(int32 act, const char * select,
	const char * window)
{
	deferred_act.prompt = 0 ;
	deferred_act.select = select ;
	deferred_act.window = window ;
	deferred_act.act = act ;
}
void ManagedKeyboards::do_deferred_act()
{
	// LogOut << "ManagedKeyboards::do_deferred_act:\n" ;
	// deferred_act.dump();
	int act = deferred_act.act ;
	if (act < 0) return ;
	const char * window = Concatenate(deferred_act.window);
	const char * select = Concatenate(deferred_act.select);
	delete (char *) deferred_act.window ;
	delete (char *) deferred_act.select ;
	deferred_act.window = deferred_act.select = 0 ;
	deferred_act.act = -1 ;
	// This allows recursive calls from do_act
	if (!abort_flag) do_act(act,select,window);
	delete (char *) select ;
	delete (char *) window ;
}
	

void ManagedKeyboards::action(int32 type, int32 act, const char * select,
	const char * window)
{
/*
 *	LogOut << "ManagedKeyboards::action(" << type << ", " << act <<
 *		", `" << select << ", ' `" << window << "')\n" ;
 */
	switch (type) {
case KEY:
case MOUSE: 
		// KEY and MOUSE are for information only they generate an
		// ACTION of there is anything to do for them.
		delete (char *) window ;
		delete (char *) select ;
		break ;
case ACTION:
		defer_act(act,select,window);
		break ;
default:
		DbgError("ManagedKeyboards::action","bad action");
	}
}

void ManagedKeyboards::menu_text(const char * string)
{
	// LogOut << "ManagedKeyboards::menu_text\n" ;
	// deferred_act.dump();
	deferred_act.act = MENU ;
	deferred_act.prompt = string ;
}

void ManagedKeyboards::dpp_text(const char * string)
{
	// LogOut << "ManagedKeyboards::dpp_text\n" ;
	deferred_act.act = DPP ;
	deferred_act.prompt = string ;
}

void ManagedKeyboards::prompt(const char * string)
{
	// LogOut << "ManagedKeyboards::prompt\n" ;
	// deferred_act.dump();
	// LogOut << "ManagedKeyboards, prompting `" << string << "'.\n" ;
	deferred_act.act = 0 ;
	deferred_act.prompt = string ;
}

void ManagedKeyboards::do_deferred()
{
	// LogOut << "ManagedKeyboards::do_deferred\n" ;
	// deferred_act.dump();
	if (deferred_act.prompt) {
		if (deferred_act.act) do_deferred_text();
		else do_deferred_prompt();
	} else do_deferred_act();
}

void ManagedKeyboards::do_deferred_prompt()
{
	const char * str = deferred_act.prompt ;
	if (!str) return ;
	deferred_act.prompt = 0 ;
	deferred_act.act = -1 ;
	if (!DspApplication::set_prompt_response(str)) 
		yyerror("cannot clear previous prompt response");
	delete (char *) str ;
}

void ManagedKeyboards::do_deferred_text()
{
	int act = deferred_act.act ;
    if (act < 0) return ;
    deferred_act.act = -1 ;
    char * text = Concatenate(deferred_act.prompt);
	for (char * txt = text + strlen(text) -1 ; txt >= text; txt--)
		if (*txt == ' ') *txt = '\0' ; else break ;
	deferred_act.prompt = 0 ;
    delete (char *) deferred_act.window ;
    delete (char *) deferred_act.select ;
    deferred_act.window = deferred_act.select = 0 ;
	const char * scr_text = text ;
	int do_cmd = 1 ;
	if (!*scr_text) scr_text = " ", do_cmd=0 ;
	switch(act) {
case MENU:
		DspApplication::record_menu_text(scr_text);
		DspApplication::menu_scroll(scr_text);
		if (do_cmd) DspApplication::do_menu_command(text);
		break ;
case DPP:
		DspApplication::record_dpp_text(scr_text);
		DspApplication::cpp_scroll(scr_text);
		if (do_cmd) DspApplication::state()->write_cpp_line(text);
		break ;
default:
		DbgError("ManagedKeyboards::do_deferred_text","bad act");
	}
	delete text ;
}
		

void ManagedKeyboards::new_line()
{
	if (the_active_file) the_active_file->new_line();
}

void ManagedKeyboards::illegal(char c)
{
	static char buf[] = "`X'" ;
	buf[1] = c ;
	while ((mygetc() != '\n') && (file_state != EOF)) ;
	new_line();
	// LogOut << "Skipped to end of line\n" ;
	yyerror("Illegal character: " , buf);
}

int ManagedKeyboards::mygetc()
{
	// LogOut << "ManagedKeybaords::mygetc:" ;
	file_state = 0 ;
	int Return = '\n' ; ;
	if (the_active_file) Return = the_active_file->mygetc();
	else file_state = EOF ;
	if (Return == EOF) file_state = EOF ;
	if (file_state == EOF) {
		yywrap_called = 1 ;
		Return = '\n' ;
	}
	last_char = Return ;
	// LogOut <<  Return << " `" << (char) Return << "'.\n" ;
	return Return ;
}

int ManagedKeyboards::yywrap()
{
	// LogOut << "yywrap called\n" ;
	yywrap_called = 1 ;
	return 0 ;
}

void ManagedKeyboards::yyerror(const char* s1,const char* s2,
	const char* s3,const char* s4,const char*s5)
{
	// LogOut << "ManagedKeyboard::yyerror(" << s1 << "," << s2 << "...)\n" ;
	if (!the_active_file) return ;
	the_active_file->yyerror(s1,s2,s3,s4,s5);
}

void ManagedKeyboards::error_callback(const char * name)
{
	continue_after_error = 0 ;
	if (!strcmp(name,"yes")) continue_after_error = 1 ;
	if (!continue_after_error) {
		delete the_active_file ;
		the_active_file = 0 ;
		abort_flag = 1 ; 
	} 
/*
 *	LogOut << "ManagedKeyboards::error_callback(" << name << ") con = " <<
 *		continue_after_error << "\n" ;
 */
}


File::File(const char * name):
	the_name(Concatenate(name)),
	the_line(0),
	the_file(0)
{
	the_file = new ifstream(the_name);
}

File::~File()
{
	delete the_file ;
	the_file = 0 ;
	delete the_name ;
	the_name = 0 ;
}

static void error_callback(const char * name, InputType, void*)
{
	DspApplication::action_read_off();
    ManagedKeyboards::manager()->error_callback(name);
	DspApplication::action_read_on();
}


void File::yyerror(const char* s1,const char* s2,
    const char* s3,const char* s4,const char*s5)
{
	char * msg = Concatenate("Error: ",s1,s2,s3,s4,s5);
	char * msg_2 = Concatenate(msg, " at line ",
		DspApplication::int_string(the_line)," in file `", the_name, "'.\n") ;
	delete msg ; msg = 0 ;
	StringList lst ;
	lst.Append(msg_2);
	lst.Append(Concatenate("Do you want to continue reading the action file?\n"));
	DspApplication::action_read_off();
    DspApplication::prompt(lst, error_callback, "no");
	DspApplication::action_read_on();
}

void File::error_at()
{
	CppOut << " at line " << dec(the_line) << " in file `" <<
		 the_name <<"'"; 
}

void File::log_current_line()
{
	TheLog << " at line " << dec(the_line) << " in file `" << the_name << "'" ;
}

int File::mygetc()
{
	return the_file->get();
}

ErrCode FileList::append(File *nt)
{
	return SingleList::Append(nt);
}

void FileList::clear()
{
	FileListIterator next(*this);
	File * file ;
	while(file = next()) delete file ;
		
}
