#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>

#include "lua/lua.h"
#include "lua/lualib.h"
#include "lua/lauxlib.h"

#include "bbs.h"

#define MENU_SUBMENU 1
#define MENU_LOGOFF 2
#define MENU_PREVMENU 3
#define MENU_AUTOMESSAGE 4
#define MENU_TEXTFILES 5
#define MENU_CHATSYSTEM 6
#define MENU_BBSLIST 7
#define MENU_LISTUSERS 8
#define MENU_BULLETINS 9
#define MENU_LAST10 10
#define MENU_SETTINGS 11
#define MENU_DOOR 12
#define MENU_MAILSCAN 13
#define MENU_READMAIL 14
#define MENU_POSTMESSAGE 15
#define MENU_CHOOSEMAILCONF 16
#define MENU_CHOOSEMAILAREA 17
#define MENU_SENDEMAIL 18
#define MENU_LISTEMAIL 19
#define MENU_NEXTMAILCONF 20
#define MENU_PREVMAILCONF 21
#define MENU_NEXTMAILAREA 22
#define MENU_PREVMAILAREA 23
#define MENU_BLUEWAVEDOWN 24
#define MENU_BLUEWAVEUP 25
#define MENU_CHOOSEFILEDIR 26
#define MENU_CHOOSEFILESUB 27
#define MENU_LISTFILES 28
#define MENU_UPLOAD 29
#define MENU_DOWNLOAD 30
#define MENU_CLEARTAGGEDFILES 31
#define MENU_NEXTFILEDIR 32
#define MENU_PREVFILEDIR 33
#define MENU_NEXTFILESUB 34
#define MENU_PREVFILESUB 35
#define MENU_LISTMESSAGES 36
#define MENU_DOSCRIPT 37
#define MENU_SENDNODEMSG 38
#define MENU_SUBUNSUBCONF 39
#define MENU_RESETPOINTERS 40
#define MENU_RESETALLPOINTERS 41
#define MENU_FILESCAN 42
#define MENU_FULLMAILSCAN 43
#define MENU_FILESEARCH 44
#define MENU_DISPTXTFILE 45
#define MENU_DISPTXTFILEPAUSE 46
#define MENU_GENWWWURLS 47
#define MENU_NLBROWSER 48
#define MENU_SENDFEEDBACK 49
#define MENU_BLOGDISPLAY 50
#define MENU_BLOGWRITE 51

extern struct bbs_config conf;
extern struct user_record *gUser;
extern int mynode;

struct menu_command {
	int command;
	char *data;
};

struct menu_item {
	char hotkey;
	struct ptr_vector commands;
	int seclevel;
};

struct key_value_map {
	const char *key;
	int value;
};

