/*
 * Copyright (C) Volition, Inc. 1999.  All rights reserved.
 *
 * All source code herein is the property of Volition, Inc. You may not sell 
 * or otherwise commercially exploit the source or things you created based on the 
 * source.
 *
*/



#include <cstdarg>

#include "anim/animplay.h"
#include "freespace.h"
#include "gamesequence/gamesequence.h"
#include "gamesnd/gamesnd.h"
#include "globalincs/alphacolors.h"
#include "graphics/font.h"
#include "io/key.h"
#include "io/mouse.h"
#include "osapi/osapi.h"
#include "parse/parselo.h"
#include "playerman/player.h"
#include "popup/popup.h"
#include "popup/popupdead.h"
#include "scripting/global_hooks.h"
#include "ui/ui.h"


#define POPUP_MAX_CHOICES			3					// max number of buttons allowed on popup

#define POPUP_MAX_LINE_CHARS		256				// max chars of msg text allowed per line
#define POPUP_MAX_LINES				30					// max lines of text allowed
#define POPUP_MAX_CHARS				2048				// total max chars 
#define POPUP_INPUT_MAX_CHARS		255				// max length of input string
#define POPUP_MAX_VALID_CHARS		32				// maximum number of unique non-alphanumeric characters in the input whitelist

#define POPUP_NOCHANGE				100
#define POPUP_ABORT					101

int Popup_max_display[GR_NUM_RESOLUTIONS] = {
	11,
	19
};

const char *Popup_slider_name[GR_NUM_RESOLUTIONS] = {
	"slider",
	"2_slider"
};

int Popup_slider_coords[GR_NUM_RESOLUTIONS][4] = {
	{ // GR_640
		121, 109, 15, 105
	},
	{ // GR_1024
		195, 177, 30, 173
	}
};

////////////////////////////////////////////////////////////////
// Internal popup flags
////////////////////////////////////////////////////////////////
#define PF_INPUT						(1<<0)			// contents of the box is an inputbox and a caption

////////////////////////////////////////////////////////////////
// Popup data struct
////////////////////////////////////////////////////////////////
typedef struct popup_info
{
	int	nchoices;														// number of choices user can pick
	char	*button_text[POPUP_MAX_CHOICES];							// button text
	int	keypress[POPUP_MAX_CHOICES];								// button keypress shortcut
	int	shortcut_index[POPUP_MAX_CHOICES];						// what char should be underlines for shortcut
	char	raw_text[POPUP_MAX_CHARS];									// the unbroken text for the popup
	char	title[POPUP_MAX_LINE_CHARS];								// title text for popup (optional)
	int	nlines;
	char	msg_lines[POPUP_MAX_LINES][POPUP_MAX_LINE_CHARS];	// lines of text in popup
	char	input_text[POPUP_INPUT_MAX_CHARS];						// input box text (if this is an inputbox popup)
	int	max_input_text_len;
	int	web_cursor_flag[POPUP_MAX_CHOICES];						// flag for using web cursor over button
	char valid_chars[POPUP_MAX_VALID_CHARS];					// whitelist of non-alphanumeric characters used for popup_input
} popup_info;

////////////////////////////////////////////////////////////////
// UI Data and constants
////////////////////////////////////////////////////////////////
UI_WINDOW	Popup_window;
UI_BUTTON	Popup_buttons[POPUP_MAX_CHOICES];			// actual lit buttons
UI_BUTTON	Popup_button_regions[POPUP_MAX_CHOICES];	// fake buttons used for mouse detection over text
UI_INPUTBOX	Popup_input;										// input box for the popup
UI_SLIDER2	Popup_slider;										// if we have more text in the popup than can be displayed at once

// extents for message portion of popup
int Popup_text_coords[GR_NUM_RESOLUTIONS][4] = {
	{ // GR_640
		137, 106, 343, 113
	},
	{ // GR_1024
		219, 169, 558, 182
	}
};

// input popup info
// offset from the first y text line value to place the centered input box
int Popup_input_y_offset[GR_NUM_RESOLUTIONS] = {
	40, 
	40
};

// offset from the first y text line value to start drawing text
int Popup_input_text_y_offset[GR_NUM_RESOLUTIONS] = {
	30,
	30
};

typedef struct popup_background
{
	const char *filename;							// filename for background
	int	coords[2];							// coords to draw background at
} popup_background;

////////////////////////////////////////////////////////////////
// Module globals
////////////////////////////////////////////////////////////////
static int Popup_is_active=0;
static int Popup_should_die=0;			// popup should quit during the next iteration of its loop

static popup_info Popup_info;
static int Popup_flags;
static int Popup_screen_id = -1;

static bool Popup_time_was_stopped_in_init = false;

static int Title_coords[GR_NUM_RESOLUTIONS][5] =
{
	{ // GR_640
		137,		// x-left
		106,		// y-top
		343,		// width
		26,		//	height
		308		// center
	},
	{ // GR_1024
		220,		// x-left
		169,		// y-top
		553,		// width
		26,		//	height
		496		// center
	}
};

static int Button_regions[GR_NUM_RESOLUTIONS][3][4] = {
	{ // GR_640		
		{464, 232, 510, 250},		// upper right pixel of text, lower right pixel of button
		{464, 262, 510, 279},		
		{464, 292, 510, 308}		
	},
	{ // GR_1024
		{752, 373, 806, 406},		// upper right pixel of text, lower right pixel of button
		{752, 421, 806, 461},		
		{752, 468, 806, 506}		
	}
};

static int Button_coords[GR_NUM_RESOLUTIONS][3][2] =
{
	{ // GR_640
		{474, 224},		// upper left pixel
		{474, 258},
		{474, 286}		
	},
	{ // GR_1024
		{758, 358},		// upper left pixel
		{758, 413},
		{758, 458},		
	}
};

static popup_background Popup_background[GR_NUM_RESOLUTIONS][3] = 
{
	{ // GR_640
		{ "Pop2",	{ 129, 99 } },
		{ "Pop2",	{ 129, 99 } },
		{ "Pop3",	{ 129, 99 } }
	},
	{ // GR_1024
		{ "2_Pop2",	{ 206, 158 } },
		{ "2_Pop2",	{ 206, 158 } },
		{ "2_Pop3",	{ 206, 158 } }
	}
};

