/*
   Copyright (C) 2014 - 2018 by Chris Beck <render787@gmail.com>
   Part of the Battle for Wesnoth Project https://www.wesnoth.org/

   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 2 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.

   See the COPYING file for more details.
*/

#include "scripting/lua_gui2.hpp"

#include "gui/auxiliary/old_markup.hpp"
#include "gui/core/canvas.hpp"
#include "gui/core/gui_definition.hpp"
#include "gui/core/window_builder.hpp"
#include "gui/dialogs/drop_down_menu.hpp"
#include "gui/dialogs/gamestate_inspector.hpp"
#include "gui/dialogs/lua_interpreter.hpp"
#include "gui/dialogs/wml_message.hpp"
#include "gui/dialogs/story_viewer.hpp"
#include "gui/dialogs/transient_message.hpp"
#include "gui/dialogs/message.hpp"
#include "gui/widgets/clickable_item.hpp"
#include "gui/widgets/styled_widget.hpp"
#include "gui/widgets/listbox.hpp"
#include "gui/widgets/multi_page.hpp"
#include "gui/widgets/multimenu_button.hpp"
#include "gui/widgets/progress_bar.hpp"
#include "gui/widgets/selectable_item.hpp"
#include "gui/widgets/slider.hpp"
#include "gui/widgets/stacked_widget.hpp"
#include "gui/widgets/text_box.hpp"
#include "gui/widgets/tree_view.hpp"
#include "gui/widgets/tree_view_node.hpp"
#include "gui/widgets/unit_preview_pane.hpp"
#include "gui/widgets/widget.hpp"
#include "gui/widgets/window.hpp"

#include "config.hpp"
#include "log.hpp"
#include "scripting/lua_common.hpp"
#include "scripting/lua_kernel_base.hpp"
#include "scripting/lua_unit.hpp"
#include "scripting/lua_unit_type.hpp"
#include "scripting/push_check.hpp"
#include "serialization/string_utils.hpp"
#include "tstring.hpp"
#include "game_data.hpp"
#include "game_state.hpp"

#include "utils/functional.hpp"

#include <map>
#include <utility>
#include <vector>
#include <boost/optional.hpp>

#include "lua/lauxlib.h"                // for luaL_checkinteger, etc
#include "lua/lua.h"                    // for lua_setfield, etc

static lg::log_domain log_scripting_lua("scripting/lua");
#define ERR_LUA LOG_STREAM(err, log_scripting_lua)

static const char dlgclbkKey[] = "dialog callback";

namespace {
	struct scoped_dialog
	{
		lua_State* L;
		scoped_dialog* prev;
		static scoped_dialog* current;
		std::unique_ptr<gui2::window> window;
		typedef std::map<gui2::widget*, int> callback_map;
		callback_map callbacks;

		scoped_dialog(lua_State* l, gui2::window* w);
		~scoped_dialog();
	private:
		scoped_dialog(const scoped_dialog&) = delete;
	};

	scoped_dialog* scoped_dialog::current = nullptr;

	scoped_dialog::scoped_dialog(lua_State* l, gui2::window* w)
		: L(l), prev(current), window(w), callbacks()
	{
		lua_pushstring(L, dlgclbkKey);
		lua_createtable(L, 1, 0);
		lua_pushvalue(L, -2);
		lua_rawget(L, LUA_REGISTRYINDEX);
		lua_rawseti(L, -2, 1);
		lua_rawset(L, LUA_REGISTRYINDEX);
		current = this;
	}

	scoped_dialog::~scoped_dialog()
	{
		current = prev;
		lua_pushstring(L, dlgclbkKey);
		lua_pushvalue(L, -1);
		lua_rawget(L, LUA_REGISTRYINDEX);
		lua_rawgeti(L, -1, 1);
		lua_remove(L, -2);
		lua_rawset(L, LUA_REGISTRYINDEX);
	}
}//unnamed namespace for scoped_dialog