static const struct key_value_map commands[] = {
	{ "SUBMENU", MENU_SUBMENU },
	{ "LOGOFF", MENU_LOGOFF },
	{ "PREVMENU", MENU_PREVMENU },
	{ "AUTOMESSAGE", MENU_AUTOMESSAGE },
	{ "TEXTFILES", MENU_TEXTFILES },
	{ "CHATSYSTEM", MENU_CHATSYSTEM },
	{ "BBSLIST", MENU_BBSLIST },
	{ "LISTUSERS", MENU_LISTUSERS },
	{ "BULLETINS", MENU_BULLETINS },
	{ "LAST10CALLERS", MENU_LAST10 },
	{ "SETTINGS", MENU_SETTINGS },
	{ "RUNDOOR", MENU_DOOR },
	{ "MAILSCAN", MENU_MAILSCAN },
	{ "READMAIL", MENU_READMAIL },
	{ "POSTMESSAGE", MENU_POSTMESSAGE },
	{ "CHOOSEMAILCONF", MENU_CHOOSEMAILCONF },
	{ "CHOOSEMAILAREA", MENU_CHOOSEMAILAREA },
	{ "SENDEMAIL", MENU_SENDEMAIL },
	{ "LISTEMAIL", MENU_LISTEMAIL },
	{ "NEXTMAILCONF", MENU_NEXTMAILCONF },
	{ "PREVMAILCONF", MENU_PREVMAILCONF },
	{ "NEXTMAILAREA", MENU_NEXTMAILAREA },
	{ "PREVMAILAREA", MENU_PREVMAILAREA },
	{ "BLUEWAVEDOWNLOAD", MENU_BLUEWAVEDOWN },
	{ "BLUEWAVEUPLOAD", MENU_BLUEWAVEUP },
	{ "CHOOSEFILEDIR", MENU_CHOOSEFILEDIR },
	{ "CHOOSEFILESUB", MENU_CHOOSEFILESUB },
	{ "LISTFILES", MENU_LISTFILES },
	{ "UPLOAD", MENU_UPLOAD },
	{ "DOWNLOAD", MENU_DOWNLOAD },
	{ "CLEARTAGGED", MENU_CLEARTAGGEDFILES },
	{ "NEXTFILEDIR", MENU_NEXTFILEDIR },
	{ "PREVFILEDIR", MENU_PREVFILEDIR },
	{ "NEXTFILESUB", MENU_NEXTFILESUB },
	{ "PREVFILESUB", MENU_PREVFILESUB },
	{ "LISTMESSAGES", MENU_LISTMESSAGES },
	{ "DOSCRIPT", MENU_DOSCRIPT },
	{ "SENDNODEMSG", MENU_SENDNODEMSG },
	{ "SUBUNSUBCONF", MENU_SUBUNSUBCONF },
	{ "RESETMSGPTRS", MENU_RESETPOINTERS },
	{ "RESETALLMSGPTRS", MENU_RESETALLPOINTERS },
	{ "FILESCAN", MENU_FILESCAN },
	{ "FULLMAILSCAN", MENU_FULLMAILSCAN },
	{ "FILESEARCH", MENU_FILESEARCH },
	{ "DISPLAYTXTFILE", MENU_DISPTXTFILE },
	{ "DISPLAYTXTPAUSE", MENU_DISPTXTFILEPAUSE },
	{ "GENWWWURLS", MENU_GENWWWURLS },
	{ "NLBROWSER", MENU_NLBROWSER },
	{ "SENDFEEDBACK", MENU_SENDFEEDBACK },
	{ "BLOGDISPLAY", MENU_BLOGDISPLAY },
	{ "BLOGWRITE", MENU_BLOGWRITE }
};

#define	ARRAY_SIZE(A) (sizeof(A)/sizeof((A)[0]))

int cmd2cmd(const char *cmd) {
	for (size_t i = 0; i < ARRAY_SIZE(commands); ++i)
		if (strncmp(cmd, commands[i].key, strlen(commands[i].key)) == 0)
			return commands[i].value;
	return -1;
}

static int badmenu(const char *menufile) {
	s_printf("Bad menu file! %s\r\n", menufile);
	return 0;
}

static void free_menu(struct ptr_vector *menu) {
	assert(menu != NULL);
	for (size_t i = 0; i < ptr_vector_len(menu); ++i) {
		struct menu_item *item = ptr_vector_get(menu, i);
		assert(item != NULL);
		for (size_t j = 0; j < ptr_vector_len(&item->commands); ++j) {
			struct menu_command *cmd = ptr_vector_get(&item->commands, j);
			assert(cmd != NULL);
			free(cmd->data);
		}
		ptr_vector_apply(&item->commands, free);
		destroy_ptr_vector(&item->commands);
	}
	ptr_vector_apply(menu, free);
	destroy_ptr_vector(menu);
}