#define BUTTON_NEGATIVE				0
#define BUTTON_POSITIVE				1
#define BUTTON_GENERIC_FIRST		2
#define BUTTON_GENERIC_SECOND		3
#define BUTTON_GENERIC_THIRD		4
static const char *Popup_button_filenames[GR_NUM_RESOLUTIONS][2][5] =
{
	{ // GR_640
		{"Pop_00",				// negative
		"Pop_01",				// positive
		"Pop_02",				// first generic
		"Pop_03",				// second generic
		"Pop_04"},				// third generic

		{"Pop_00",				// negative
		"Pop_01",				// positive
		"PopD_00",				// first generic
		"PopD_01",				// second generic
		"PopD_02"},				// third generic
	},
	{ // GR_1024
		{"2_Pop_00",			// negative
		"2_Pop_01",				// positive
		"2_Pop_02",				// first generic
		"2_Pop_03",				// second generic
		"2_Pop_04"},			// third generic

		{"2_Pop_00",			// negative
		"2_Pop_01",				// positive
		"2_PopD_00",			// first generic
		"2_PopD_01",			// second generic
		"2_PopD_02"},			// third generic
	}
};

int Popup_running_state;
int Popup_default_choice;	// which choice is highlighted (ie what gets choosen when enter is pressed)

// see if any popup buttons have been pressed
// exit: POPUP_NOCHANGE		=> no buttons pressed
//			>=0					=>	button index that was pressed
int popup_check_buttons(popup_info *pi)
{
	int			i;
	UI_BUTTON	*b;

	for ( i = 0; i < pi->nchoices; i++ ) {
		b = &Popup_button_regions[i];
		if ( b->pressed() ) {
			return i;
		}

		b = &Popup_buttons[i];
		if ( b->pressed() ) {
			return i;
		}
	}

	return POPUP_NOCHANGE;
}

// maybe play a sound when key up/down is pressed to switch default choice
void popup_play_default_change_sound(popup_info *pi)
{
	if ( pi->nchoices > 1 ) {
		int i, mouse_over=0;
		UI_BUTTON *br, *b;

		// only play if mouse not currently highlighting a choice

		for ( i = 0; i < pi->nchoices; i++ ) {
			br = &Popup_button_regions[i];
			b = &Popup_buttons[i];
			if ( br->button_down() ) {
				mouse_over=1;
				break;
			}

			if ( br->button_hilighted() && !b->button_down() ) {
				mouse_over=1;
				break;
			}

			if ( b->button_hilighted() ) {
				mouse_over=1;
			}
		}

		if (!mouse_over) {
			gamesnd_play_iface(InterfaceSounds::USER_SELECT);
		}
	}
}

// do any key processing here
// input:	pi					=>	data about the popup
//				k					=> key that was pressed
//
// exit: 0 .. nchoices-1	=> choice selected through keypress
//			POPUP_ABORT			=>	abort the popup
//			POPUP_NOCHANGE		=> nothing happenned
int popup_process_keys(popup_info *pi, int k, int flags)
{
	if ( k <= 0 ) {
		return POPUP_NOCHANGE;
	}

	for ( int i = 0; i < pi->nchoices; i++ ) {
		if ( pi->keypress[i] == key_to_ascii(k) ) {
			Popup_default_choice=i;
			Popup_buttons[i].press_button();
			return i;
		}
	}
	
	switch(k) {

	case KEY_ENTER:
		// select the current default choice
		return Popup_default_choice;
		break;

	case KEY_ESC:
		// only process the escape key if this flag is not set
		if(!(flags & PF_IGNORE_ESC)){
			return POPUP_ABORT;
		}
		break;

	case KEY_DOWN:
	case KEY_PAD2:
	case KEY_TAB:
		popup_play_default_change_sound(pi);
		Popup_default_choice++;
		if ( Popup_default_choice >= pi->nchoices ) {
			Popup_default_choice=0;
		}
		break;

	case KEY_UP:
	case KEY_PAD8:
	case KEY_SHIFTED+KEY_TAB:
		popup_play_default_change_sound(pi);
		Popup_default_choice--;
		if ( Popup_default_choice < 0 ) {
			Popup_default_choice=pi->nchoices-1;
		}
		break;

	default:
		break;
	} // end switch

	return POPUP_NOCHANGE;
}

// Split off the title and break up the body lines
void popup_split_lines(popup_info *pi, int flags)
{
	int	nlines, i, body_offset = 0;
	int	n_chars[POPUP_MAX_LINES];
	const char	*p_str[POPUP_MAX_LINES];

	font::set_font(font::FONT1);
	n_chars[0]=0;

	nlines = split_str(pi->raw_text, 1000, n_chars, p_str, POPUP_MAX_LINES, POPUP_MAX_LINE_CHARS);
	Assert(nlines >= 0 && nlines <= POPUP_MAX_LINES );

	if ( flags & (PF_TITLE | PF_TITLE_BIG) ) {
		// get first line out
		strncpy(pi->title, p_str[0], n_chars[0]);
		pi->title[n_chars[0]] = 0;
		body_offset = 1;
	}

	if ( flags & PF_BODY_BIG ) {
		font::set_font(font::FONT2);
	}

	nlines = split_str(pi->raw_text, Popup_text_coords[gr_screen.res][2], n_chars, p_str, POPUP_MAX_LINES, POPUP_MAX_LINE_CHARS);
	Assert(nlines >= 0 && nlines <= POPUP_MAX_LINES );

	pi->nlines = nlines - body_offset;

	for ( i = 0; i < pi->nlines; i++ ) {
		Assert(n_chars[i+body_offset] < POPUP_MAX_LINE_CHARS);
		strncpy(pi->msg_lines[i], p_str[i+body_offset], n_chars[i+body_offset]);
		pi->msg_lines[i][n_chars[i+body_offset]] = 0;
	}

	font::set_font(font::FONT1);
}