static gui2::widget* find_widget(lua_State* L, int i, bool readonly)
{
	if(!scoped_dialog::current) {
		luaL_error(L, "no visible dialog");
		error_oob_call_dtors:
		luaL_argerror(L, i, "out of bounds");
		error_not_str_call_dtors:
		luaW_type_error(L, i, "string");
		error_no_wgt_call_dtors:
		luaL_argerror(L, i, "widget not found");
		return nullptr;
	}

	gui2::widget* w = scoped_dialog::current->window.get();
	for(; !lua_isnoneornil(L, i); ++i)
	{
		if(gui2::listbox* list = dynamic_cast<gui2::listbox*>(w))
		{
			int v = lua_tointeger(L, i);
			if(v < 1) {
				goto error_oob_call_dtors;
			}
			int n = list->get_item_count();
			if(v > n) {
				if(readonly) {
					goto error_oob_call_dtors;
				}
				utils::string_map dummy;
				for(; n < v; ++n) {
					list->add_row(dummy);
				}
			}
			w = list->get_row_grid(v - 1);
		} else if(gui2::multi_page* multi_page = dynamic_cast<gui2::multi_page*>(w)) {
			int v = lua_tointeger(L, i);
			if(v < 1) {
				goto error_oob_call_dtors;
			}
			int n = multi_page->get_page_count();
			if(v > n) {
				if(readonly) {
					goto error_oob_call_dtors;
				}
				utils::string_map dummy;
				for(; n < v; ++n) {
					multi_page->add_page(dummy);
				}
			}
			w = &multi_page->page_grid(v - 1);
		} else if(gui2::tree_view* tree_view = dynamic_cast<gui2::tree_view*>(w)) {
			gui2::tree_view_node& tvn = tree_view->get_root_node();
			if(lua_isnumber(L, i)) {
				int v = lua_tointeger(L, i);
				if(v < 1) {
					goto error_oob_call_dtors;
				}
				int n = tvn.count_children();
				if(v > n) {
					goto error_oob_call_dtors;
				}
				w = &tvn.get_child_at(v - 1);

			} else {
				std::string m = luaL_checkstring(L, i);
				w = tvn.find(m, false);
			}
		} else if(gui2::tree_view_node* tree_view_node = dynamic_cast<gui2::tree_view_node*>(w)) {
			if(lua_isnumber(L, i)) {
				int v = lua_tointeger(L, i);
				if(v < 1) {
					goto error_oob_call_dtors;
				}
				int n = tree_view_node->count_children();
				if(v > n) {
					goto error_oob_call_dtors;
				}
				w = &tree_view_node->get_child_at(v - 1);

			} else {
				std::string m = luaL_checkstring(L, i);
				w = tree_view_node->find(m, false);
			}
		} else if(gui2::stacked_widget* stacked_widget = dynamic_cast<gui2::stacked_widget*>(w)) {
			if(lua_isnumber(L, i)) {
				int v = lua_tointeger(L, i);
				if(v < 1) {
					goto error_oob_call_dtors;
				}
				int n = stacked_widget->get_layer_count();
				if(v > n) {
					goto error_oob_call_dtors;
				}
				w = stacked_widget->get_layer_grid(v - 1);
			} else {
				std::string m = luaL_checkstring(L, i);
				w = stacked_widget->find(m, false);
			}
		} else {
			char const *m = lua_tostring(L, i);
			if(!m) {
				goto error_not_str_call_dtors;
			}
			w = w->find(m, false);
		}
		if(!w) {
			goto error_no_wgt_call_dtors;
		}
	}

	return w;
}