int menu_system(char *menufile) {
	FILE *fptr;
	char buffer[PATH_MAX];
	struct ptr_vector menu = EMPTY_PTR_VECTOR;
	struct menu_item *this_menu = NULL;
	struct menu_command *this_command = NULL;
	char *lua_script = NULL;
	int do_lua_menu = 0;
	char *ansi_file = NULL;
	int i;
	int j;
	int k;
	int m;
	struct stat s;
	char *lRet;
	lua_State *L;
	int result;
	int doquit = 0;
	char c;
	int clearscreen = 0;
	char confirm;
	char *msg;

	dolog("%s is loading menu: %s", gUser->loginname, menufile);
	broadcast("USER: %s; NODE:%d; STATUS: Browsing menu %s.", gUser->loginname, mynode, menufile);

	if (menufile[0] == '/') {
		snprintf(buffer, PATH_MAX, "%s.mnu", menufile);
	} else {
		snprintf(buffer, PATH_MAX, "%s/%s.mnu", conf.menu_path, menufile);
	}
	fptr = fopen(buffer, "r");
	if (!fptr)
		return badmenu(menufile);

	init_ptr_vector(&menu);
	while (fgets(buffer, sizeof buffer, fptr) != NULL && !feof(fptr)) {
		chomp(buffer);

		if (strncasecmp(buffer, "HOTKEY", 6) == 0) {
			this_menu = malloz(sizeof(struct menu_item));
			this_menu->hotkey = buffer[7];
			init_ptr_vector(&this_menu->commands);
			this_menu->seclevel = 0;
			ptr_vector_append(&menu, this_menu);
		} else if (strncasecmp(buffer, "COMMAND", 7) == 0) {
			if (this_menu == NULL)
				return badmenu(menufile);
			int cmd = cmd2cmd(buffer + 8);
			if (cmd < 0)
				continue;
			this_command = malloz(sizeof(struct menu_command));
			this_command->command = cmd;
			this_command->data = NULL;
			ptr_vector_append(&this_menu->commands, this_command);
		} else if (strncasecmp(buffer, "SECLEVEL", 8) == 0) {
			if (this_menu == NULL)
				return badmenu(menufile);
			this_menu->seclevel = atoi(buffer + 9);
		} else if (strncasecmp(buffer, "DATA", 4) == 0) {
			if (this_command == NULL)
				return badmenu(menufile);
			free(this_command->data);
			this_command->data = strdup(buffer + 5);
		} else if (strncasecmp(buffer, "LUASCRIPT", 9) == 0) {
			free(lua_script);
			lua_script = strdup(buffer + 10);
		} else if (strncasecmp(buffer, "ANSIFILE", 8) == 0) {
			free(ansi_file);
			ansi_file = strdup(buffer + 9);
		} else if (strncasecmp(buffer, "CLEARSCREEN", 11) == 0) {
			clearscreen = 1;
		}
	}
	fclose(fptr);

	do_lua_menu = 0;

	if (lua_script != NULL) {
		if (conf.script_path != NULL && lua_script[0] != '/') {
			snprintf(buffer, PATH_MAX, "%s/%s.lua", conf.script_path, lua_script);
			do_lua_menu = 1;
		} else if (lua_script[0] == '/') {
			snprintf(buffer, PATH_MAX, "%s.lua", lua_script);
			do_lua_menu = 1;
		}

		if (do_lua_menu) {
			if (stat(buffer, &s) == 0) {
				L = luaL_newstate();
				luaL_openlibs(L);
				lua_push_cfunctions(L);
				luaL_loadfile(L, buffer);
				do_lua_menu = 1;
				result = lua_pcall(L, 0, 1, 0);
				if (result) {
					dolog("Failed to run script: %s", lua_tostring(L, -1));
					do_lua_menu = 0;
				}
			} else {
				do_lua_menu = 0;
			}
		}
	}

	while (!doquit) {
		if (gUser->nodemsgs) {
			snprintf(buffer, PATH_MAX, "%s/node%d/nodemsg.txt", conf.bbs_path, mynode);

			if (stat(buffer, &s) == 0) {
				fptr = fopen(buffer, "r");
				if (fptr) {
					fgets(buffer, PATH_MAX, fptr);
					while (!feof(fptr)) {
						chomp(buffer);
						s_printf("\r\n%s\r\n", buffer);
						fgets(buffer, PATH_MAX, fptr);
					}
					fclose(fptr);
					snprintf(buffer, PATH_MAX, "%s/node%d/nodemsg.txt", conf.bbs_path, mynode);
					unlink(buffer);

					s_printf(get_string(6));
					c = s_getc();
				}
			}
		}

		if (clearscreen) {
			s_printf("\e[2J\e[1;1H");
		}

		if (do_lua_menu == 0) {
			if (ansi_file != NULL) {
				s_displayansi(ansi_file);
			}
			s_printf(get_string(142), gUser->timeleft);
			c = s_getc();
		} else {
			lua_getglobal(L, "menu");
			result = lua_pcall(L, 0, 1, 0);
			if (result) {
				dolog("Failed to run script: %s", lua_tostring(L, -1));
				do_lua_menu = 0;
				lua_close(L);
				continue;
			}
			lRet = (char *)lua_tostring(L, -1);
			c = lRet[0];
			lua_pop(L, 1);
		}

		for (size_t i = 0; i < ptr_vector_len(&menu); i++) {
			struct menu_item *item = ptr_vector_get(&menu, i);
			if (tolower(item->hotkey) == tolower(c)) {
				if (item->seclevel <= gUser->sec_level) {
					for (size_t j = 0; j < ptr_vector_len(&item->commands); ++j) {
						struct menu_command *cmd = ptr_vector_get(&item->commands, j);
						switch (cmd->command) {
							case MENU_SUBMENU:
								doquit = menu_system(cmd->data);
								if (doquit == 1) {
									// free menus
									free_menu(&menu);
									free(ansi_file);
									if (do_lua_menu)
										lua_close(L);
									free(lua_script);
									return doquit;
								}
								break;
							case MENU_LOGOFF:
								free(ansi_file);
								if (do_lua_menu)
									lua_close(L);
								free(lua_script);
								free_menu(&menu);
								return 1;
							case MENU_PREVMENU:
								if (do_lua_menu)
									lua_close(L);
								free(lua_script);
								free(ansi_file);
								free_menu(&menu);
								return 0;
							case MENU_AUTOMESSAGE:
								broadcast("USER: %s; NODE:%d; STATUS: Viewing/Changing Automessage.", gUser->loginname, mynode);
								automessage();
								break;
							case MENU_TEXTFILES:
								broadcast("USER: %s; NODE:%d; STATUS: Browsing Textfiles.", gUser->loginname, mynode);
								display_textfiles();
								break;
							case MENU_CHATSYSTEM:
								broadcast("USER: %s; NODE:%d; STATUS: In Chat System.", gUser->loginname, mynode);
								chat_system(gUser);
								break;
							case MENU_BBSLIST:
								broadcast("USER: %s; NODE:%d; STATUS: Browsing BBS List.", gUser->loginname, mynode);
								bbs_list(gUser);
								break;
							case MENU_LISTUSERS:
								broadcast("USER: %s; NODE:%d; STATUS: Browsing User List.", gUser->loginname, mynode);
								list_users(gUser);
								break;
							case MENU_BULLETINS:
								broadcast("USER: %s; NODE:%d; STATUS: Reading Bulletins.", gUser->loginname, mynode);
								display_bulletins();
								break;
							case MENU_LAST10:
								broadcast("USER: %s; NODE:%d; STATUS: Viewing Last 10 Callers.", gUser->loginname, mynode);
								display_last10_callers(gUser);
								break;
							case MENU_SETTINGS:
								settings_menu(gUser);
								break;
							case MENU_DOOR: {
								for (m = 0; m < ptr_vector_len(&conf.doors); m++) {
									struct door_config *door = ptr_vector_get(&conf.doors, m);
									if (strcasecmp(cmd->data, door->name) == 0) {
										dolog("%s launched door %s, on node %d", gUser->loginname, door->name, mynode);
										broadcast("USER: %s; NODE:%d; STATUS: Executing Door %s.", gUser->loginname, mynode, door->name);
										rundoor(gUser, door->command, door->stdio, door->codepage);
										dolog("%s returned from door %s, on node %d", gUser->loginname, door->name, mynode);
										break;
									}
								}
							} break;
							case MENU_MAILSCAN:
								broadcast("USER: %s; NODE:%d; STATUS: Performing Mail Scan.", gUser->loginname, mynode);
								mail_scan(gUser);
								break;
							case MENU_READMAIL:
								broadcast("USER: %s; NODE:%d; STATUS: Reading Mail.", gUser->loginname, mynode);
								read_mail(gUser);
								break;
							case MENU_POSTMESSAGE:
								broadcast("USER: %s; NODE:%d; STATUS: Posting a Message.", gUser->loginname, mynode);
								post_message(gUser);
								break;
							case MENU_CHOOSEMAILCONF:
								broadcast("USER: %s; NODE:%d; STATUS: Choosing Mail Conference.", gUser->loginname, mynode);
								choose_conference();
								break;
							case MENU_CHOOSEMAILAREA:
								broadcast("USER: %s; NODE:%d; STATUS: Choosing Mail Area.", gUser->loginname, mynode);
								choose_area();
								break;
							case MENU_SENDEMAIL:
								broadcast("USER: %s; NODE:%d; STATUS: Sending an Email.", gUser->loginname, mynode);
								send_email(gUser);
								break;
							case MENU_LISTEMAIL:
								broadcast("USER: %s; NODE:%d; STATUS: Browsing their Emails.", gUser->loginname, mynode);
								list_emails(gUser);
								break;
							case MENU_NEXTMAILCONF:
								next_mail_conf(gUser);
								break;
							case MENU_PREVMAILCONF:
								prev_mail_conf(gUser);
								break;
							case MENU_NEXTMAILAREA:
								next_mail_area(gUser);
								break;
							case MENU_PREVMAILAREA:
								prev_mail_area(gUser);
								break;
							case MENU_BLUEWAVEDOWN:
								broadcast("USER: %s; NODE:%d; STATUS: Downloading Bluewave Packet.", gUser->loginname, mynode);
								bwave_create_packet();
								break;
							case MENU_BLUEWAVEUP:
								broadcast("USER: %s; NODE:%d; STATUS: Uploading Bluewave Packet.", gUser->loginname, mynode);
								bwave_upload_reply();
								break;
							case MENU_CHOOSEFILEDIR:
								broadcast("USER: %s; NODE:%d; STATUS: Choosing a file directory.", gUser->loginname, mynode);
								choose_directory();
								break;
							case MENU_CHOOSEFILESUB:
								broadcast("USER: %s; NODE:%d; STATUS: Choosing a file sub-directory.", gUser->loginname, mynode);
								choose_subdir();
								break;
							case MENU_LISTFILES:
								broadcast("USER: %s; NODE:%d; STATUS: Browsing Files.", gUser->loginname, mynode);
								list_files(gUser);
								break;
							case MENU_UPLOAD: {
								struct file_directory *dir = ptr_vector_get(&conf.file_directories, gUser->cur_file_dir);
								assert(dir != NULL);
								struct file_sub *sub = ptr_vector_get(&dir->file_subs, gUser->cur_file_sub);
								assert(sub != NULL);
								if (gUser->sec_level >= sub->upload_sec_level) {
									broadcast("USER: %s; NODE:%d; STATUS: Uploading a File.", gUser->loginname, mynode);
									upload(gUser);
								} else {
									s_printf(get_string(84));
								}
								break;
							}
							case MENU_DOWNLOAD:
								broadcast("USER: %s; NODE:%d; STATUS: Downloading Files.", gUser->loginname, mynode);
								download(gUser);
								break;
							case MENU_CLEARTAGGEDFILES:
								clear_tagged_files();
								break;
							case MENU_NEXTFILEDIR:
								next_file_dir(gUser);
								break;
							case MENU_PREVFILEDIR:
								prev_file_dir(gUser);
								break;
							case MENU_NEXTFILESUB:
								next_file_sub(gUser);
								break;
							case MENU_PREVFILESUB:
								prev_file_sub(gUser);
								break;
							case MENU_LISTMESSAGES:
								list_messages(gUser);
								break;
							case MENU_DOSCRIPT:
								broadcast("USER: %s; NODE:%d; STATUS: Executing a script %s.", gUser->loginname, mynode, cmd->data);
								do_lua_script(cmd->data);
								break;
							case MENU_SENDNODEMSG:
								broadcast("USER: %s; NODE:%d; STATUS: Sending a node Message.", gUser->loginname, mynode);
								send_node_msg();
								break;
							case MENU_SUBUNSUBCONF:
								broadcast("USER: %s; NODE:%d; STATUS: Subscribing to conferences.", gUser->loginname, mynode);
								msg_conf_sub_bases();
								break;
							case MENU_RESETPOINTERS:
								s_printf(get_string(229));
								s_readstring(buffer, 10);
								if (tolower(buffer[0]) == 'r') {
									k = -1;
									m = 1;
								} else if (tolower(buffer[0]) == 'u') {
									k = -1;
									m = 0;
								} else if (buffer[0] < '0' || buffer[0] > '9') {
									s_printf(get_string(39));
									break;
								} else {
									k = atoi(buffer) - 1;
								}

								msgbase_reset_pointers(gUser->cur_mail_conf, gUser->cur_mail_area, m, k);

								break;
							case MENU_RESETALLPOINTERS:
								s_printf(get_string(230));
								confirm = s_getc();
								if (confirm == 'r' || confirm == 'R') {
									m = 1;
								} else if (confirm == 'u' || confirm == 'U') {
									m = 0;
								} else {
									s_printf(get_string(39));
									break;
								}
								msgbase_reset_all_pointers(m);
								break;
							case MENU_FILESCAN:
								broadcast("USER: %s; NODE:%d; STATUS: Doing a filescan.", gUser->loginname, mynode);
								file_scan();
								break;
							case MENU_FULLMAILSCAN:
								if (cmd->data != NULL) {
									if (strcasecmp(cmd->data, "PERSONAL") == 0) {
										broadcast("USER: %s; NODE:%d; STATUS: Scanning for personal mail.", gUser->loginname, mynode);
										full_mail_scan_personal(gUser);
									} else {
										broadcast("USER: %s; NODE:%d; STATUS: Scanning all mail.", gUser->loginname, mynode);
										full_mail_scan(gUser);
									}
								} else {
									full_mail_scan(gUser);
								}
								break;
							case MENU_FILESEARCH:
								broadcast("USER: %s; NODE:%d; STATUS: Executing a filesearch.", gUser->loginname, mynode);
								file_search();
								break;
							case MENU_DISPTXTFILE:
								if (cmd->data != NULL) {
									broadcast("USER: %s; NODE:%d; STATUS: Displaying Text File: %s.", gUser->loginname, mynode, cmd->data);
									s_displayansi_pause(cmd->data, 0);
								}
								break;
							case MENU_DISPTXTFILEPAUSE:
								if (cmd->data != NULL) {
									broadcast("USER: %s; NODE:%d; STATUS: Displaying Text File: %s.", gUser->loginname, mynode, cmd->data);
									s_displayansi_pause(cmd->data, 1);
								}
								s_printf(get_string(6));
								s_getc();
								break;
							case MENU_GENWWWURLS:
								genurls();
								break;
							case MENU_NLBROWSER:
								broadcast("USER: %s; NODE:%d; STATUS: Executing a filesearch.", gUser->loginname, mynode);
								nl_browser();
								break;
							case MENU_SENDFEEDBACK:
								if (check_user(conf.sysop_name)) {
									break;
								}
								broadcast("USER: %s; NODE:%d; STATUS: Sending feedback to Sysop.", gUser->loginname, mynode);
								msg = external_editor(gUser, conf.sysop_name, gUser->loginname, NULL, 0, NULL, "Feedback", 1, 0);
								if (msg != NULL) {
									commit_email(conf.sysop_name, "Feedback", msg);
									free(msg);
								}
								break;
							case MENU_BLOGDISPLAY:
								broadcast("USER: %s; NODE:%d; STATUS: Displaying Blog.", gUser->loginname, mynode);
								blog_display();
								break;
							case MENU_BLOGWRITE:
								broadcast("USER: %s; NODE:%d; STATUS: Writing a Blog Entry.", gUser->loginname, mynode);
								blog_write();
								break;
							default:
								break;
						}
					}
					break;
				}
			}
		}
	}

	free(ansi_file);
	if (do_lua_menu)
		lua_close(L);
	free(lua_script);
	free_menu(&menu);

	return doquit;
}