// figure out what filename to use for the button icon
const char *popup_get_button_filename(popup_info *pi, int i, int flags)
{
	const char *fname = NULL;
	int is_tiny=0;	

	// check for special button texts and if found, use specialized buttons for them.
	if ((!stricmp(pi->button_text[i], POPUP_OK + 1) || !stricmp(pi->button_text[i], POPUP_YES + 1)) && !(flags & PF_NO_SPECIAL_BUTTONS)){
		return Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_POSITIVE];
	}

	if ((!stricmp(pi->button_text[i], POPUP_CANCEL + 1) || !stricmp(pi->button_text[i], POPUP_NO + 1)) && !(flags & PF_NO_SPECIAL_BUTTONS)){
		return Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_NEGATIVE];
	}

	switch (pi->nchoices) {
	case 0:
		fname = "";
		break;
	case 1:
		if ( (flags & PF_USE_AFFIRMATIVE_ICON) && !(flags & PF_NO_SPECIAL_BUTTONS) ) {
			fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_POSITIVE];
		} else if ( flags & PF_USE_NEGATIVE_ICON && !(flags & PF_NO_SPECIAL_BUTTONS) ) {
			fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_NEGATIVE];
		} else {
			fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_FIRST];
		}
		break;

	case 2:
		if ( flags & PF_USE_NEGATIVE_ICON && i==0 ) {
			fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_NEGATIVE];
			break;
		} 

		if ( flags & PF_USE_AFFIRMATIVE_ICON && i==1 ) {
			fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_POSITIVE];
			break;
		} 

		if ( i == 0 ) {
			fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_FIRST];
		} else {
			fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_SECOND];
		}

		break;

	case 3:
		switch(i) {
		case 0:
			fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_FIRST];
			break;
		case 1:
			fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_SECOND];
			break;
		case 2:
			fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_THIRD];
			break;
		}
		break;
	}

	return fname;
}

// bogus handling function for slider (we don't need it to do anything)
void popup_slider_bogus()
{
}

// init the Popup window
int popup_init(popup_info *pi, int flags)
{
	int					i;
	UI_BUTTON			*b;
	popup_background	*pbg;
	const char			*fname;

	if(pi->nchoices == 0){
		pbg = &Popup_background[gr_screen.res][0];
	} else {
		pbg = &Popup_background[gr_screen.res][pi->nchoices-1];
	}

	// anytime in single player, and multiplayer, not in mission, go ahead and stop time
	Popup_time_was_stopped_in_init = false;
	if ( (Game_mode & GM_NORMAL) || ((Game_mode & GM_MULTIPLAYER) && !(Game_mode & GM_IN_MISSION)) ){
		if (!game_time_is_stopped()) {
			game_stop_time();
			Popup_time_was_stopped_in_init = true;
		}
	}

	Popup_default_choice = 0;
	Popup_should_die = 0;

	if (flags & PF_RUN_STATE) {
		Popup_running_state = 1;
	}
	else {
		Popup_running_state = 0;
	}

	if (scripting::hooks::OnDialogInit->isActive())
	{
		luacpp::LuaTable buttons = luacpp::LuaTable::create(Script_system.GetLuaSession());
		for (int cnt = 0; cnt < pi->nchoices; cnt++) {
			luacpp::LuaTable button = luacpp::LuaTable::create(Script_system.GetLuaSession());
			int positivity = 0;
			switch (pi->nchoices) {
			case 1:
				if (!(flags & PF_NO_SPECIAL_BUTTONS)) {
					if (flags & PF_USE_AFFIRMATIVE_ICON)
						positivity = 1;
					else if (flags & PF_USE_NEGATIVE_ICON)
						positivity = -1;
				}
				break;
			case 2:
				if (flags & PF_USE_NEGATIVE_ICON && cnt == 0)
					positivity = -1;
				if (flags & PF_USE_AFFIRMATIVE_ICON && cnt == 1)
					positivity = 1;
				break;
			default:
				//For 0 or 3 buttons, all buttons are netural
				break;
			}
			button.addValue("Positivity", luacpp::LuaValue::createValue(Script_system.GetLuaSession(), positivity));
			button.addValue("Text", luacpp::LuaValue::createValue(Script_system.GetLuaSession(), pi->button_text[cnt]));
			//FSO currently uses the ASCII keycode for these shortcuts, so just convert that back to the key, package it, and send it to Lua
			SCP_string thisKey(1, (char)pi->keypress[cnt]);
			button.addValue("Shortcut", luacpp::LuaValue::createValue(Script_system.GetLuaSession(), thisKey.c_str()));
			buttons.addValue(cnt + 1, button);
		}

		auto paramList = scripting::hook_param_list(
			scripting::hook_param("Choices", 't', buttons),
			scripting::hook_param("IsTimeStopped", 'b', Popup_time_was_stopped_in_init),
			scripting::hook_param("IsStateRunning", 'b', static_cast<bool>(Popup_running_state)),
			scripting::hook_param("IsInputPopup", 'b', static_cast<bool>(flags & PF_INPUT)),
			scripting::hook_param("IsDeathPopup", 'b', false),
			scripting::hook_param("Title", 's', pi->title),
			scripting::hook_param("Text", 's', pi->raw_text),
			scripting::hook_param("AllowedInput", 's', pi->valid_chars, flags & PF_INPUT));

		scripting::hooks::OnDialogInit->run(paramList);
		if (scripting::hooks::OnDialogInit->isOverride(std::move(paramList)))
			return 0;
	}

	// create base window
	Popup_window.create(pbg->coords[0], pbg->coords[1], Popup_text_coords[gr_screen.res][2]+100, Popup_text_coords[gr_screen.res][3]+50, 0);
	Popup_window.set_foreground_bmap(pbg->filename);

	// create buttons
	for (i=0; i<pi->nchoices; i++) {
		b = &Popup_buttons[i];
		// accommodate single-choice positive icon being positioned differently
		if ( (pi->nchoices == 1) && (flags&PF_USE_AFFIRMATIVE_ICON) ) {
			b->create(&Popup_window, "", Button_coords[gr_screen.res][i+1][0], Button_coords[gr_screen.res][i+1][1], 30, 25, 0, 1);
		} else {
			b->create(&Popup_window, "", Button_coords[gr_screen.res][i][0], Button_coords[gr_screen.res][i][1], 30, 25, 0, 1);
		}

		fname = popup_get_button_filename(pi, i, flags);
		b->set_bmaps(fname, 3, 0);
		b->set_highlight_action(common_play_highlight_sound);
		if ( pi->keypress[i] >= 0 ) {
			b->set_hotkey(pi->keypress[i]);
		}

		// create invisible buttons to detect mouse presses... can't use mask since button region is dynamically sized
		int lx, w, h;

		gr_get_string_size(&w, &h, pi->button_text[i]);
		lx = Button_regions[gr_screen.res][i][0] - w;
		b = &Popup_button_regions[i];	

		// accommodate single-choice positive icon being positioned differently
		if ( (pi->nchoices == 1) && (flags&PF_USE_AFFIRMATIVE_ICON) ) {
			b->create(&Popup_window, "", lx, Button_regions[gr_screen.res][i+1][1], Button_regions[gr_screen.res][i+1][2]-lx, Button_regions[gr_screen.res][i+1][3]-Button_regions[gr_screen.res][i+1][1], 0, 1);
		} else {
			b->create(&Popup_window, "", lx, Button_regions[gr_screen.res][i][1], Button_regions[gr_screen.res][i][2]-lx, Button_regions[gr_screen.res][i][3]-Button_regions[gr_screen.res][i][1], 0, 1);
		}

		b->hide();
	}

	// webcursor setup
	if (Web_cursor != NULL) {
		if (flags & PF_WEB_CURSOR_1) {
			Popup_buttons[1].set_custom_cursor(Web_cursor);
		}
		if (flags & PF_WEB_CURSOR_2) {
			Popup_buttons[2].set_custom_cursor(Web_cursor);
		}
	}

	// if this is an input popup, create and center the popup
	if(flags & PF_INPUT){
		Popup_input.create(&Popup_window, Popup_text_coords[gr_screen.res][0], pbg->coords[1] + Popup_input_y_offset[gr_screen.res], Popup_text_coords[gr_screen.res][2], pi->max_input_text_len, pi->input_text, UI_INPUTBOX_FLAG_INVIS | UI_INPUTBOX_FLAG_ESC_CLR | UI_INPUTBOX_FLAG_ESC_FOC | UI_INPUTBOX_FLAG_KEYTHRU | UI_INPUTBOX_FLAG_TEXT_CEN);
		Popup_input.set_focus();
		Popup_input.set_valid_chars(pi->valid_chars);
	}	

	popup_split_lines(pi, flags);

	// create the popup slider (which we may not need to use
	Popup_slider.create(&Popup_window, Popup_slider_coords[gr_screen.res][0], Popup_slider_coords[gr_screen.res][1], Popup_slider_coords[gr_screen.res][2], Popup_slider_coords[gr_screen.res][3], pi->nlines > Popup_max_display[gr_screen.res] ? pi->nlines - Popup_max_display[gr_screen.res] : 0,
								Popup_slider_name[gr_screen.res], popup_slider_bogus, popup_slider_bogus, NULL);

	return 0;
}