namespace lua_gui2 {

/**
 * Displays a window.
 * - Arg 1: WML table describing the window.
 * - Arg 2: function called at pre-show.
 * - Arg 3: function called at post-show.
 * - Ret 1: integer.
 */
int show_dialog(lua_State* L)
{
	config def_cfg = luaW_checkconfig(L, 1);

	gui2::builder_window::window_resolution def(def_cfg);
	scoped_dialog w(L, gui2::build(&def));

	if(!lua_isnoneornil(L, 2)) {
		lua_pushvalue(L, 2);
		lua_call(L, 0, 0);
	}

	int v = scoped_dialog::current->window->show(true, 0);

	if (!lua_isnoneornil(L, 3)) {
		lua_pushvalue(L, 3);
		lua_call(L, 0, 0);
	}

	lua_pushinteger(L, v);
	return 1;
}

/**
 * Displays a message window
 * - Arg 1: Table describing the window
 * - Arg 2: List of options (nil or empty table - no options)
 * - Arg 3: Text input specifications (nil or empty table - no text input)
 * - Ret 1: option chosen (if no options: 0 if there's text input, -2 if escape pressed, else -1)
 * - Ret 2: string entered (empty if none, nil if no text input)
 */
int show_message_dialog(lua_State* L)
{
	config txt_cfg;
	const bool has_input = !lua_isnoneornil(L, 3) && luaW_toconfig(L, 3, txt_cfg) && !txt_cfg.empty();

	gui2::dialogs::wml_message_input input;
	input.caption = txt_cfg["label"].str();
	input.text = txt_cfg["text"].str();
	input.maximum_length = txt_cfg["max_length"].to_int(256);
	input.text_input_was_specified = has_input;

	gui2::dialogs::wml_message_options options;
	if(!lua_isnoneornil(L, 2)) {
		luaL_checktype(L, 2, LUA_TTABLE);
		size_t n = lua_rawlen(L, 2);
		for(size_t i = 1; i <= n; i++) {
			lua_rawgeti(L, 2, i);
			t_string short_opt;
			config opt;
			if(luaW_totstring(L, -1, short_opt)) {
				// Note: Although this currently uses the legacy_menu_item class
				// for the deprecated syntax, this branch should still be retained
				// when the deprecated syntax is removed, as a simpler method
				// of specifying options when only a single string is needed.
				const std::string& opt_str = short_opt;
				gui2::legacy_menu_item item(opt_str, "Use image=, label=, description=, default= instead.");
				opt["image"] = item.icon();
				opt["label"] = item.label();
				opt["description"] = item.description();
				opt["default"] = item.is_default();
			} else if(!luaW_toconfig(L, -1, opt)) {
				std::ostringstream error;
				error << "expected array of config and/or translatable strings, but index ";
				error << i << " was a " << lua_typename(L, lua_type(L, -1));
				return luaL_argerror(L, 2, error.str().c_str());
			}
			gui2::dialogs::wml_message_option option(opt["label"], opt["description"], opt["image"]);
			if(opt["default"].to_bool(false)) {
				options.chosen_option = i - 1;
			}
			options.option_list.push_back(option);
			lua_pop(L, 1);
		}
		lua_getfield(L, 2, "default");
		if(lua_isnumber(L, -1)) {
			int i = lua_tointeger(L, -1);
			if(i < 1 || size_t(i) > n) {
				std::ostringstream error;
				error << "default= key in options list is not a valid option index (1-" << n << ")";
				return luaL_argerror(L, 2, error.str().c_str());
			}
			options.chosen_option = i - 1;
		}
		lua_pop(L, 1);
	}

	const config& def_cfg = luaW_checkconfig(L, 1);
	const std::string& title = def_cfg["title"];
	const std::string& message = def_cfg["message"];

	using portrait = gui2::dialogs::wml_message_portrait;
	std::unique_ptr<portrait> left;
	std::unique_ptr<portrait> right;
	const bool is_double = def_cfg.has_attribute("second_portrait");
	const bool left_side = def_cfg["left_side"].to_bool(true);
	if(is_double || left_side) {
		left.reset(new portrait {def_cfg["portrait"], def_cfg["mirror"].to_bool(false)});
	} else {
		// This means right side only.
		right.reset(new portrait {def_cfg["portrait"], def_cfg["mirror"].to_bool(false)});
	}
	if(is_double) {
		right.reset(new portrait {def_cfg["second_portrait"], def_cfg["second_mirror"].to_bool(false)});
	}

	int dlg_result = gui2::dialogs::show_wml_message(title, message, left.get(), right.get(), options, input);

	if(!has_input && options.option_list.empty()) {
		lua_pushinteger(L, dlg_result);
	} else {
		lua_pushinteger(L, options.chosen_option + 1);
	}

	if(has_input) {
		lua_pushlstring(L, input.text.c_str(), input.text.length());
	} else {
		lua_pushnil(L);
	}

	return 2;
}

/**
 * Displays a popup message
 * - Arg 1: Title (allows Pango markup)
 * - Arg 2: Message (allows Pango markup)
 * - Arg 3: Image (optional)
 */
int show_popup_dialog(lua_State *L) {
	t_string title = luaW_checktstring(L, 1);
	t_string msg = luaW_checktstring(L, 2);
	std::string image = lua_isnoneornil(L, 3) ? "" : luaL_checkstring(L, 3);

	gui2::show_transient_message(title, msg, image, true, true);
	return 0;
}

/**
 * Displays a story screen
 * - Arg 1: The story config
 * - Arg 2: The default title
 */
int show_story(lua_State* L) {
	config story = luaW_checkconfig(L, 1);
	t_string title = luaW_checktstring(L, 2);
	gui2::dialogs::story_viewer::display(title, story);
	return 0;
}

/**
 * Displays a popup menu at the current mouse position
 * Best used from a [set_menu_item], to show a submenu
 * - Arg 1: Configs defining each item, with keys icon, image/label, second_label, tooltip
 * - Args 2, 3: Initial selection (integer); whether to parse markup (boolean)
 */
int show_menu(lua_State* L) {
	std::vector<config> items = lua_check<std::vector<config>>(L, 1);
	SDL_Rect pos {1,1,1,1};
	SDL_GetMouseState(&pos.x, &pos.y);

	int initial = -1;
	bool markup = false;
	if(lua_isnumber(L, 2)) {
		initial = lua_tointeger(L, 2) - 1;
		markup = luaW_toboolean(L, 3);
	} else if(lua_isnumber(L, 3)) {
		initial = lua_tointeger(L, 3) - 1;
		markup = luaW_toboolean(L, 2);
	} else if(lua_isboolean(L, 2)) {
		markup = luaW_toboolean(L, 2);
	}

	gui2::dialogs::drop_down_menu menu(pos, items, initial, markup, false);
	menu.show();
	lua_pushinteger(L, menu.selected_item() + 1);
	return 1;
}

/**
 * Displays a simple message box.
 */
int show_message_box(lua_State* L) {
	const t_string title = luaW_checktstring(L, 1), message = luaW_checktstring(L, 2);
	std::string button = luaL_optstring(L, 3, "ok"), btn_style;
	std::transform(button.begin(), button.end(), std::inserter(btn_style, btn_style.begin()), [](char c) { return std::tolower(c); });
	bool markup = lua_isnoneornil(L, 3) ? luaW_toboolean(L, 3) : luaW_toboolean(L, 4);
	using button_style = gui2::dialogs::message::button_style;
	boost::optional<button_style> style;
	if(btn_style.empty()) {
		style = button_style::auto_close;
	} else if(btn_style == "ok") {
		style = button_style::ok_button;
	} else if(btn_style == "close") {
		style = button_style::close_button;
	} else if(btn_style == "ok_cancel") {
		style = button_style::ok_cancel_buttons;
	} else if(btn_style == "cancel") {
		style = button_style::cancel_button;
	} else if(btn_style == "yes_no") {
		style = button_style::yes_no_buttons;
	}
	if(style) {
		int result = gui2::show_message(title, message, *style, markup, markup);
		if(style == button_style::ok_cancel_buttons || style == button_style::yes_no_buttons) {
			lua_pushboolean(L, result == gui2::retval::OK);
			return 1;
		}
	} else {
		gui2::show_message(title, message, button, false, markup, markup);
	}
	return 0;
}

/**
 * Sets the value of a widget on the current dialog.
 * - Arg 1: scalar.
 * - Args 2..n: path of strings and integers.
 */
int intf_set_dialog_value(lua_State* L)
{
	gui2::widget *w = find_widget(L, 2, false);

	if(gui2::listbox* list = dynamic_cast<gui2::listbox*>(w))
	{
		if(lua_istable(L, 1)) {
			// Do two passes in case has_minimum is true
			// Probably not the best way to do it, but should work in the majority of cases.
			std::vector<int> selected_vec = lua_check<std::vector<int>>(L, 1);
			std::set<int> selected(selected_vec.begin(), selected_vec.end());
			for(unsigned i = 0; i < list->get_item_count(); i++) {
				list->select_row(i, selected.count(i + 1) > 0);
			}
			for(unsigned i = 0; i < list->get_item_count(); i++) {
				list->select_row(i, selected.count(i + 1) > 0);
			}
		} else {
			int v = luaL_checkinteger(L, 1);
			int n = list->get_item_count();
			if(1 <= v && v <= n) {
				list->select_row(v - 1);
			} else {
				return luaL_argerror(L, 1, "out of bounds");
			}
		}
	} else if(gui2::multi_page* multi_page = dynamic_cast<gui2::multi_page*>(w)) {
		int v = luaL_checkinteger(L, 1);
		int n = multi_page->get_page_count();
		if(1 <= v && v <= n) {
			multi_page->select_page(v - 1);
		} else {
			return luaL_argerror(L, 1, "out of bounds");
		}
	} else if (gui2::selectable_item* selectable = dynamic_cast<gui2::selectable_item*>(w)) {
		if(selectable->num_states() == 2) {
			selectable->set_value_bool(luaW_toboolean(L, 1));
		} else {
			selectable->set_value(luaL_checkinteger(L, 1) -1);
		}
	} else if (gui2::text_box* text_box = dynamic_cast<gui2::text_box*>(w)) {
		w->get_window()->invalidate_layout();
		const t_string& text = luaW_checktstring(L, 1);
		text_box->set_value(text.str());
	} else if (gui2::slider* slider = dynamic_cast<gui2::slider*>(w)) {
		const int v = luaL_checkinteger(L, 1);
		const int m = slider->get_minimum_value();
		const int n = slider->get_maximum_value();
		if(m <= v && v <= n) {
			slider->set_value(v);
		} else {
			return luaL_argerror(L, 1, "out of bounds");
		}
	} else if(gui2::progress_bar* progress_bar = dynamic_cast<gui2::progress_bar*>(w)) {
		const int v = luaL_checkinteger(L, 1);
		if(0 <= v && v <= 100) {
			progress_bar->set_percentage(v);
		} else {
			return luaL_argerror(L, 1, "out of bounds");
		}
	} else if(gui2::stacked_widget* stacked_widget = dynamic_cast<gui2::stacked_widget*>(w)) {
		if(lua_istable(L, 1)) {
			boost::dynamic_bitset<> states;
			states.resize(stacked_widget->get_layer_count());
			for(unsigned i : lua_check<std::vector<unsigned>>(L, 1)) {
				if(i > 0 && i <= stacked_widget->get_layer_count()) {
					states[i] = true;
				}
			}
			stacked_widget->select_layers(states);
		} else {
			const int v = luaL_checkinteger(L, 1);
			const int n = stacked_widget->get_layer_count();
			if(v >= 0 && v <= n) {
				stacked_widget->select_layer(v - 1);
			}
		}
	} else if(gui2::unit_preview_pane* unit_preview_pane = dynamic_cast<gui2::unit_preview_pane*>(w)) {
		w->get_window()->invalidate_layout();
		if(const unit_type* ut = luaW_tounittype(L, 1)) {
			unit_preview_pane->set_displayed_type(*ut);
		} else if(unit* u = luaW_tounit(L, 1)) {
			unit_preview_pane->set_displayed_unit(*u);
		} else {
			return luaW_type_error(L, 1, "unit or unit type");
		}
	} else if(gui2::tree_view_node* node = dynamic_cast<gui2::tree_view_node*>(w)) {
		const bool unfolded = luaW_toboolean(L, 1);
		if(unfolded) {
			node->unfold();
		} else {
			node->fold();
		}
	} else if(gui2::multimenu_button* menu = dynamic_cast<gui2::multimenu_button*>(w)) {
		if(lua_istable(L, 1)) {
			boost::dynamic_bitset<> states;
			states.resize(menu->num_options());
			for(unsigned i : lua_check<std::vector<unsigned>>(L, 1)) {
				if(i > 0 && i <= menu->num_options()) {
					states[i] = true;
				}
			}
			menu->select_options(states);
		} else {
			int v = luaL_checkinteger(L, 1);
			int n = menu->num_options();
			if(1 <= v && v <= n) {
				menu->select_option(v - 1);
			} else {
				return luaL_argerror(L, 1, "out of bounds");
			}
		}
	} else {
		w->get_window()->invalidate_layout();
		t_string v = luaW_checktstring(L, 1);
		gui2::styled_widget* c = dynamic_cast<gui2::styled_widget*>(w);
		if(!c) {
			return luaL_argerror(L, lua_gettop(L), "unsupported widget");
		}
		c->set_label(v);
	}

	return 0;
}

/**
 * Gets the value of a widget on the current dialog.
 * - Args 1..n: path of strings and integers.
 * - Ret 1: scalar.
 */
int intf_get_dialog_value(lua_State* L)
{
	gui2::widget *w = find_widget(L, 1, true);
	int num_rets = 1;

	if(gui2::listbox* list = dynamic_cast<gui2::listbox*>(w))
	{
		lua_pushinteger(L, list->get_selected_row() + 1);
		num_rets = 2;
		lua_newtable(L);
		int count = 0;
		for(unsigned i = 0; i < list->get_item_count(); i++) {
			if(list->row_selected(i)) {
				count++;
				lua_pushnumber(L, i + 1);
				lua_rawseti(L, -2, count);
			}
		}
	} else if(gui2::multi_page* multi_page = dynamic_cast<gui2::multi_page*>(w)) {
		lua_pushinteger(L, multi_page->get_selected_page() + 1);
	} else if(gui2::selectable_item* selectable = dynamic_cast<gui2::selectable_item*>(w)) {
		if(selectable->num_states() == 2) {
			lua_pushboolean(L, selectable->get_value_bool());
		} else {
			lua_pushinteger(L, selectable->get_value() + 1);
		}
	} else if(gui2::text_box* text_box = dynamic_cast<gui2::text_box*>(w)) {
		lua_pushstring(L, text_box->get_value().c_str());
	} else if(gui2::slider* slider = dynamic_cast<gui2::slider*>(w)) {
		lua_pushinteger(L, slider->get_value());
	} else if(gui2::progress_bar* progress_bar = dynamic_cast<gui2::progress_bar*>(w)) {
		lua_pushinteger(L, progress_bar->get_percentage());
	} else if(gui2::tree_view* tree_view = dynamic_cast<gui2::tree_view*>(w)) {
		std::vector<int> path = tree_view->selected_item()->describe_path();
		lua_createtable(L, path.size(), 0);
		for(size_t i = 0; i < path.size(); ++i) {
			lua_pushinteger(L, path[i] + 1);
			lua_rawseti(L, -2, i + 1);
		}
	} else if(gui2::stacked_widget* stacked_widget = dynamic_cast<gui2::stacked_widget*>(w)) {
		lua_pushinteger(L, stacked_widget->current_layer());
		num_rets = 2;
		lua_newtable(L);
		int count = 0;
		for(unsigned i = 0; i < stacked_widget->get_layer_count(); i++) {
			if(stacked_widget->layer_selected(i)) {
				count++;
				lua_pushnumber(L, i + 1);
				lua_rawseti(L, -2, count);
			}
		}
	} else if(gui2::multimenu_button* menu = dynamic_cast<gui2::multimenu_button*>(w)) {
		auto selected = menu->get_toggle_states();
		int count = 0;
		lua_newtable(L);
		for(unsigned i = 0; i < selected.size(); i++) {
			if(selected[i]) {
				count++;
				lua_pushnumber(L, i + 1);
				lua_rawseti(L, -2, count);
			}
		}
	} else {
		return luaL_argerror(L, lua_gettop(L), "unsupported widget");
	}

	return num_rets;
}
namespace
{
	void remove_treeview_node(gui2::tree_view_node& node, size_t pos, int number)
	{
		//Not tested yet.
		gui2::tree_view& tv = node.get_tree_view();
		if(pos >= node.count_children()) {
			return;
		}
		if(number <= 0 || number + pos > node.count_children()) {
			number = node.count_children() - pos;
		}
		for(int i = 0; i < number; ++i) {
			tv.remove_node(&node.get_child_at(pos));
		}
	}
}
/**
 * Removes an entry from a list.
 * - Arg 1: number, index of the element to delete.
 * - Arg 2: number, number of the elements to delete. (0 to delete all elements after index)
 * - Args 2..n: path of strings and integers.
 */
int intf_remove_dialog_item(lua_State* L)
{
	int pos = luaL_checkinteger(L, 1) - 1;
	int number = luaL_checkinteger(L, 2);
	gui2::widget* w = find_widget(L, 3, true);

	if(gui2::listbox* list = dynamic_cast<gui2::listbox*>(w))
	{
		list->remove_row(pos, number);
	} else if(gui2::multi_page* multi_page = dynamic_cast<gui2::multi_page*>(w)) {
		multi_page->remove_page(pos, number);
	} else if(gui2::tree_view* tree_view = dynamic_cast<gui2::tree_view*>(w)) {
		remove_treeview_node(tree_view->get_root_node(), pos, number);
	} else if(gui2::tree_view_node* tree_view_node = dynamic_cast<gui2::tree_view_node*>(w)) {
		remove_treeview_node(*tree_view_node, pos, number);
	} else {
		return luaL_argerror(L, lua_gettop(L), "unsupported widget");
	}

	return 1;
}

namespace { // helpers of intf_set_dialog_callback()
	void dialog_callback(gui2::widget& w)
	{
		int cb;
		{
			scoped_dialog::callback_map &m = scoped_dialog::current->callbacks;
			scoped_dialog::callback_map::const_iterator i = m.find(&w);
			if(i == m.end()) {
				return;
			}
			cb = i->second;
		}
		lua_State *L = scoped_dialog::current->L;
		lua_pushstring(L, dlgclbkKey);
		lua_rawget(L, LUA_REGISTRYINDEX);
		lua_rawgeti(L, -1, cb);
		lua_remove(L, -2);
		lua_call(L, 0, 0);
	}

