/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "ultima/shared/std/string.h"
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/conf/configuration.h"
#include "ultima/nuvie/misc/u6_misc.h"
#include "ultima/nuvie/fonts/font_manager.h"
#include "ultima/nuvie/fonts/font.h"
#include "ultima/nuvie/screen/game_palette.h"
#include "ultima/nuvie/gui/gui.h"
#include "ultima/nuvie/gui/widgets/msg_scroll.h"
#include "ultima/nuvie/core/events.h"
#include "ultima/nuvie/core/game.h"
#include "ultima/nuvie/core/effect.h"
#include "ultima/nuvie/keybinding/keys.h"

namespace Ultima {
namespace Nuvie {

// MsgText Class
MsgText::MsgText() : font(nullptr), color(0) {
}

MsgText::MsgText(const Std::string &new_string, Font *f) : font(f), color(0) {
	s.assign(new_string);
	if (font) {
		color = font->getDefaultColor();
	}
}

MsgText::~MsgText() {
}

void MsgText::append(const Std::string &new_string) {
	s.append(new_string);
}

void MsgText::copy(MsgText *msg_text) {
	s.assign(msg_text->s);
	font = msg_text->font;
	color = msg_text->color;
}

uint32 MsgText::length() {
	return (uint32)s.length();
}

uint16 MsgText::getDisplayWidth() {
	return font->getStringWidth(s.c_str());
}

MsgLine::~MsgLine() {
	for (MsgText *token : text) {
		delete token;
	}
}

void MsgLine::append(MsgText *new_text) {
	MsgText *msg_text = nullptr;

	if (text.size() > 0)
		msg_text = text.back();

	if (msg_text && msg_text->font == new_text->font && msg_text->color == new_text->color && new_text->s.length() == 1 && new_text->s[0] != ' ')
		msg_text->s.append(new_text->s);
	else {
		msg_text = new MsgText();
		msg_text->copy(new_text);
		text.push_back(msg_text);
	}

	total_length += new_text->s.length();

	return;
}

// remove the last char in the line.
void MsgLine::remove_char() {
	MsgText *msg_text;

	if (total_length == 0)
		return;

	msg_text = text.back();
	msg_text->s.erase(msg_text->s.length() - 1, 1);

	if (msg_text->s.length() == 0) {
		text.pop_back();
		delete msg_text;
	}

	total_length--;

	return;
}

uint32 MsgLine::length() {
	return total_length;
}

// gets the MsgText object that contains the text at line position pos
MsgText *MsgLine::get_text_at_pos(uint16 pos) {
	uint16 i;
	Std::list<MsgText *>::iterator iter;

	if (pos > total_length)
		return nullptr;

	for (i = 0, iter = text.begin(); iter != text.end(); iter++) {
		if (i + (*iter)->s.length() >= pos)
			return (*iter);

		i += (*iter)->s.length();
	}

	return nullptr;
}

uint16 MsgLine::get_display_width() {
	uint16 len = 0;
	for (MsgText *token : text) {
		len += token->font->getStringWidth(token->s.c_str());
	}
	return len;
}

// MsgScroll Class

void MsgScroll::init(const Configuration *cfg, Font *f) {
	font = f;

	config = cfg;
	config->value("config/GameType", game_type);

	input_char = 0;
	yes_no_only = false;
	aye_nay_only = false;
	numbers_only = false;
	scroll_updated = false;
	discard_whitespace = true;
	page_break = false;
	show_cursor = true;
	talking = false;
	autobreak = false;
	just_finished_page_break = false;
	using_target_cursor = false;

	callback_target = nullptr;
	callback_user_data = nullptr;

	scrollback_height = MSGSCROLL_SCROLLBACK_HEIGHT;
	capitalise_next_letter = false;

	font_color = FONT_COLOR_U6_NORMAL;
	font_highlight_color = FONT_COLOR_U6_HIGHLIGHT;
	if (game_type != NUVIE_GAME_U6) {
		font_color = FONT_COLOR_WOU_NORMAL;
		font_highlight_color = FONT_COLOR_WOU_HIGHLIGHT;
	}
}

MsgScroll::MsgScroll(const Configuration *cfg, Font *f) : GUI_Widget(nullptr, 0, 0, 0, 0),
		input_mode(false), permit_input(nullptr), just_displayed_prompt(false),
		permit_inputescape(false), screen_x(0), screen_y(0), keyword_highlight(false) {
	uint16 x, y;

	init(cfg, f);

	switch (game_type) {
	case NUVIE_GAME_MD :
		scroll_width = MSGSCROLL_MD_WIDTH;
		scroll_height = MSGSCROLL_MD_HEIGHT;
		x = 184;
		y = 128;
		break;

	case NUVIE_GAME_SE :
		scroll_width = MSGSCROLL_SE_WIDTH;
		scroll_height = MSGSCROLL_SE_HEIGHT;
		x = 184;
		y = 128;
		break;
	case NUVIE_GAME_U6 :
	default :
		scroll_width = MSGSCROLL_U6_WIDTH;
		scroll_height = MSGSCROLL_U6_HEIGHT;
		x = 176;
		y = 112;
		break;
	}

	if (Game::get_game()->is_original_plus())
		x += Game::get_game()->get_game_width() - 320;

	uint16 x_off = Game::get_game()->get_game_x_offset();
	uint16 y_off = Game::get_game()->get_game_y_offset();

	GUI_Widget::Init(nullptr, x + x_off, y + y_off, scroll_width * 8, scroll_height * 8);

	cursor_char = 0;
	cursor_x = 0;
	cursor_y = scroll_height - 1;
	line_count = 0;

	cursor_wait = 0;

	display_pos = 0;

	bg_color = Game::get_game()->get_palette()->get_bg_color();

	capitalise_next_letter = false;

	left_margin = 0;

	add_new_line();
}

MsgScroll::~MsgScroll() {
	// delete the scroll buffer
	for (MsgLine *line : msg_buf)
		delete line;

	// delete the holding buffer
	for (MsgText *token : holding_buffer)
		delete token;
}

bool MsgScroll::init(const char *player_name) {
	Std::string prompt_string;

	prompt_string.append(player_name);
	if (game_type == NUVIE_GAME_U6) {
		prompt_string.append(":\n");
	}

	prompt_string.append(">");

	if (set_prompt(prompt_string.c_str(), font) == false)
		return false;

	set_input_mode(false);

	return true;
}

void MsgScroll::set_scroll_dimensions(uint16 w, uint16 h) {
	scroll_width = w;
	scroll_height = h;

	cursor_char = 0;
	cursor_x = 0;
	cursor_y = scroll_height - 1;
	line_count = 0;

	cursor_wait = 0;

	display_pos = 0;
}

int MsgScroll::print_internal(const Std::string *format, ...) {

	va_list ap;
	int printed = 0;
	static size_t bufsize = 128; // will be resized if needed
	static char *buffer = (char *) malloc(bufsize); // static so we don't have to reallocate all the time.

	while (1) {
		if (buffer == nullptr) {
			DEBUG(0, LEVEL_ALERT, "MsgScroll::printf: Couldn't allocate %d bytes for buffer\n", bufsize);
			/* try to shrink the buffer to at least have a change next time,
			 * but if we're low on memory probably have worse issues...
			 */
			bufsize >>= 1;
			buffer = (char *) malloc(bufsize); // no need to check now.
			/*
			 * We don't retry, or if we need e.g. 4 bytes for the format and
			 * there's only 3 available, we'd grow and shrink forever.
			 */
			return printed;
		}

		/* try formatting */
		va_start(ap, format);
		printed = vsnprintf(buffer, bufsize, format->c_str(), ap);
		va_end(ap);

		if (printed < 0) {
			DEBUG(0, LEVEL_ERROR, "MsgScroll::printf: vsnprintf returned < 0: either output error or glibc < 2.1\n");
			free(buffer);
			bufsize *= 2; // In case of an output error, we'll just keep doubling until the malloc fails.
			buffer = (char *) malloc(bufsize); // if this fails, will be caught later on
			/* try again */
			continue;
		} else if ((size_t)printed >= bufsize) {
			DEBUG(0, LEVEL_DEBUGGING, "MsgScroll::printf: needed buffer of %d bytes, only had %d bytes.\n", printed + 1, bufsize);
			bufsize = printed + 1; //this should be enough
			free(buffer);
			buffer = (char *) malloc(bufsize); // if this fails, will be caught later on
			/* try again */
			continue;
		}
		/* if we're here, formatting probably worked. We can stop looping */
		break;
	}
	/* use the string */
	display_string(buffer);

	return printed;
}

void MsgScroll::display_fmt_string(const char *format, ...) {
	char buf[1024];
	memset(buf, 0, 1024);
	va_list args;
	va_start(args, format);
	vsnprintf(buf, 1024, format, args);
	va_end(args);

	display_string(buf);
}

void MsgScroll::display_string(const Std::string &s, uint16 length, uint8 lang_num) {

}

void MsgScroll::display_string(const Std::string &s, bool include_on_map_window) {
	display_string(s, font, include_on_map_window);
}

void MsgScroll::display_string(const Std::string &s, uint8 color, bool include_on_map_window) {
	display_string(s, font, color, include_on_map_window);
}

void MsgScroll::display_string(const Std::string &s, Font *f, bool include_on_map_window) {
	display_string(s, f, font_color, include_on_map_window);
}

void MsgScroll::display_string(const Std::string &s, Font *f, uint8 color, bool include_on_map_window) {
	MsgText *msg_text;

	if (s.empty())
		return;

	if (f == nullptr)
		f = font;

	msg_text = new MsgText(s, f);
	msg_text->color = color;
	//::debug(1, "%s", msg_text->s.c_str());

	holding_buffer.push_back(msg_text);

	process_holding_buffer();

}

// process text tokens till we either run out or hit a page break.

void MsgScroll::process_holding_buffer() {
	MsgText *token;

	if (!page_break) {
		token = holding_buffer_get_token();

		for (; token != nullptr && !page_break;) {
			parse_token(token);
			delete token;
			scroll_updated = true;

			if (!page_break)
				token = holding_buffer_get_token();
		}
	}
}

MsgText *MsgScroll::holding_buffer_get_token() {
	MsgText *input, *token;
	int i;

	if (holding_buffer.empty())
		return nullptr;

	input = holding_buffer.front();

	if (input->font == nullptr) {
		line_count = 0;
		holding_buffer.pop_front();
		delete input;
		return nullptr;
	}

	i = input->s.findFirstOf(" \t\n*<>`", 0);
	if (i == 0) i++;

	if (i == -1)
		i = input->s.length();

	if (i > 0) {
		token = new MsgText(input->s.substr(0, i), font); // FIX maybe a format flag. // input->font);
		token->color = input->color;
		input->s.erase(0, i);
		if (input->s.length() == 0) {
			holding_buffer.pop_front();
			delete input;
		}
		return token;
	}

	return nullptr;
}

bool MsgScroll::can_fit_token_on_msgline(MsgLine *msg_line, MsgText *token) {
	if (msg_line->total_length + token->length() > scroll_width) {
		return false; //token doesn't fit on the current line.
	}

	return true;
}

bool MsgScroll::parse_token(MsgText *token) {
	MsgLine *msg_line = nullptr;

	if (!(token && token->s.length()))
		return true;

	if (!msg_buf.empty())
		msg_line = msg_buf.back(); // retrieve the last line from the scroll buffer.

	switch (token->s[0]) {
	case '\n' :
		add_new_line();
		break;

	case '\t' :
		set_autobreak(false);
		break;

	case '`'  :
		capitalise_next_letter = true;
		break;

	case '<'  :
		set_font(NUVIE_FONT_GARG); // runic / gargoyle font
		break;

	case '>'  :
		if (is_garg_font()) {
			set_font(NUVIE_FONT_NORMAL); // english font
			break;
		}
		// falls through
		// ...if we haven't already seen a '<' char

	default   :
		if (msg_line) {
			if (can_fit_token_on_msgline(msg_line, token) == false) { // the token is to big for the current line
				msg_line = add_new_line();
			}
			// This adds extra newlines. (SB-X)
			//                 if(msg_line->total_length + token->length() == scroll_width) //we add a new line but write to the old line.
			//                    add_new_line();

			if (msg_line->total_length == 0 && token->s[0] == ' ' && discard_whitespace) // discard whitespace at the start of a line.
				return true;
		}
		if (token->s[0] == '*') {
			if (just_finished_page_break == false) //we don't want to add another break if we've only just completed an autobreak
				set_page_break();
		} else {
			if (capitalise_next_letter) {
				token->s[0] = toupper(token->s[0]);
				capitalise_next_letter = false;
			}

			if (msg_line == nullptr) {
				msg_line = add_new_line();
			}

			add_token(token);
			if (msg_line->total_length == scroll_width // add another line for cursor.
			        && (talking || Game::get_game()->get_event()->get_mode() == INPUT_MODE
			            || Game::get_game()->get_event()->get_mode() == KEYINPUT_MODE)) {
				msg_line = add_new_line();
			}
		}
		break;
	}

	if (msg_buf.size() > scroll_height)
		display_pos = msg_buf.size() - scroll_height;
	just_finished_page_break = false;
	just_displayed_prompt = false;
	return true;
}

void MsgScroll::add_token(MsgText *token) {
	msg_buf.back()->append(token);
}

bool MsgScroll::remove_char() {
	MsgLine *msg_line;

	msg_line = msg_buf.back(); // retrieve the last line from the scroll buffer.
	msg_line->remove_char();

	if (msg_line->total_length == 0) { // remove empty line from scroll buffer
		msg_buf.pop_back();
		delete msg_line;
	}

	return true;
}

void MsgScroll::set_font(uint8 font_type) {
	font = Game::get_game()->get_font_manager()->get_font(font_type); // 0 = normal english; 1 = runic / gargoyle font
}

bool MsgScroll::is_garg_font() {
	return (font == Game::get_game()->get_font_manager()->get_font(NUVIE_FONT_GARG));
}

void MsgScroll::clear_scroll() {
	for (MsgLine *line : msg_buf) {
		delete line;
	}

	msg_buf.clear();
	line_count = 0;
	display_pos = 0;
	scroll_updated = true;
	add_new_line();
}

void MsgScroll::delete_front_line() {
	MsgLine *msg_line_front = msg_buf.front();
	msg_buf.pop_front();
	delete msg_line_front;
}

MsgLine *MsgScroll::add_new_line() {
	MsgLine *msg_line = new MsgLine();
	msg_buf.push_back(msg_line);
	line_count++;

	if (msg_buf.size() > scrollback_height) {
		delete_front_line();
	}

	if (autobreak && line_count > scroll_height - 1)
		set_page_break();

	return msg_line;
}

bool MsgScroll::set_prompt(const char *new_prompt, Font *f) {

	prompt.s.assign(new_prompt);
	prompt.font = f;

	return true;
}

void MsgScroll::display_prompt() {
	if (!talking && !just_displayed_prompt) {
//line_count = 0;
		display_string(prompt.s, prompt.font, MSGSCROLL_NO_MAP_DISPLAY);
//line_count = 0;

		clear_page_break();
		just_displayed_prompt = true;
	}
}

void MsgScroll::display_converse_prompt() {
	display_string("\nyou say:", MSGSCROLL_NO_MAP_DISPLAY);
}

void MsgScroll::set_keyword_highlight(bool state) {
	keyword_highlight = state;
}

void MsgScroll::set_permitted_input(const char *allowed) {
	permit_input = allowed;
	if (permit_input) {
		if (strcmp(permit_input, "yn") == 0)
			yes_no_only = true;
		else if (strncmp(permit_input, "0123456789", strlen(permit_input)) == 0)
			numbers_only = true;
		else if (game_type == NUVIE_GAME_U6 && strcmp(permit_input, "ayn") == 0) // Heftimus npc 47
			aye_nay_only = true;
	}
}

void MsgScroll::clear_permitted_input() {
	permit_input = nullptr;
	yes_no_only = false;
	numbers_only = false;
	aye_nay_only = false;
}

void MsgScroll::set_input_mode(bool state, const char *allowed, bool can_escape, bool use_target_cursor, bool set_numbers_only_to_true) {
	bool do_callback = false;

	input_mode = state;
	clear_permitted_input();
	permit_inputescape = can_escape;
	using_target_cursor = use_target_cursor;
	if (set_numbers_only_to_true)
		numbers_only = true;

	line_count = 0;

	clear_page_break();

	if (input_mode == true) {
		if (allowed && strlen(allowed))
			set_permitted_input(allowed);
		//FIXME SDL2 SDL_EnableUNICODE(1); // allow character translation
		input_buf.erase(0, input_buf.length());
	} else {
		//FIXME SDL2 SDL_EnableUNICODE(0); // reduce translation overhead when not needed
		if (callback_target)
			do_callback = true; // **DELAY until end-of-method so callback can set_input_mode() again**
	}
	Game::get_game()->get_gui()->lock_input((input_mode && !using_target_cursor) ? this : nullptr);

// send whatever input was collected to target that requested it
	if (do_callback) {
		CallBack *requestor = callback_target; // copy to temp
		char *user_data = callback_user_data;
		cancel_input_request(); // clear originals (callback may request again)

		Std::string input_str = input_buf;
		requestor->set_user_data(user_data); // use temp requestor/user_data
		requestor->callback(MSGSCROLL_CB_TEXT_READY, this, &input_str);
	}
}

void MsgScroll::move_scroll_down() {
	if (msg_buf.size() > scroll_height && display_pos < msg_buf.size() - scroll_height) {
		display_pos++;
		scroll_updated = true;
	}
}

void MsgScroll::move_scroll_up() {
	if (display_pos > 0) {
		display_pos--;
		scroll_updated = true;
	}
}

void MsgScroll::page_up() {
	uint8 i = 0;
	for (; display_pos > 0 && i < scroll_height; i++)
		display_pos--;
	if (i > 0)
		scroll_updated = true;
}

void MsgScroll::page_down() {
	uint8 i = 0;
	for (; msg_buf.size() > scroll_height && i < scroll_height
	        && display_pos < msg_buf.size() - scroll_height ; i++)
		display_pos++;
	if (i > 0)
		scroll_updated = true;
}

void MsgScroll::process_page_break() {
	page_break = false;
	just_finished_page_break = true;
	if (!input_mode)
		Game::get_game()->get_gui()->unlock_input();
	process_holding_buffer(); // Process any text in the holding buffer.
	just_displayed_prompt = true;
}

/* Take input from the main event handler and do something with it
 * if necessary.
 */
GUI_status MsgScroll::KeyDown(const Common::KeyState &keyState) {
	Common::KeyState key = keyState;
	char ascii = get_ascii_char_from_keysym(key);

	if (page_break == false && input_mode == false)
		return GUI_PASS;

	bool is_printable = Common::isPrint(ascii);
	KeyBinder *keybinder = Game::get_game()->get_keybinder();
	ActionType a = keybinder->get_ActionType(key);
	ActionKeyType action_key_type = keybinder->GetActionKeyType(a);

	if (using_target_cursor && !is_printable && action_key_type <= DO_ACTION_KEY) // directional keys, toggle_cursor, and do_action
		return GUI_PASS;

	if (!input_mode || !is_printable) {
		switch (action_key_type) {
		case WEST_KEY:
			key.keycode = Common::KEYCODE_LEFT;
			break;
		case EAST_KEY:
			key.keycode = Common::KEYCODE_RIGHT;
			break;
		case SOUTH_KEY:
			key.keycode = Common::KEYCODE_DOWN;
			break;
		case NORTH_KEY:
			key.keycode = Common::KEYCODE_UP;
			break;
		case CANCEL_ACTION_KEY:
			key.keycode = Common::KEYCODE_ESCAPE;
			break;
		case DO_ACTION_KEY:
			key.keycode = Common::KEYCODE_RETURN;
			break;
		case MSGSCROLL_UP_KEY:
			key.keycode = Common::KEYCODE_PAGEUP;
			break;
		case MSGSCROLL_DOWN_KEY:
			key.keycode = Common::KEYCODE_PAGEDOWN;
			break;
		default:
			if (keybinder->handle_always_available_keys(a)) return GUI_YUM;
			break;
		}
	}
	switch (key.keycode) {
	case Common::KEYCODE_UP :
		if (input_mode) break; //will select printable ascii
		move_scroll_up();
		return GUI_YUM;

	case Common::KEYCODE_DOWN:
		if (input_mode) break; //will select printable ascii
		move_scroll_down();
		return GUI_YUM;
	case Common::KEYCODE_PAGEUP:
		if (Game::get_game()->is_new_style())
			move_scroll_up();
		else page_up();
		return GUI_YUM;
	case Common::KEYCODE_PAGEDOWN:
		if (Game::get_game()->is_new_style())
			move_scroll_down();
		else page_down();
		return GUI_YUM;
	default :
		break;
	}

	if (page_break) {
		process_page_break();
		return GUI_YUM;
	}

	switch (key.keycode) {
	case Common::KEYCODE_ESCAPE:
		if (permit_inputescape) {
			// reset input buffer
			permit_input = nullptr;
			if (input_mode)
				set_input_mode(false);
		}
		return GUI_YUM;
	case Common::KEYCODE_KP_ENTER:
	case Common::KEYCODE_RETURN:
		if (permit_inputescape || input_char != 0) { // input_char should only be permit_input
			if (input_char != 0)
				input_buf_add_char(get_char_from_input_char());
			if (input_mode)
				set_input_mode(false);
		}
		return GUI_YUM;
	case Common::KEYCODE_RIGHT:
		if (input_char != 0 && permit_input == nullptr)
			input_buf_add_char(get_char_from_input_char());
		break;
	case Common::KEYCODE_DOWN:
		increase_input_char();
		break;
	case Common::KEYCODE_UP:
		decrease_input_char();
		break;
	case Common::KEYCODE_LEFT :
	case Common::KEYCODE_BACKSPACE :
		if (input_mode) {
			if (input_char != 0)
				input_char = 0;
			else
				input_buf_remove_char();
		}
		break;
	case Common::KEYCODE_LSHIFT :
		return GUI_YUM;
	case Common::KEYCODE_RSHIFT :
		return GUI_YUM;
	default: // alphanumeric characters
		if (input_mode && is_printable) {
			if (permit_input == nullptr) {
				if (!numbers_only || Common::isDigit(ascii)) {
					if (input_char != 0)
						input_buf_add_char(get_char_from_input_char());
					input_buf_add_char(ascii);
				}
			} else if (strchr(permit_input, ascii) || strchr(permit_input, tolower(ascii))) {
				input_buf_add_char(toupper(ascii));
				set_input_mode(false);
			}
		}
		break;
	}

	return GUI_YUM;
}


GUI_status MsgScroll::MouseWheel(sint32 x, sint32 y) {
	if (page_break) { // any click == scroll-to-end
		process_page_break();
		return GUI_YUM;
	}

	Game *game = Game::get_game();

	if (game->is_new_style()) {
		if (!input_mode)
			return GUI_PASS;
		if (y > 0)
			move_scroll_up();
		if (y < 0)
			move_scroll_down();
	} else {
		if (y > 0)
			page_up();
		if (y < 0)
			page_down();
	}
	return GUI_YUM;
}

GUI_status MsgScroll::MouseUp(int x, int y, Shared::MouseButton button) {
	uint16 i;
	Std::string token_str;

	if (page_break) { // any click == scroll-to-end
		process_page_break();
		return GUI_YUM;
	}

	if (button == 1) { // left click == select word
		if (input_mode) {
			token_str = get_token_string_at_pos(x, y);
			if (permit_input != nullptr && token_str.length()) {
				if (strchr(permit_input, token_str[0])
				        || strchr(permit_input, tolower(token_str[0]))) {
					input_buf_add_char(token_str[0]);
					set_input_mode(false);
				}
				return GUI_YUM;
			}

			for (i = 0; i < token_str.length(); i++) {
				if (Common::isAlnum(token_str[i]))
					input_buf_add_char(token_str[i]);
			}

		} else if (!Game::get_game()->is_new_style())
			Game::get_game()->get_event()->cancelAction();
	} else if (button == 3) { // right click == send input
		if (input_mode) {
			if (permit_inputescape) {
				set_input_mode(false);
				return GUI_YUM;
			}
		} else if (!Game::get_game()->is_new_style())
			Game::get_game()->get_event()->cancelAction();
	}
	return GUI_PASS;
}

Std::string MsgScroll::get_token_string_at_pos(uint16 x, uint16 y) {
	uint16 i;
	sint32 buf_x, buf_y;
	MsgText *token = nullptr;
	Std::list<MsgLine *>::iterator iter;

	buf_x = (x - area.left) / 8;
	buf_y = (y - area.top) / 8;

	if (buf_x < 0 || buf_x >= scroll_width || // click not in MsgScroll area.
	        buf_y < 0 || buf_y >= scroll_height)
		return "";

	if (msg_buf.size() <= scroll_height) {
		if ((sint32)msg_buf.size() < buf_y + 1)
			return "";
	} else {
		buf_y = display_pos + buf_y;
	}

	for (i = 0, iter = msg_buf.begin(); i < buf_y && iter != msg_buf.end();) {
		iter++;
		i++;
	}

	if (iter != msg_buf.end()) {
		token = (*iter)->get_text_at_pos(buf_x);
		if (token) {
			DEBUG(0, LEVEL_DEBUGGING, "Token at (%d,%d) = %s\n", buf_x, buf_y, token->s.c_str());
			return token->s;
		}
	}

	return "";
}

void MsgScroll::Display(bool full_redraw) {
	uint16 i;
	Std::list<MsgLine *>::iterator iter;
	MsgLine *msg_line = nullptr;



	if (scroll_updated || full_redraw || Game::get_game()->is_original_plus_full_map()) {
		screen->fill(bg_color, area.left, area.top, area.width(), area.height()); //clear whole scroll

		iter = msg_buf.begin();
		for (i = 0; i < display_pos; i++)
			iter++;

		for (i = 0; i < scroll_height && iter != msg_buf.end(); i++, iter++) {
			msg_line = *iter;
			drawLine(screen, msg_line, i);
		}
		scroll_updated = false;

		screen->update(area.left, area.top, area.width(), area.height());

		cursor_y = i - 1;
		if (msg_line) {
			cursor_x = msg_line->total_length;
			if (cursor_x == scroll_width) { // don't draw the cursor outside the scroll (SB-X)
				if (cursor_y + 1 < scroll_height)
					cursor_y++;
				cursor_x = 0;
			}
		} else
			cursor_x = area.left;
	} else {
		clearCursor(area.left + 8 * cursor_x, area.top + cursor_y * 8);
	}

	if (show_cursor && (msg_buf.size() <= scroll_height || display_pos == msg_buf.size() - scroll_height)) {
		drawCursor(area.left + left_margin + 8 * cursor_x, area.top + cursor_y * 8);
	}

}

inline void MsgScroll::drawLine(Screen *theScreen, MsgLine *msg_line, uint16 line_y) {
	uint16 total_length = 0;

	for (MsgText *token : msg_line->text) {
		token->font->drawString(theScreen, token->s.c_str(), area.left + left_margin + total_length * 8, area.top + line_y * 8, token->color, font_highlight_color); //FIX for hardcoded font height
		total_length += token->s.length();
	}
}

void MsgScroll::clearCursor(uint16 x, uint16 y) {
	screen->fill(bg_color, x, y, 8, 8);
}

void MsgScroll::drawCursor(uint16 x, uint16 y) {
	uint8 cursor_color = input_mode ? get_input_font_color() : font_color;

	if (input_char != 0) { // show letter selected by arrow keys
		font->drawChar(screen, get_char_from_input_char(), x, y, cursor_color);
		screen->update(x, y, 8, 8);
		return;
	}
	if (page_break) {
		if (cursor_wait <= 2) // flash arrow
			font->drawChar(screen, 1, x, y, cursor_color); // down arrow
	} else
		font->drawChar(screen, cursor_char + 5, x, y, cursor_color); //spinning ankh

	screen->update(x, y, 8, 8);
	if (cursor_wait == MSGSCROLL_CURSOR_DELAY) {
		cursor_char = (cursor_char + 1) % 4;
		cursor_wait = 0;
	} else
		cursor_wait++;
}


void MsgScroll::set_page_break() {
	line_count = 1;
	page_break = true;

	if (!input_mode) {
		Game::get_game()->get_gui()->lock_input(this);
	}

	return;
}

bool MsgScroll::input_buf_add_char(char c) {
	MsgText token;
	input_char = 0;
	if (permit_input != nullptr)
		input_buf_remove_char();
	input_buf.push_back(c);
	scroll_updated = true;

// Add char to scroll buffer

	token.s.assign(&c, 1);
	token.font = font;
	token.color = get_input_font_color();

	parse_token(&token);

	return true;
}

bool MsgScroll::input_buf_remove_char() {
	if (input_buf.length()) {
		input_buf.erase(input_buf.length() - 1, 1);
		scroll_updated = true;
		remove_char();

		return true;
	}

	return false;
}

bool MsgScroll::has_input() {
	if (input_mode == false) //we only have input ready after the user presses enter.
		return true;

	return false;
}

Std::string MsgScroll::get_input() {
// MsgScroll sets input_mode to false when it receives Common::KEYCODE_ENTER
	Std::string s;

	if (input_mode == false) {
		s.assign(input_buf);
	}
	//::debug(1, "%s", s.c_str());

	return s;
}

void MsgScroll::clear_page_break() {
	MsgText *msg_text = new MsgText("", nullptr);
	holding_buffer.push_back(msg_text);

	process_holding_buffer();
}

/* Set callback & callback_user_data so that a message will be sent to the
 * caller when input has been gathered. */
void MsgScroll::request_input(CallBack *caller, void *user_data) {
	callback_target = caller;
	callback_user_data = (char *)user_data;
}

// 0 is no char, 1 - 26 is alpha, 27 is space, 28 - 37 is numbers
void MsgScroll::increase_input_char() {
	if (permit_input != nullptr && strcmp(permit_input, "\n") == 0) // blame hacky PauseEffect
		return;
	if (yes_no_only)
		input_char = input_char == 25 ? 14 : 25;
	else if (aye_nay_only)
		input_char = input_char == 1 ? 14 : 1;
	else if (numbers_only)
		input_char = (input_char == 0 || input_char == 37) ? 28 : input_char + 1;
	else
		input_char = (input_char + 1) % 38;
	if (permit_input != nullptr && !strchr(permit_input, get_char_from_input_char())) // might only be needed for the teleport cheat menu
		increase_input_char();
}

void MsgScroll::decrease_input_char() {
	if (permit_input != nullptr && strcmp(permit_input, "\n") == 0) // blame hacky PauseEffect
		return;
	if (yes_no_only)
		input_char = input_char == 25 ? 14 : 25;
	else if (numbers_only)
		input_char = (input_char == 0 || input_char == 28) ? 37 : input_char - 1;
	else if (aye_nay_only)
		input_char = input_char == 1 ? 14 : 1;
	else
		input_char = input_char == 0 ? 37 : input_char - 1;
	if (permit_input != nullptr && !strchr(permit_input, get_char_from_input_char())) // might only be needed for the teleport cheat menu
		decrease_input_char();
}

uint8 MsgScroll::get_char_from_input_char() {

	if (input_char > 27)
		return (input_char - 28 + Common::KEYCODE_0);
	else if (input_char == 27)
		return Common::KEYCODE_SPACE;
	else
		return (input_char + Common::KEYCODE_a - 1);
}

} // End of namespace Nuvie
} // End of namespace Ultima