// called when a popup goes away
void popup_close(popup_info *pi, int screen_id)
{
	int i;
	
	gamesnd_play_iface(InterfaceSounds::POPUP_DISAPPEAR); 	// play sound when popup disappears

	for (i=0; i<pi->nchoices; i++ )	{
		if ( pi->button_text[i] != NULL ) {
			vm_free(pi->button_text[i]);
			pi->button_text[i] = NULL;
		}
	}

	if(screen_id >= 0){
		gr_free_screen(screen_id);	
	}
	Popup_window.destroy();
	anim_ignore_next_frametime();					// to avoid skips in animation since next frametime is saturated
	game_flush();

	Popup_is_active = 0;
	Popup_running_state = 0;

	// if we had previously stopped time, go ahead and resume time
	if ( Popup_time_was_stopped_in_init )
		game_start_time();


	if (scripting::hooks::OnDialogClose->isActive())
		scripting::hooks::OnDialogClose->run(scripting::hook_param_list(
			scripting::hook_param("IsDeathPopup", 'b', false))
		);
}

// set the popup text color
void popup_set_text_color(int flags)
{
	if ( flags & PF_BODY_RED ) {
		gr_set_color_fast(&Color_red);
		return;
	}

	if ( flags & PF_BODY_GREEN ) {
		gr_set_color_fast(&Color_green);
		return;
	}

	if ( flags & PF_BODY_BLUE ) {
		gr_set_color_fast(&Color_blue);
		return;
	}

	gr_set_color_fast(&Color_bright_blue);
}

// set the popup text color
void popup_set_title_color(int flags)
{
	if ( flags & PF_TITLE_RED ) {
		gr_set_color_fast(&Color_red);
		return;
	}

	if ( flags & PF_TITLE_GREEN ) {
		gr_set_color_fast(&Color_green);
		return;
	}

	if ( flags & PF_TITLE_BLUE ) {
		gr_set_color_fast(&Color_blue);
		return;
	}

	if ( flags & PF_TITLE_WHITE ) {
		gr_set_color_fast(&Color_bright_white);
		return;
	}

	gr_set_color_fast(&Color_bright_blue);
}

// Draw the title centered within the popup
void popup_draw_title(int sy, char *line, int flags)
{
	int w, h, sx;

	if ( flags & PF_TITLE_BIG ) {
		font::set_font(font::FONT2);
	} else {
		font::set_font(font::FONT1);
	}

	gr_get_string_size(&w, &h, line);
	sx = (int)std::lround(Title_coords[gr_screen.res][4] - w/2.0f);

	popup_set_title_color(flags);
	gr_string(sx,sy,line,GR_RESIZE_MENU);
}