	/** Helper struct for intf_set_dialog_callback. */
	struct dialog_callback_wrapper
	{
		void forward(gui2::widget* widget, bool& handled, bool& halt)
		{
			assert(widget);
			dialog_callback(*widget);
			handled = true;
			halt = true;
		}
	};
}//unnamed namespace for helpers of intf_set_dialog_callback()

/**
 * Sets a callback on a widget of the current dialog.
 * - Arg 1: function.
 * - Args 2..n: path of strings and integers.
 */
int intf_set_dialog_callback(lua_State* L)
{
	gui2::widget* w = find_widget(L, 2, true);

	scoped_dialog::callback_map &m = scoped_dialog::current->callbacks;
	scoped_dialog::callback_map::iterator i = m.find(w);
	if(i != m.end()) {
		lua_pushstring(L, dlgclbkKey);
		lua_rawget(L, LUA_REGISTRYINDEX);
		lua_pushnil(L);
		lua_rawseti(L, -2, i->second);
		lua_pop(L, 1);
		m.erase(i);
	}

	if(lua_isnil(L, 1)) {
		return 0;
	}

	if(gui2::clickable_item* c = dynamic_cast<gui2::clickable_item*>(w)) {
		static dialog_callback_wrapper wrapper;
		c->connect_click_handler(std::bind(&dialog_callback_wrapper::forward, wrapper, w, _3, _4));
	} else if(gui2::selectable_item* si = dynamic_cast<gui2::selectable_item*>(w)) {
		connect_signal_notify_modified(dynamic_cast<gui2::widget&>(*si), std::bind(dialog_callback, _1));
	} else if(gui2::integer_selector* is = dynamic_cast<gui2::integer_selector*>(w)) {
		connect_signal_notify_modified(dynamic_cast<gui2::widget&>(*is), std::bind(dialog_callback, _1));
	} else if(gui2::listbox* l = dynamic_cast<gui2::listbox*>(w)) {
		static dialog_callback_wrapper wrapper;
		connect_signal_notify_modified(*l, std::bind(&dialog_callback_wrapper::forward, wrapper, w, _3, _4));
	} else if(gui2::tree_view* tv = dynamic_cast<gui2::tree_view*>(w)) {
		tv->set_selection_change_callback(&dialog_callback);
	} else {
		return luaL_argerror(L, lua_gettop(L), "unsupported widget");
	}

	lua_pushstring(L, dlgclbkKey);
	lua_rawget(L, LUA_REGISTRYINDEX);
	int n = lua_rawlen(L, -1) + 1;
	m[w] = n;
	lua_pushvalue(L, 1);
	lua_rawseti(L, -2, n);
	lua_pop(L, 1);

	return 0;
}

/**
 * Enables/disables Pango markup on the label of a widget of the current dialog.
 * - Arg 1: boolean.
 * - Args 2..n: path of strings and integers.
 */
int intf_set_dialog_markup(lua_State* L)
{
	bool b = luaW_toboolean(L, 1);
	gui2::widget* w = find_widget(L, 2, true);
	gui2::styled_widget* c = dynamic_cast<gui2::styled_widget*>(w);
	if(!c) {
		return luaL_argerror(L, lua_gettop(L), "unsupported widget");
	}

	c->set_use_markup(b);
	return 0;
}

/**
 * Sets a canvas on a widget of the current dialog.
 * - Arg 1: integer.
 * - Arg 2: WML table.
 * - Args 3..n: path of strings and integers.
 */
int intf_set_dialog_canvas(lua_State* L)
{
	int i = luaL_checkinteger(L, 1);
	gui2::widget* w = find_widget(L, 3, true);
	gui2::styled_widget* c = dynamic_cast<gui2::styled_widget*>(w);
	if(!c) {
		return luaL_argerror(L, lua_gettop(L), "unsupported widget");
	}

	std::vector<gui2::canvas> &cv = c->get_canvases();
	if(i < 1 || static_cast<unsigned>(i) > cv.size()) {
		return luaL_argerror(L, 1, "out of bounds");
	}

	config cfg = luaW_checkconfig(L, 2);
	cv[i - 1].set_cfg(cfg);
	c->set_is_dirty(true);
	return 0;
}

/**
 * Sets a widget to have the focus
 * - Args 1..n: path of strings and integers.
 */
int intf_set_dialog_focus(lua_State* L)
{
	gui2::widget* w = find_widget(L, 1, true);
	scoped_dialog::current->window->keyboard_capture(w);
	return 0;
}

/**
 * Sets a widget's state to active or inactive
 * - Arg 1: boolean.
 * - Args 2..n: path of strings and integers.
 */
int intf_set_dialog_active(lua_State* L)
{
	const bool b = luaW_toboolean(L, 1);
	gui2::widget* w = find_widget(L, 2, true);
	gui2::styled_widget* c = dynamic_cast<gui2::styled_widget*>(w);
	if(!c) {
		return luaL_argerror(L, lua_gettop(L), "unsupported widget");
	}

	c->set_active(b);
	return 0;
}

/**
 * Sets the visiblity of a widget in the current dialog.
 * - Arg 1: boolean.
 * - Args 2..n: path of strings and integers.
 */
int intf_set_dialog_visible(lua_State* L)
{
	typedef gui2::styled_widget::visibility visibility;

	visibility flag = visibility::visible;

	switch(lua_type(L, 1)) {
		case LUA_TBOOLEAN:
			flag = luaW_toboolean(L, 1)
					? visibility::visible
					: visibility::invisible;
			break;
		case LUA_TSTRING:
			{
				const std::string& str = lua_tostring(L, 1);
				if(str == "visible") {
					flag = visibility::visible;
				} else if(str == "hidden") {
					flag = visibility::hidden;
				} else if(str == "invisible") {
					flag = visibility::invisible;
				} else {
					return luaL_argerror(L, 1, "string must be one of: visible, hidden, invisible");
				}
			}
			break;
		default:
			return luaW_type_error(L, 1, "boolean or string");
	}

	gui2::widget* w = find_widget(L, 2, true);
	gui2::styled_widget* c = dynamic_cast<gui2::styled_widget*>(w);
	if(!c) {
		return luaL_argerror(L, lua_gettop(L), "unsupported widget");
	}

	c->set_visible(flag);

	if(flag == visibility::hidden) {
		// HACK: this is needed to force the widget to be repainted immediately
		//       to get rid of its ghost image.
		scoped_dialog::current->window->invalidate_layout();
	}

	return 0;
}

int show_lua_console(lua_State* /*L*/, lua_kernel_base* lk)
{
	gui2::dialogs::lua_interpreter::display(lk);
	return 0;
}

int show_gamestate_inspector(const vconfig& cfg, const game_data& data, const game_state& state)
{
	gui2::dialogs::gamestate_inspector inspect_dialog(data.get_variables(), *state.events_manager_, state.board_, cfg["name"]);
	inspect_dialog.show();
	return 0;
}

/**
 * Sets a widget's state to active or inactive
 * - Arg 1: string, the type (id of [node_definition]) of the new node.
 * - Arg 3: integer, where to insert the new node.
 * - Args 3..n: path of strings and integers.
 */

int intf_add_dialog_tree_node(lua_State* L)
{
	const std::string node_type = luaL_checkstring(L, 1);
	const int insert_pos = luaL_checkinteger(L, 2);
	static const std::map<std::string, string_map> data;
	gui2::widget* w = find_widget(L, 3, false);

	if(gui2::tree_view_node* twn = dynamic_cast<gui2::tree_view_node*>(w)) {
		twn->add_child(node_type, data, insert_pos);
	} else if(gui2::tree_view* tw = dynamic_cast<gui2::tree_view*>(w)) {
		tw->get_root_node().add_child(node_type, data, insert_pos);
	} else if(gui2::multi_page* mp = dynamic_cast<gui2::multi_page*>(w)) {
		mp->add_page(node_type, insert_pos, data);
	} else {
		return luaL_argerror(L, lua_gettop(L), "unsupported widget");
	}
	return 0;
}

/**
 * - Arg 1: string, widget type
 * - Arg 3: string, id
 * - Arg 3: config,
 */

int intf_add_widget_definition(lua_State* L)
{
	std::string type = luaL_checkstring(L, 1);
	std::string id = luaL_checkstring(L, 2);
	try {
		if(gui2::add_single_widget_definition(type, id, luaW_checkconfig(L, 3))) {
			lua_kernel_base::get_lua_kernel<lua_kernel_base>(L).add_widget_definition(type, id);
		}
	} catch(const std::invalid_argument& e) {
		return luaL_argerror(L, 1, e.what());
	}
	return 0;
}
} // end namespace lua_gui2