// calculate the starting display index
int popup_calc_starting_index(popup_info *pi)
{
	// we're basically ignoring any titles here. 
	if(pi->nlines <= Popup_max_display[gr_screen.res]){
		return 0;
	}

	// otherwise, we want to see what item index the slider is on
	return Popup_slider.get_currentItem();
}

// Figure out the y-coord to start drawing the popup text.  The text
// is centered vertically within the popup.
int popup_calc_starting_y(popup_info *pi, int flags)
{
	int sy, total_h=0;
	int num_lines = pi->nlines > Popup_max_display[gr_screen.res] ? Popup_max_display[gr_screen.res] : pi->nlines;

	if ( flags & (PF_TITLE | PF_TITLE_BIG) ) {
		if ( flags & PF_TITLE_BIG ) {
			font::set_font(font::FONT2);
		} else {
			font::set_font(font::FONT1);
		}
		total_h += gr_get_font_height();
	}

	if ( flags & PF_BODY_BIG ) {
		font::set_font(font::FONT2);
	} else {
		font::set_font(font::FONT1);
	}

	total_h += num_lines * gr_get_font_height();
	sy = (int)std::lround((Popup_text_coords[gr_screen.res][1] + Popup_text_coords[gr_screen.res][3]/2.0f) - total_h/2.0f);

	// if this is an input style box, add in some y
	if(flags & PF_INPUT){
		sy += Popup_input_text_y_offset[gr_screen.res];
	}

	return sy;
}

// Draw the message text nicely formatted in the popup
void popup_draw_msg_text(popup_info *pi, int flags)
{
	int sx, sy, i, w, h;
	int line_index;
	int line_count;

	// figure out the starting display 
	line_index = popup_calc_starting_index(pi);

	// figure out the starting y:
	sy = popup_calc_starting_y(pi, flags);

	// draw title if required
	if ( flags & (PF_TITLE | PF_TITLE_BIG) ) {
		popup_draw_title(sy, pi->title, flags);
		sy += gr_get_font_height();
	}

	// draw body 
	if ( flags & PF_BODY_BIG ) {
		font::set_font(font::FONT2);
	} else {
		font::set_font(font::FONT1);
	}

	popup_set_text_color(flags);
	line_count = 0;
	for ( i = line_index; i < pi->nlines; i++, line_count++ ) {
		// if we've already displayed the max # of lines
		if(line_count >= Popup_max_display[gr_screen.res]){
			break;
		}

		gr_get_string_size(&w, &h, pi->msg_lines[i]);
		sx = (int)std::lround(Title_coords[gr_screen.res][4] - w/2.0f);
		gr_string(sx, sy + line_count * h, pi->msg_lines[i], GR_RESIZE_MENU);
	}

	// maybe draw "more"
	h = 10;
	if(i < pi->nlines){
		gr_set_color_fast(&Color_more_bright);
		gr_string(Title_coords[gr_screen.res][4], sy + (Popup_max_display[gr_screen.res]) * h, XSTR("More", 459), GR_RESIZE_MENU);
	}

	font::set_font(font::FONT1);	// reset back to regular font size
}

// Draw the button text nicely formatted in the popup
void popup_draw_button_text(popup_info *pi, int flags)
{
	int w, h, i, sx, sy;

	gr_set_color_fast(&Color_bright_blue);

	for ( i=0; i < pi->nchoices; i++ ) {
		gr_get_string_size(&w, &h, pi->button_text[i]);

		if ( (pi->nchoices == 1) && (flags&PF_USE_AFFIRMATIVE_ICON) ) {
			sx = Button_regions[gr_screen.res][i+1][0]-w;
			sy = Button_regions[gr_screen.res][i+1][1]+4;
		} else {
			sx = Button_regions[gr_screen.res][i][0]-w;
			sy = Button_regions[gr_screen.res][i][1]+4;
		}

		gr_string(sx, sy, pi->button_text[i], GR_RESIZE_MENU);

		// figure out where to draw underline char
		if ( pi->shortcut_index[i] > 0 ) {
			int	cut=pi->shortcut_index[i];
			char	save_char=pi->button_text[i][cut];
			pi->button_text[i][cut] = 0;
			gr_get_string_size(&w, &h, pi->button_text[i]);
			pi->button_text[i][cut] = save_char;
			sx += w;
		}
		
		if ( pi->shortcut_index[i] >= 0 ) {
			gr_printf_menu(sx, sy, NOX("%c"), 95);
		}
	}
}

// See if any of the button should change appearance based on mouse position
void popup_force_draw_buttons(popup_info *pi)
{
	int i,mouse_is_highlighting=0;
	UI_BUTTON *br, *b;

	for ( i = 0; i < pi->nchoices; i++ ) {
		br = &Popup_button_regions[i];
		b = &Popup_buttons[i];
		if ( br->button_down() ) {
			b->draw_forced(2);
			mouse_is_highlighting=1;
			continue;
		}

		if ( (b->button_hilighted()) || (br->button_hilighted() && !b->button_down()) ) {
			Popup_default_choice=i;
			mouse_is_highlighting=1;
			b->draw_forced(1);
		}
	}

	// Only if mouse is not highlighting an option, let the default choice be drawn highlighted
	if ( (!mouse_is_highlighting) && (pi->nchoices>1) ) {
		for ( i = 0; i < pi->nchoices; i++ ) {
			b = &Popup_buttons[i];
			// highlight the default choice
			if ( i == Popup_default_choice ) {
				b->draw_forced(1);
			}
		}
	}
}

bool popup_resolve_scripting(lua_State* L, int& choice, char(&input)[POPUP_INPUT_MAX_CHARS], const luacpp::LuaValueList& arguments) {
	if (arguments.empty() || !arguments[0].isValid()) {
		choice = POPUP_ABORT;
		return true;
	}

	auto type = arguments[0].getValueType();
	
	if (type == luacpp::ValueType::NUMBER) {
		choice = arguments[0].getValue<int>();
	}
	else if (type == luacpp::ValueType::STRING) {
		const SCP_string& text = arguments[0].getValue<SCP_string>();
		if (text.size() >= POPUP_INPUT_MAX_CHARS) {
			LuaError(L, "String %s is too long for a dialog result! Maximum length is %d.", text.c_str(), POPUP_INPUT_MAX_CHARS - 1);
			return false;
		}
		strcpy_s(input, text.c_str());
	}
	else if (type == luacpp::ValueType::NIL) {
		choice = POPUP_ABORT;
	}
	else {
		LuaError(L, "Invalid type supplied to dialog submit function!");
		return false;
	}

	return true;
}

// exit: -1						=>	error
//			0..nchoices-1		=> choice
int popup_do(popup_info *pi, int flags)
{
	int screen_id = -1, choice = -1, done = 0;

	if ( popup_init(pi, flags) == -1 ){
		return -1;
	}

	if ( !(flags & PF_RUN_STATE) ) {
		screen_id = gr_save_screen();
	}

	int old_max_w_unscaled = gr_screen.max_w_unscaled;
	int old_max_h_unscaled = gr_screen.max_h_unscaled;
	int old_max_w_unscaled_zoomed = gr_screen.max_w_unscaled_zoomed;
	int old_max_h_unscaled_zoomed = gr_screen.max_h_unscaled_zoomed;

	gr_reset_screen_scale();

	while(!done) {
		int k;

		os_poll();

		// if we were killed by a call to popup_kill_any_active(), kill the popup
		if(Popup_should_die){
			choice = -1;
			break;
		}

		// if we're flagged as should be running the state underneath, then do so
		if(flags & PF_RUN_STATE){
			game_do_state(gameseq_get_state());
		}
		// otherwise just run the common functions (for networking,etc)
		else {
			game_set_frametime(-1);
			game_do_state_common(gameseq_get_state(),flags & PF_NO_NETWORKING);	// do stuff common to all states 
		}

		// don't draw anything 
		if (!(flags & PF_RUN_STATE)) {
			//gr_clear();
			gr_restore_screen(screen_id);
		}

		bool isScriptingOverride = false;
		if (scripting::hooks::OnDialogFrame->isActive()) {
			auto paramList = scripting::hook_param_list(
				scripting::hook_param("Submit", 'u', luacpp::LuaFunction::createFromStdFunction(Script_system.GetLuaSession(), [&done, &choice, pi](lua_State* L, const luacpp::LuaValueList& resolveVals) {
					done = popup_resolve_scripting(L, choice, pi->input_text, resolveVals) ? 1 : 0;
					return luacpp::LuaValueList{};
					})),
				scripting::hook_param("Text", 's', pi->raw_text),
				scripting::hook_param("IsDeathPopup", 'b', false));

			scripting::hooks::OnDialogFrame->run(paramList);
			isScriptingOverride = scripting::hooks::OnDialogFrame->isOverride(std::move(paramList));
		}

		if (!isScriptingOverride) {
			k = Popup_window.process();						// poll for input, handle mouse
			choice = popup_process_keys(pi, k, flags);
			if ( choice != POPUP_NOCHANGE ) {
				done=1;
			}

			if ( !done ) {
				choice = popup_check_buttons(pi);
				if ( choice != POPUP_NOCHANGE ) {
					done=1;
				}
			}

			// if this is an input popup, store the input text
			if(flags & PF_INPUT){
				Popup_input.get_text(pi->input_text);
			}

			Popup_window.draw();
			popup_force_draw_buttons(pi);
			popup_draw_msg_text(pi, flags);
			popup_draw_button_text(pi, flags);
		}

		gr_flip();
	}

	gr_set_screen_scale(old_max_w_unscaled, old_max_h_unscaled, old_max_w_unscaled_zoomed, old_max_h_unscaled_zoomed);

	popup_close(pi,screen_id);
	return choice;
}

int popup_do_with_condition(popup_info *pi, int flags, int(*condition)())
{
	int screen_id, choice = -1, done = 0;
	int test;
	screen_id = gr_save_screen();
	if ( popup_init(pi, flags) == -1 )
		return -1;

	int old_max_w_unscaled = gr_screen.max_w_unscaled;
	int old_max_h_unscaled = gr_screen.max_h_unscaled;
	int old_max_w_unscaled_zoomed = gr_screen.max_w_unscaled_zoomed;
	int old_max_h_unscaled_zoomed = gr_screen.max_h_unscaled_zoomed;

	gr_reset_screen_scale();

	while(!done) {
		int k;

		os_poll();
		
		game_set_frametime(-1);
		game_do_state_common(gameseq_get_state());	// do stuff common to all states 
		gr_restore_screen(screen_id);

		bool isScriptingOverride = false;
		if (scripting::hooks::OnDialogFrame->isActive()) {
			auto paramList = scripting::hook_param_list(
				scripting::hook_param("Submit", 'u', luacpp::LuaFunction::createFromStdFunction(Script_system.GetLuaSession(), [&done, &choice, pi](lua_State* L, const luacpp::LuaValueList& resolveVals) {
					done = popup_resolve_scripting(L, choice, pi->input_text, resolveVals) ? 1 : 0;
					return luacpp::LuaValueList{};
					})),
				scripting::hook_param("Text", 's', pi->raw_text));

			scripting::hooks::OnDialogFrame->run(paramList);
			isScriptingOverride = scripting::hooks::OnDialogFrame->isOverride(std::move(paramList));
			gr_flip();
		}

		if (!isScriptingOverride) {
			// draw one frame first
			Popup_window.draw();
			popup_force_draw_buttons(pi);
			popup_draw_msg_text(pi, flags);
			popup_draw_button_text(pi, flags);
			gr_flip();

			// test the condition function or process for the window
			if ((test = condition()) > 0) {
				done = 1;
				choice = test;
			} else {
				k = Popup_window.process();						// poll for input, handle mouse
				choice = popup_process_keys(pi, k, flags);
				if ( choice != POPUP_NOCHANGE ) {
					done=1;
				}

				if ( !done ) {
					choice = popup_check_buttons(pi);
					if ( choice != POPUP_NOCHANGE ) {
						done=1;
					}
				}
			}
		}
		else if ((test = condition()) > 0) {
			done = 1;
			choice = test;
		}
	}

	gr_set_screen_scale(old_max_w_unscaled, old_max_h_unscaled, old_max_w_unscaled_zoomed, old_max_h_unscaled_zoomed);

	popup_close(pi,screen_id);
	return choice;
}


// maybe assign a keyboard shortcut to this button
// input:	pi		=>	popup information so far
//				i		=>	number of choice
//				str	=> string for button press	
void popup_maybe_assign_keypress(popup_info *pi, int n, char *str)
{
	int i,j,len=0,next_char_is_shortcut=0;

	pi->keypress[n]=-1;
	pi->shortcut_index[n]=-1;

	len=(int)strlen(str)+1;

	pi->button_text[n] = (char*)vm_malloc(len);
	memset(pi->button_text[n], 0, len);

	j=0;
	// copy chars over, watching for underline meta-char '&'
	for (i=0; i<len-1; i++) {
		if ( str[i] == '&' ) {
			pi->shortcut_index[n]=i;
			next_char_is_shortcut=1;
		} else {
			if ( next_char_is_shortcut ) {
				next_char_is_shortcut=0;
				char first_char_string[2];
				first_char_string[0]=str[i];
				first_char_string[1]=0;
				strlwr(first_char_string);
				pi->keypress[n] = first_char_string[0];
			}
			pi->button_text[n][j++]=str[i];	
		}
	}
}

// input:	flags			=>		flags			=>		formatting specificatons (PF_...)
//				nchoices		=>		number of choices popup has
//				text_1		=>		text for first button
//				...			=>		
//				text_n		=>		text for last button
//				msg text		=>		text msg for popup (can be of form "%s",pl->text)
//
// exit: choice selected (0..nchoices-1)
//			will return -1 if there was an error or popup was aborted
//
// typical usage:
//
//	rval = popup(0, 2, POPUP_OK, POPUP_CANCEL, "Sorry %s, try again", pl->callsign);
int popup(int flags, int nchoices, ... )
{
 	int			i, choice;
	char			*format, *s;
	va_list		args;	

	if ( Popup_is_active ) {
		Int3();		// should never happen
		return -1;
	}

	Popup_flags = flags;

	Assert( nchoices > 0 && nchoices <= POPUP_MAX_CHOICES );
	Popup_info.nchoices = nchoices;

	va_start(args, nchoices );

	// get button text
	for (i=0; i<nchoices; i++ )	{
		s = va_arg( args, char * );
		Popup_info.button_text[i] = NULL;
		popup_maybe_assign_keypress(&Popup_info, i, s);
	}

	// get msg text
	format = va_arg( args, char * );
	vsnprintf(Popup_info.raw_text, sizeof(Popup_info.raw_text)-1, format, args);
	va_end(args);
	Popup_info.raw_text[sizeof(Popup_info.raw_text)-1] = '\0';
	
	gamesnd_play_iface(InterfaceSounds::POPUP_APPEAR); 	// play sound when popup appears

	io::mouse::CursorManager::get()->pushStatus();
	io::mouse::CursorManager::get()->showCursor(true);
	Popup_is_active = 1;

	choice = popup_do( &Popup_info, flags );
	
	io::mouse::CursorManager::get()->popStatus();
	
	switch(choice) {
	case POPUP_ABORT:
		return -1;
	default:
		return choice;
	} // end switch
}

// determine if a popup is being drawn
int popup_active()
{
	return Popup_is_active;
}

// This function displays a popup message box and every frame it checks the condition() function
// which is passed in as an argument.
// If the condition() returns TRUE, the message box ends itself.  This function returns whatever
// the condition() did if the condition() occurred, and FALSE if the cancel button was pressed.
int popup_till_condition(int (*condition)(), ...) 
{
 	int			choice;
	char			*format, *s;
	va_list		args;
	int flags = 0;		

	if ( Popup_is_active ) {
		Int3();		// should never happen
		return -1;
	}
	//int nchoices = 1;
	Popup_info.nchoices = 1;

	Popup_flags = 0;

	va_start(args, condition );

	// get button text
	s = va_arg( args, char * );
	Popup_info.button_text[0] = NULL;
	popup_maybe_assign_keypress(&Popup_info, 0, s);

	// get msg text
	format = va_arg( args, char * );
	vsnprintf(Popup_info.raw_text, sizeof(Popup_info.raw_text)-1, format, args);
	va_end(args);
	Popup_info.raw_text[sizeof(Popup_info.raw_text)-1] = '\0';

	gamesnd_play_iface(InterfaceSounds::POPUP_APPEAR); 	// play sound when popup appears

	io::mouse::CursorManager::get()->pushStatus();
	io::mouse::CursorManager::get()->showCursor(true);
	Popup_is_active = 1;

	choice = popup_do_with_condition( &Popup_info, flags, condition );
	
	io::mouse::CursorManager::get()->popStatus();
	
	switch(choice) {
	case POPUP_ABORT:
		return 0;
	default:
		return choice;
	} // end switch
}

// popup to return the value from an input box
char *popup_input(int flags, const char *caption, int max_output_len, const char *default_input, const char *vchar)
{
	if ( Popup_is_active ) {
		Int3();		// should never happen
		return NULL;
	}

	// make it an inputbox type popup
	Popup_flags = flags;
	Popup_flags |= PF_INPUT;

	// add a cancel button
	Popup_info.nchoices = 0;
	// popup_maybe_assign_keypress(&Popup_info, 0, "&Cancel");	

	// get msg text
	Assert(caption != NULL);
	strcpy_s(Popup_info.raw_text, caption);	
	Assert(strlen(Popup_info.raw_text) < POPUP_MAX_CHARS );

	// set input text length
	if((max_output_len >= POPUP_INPUT_MAX_CHARS) || (max_output_len == -1)){
		Popup_info.max_input_text_len = POPUP_INPUT_MAX_CHARS - 1;
	} else {
		Popup_info.max_input_text_len = max_output_len;
	}

	// zero the popup input text and set default, if provided
	memset(Popup_info.input_text, 0, POPUP_INPUT_MAX_CHARS);
	strcpy_s(Popup_info.input_text, default_input);

	// Set the character whitelist for non-alphanumerics
	if (vchar != nullptr) {
		strcpy_s(Popup_info.valid_chars, vchar);
	} else {
		memset(Popup_info.valid_chars, 0, POPUP_MAX_VALID_CHARS);
	}
	
	gamesnd_play_iface(InterfaceSounds::POPUP_APPEAR); 	// play sound when popup appears

	io::mouse::CursorManager::get()->showCursor(true);
	Popup_is_active = 1;

	// if the user cancelled
	if(popup_do(&Popup_info, Popup_flags) == POPUP_ABORT){
		return NULL;
	}
	
	// otherwise return the
	return Popup_info.input_text;	
}

int popup_running_state()
{
	return Popup_running_state;
}

// kill any active popup, forcing it to return -1 (similar to ESC)
void popup_kill_any_active()
{
	if(Popup_is_active){
		Popup_should_die = 1;
	}
}

// change the text inside of the popup 
void popup_change_text(const char *new_text)
{
	// copy the raw text
	strcpy_s(Popup_info.raw_text,new_text);

	// recalculate all display information
	popup_split_lines(&Popup_info,Popup_flags);
}

// If we're running on demo data, certain menus can't be displayed; this message will get shown instead.
void popup_game_feature_not_in_demo()
{
	popup(PF_USE_AFFIRMATIVE_ICON|PF_BODY_BIG, 1, POPUP_OK, XSTR( "Sorry, this feature is available only in the retail version", 200));
}

// the same as popup_till_condition(), but split into separate parts such that
// it can run multiple conditions with a single popup
bool popup_conditional_create(int flags, ...)
{
	char *format, *s;
	va_list args;

	if (Popup_is_active) {
		UNREACHABLE("Can't create a conditional popup while another popup is open!");
		return false;
	}

	flags = 0;	// no flags supported at this time

	Popup_info.nchoices = 1;

	Popup_flags = flags;

	va_start(args, flags);

	// get button text
	s = va_arg(args, char *);
	Popup_info.button_text[0] = nullptr;
	popup_maybe_assign_keypress(&Popup_info, 0, s);

	// get msg text
	format = va_arg( args, char * );
	vsnprintf(Popup_info.raw_text, sizeof(Popup_info.raw_text)-1, format, args);
	Popup_info.raw_text[sizeof(Popup_info.raw_text)-1] = '\0';

	va_end(args);

	gamesnd_play_iface(InterfaceSounds::POPUP_APPEAR); 	// play sound when popup appears

	io::mouse::CursorManager::get()->pushStatus();
	io::mouse::CursorManager::get()->showCursor(true);
	Popup_is_active = 1;

	Popup_screen_id = gr_save_screen();

	if ( popup_init(&Popup_info, flags) == -1 ) {
		popup_conditional_close();
		return false;
	}

	return true;
}

int popup_conditional_do(int (*condition)(), const char *text)
{
	int choice = -1;
	bool done = false;
	int k, test;
	bool self_popup = false;

	// if no popup, create a default
	if ( !Popup_is_active ) {
		if ( !popup_conditional_create(0, XSTR("&Cancel", 667), text ? text : "") ) {
			return -1;
		}

		self_popup = true;
	} else if (text) {
		popup_change_text(text);
	}

	int old_max_w_unscaled = gr_screen.max_w_unscaled;
	int old_max_h_unscaled = gr_screen.max_h_unscaled;
	int old_max_w_unscaled_zoomed = gr_screen.max_w_unscaled_zoomed;
	int old_max_h_unscaled_zoomed = gr_screen.max_h_unscaled_zoomed;

	gr_reset_screen_scale();


	while ( !done ) {
		os_poll();

		game_set_frametime(-1);
		game_do_state_common(gameseq_get_state());	// do stuff common to all states
		gr_restore_screen(Popup_screen_id);

		bool isScriptingOverride = false;
		if (scripting::hooks::OnDialogFrame->isActive()) {
			auto pi = &Popup_info;
			auto paramList = scripting::hook_param_list(
				scripting::hook_param("Submit", 'u', luacpp::LuaFunction::createFromStdFunction(Script_system.GetLuaSession(), [&done, &choice, pi](lua_State* L, const luacpp::LuaValueList& resolveVals) {
					done = popup_resolve_scripting(L, choice, pi->input_text, resolveVals);
					return luacpp::LuaValueList{};
					})),
				scripting::hook_param("Text", 's', pi->raw_text));

			scripting::hooks::OnDialogFrame->run(paramList);
			isScriptingOverride = scripting::hooks::OnDialogFrame->isOverride(std::move(paramList));
			gr_flip();
		}

		if (!isScriptingOverride) {
			// draw one frame first
			Popup_window.draw();
			popup_force_draw_buttons(&Popup_info);
			popup_draw_msg_text(&Popup_info, Popup_flags);
			popup_draw_button_text(&Popup_info, Popup_flags);
			gr_flip();

			// test the condition function or process for the window
			if ((test = condition()) > 0) {
				done = true;
				choice = test;
			} else {
				k = Popup_window.process(); // poll for input, handle mouse
				choice = popup_process_keys(&Popup_info, k, Popup_flags);

				if (choice != POPUP_NOCHANGE) {
					done = true;
				}

				if (!done) {
					choice = popup_check_buttons(&Popup_info);

					if (choice != POPUP_NOCHANGE) {
						done = true;
					}
				}
			}
		} else if ((test = condition()) > 0) {
			done = true;
			choice = test;
		}
	}

	gr_set_screen_scale(old_max_w_unscaled, old_max_h_unscaled, old_max_w_unscaled_zoomed, old_max_h_unscaled_zoomed);

	// close popup if we created it here
	if (self_popup) {
		popup_conditional_close();
	}

	switch (choice) {
		case POPUP_ABORT:
			return 0;

		default:
			return choice;
	}
}

void popup_conditional_close()
{
	if ( !Popup_is_active ) {
		return;
	}

	popup_close(&Popup_info, Popup_screen_id);

	io::mouse::CursorManager::get()->popStatus();
}