/*
 * Portions of this file are copyright Rebirth contributors and licensed as
 * described in COPYING.txt.
 * Portions of this file are copyright Parallax Software and licensed
 * according to the Parallax license below.
 * See COPYING.txt for license details.

THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
COPYRIGHT 1993-1998 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
*/

/*
 *
 * Editor loop for Inferno
 *
 */

//#define DEMO 1

#define	DIAGNOSTIC_MESSAGE_MAX				90
#define	EDITOR_STATUS_MESSAGE_DURATION	4		//	Shows for 3+..4 seconds

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>

#include "inferno.h"
#include "args.h"
#include "segment.h"
#include "gr.h"
#include "palette.h"
#include "physfsx.h"
#include "event.h"
#include "window.h"
#include "game.h"
#include "messagebox.h"
#include "ui.h"
#include "editor.h"
#include "editor/esegment.h"
#include "state.h"
#include "gamesave.h"
#include "gameseg.h"
#include "key.h"
#include "kconfig.h"
#include "mouse.h"
#include "dxxerror.h"
#include "kfuncs.h"
#ifdef INCLUDE_XLISP
#include "medlisp.h"
#endif
#include "u_mem.h"
#include "render.h"
#include "game.h"
#include "gamefont.h"
#include "menu.h"
#include "slew.h"
#include "player.h"
#include "kdefs.h"
#include "func.h"
#include "textures.h"
#include "text.h"
#include "screens.h"
#include "texmap.h"
#include "object.h"
#include "effects.h"
#include "info.h"
#include "console.h"
#include "texpage.h"		// Textue selection paging stuff
#include "objpage.h"		// Object selection paging stuff
#include "d_enumerate.h"

#include "medmisc.h"
#include "meddraw.h"
#include "medsel.h"
#include "medwall.h"

#include "fuelcen.h"
#include "gameseq.h"
#include "mission.h"
#include "newmenu.h"

#if defined(DXX_BUILD_DESCENT_II)
#include "gamepal.h"
#endif

#include "compiler-range_for.h"
#include "d_levelstate.h"
#include "d_zip.h"

//#define _MARK_ON 1
//#include <wsample.h>		//should come after inferno.h to get mark setting //Not included here.

#define COMPRESS_INTERVAL	5			// seconds

static void med_show_warning(std::span<const char> s);

//char *undo_status[128];

int initializing;

//these are instances of canvases, pointed to by variables below
grs_subcanvas _canv_editor_game; // the game on the editor screen
static grs_subcanvas _canv_editor; // the canvas that the editor writes to

//these are pointers to our canvases
grs_canvas *Canv_editor;			//the editor screen
grs_subcanvas *const Canv_editor_game = &_canv_editor_game; //the game on the editor screen

window *Pad_info;		// Keypad text

grs_font_ptr editor_font;

//where the editor is looking
vms_vector Ed_view_target;

editor_gamestate gamestate = editor_gamestate::none;

editor_dialog *EditorWindow;

int	Large_view_index = -1;

std::unique_ptr<UI_GADGET_USERBOX> GameViewBox, LargeViewBox, GroupViewBox;

static std::unique_ptr<UI_GADGET_ICON>
	ViewIcon,
	AllIcon,
	AxesIcon,
	ChaseIcon,
	OutlineIcon,
	LockIcon;

//grs_canvas * BigCanvas[2];
//int CurrentBigCanvas = 0;
//int BigCanvasFirstTime = 1;

int	Found_seg_index=0;				// Index in Found_segs corresponding to Cursegp

static void print_status_bar(const std::array<char, DIAGNOSTIC_MESSAGE_MAX> &message)
{
	gr_set_default_canvas();
	auto &canvas = *grd_curcanv;
	const auto &editor_font = *::editor_font;
	gr_set_fontcolor(canvas, CBLACK, CGREY);
	const auto &&[w, h] = gr_get_string_size(editor_font, message.data());
	gr_string(canvas, editor_font, 4, 583, message.data(), w, h);
	gr_set_fontcolor(canvas, CBLACK, CWHITE);
	gr_rect(canvas, 4+w, 583, 799, 599, CGREY);
}

static std::array<char, DIAGNOSTIC_MESSAGE_MAX> status_line;

struct tm	Editor_status_last_time;

void (editor_status_fmt)( const char *format, ... )
{
	va_list ap;

	va_start(ap, format);
	vsnprintf(status_line.data(), status_line.size(), format, ap);
	va_end(ap);

	Editor_status_last_time = Editor_time_of_day;
}

void editor_status( const char *text)
{
	Editor_status_last_time = Editor_time_of_day;
	strcpy(status_line.data(), text);
}

// 	int  tm_sec;	/* seconds after the minute -- [0,61] */
// 	int  tm_min;	/* minutes after the hour	-- [0,59] */
// 	int  tm_hour;	/* hours after midnight	-- [0,23] */
// 	int  tm_mday;	/* day of the month		-- [1,31] */
// 	int  tm_mon;	/* months since January	-- [0,11] */
// 	int  tm_year;	/* years since 1900				*/
// 	int  tm_wday;	/* days since Sunday		-- [0,6]  */
// 	int  tm_yday;	/* days since January 1	-- [0,365]*/
// 	int  tm_isdst;	/* Daylight Savings Time flag */

static void clear_editor_status(void)
{
	int cur_time = Editor_time_of_day.tm_hour * 3600 + Editor_time_of_day.tm_min*60 + Editor_time_of_day.tm_sec;
	int erase_time = Editor_status_last_time.tm_hour * 3600 + Editor_status_last_time.tm_min*60 + Editor_status_last_time.tm_sec + EDITOR_STATUS_MESSAGE_DURATION;

	if (cur_time > erase_time) {
		std::fill(status_line.begin(), std::prev(status_line.end()), ' ');
		status_line.back() = 0;
		Editor_status_last_time.tm_hour = 99;
	}
}

static inline void editor_slew_init()
{
	auto &Objects = LevelUniqueObjectState.Objects;
	auto &vmobjptr = Objects.vmptr;
	Viewer = ConsoleObject;
	slew_init(vmobjptr(ConsoleObject));
	init_player_object(LevelSharedPolygonModelState, *ConsoleObject);
}

int DropIntoDebugger()
{
	Int3();
	return 1;
}


#ifdef INCLUDE_XLISP
int CallLisp()
{
	medlisp_go();
	return 1;
}
#endif


int ExitEditor()
{
	if (SafetyCheck())  {
		ModeFlag = 1;
	}
	return 1;
}

int	GotoGameScreen()
{
	auto &Objects = LevelUniqueObjectState.Objects;
	auto &vmobjptr = Objects.vmptr;
	pause_game_world_time p;

//@@	init_player_stats();
//@@
//@@	Player_init.pos = Player->pos;
//@@	Player_init.orient = Player->orient;
//@@	Player_init.segnum = Player->segnum;	


// -- must always save gamesave.sge/lvl because the restore-objects code relies on it
// -- that code could be made smarter and use the original file, if appropriate.
//	if (mine_changed)
	switch (gamestate)
	{
		case editor_gamestate::none:
			// Always use the simple mission when playing level (for now at least)
			create_new_mission();
			Current_level_num = 1;
			if (save_level(
#if defined(DXX_BUILD_DESCENT_II)
					LevelSharedSegmentState.DestructibleLights,
#endif
					"GAMESAVE.LVL"))
				return 0;
			editor_status("Level saved.\n");
			break;

		case editor_gamestate::unsaved:
		case editor_gamestate::saved:
			if (!SafetyCheck())		// if mine edited but not saved, warn the user!
				return 0;

			const auto &&console = vmobjptr(get_local_player().objnum);
			Viewer = ConsoleObject = console;
			set_player_id(console, Player_num);
			fly_init(*ConsoleObject);

			if (!state_save_all_sub(PLAYER_DIRECTORY_STRING("gamesave.sge"), "Editor generated"))
			{
				editor_slew_init();

				return 0;
			}
			gamestate = editor_gamestate::saved;
			editor_status("Game saved.\n");
			break;
	}

	ModeFlag = 3;
	return 1;
}

int GotoMainMenu()
{
	ModeFlag = 2;
	return 1;
}

static std::array<int (*)(), 2048> KeyFunction;

static void medkey_init()
{
	char keypress[100];
	int key;
	int np;
	char LispCommand[DIAGNOSTIC_MESSAGE_MAX];

	KeyFunction = {};

	if (auto keyfile = PHYSFSX_openReadBuffered("GLOBAL.KEY").first)
	{
		PHYSFSX_gets_line_t<200> line_buffer;
		while (PHYSFSX_fgets(line_buffer, keyfile))
		{
			sscanf(line_buffer, " %s %s ", keypress, LispCommand);
			//ReadLispMacro( keyfile, LispCommand );

			if ( (key=DecodeKeyText( keypress ))!= -1 )
			{
				Assert( key < 2048);
				KeyFunction[key] = func_get( LispCommand, &np );
			} else {
				UserError( "Bad key %s in GLOBAL.KEY!", keypress );
			}
		}
	}
}

static int padnum=0;
//@@short camera_objnum;			//a camera for viewing. Who knows, might become handy

static void init_editor_screen(grs_canvas &canvas);
static void gamestate_restore_check();
static void close_editor();

namespace dsx {
void init_editor()
{
	static const char pads[][13] = {
		"segmove.pad",
		"segsize.pad",
		"curve.pad",
		"texture.pad",
		"object.pad",
		"objmov.pad",
		"group.pad",
		"lighting.pad",
		"test.pad"
					};
	ModeFlag = Game_wind ? 3 : 2;	// go back to where we were unless we loaded everything properly

	// first, make sure we can find the files we need
	std::array<char, PATH_MAX> pathname;
	PHYSFSX_addRelToSearchPath("editor/data", pathname, physfs_search_path::append);	// look in source directory first (for work in progress)
	PHYSFSX_addRelToSearchPath("editor", pathname, physfs_search_path::append);		// then in editor directory
	PHYSFSX_addRelToSearchPath("editor.zip", pathname, physfs_search_path::append);	// then in a zip file
	PHYSFSX_addRelToSearchPath("editor.dxa", pathname, physfs_search_path::append);	// or addon pack

	if (!ui_init())
	{
		close_editor();
		return;
	}

	init_med_functions();	// Must be called before medlisp_init

	for (auto &&[idx, value] : enumerate(pads))
		if (!ui_pad_read(idx, value))
		{
			close_editor();
			return;
		}

	medkey_init();

	game_flush_inputs(Controls);
	
	editor_font = gr_init_font(*grd_curcanv, "pc8x16.fnt");
	if (!editor_font)
	{
		Warning("Could not find pc8x16.fnt");
		close_editor();
		return;
	}
	
	if (!menubar_init(*grd_curcanv, "MED.MNU"))
	{
		close_editor();
		return;
	}

	Draw_all_segments = 1;						// Say draw all segments, not just connected ones
	
	if (!Cursegp)
		Cursegp = imsegptridx(segment_first);

	init_autosave();
  
	Clear_window = 1;	//	do full window clear.
	
	InitCurve();
	
	restore_effect_bitmap_icons();
	
	if (!set_screen_mode(SCREEN_EDITOR))
	{
		close_editor();
		return;
	}
#if defined(DXX_BUILD_DESCENT_I)
	gr_use_palette_table( "palette.256" );
	gr_palette_load( gr_palette );
#elif defined(DXX_BUILD_DESCENT_II)
	load_palette(Current_level_palette,1,0);
#endif
	
	//Editor renders into full (320x200) game screen 
	
	game_init_render_sub_buffers(*grd_curcanv, 0, 0, 320, 200);
	gr_init_sub_canvas(_canv_editor, grd_curscreen->sc_canvas, 0, 0, SWIDTH, SHEIGHT);
	Canv_editor = &_canv_editor;
	gr_set_current_canvas(*Canv_editor);
	init_editor_screen(*grd_curcanv); // load the main editor dialog
	gr_set_default_canvas();
	set_warn_func(med_show_warning);
	
	//	_MARK_("start of editor");//Nuked to compile -KRB
	
	//@@	//create a camera for viewing in the editor. copy position from ConsoleObject
	//@@	camera_objnum = obj_create(OBJ_CAMERA,0,ConsoleObject->segnum,&ConsoleObject->pos,&ConsoleObject->orient,0);
	//@@	Viewer = &Objects[camera_objnum];
	//@@	slew_init(Viewer);		//camera is slewing
	
	editor_slew_init();
	
	Update_flags = UF_ALL;
	
	//set the wire-frame window to be the current view
	current_view = &LargeView;
	
	gr_set_current_canvas( GameViewBox->canvas );
	gr_set_curfont(*grd_curcanv, *editor_font);
	// No font scaling!
	FNTScaleX.reset(1);
	FNTScaleY.reset(1);
	ui_pad_goto(padnum);
	
	ModeFlag = 0;	// success!
	
	gamestate_restore_check();
}
}

int ShowAbout()
{
	ui_messagebox( -2, -2, 1, 	"INFERNO Mine Editor\n\n"		\
									"Copyright (c) 1993  Parallax Software Corp.",
									"OK");
	return 0;
}

int SetPlayerFromCurseg()
{
	move_player_2_segment(Cursegp,Curside);
	Update_flags |= UF_ED_STATE_CHANGED | UF_GAME_VIEW_CHANGED;
	return 1;
}

int fuelcen_create_from_curseg()
{
	Cursegp->special = segment_special::fuelcen;
	fuelcen_activate(Cursegp);
	return 1;
}

int repaircen_create_from_curseg()
{
	Cursegp->special = segment_special::repaircen;
	fuelcen_activate(Cursegp);
	return 1;
}

int controlcen_create_from_curseg()
{
	Cursegp->special = segment_special::controlcen;
	fuelcen_activate(Cursegp);
	return 1;
}

int robotmaker_create_from_curseg()
{
	Cursegp->special = segment_special::robotmaker;
	fuelcen_activate(Cursegp);
	return 1;
}

int fuelcen_reset_all()	{
	fuelcen_reset();
	return 1;
}

int fuelcen_delete_from_curseg() {
	fuelcen_delete( Cursegp );
	return 1;
}


//@@//this routine places the viewer in the center of the side opposite to curside,
//@@//with the view toward the center of curside
//@@int SetPlayerFromCursegMinusOne()
//@@{
//@@	vms_vector vp;
//@@
//@@//	int newseg,newside;
//@@//	get_previous_segment(SEG_PTR_2_NUM(Cursegp),Curside,&newseg,&newside);
//@@//	move_player_2_segment(&Segments[newseg],newside);
//@@
//@@	med_compute_center_point_on_side(&Player->obj_position,Cursegp,Side_opposite[Curside]);
//@@	med_compute_center_point_on_side(&vp,Cursegp,Curside);
//@@	vm_vec_sub2(&vp,&Player->position);
//@@	vm_vector_2_matrix(&Player->orient,&vp,NULL,NULL);
//@@
//@@	Player->seg = SEG_PTR_2_NUM(Cursegp);
//@@
//@@	Update_flags |= UF_GAME_VIEW_CHANGED;
//@@	return 1;
//@@}

//this constant determines how much of the window will be occupied by the
//viewed side when SetPlayerFromCursegMinusOne() is called.  It actually
//determine how from from the center of the window the farthest point will be
#define SIDE_VIEW_FRAC (f1_0*8/10)	//80%

static void move_player_2_segment_and_rotate(const vmsegptridx_t seg, const sidenum_t side)
{
	auto &Objects = LevelUniqueObjectState.Objects;
	auto &vmobjptr = Objects.vmptr;
	auto &vmobjptridx = Objects.vmptridx;
	static side_relative_vertnum edgenum;

	auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
	auto &Vertices = LevelSharedVertexState.get_vertices();
	auto &vcvertptr = Vertices.vcptr;
	compute_segment_center(vcvertptr, ConsoleObject->pos,seg);
	auto vp = compute_center_point_on_side(vcvertptr, seg, side);
	vm_vec_sub2(vp,ConsoleObject->pos);

	auto &sv = Side_to_verts[Curside];
	auto &verts = Cursegp->verts;
	const auto en = std::exchange(edgenum, next_side_vertex(edgenum));
	const auto upvec = vm_vec_sub(vcvertptr(verts[sv[en]]), vcvertptr(verts[sv[next_side_vertex(en, 3)]]));

	vm_vector_2_matrix(ConsoleObject->orient,vp,&upvec,nullptr);
//	vm_vector_2_matrix(&ConsoleObject->orient,&vp,NULL,NULL);

	obj_relink(vmobjptr, vmsegptr, vmobjptridx(ConsoleObject), seg);
}

int SetPlayerFromCursegAndRotate()
{
	move_player_2_segment_and_rotate(Cursegp,Curside);
	Update_flags |= UF_ED_STATE_CHANGED | UF_GAME_VIEW_CHANGED;
	return 1;
}

//sets the player facing curseg/curside, normal to face0 of curside, and
//far enough away to see all of curside
int SetPlayerFromCursegMinusOne()
{
	auto &Objects = LevelUniqueObjectState.Objects;
	auto &vmobjptr = Objects.vmptr;
	auto &vmobjptridx = Objects.vmptridx;
	std::array<g3s_point, 4> corner_p;
	fix max,view_dist=f1_0*10;
	static side_relative_vertnum edgenum;
	const auto view_vec = vm_vec_negated(Cursegp->shared_segment::sides[Curside].normals[0]);

	auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
	auto &Vertices = LevelSharedVertexState.get_vertices();
	auto &vcvertptr = Vertices.vcptr;
	const auto &&side_center = compute_center_point_on_side(vcvertptr, Cursegp, Curside);
	const auto view_vec2 = vm_vec_copy_scale(view_vec,view_dist);
	vm_vec_sub(ConsoleObject->pos,side_center,view_vec2);

	auto &sv = Side_to_verts[Curside];
	auto &verts = Cursegp->verts;
	const auto en = std::exchange(edgenum, next_side_vertex(edgenum));
	const auto upvec = vm_vec_sub(vcvertptr(verts[sv[en]]), vcvertptr(verts[sv[next_side_vertex(en, 3)]]));

	vm_vector_2_matrix(ConsoleObject->orient,view_vec,&upvec,nullptr);

	gr_set_current_canvas(*Canv_editor_game);
	g3_start_frame(*grd_curcanv);
	g3_set_view_matrix(ConsoleObject->pos,ConsoleObject->orient,Render_zoom);

	max = 0;
	for (auto &&[corner, vert] : zip(corner_p, sv))
	{
		g3_rotate_point(corner, vcvertptr(verts[vert]));
		if (labs(corner.p3_x) > max) max = labs(corner.p3_x);
		if (labs(corner.p3_y) > max) max = labs(corner.p3_y);
	}

	view_dist = fixmul(view_dist,fixdiv(fixdiv(max,SIDE_VIEW_FRAC),corner_p[0].p3_z));
	const auto view_vec3 = vm_vec_copy_scale(view_vec,view_dist);
	vm_vec_sub(ConsoleObject->pos,side_center,view_vec3);

	//obj_relink(ConsoleObject-Objects, SEG_PTR_2_NUM(Cursegp) );
	//update_object_seg(ConsoleObject);		//might have backed right out of curseg

	const auto &&newseg = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, ConsoleObject->pos, Cursegp);
	if (newseg != segment_none)
		obj_relink(vmobjptr, vmsegptr, vmobjptridx(ConsoleObject), newseg);

	Update_flags |= UF_ED_STATE_CHANGED | UF_GAME_VIEW_CHANGED;
	return 1;
}

#if !DXX_USE_OGL
static int ToggleLighting(void)
{
	Lighting_on++;
	if (Lighting_on >= 2)
		Lighting_on = 0;

	Update_flags |= UF_GAME_VIEW_CHANGED;

	switch (Lighting_on) {
		case 0:
			diagnostic_message("Lighting off.");
			break;
		case 1:
			diagnostic_message("Static lighting.");
			break;
		case 2:
			diagnostic_message("Ship lighting.");
			break;
		case 3:
			diagnostic_message("Ship and static lighting.");
			break;
	}

	return Lighting_on;
}
#endif

int FindConcaveSegs()
{
	find_concave_segs();

	Update_flags |= UF_ED_STATE_CHANGED;		//list may have changed

	return 1;
}

static int ToggleOutlineMode()
{
#ifndef NDEBUG
	int mode;

	mode=toggle_outline_mode();

        if (mode)
         {
		//if (keypress != KEY_O)
			diagnostic_message("[Ctrl-O] Outline Mode ON.");
		//else
		//	diagnostic_message("Outline Mode ON.");
         }
	else
         {
		//if (keypress != KEY_O)
			diagnostic_message("[Ctrl-O] Outline Mode OFF.");
		//else
		//	diagnostic_message("Outline Mode OFF.");
         }

	Update_flags |= UF_GAME_VIEW_CHANGED;
	return mode;
#else
	return 1;
#endif
}

//@@int do_reset_orient()
//@@{
//@@	slew_reset_orient(SlewObj);
//@@
//@@	Update_flags |= UF_GAME_VIEW_CHANGED;
//@@
//@@	* reinterpret_cast<uint8_t *>(0x417) &= ~0x20;
//@@
//@@	return 1;
//@@}

int GameZoomOut()
{
	Render_zoom = fixmul(Render_zoom,68985);
	Update_flags |= UF_GAME_VIEW_CHANGED;
	return 1;
}

int GameZoomIn()
{
	Render_zoom = fixmul(Render_zoom,62259);
	Update_flags |= UF_GAME_VIEW_CHANGED;
	return 1;
}


static int med_keypad_goto_0()	{	ui_pad_goto(0);	return 0;	}
static int med_keypad_goto_1()	{	ui_pad_goto(1);	return 0;	}
static int med_keypad_goto_2()	{	ui_pad_goto(2);	return 0;	}
static int med_keypad_goto_3()	{	ui_pad_goto(3);	return 0;	}
static int med_keypad_goto_4()	{	ui_pad_goto(4);	return 0;	}
static int med_keypad_goto_5()	{	ui_pad_goto(5);	return 0;	}
static int med_keypad_goto_6()	{	ui_pad_goto(6);	return 0;	}
static int med_keypad_goto_7()	{	ui_pad_goto(7);	return 0;	}
static int med_keypad_goto_8()	{	ui_pad_goto(8);	return 0;	}

#define	PAD_WIDTH	30
#define	PAD_WIDTH1	(PAD_WIDTH + 7)

int editor_screen_open = 0;

//setup the editors windows, canvases, gadgets, etc.
static void init_editor_screen(grs_canvas &canvas)
{	
//	grs_bitmap * bmp;

	if (editor_screen_open) return;

	grd_curscreen->sc_canvas.cv_font = editor_font.get();
	
	//create canvas for game on the editor screen
	initializing = 1;
	gr_set_current_canvas(*Canv_editor);
	Canv_editor->cv_font = editor_font.get();
	gr_init_sub_canvas(*Canv_editor_game, *Canv_editor, GAMEVIEW_X, GAMEVIEW_Y, GAMEVIEW_W, GAMEVIEW_H);
	
	//Editor renders into full (320x200) game screen 

	init_info = 1;

	//do other editor screen setup

	// Since the palette might have changed, find some good colors...
	CBLACK = gr_find_closest_color( 1, 1, 1 );
	CGREY = gr_find_closest_color( 28, 28, 28 );
	CWHITE = gr_find_closest_color( 38, 38, 38 );
	CBRIGHT = gr_find_closest_color( 60, 60, 60 );
	CRED = gr_find_closest_color( 63, 0, 0 );

	gr_set_curfont(canvas, *editor_font);
	gr_set_fontcolor(canvas, CBLACK, CWHITE);

	EditorWindow = window_create<editor_dialog>(0, 0, ED_SCREEN_W, ED_SCREEN_H, DF_FILLED);
	auto e = EditorWindow;

	LargeViewBox = ui_add_gadget_userbox(*e, LVIEW_X, LVIEW_Y, LVIEW_W, LVIEW_H);
	ui_gadget_calc_keys(*e);	//make tab work for all windows

	GameViewBox	= ui_add_gadget_userbox(*e, GAMEVIEW_X, GAMEVIEW_Y, GAMEVIEW_W, GAMEVIEW_H);

//	GameViewBox->when_tab = GameViewBox->when_btab =  LargeViewBox;
//	LargeViewBox->when_tab = LargeViewBox->when_btab =  GameViewBox;

	ViewIcon	= ui_add_gadget_icon(*e, "Lock\nview", 455, 25 + 530, 40, 22,	KEY_V+KEY_CTRLED, ToggleLockViewToCursegp);
	AllIcon	= ui_add_gadget_icon(*e, "Draw\nall", 500, 25 + 530, 40, 22,	-1, ToggleDrawAllSegments);
	AxesIcon	= ui_add_gadget_icon(*e, "Coord\naxes", 545, 25 + 530, 40, 22,	KEY_D+KEY_CTRLED, ToggleCoordAxes);
	ChaseIcon	= ui_add_gadget_icon(*e, "Chase\nmode", 635, 25 + 530, 40, 22,	-1,				ToggleChaseMode);
	OutlineIcon = ui_add_gadget_icon(*e, "Out\nline", 680, 25 + 530, 40, 22,	KEY_O+KEY_CTRLED,			ToggleOutlineMode);
	LockIcon	= ui_add_gadget_icon(*e, "Lock\nstep", 725, 25 + 530, 40, 22,	KEY_L+KEY_CTRLED,			ToggleLockstep);

	meddraw_init_views(LargeViewBox->canvas.get());

	ui_pad_activate(*e, PAD_X, PAD_Y);
	Pad_info = info_window_create();
	e->pad_prev = ui_add_gadget_button(*e, PAD_X + 6, PAD_Y + (30 * 5) + 22, PAD_WIDTH, 20, "<<", med_keypad_goto_prev);
	e->pad_next = ui_add_gadget_button(*e, PAD_X + PAD_WIDTH1 + 6, PAD_Y + (30 * 5) + 22, PAD_WIDTH, 20, ">>", med_keypad_goto_next);

	{	int	i;
		i = 0;	e->pad_goto[i] = ui_add_gadget_button(*e, PAD_X+16+(i+2)*PAD_WIDTH1, PAD_Y+(30*5)+22, PAD_WIDTH, 20, "SR", med_keypad_goto_0);
		i++;		e->pad_goto[i] = ui_add_gadget_button(*e, PAD_X+16+(i+2)*PAD_WIDTH1, PAD_Y+(30*5)+22, PAD_WIDTH, 20, "SS", med_keypad_goto_1);
		i++;		e->pad_goto[i] = ui_add_gadget_button(*e, PAD_X+16+(i+2)*PAD_WIDTH1, PAD_Y+(30*5)+22, PAD_WIDTH, 20, "CF", med_keypad_goto_2);
		i++;		e->pad_goto[i] = ui_add_gadget_button(*e, PAD_X+16+(i+2)*PAD_WIDTH1, PAD_Y+(30*5)+22, PAD_WIDTH, 20, "TM", med_keypad_goto_3);
		i++;		e->pad_goto[i] = ui_add_gadget_button(*e, PAD_X+16+(i+2)*PAD_WIDTH1, PAD_Y+(30*5)+22, PAD_WIDTH, 20, "OP", med_keypad_goto_4);
		i++;		e->pad_goto[i] = ui_add_gadget_button(*e, PAD_X+16+(i+2)*PAD_WIDTH1, PAD_Y+(30*5)+22, PAD_WIDTH, 20, "OR", med_keypad_goto_5);
		i++;		e->pad_goto[i] = ui_add_gadget_button(*e, PAD_X+16+(i+2)*PAD_WIDTH1, PAD_Y+(30*5)+22, PAD_WIDTH, 20, "GE", med_keypad_goto_6);
		i++;		e->pad_goto[i] = ui_add_gadget_button(*e, PAD_X+16+(i+2)*PAD_WIDTH1, PAD_Y+(30*5)+22, PAD_WIDTH, 20, "LI", med_keypad_goto_7);
		i++;		e->pad_goto[i] = ui_add_gadget_button(*e, PAD_X+16+(i+2)*PAD_WIDTH1, PAD_Y+(30*5)+22, PAD_WIDTH, 20, "TT", med_keypad_goto_8);
	}

	menubar_show();

	// INIT TEXTURE STUFF
	texpage_init(e);
	objpage_init(e);

	e->keyboard_focus_gadget = LargeViewBox.get();

	Update_flags = UF_ALL;
	initializing = 0;
	editor_screen_open = 1;
}

//shutdown ui on the editor screen
void close_editor_screen()
{
	if (!editor_screen_open) return;

	editor_screen_open = 0;
	ui_pad_deactivate();
	if (Pad_info)
		window_close(Pad_info);

	close_all_windows();

	// CLOSE TEXTURE STUFF
	texpage_close();
	objpage_close();

	menubar_hide();

}

static void med_show_warning(std::span<const char> s)
{
	grs_canvas &save_canv = *grd_curcanv;

	//gr_pal_fade_in(grd_curscreen->pal);	//in case palette is blacked

	ui_messagebox(-2, -2, 1, s.data(), "OK");
	gr_set_current_canvas(save_canv);
}

// Returns 1 if OK to trash current mine.
int SafetyCheck()
{
	if (mine_changed) {
		const auto x = nm_messagebox_str(menu_title{"Warning!"}, nm_messagebox_tie("Cancel", TXT_OK), menu_subtitle{"You are about to lose work."});
		if (x<1) {
			return 0;
		}
	}
	return 1;
}

//called at the end of the program

static void close_editor()
{
	//	_MARK_("end of editor");//Nuked to compile -KRB
	
#if defined(WIN32) || defined(__APPLE__) || defined(__MACH__)
	set_warn_func(msgbox_warning);
#else
	clear_warn_func();
#endif
	
	close_editor_screen();
	
	//kill our camera object
	
	Viewer = ConsoleObject;					//reset viewer
	//@@obj_delete(camera_objnum);
	
	padnum = ui_pad_get_current();
	
	close_autosave();

	ui_close();

	editor_font.reset();

	PHYSFSX_removeRelFromSearchPath("editor/data");
	PHYSFSX_removeRelFromSearchPath("editor");
	PHYSFSX_removeRelFromSearchPath("editor.zip");
	PHYSFSX_removeRelFromSearchPath("editor.dxa");

	switch (ModeFlag)
	{
		case 1:
			break;

		case 2:
			set_screen_mode(SCREEN_MENU);		//put up menu screen
			show_menus();
			break;

		case 3:
			if (Game_wind)
				return;	// if we're already playing a game, don't touch!

			switch (gamestate)
			{
				case editor_gamestate::none:
					StartNewGame(Current_level_num);
					break;

				case editor_gamestate::saved:
					state_restore_all_sub(
#if defined(DXX_BUILD_DESCENT_II)
						LevelSharedSegmentState.DestructibleLights, secret_restore::none,
#endif
						PLAYER_DIRECTORY_STRING("gamesave.sge")
					);
					break;

				default:
					Int3();	// shouldn't happen
					break;
			}
			break;
	}

	return;
}

//variables for find segments process

// ---------------------------------------------------------------------------------------------------
//	Subtract all elements in Found_segs from selected list.
static void subtract_found_segments_from_selected_list(void)
{
	range_for (const auto &foundnum, Found_segs)
	{
		selected_segment_array_t::iterator i = Selected_segs.find(foundnum), e = Selected_segs.end();
		if (i != e)
		{
			*i = *-- e;
			Selected_segs.erase(e);
		}
	}
}

// ---------------------------------------------------------------------------------------------------
//	Add all elements in Found_segs to selected list.
static void add_found_segments_to_selected_list(void) {
	range_for (const auto &foundnum, Found_segs)
	{
		selected_segment_array_t::iterator i = Selected_segs.find(foundnum), e = Selected_segs.end();
		if (i == e)
			Selected_segs.emplace_back(foundnum);
	}
}

void gamestate_restore_check()
{
	auto &Objects = LevelUniqueObjectState.Objects;
	auto &vmobjptr = Objects.vmptr;
	auto &vmobjptridx = Objects.vmptridx;
	obj_position Save_position;

	if (gamestate == editor_gamestate::saved)
	{
		if (ui_messagebox(-2, -2, 2, "Do you wish to restore game state?\n", "Yes", "No") == 1)
		{

			// Save current position
			Save_position.pos = ConsoleObject->pos;
			Save_position.orient = ConsoleObject->orient;
			Save_position.segnum = ConsoleObject->segnum;

			if (!state_restore_all_sub(
#if defined(DXX_BUILD_DESCENT_II)
					LevelSharedSegmentState.DestructibleLights, secret_restore::none,
#endif
					PLAYER_DIRECTORY_STRING("gamesave.sge")
			))
				return;

			// Switch back to slew mode - loading saved game made ConsoleObject flying
			editor_slew_init();

			// Restore current position
			if (Save_position.segnum <= Highest_segment_index) {
				ConsoleObject->pos = Save_position.pos;
				ConsoleObject->orient = Save_position.orient;
				obj_relink(vmobjptr, vmsegptr, vmobjptridx(ConsoleObject), vmsegptridx(Save_position.segnum));
			}

			Update_flags |= UF_WORLD_CHANGED;	
		}
		else
			gamestate = editor_gamestate::none;
	}
}

int RestoreGameState()
{
	if (!SafetyCheck())
		return 0;

	if (!state_restore_all_sub(
#if defined(DXX_BUILD_DESCENT_II)
			LevelSharedSegmentState.DestructibleLights, secret_restore::none,
#endif
			PLAYER_DIRECTORY_STRING("gamesave.sge")
	))
		return 0;

	// Switch back to slew mode - loading saved game made ConsoleObject flying
	editor_slew_init();
	
	gamestate = editor_gamestate::saved;

	editor_status("Gamestate restored.\n");

	Update_flags |= UF_WORLD_CHANGED;
	return 1;
}

// Handler for the main editor dialog
window_event_result editor_dialog::callback_handler(const d_event &event)
{
	auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
	auto &Vertices = LevelSharedVertexState.get_vertices();
	editor_view *new_cv;
	int keypress = 0;
	window_event_result rval = window_event_result::ignored;

	if (event.type == EVENT_WINDOW_CREATED)
		return window_event_result::ignored;

	if (event.type == EVENT_KEY_COMMAND)
		keypress = event_key_get(event);
	else if (event.type == EVENT_WINDOW_CLOSE)
	{
		EditorWindow = NULL;
		close_editor();
		return window_event_result::ignored;
	}
	
	// Update the windows

	if (event.type == EVENT_UI_DIALOG_DRAW)
	{
		// Draw status box
		gr_set_default_canvas();
		gr_rect(*grd_curcanv, STATUS_X,STATUS_Y,STATUS_X+STATUS_W-1,STATUS_Y+STATUS_H-1, CGREY);
		
		medlisp_update_screen();
		calc_frame_time();
		texpage_do(event);
		objpage_do(event);
		ui_pad_draw(EditorWindow, PAD_X, PAD_Y);

		print_status_bar(status_line);
		TimedAutosave(mine_filename);	// shows the time, hence here
		set_editor_time_of_day();
		return window_event_result::handled;
	}
	
	if ((selected_gadget == GameViewBox.get() && !render_3d_in_big_window) ||
		(selected_gadget == LargeViewBox.get() && render_3d_in_big_window))
		switch (event.type)
		{
			case EVENT_MOUSE_BUTTON_UP:
			case EVENT_MOUSE_BUTTON_DOWN:
				break;
			case EVENT_MOUSE_MOVED:
				if (!keyd_pressed[ KEY_LCTRL ] && !keyd_pressed[ KEY_RCTRL ])
					break;
				[[fallthrough]];
#if DXX_MAX_BUTTONS_PER_JOYSTICK
			case EVENT_JOYSTICK_BUTTON_UP:
			case EVENT_JOYSTICK_BUTTON_DOWN:
#endif
#if DXX_MAX_AXES_PER_JOYSTICK
			case EVENT_JOYSTICK_MOVED:
#endif
			case EVENT_KEY_COMMAND:
			case EVENT_KEY_RELEASE:
			case EVENT_IDLE:
				kconfig_read_controls(Controls, event, 1);

				if (slew_frame(0))
				{		//do movement and check keys
					Update_flags |= UF_GAME_VIEW_CHANGED;
					if (Gameview_lockstep)
					{
						Cursegp = imsegptridx(ConsoleObject->segnum);
						med_create_new_segment_from_cursegp();
						Update_flags |= UF_ED_STATE_CHANGED;
					}

					rval = window_event_result::handled;
				}
				break;
			case EVENT_LOOP_BEGIN_LOOP:
				kconfig_begin_loop(Controls);
				break;
			case EVENT_LOOP_END_LOOP:
				kconfig_end_loop(Controls, FrameTime);
				break;

			default:
				break;
		}

	//do non-essential stuff in idle event
	if (event.type == EVENT_IDLE)
	{
		check_wall_validity();

		if (Gameview_lockstep) {
			static segment *old_cursegp=NULL;
			static sidenum_t old_curside = side_none;

			if (old_cursegp!=Cursegp || old_curside!=Curside) {
				SetPlayerFromCursegMinusOne();
				old_cursegp = Cursegp;
				old_curside = Curside;
			}
		}

		if ( event_get_idle_seconds() > COMPRESS_INTERVAL ) 
		{
			med_compress_mine();
			event_reset_idle_seconds();
		}

	//	Commented out because it occupies about 25% of time in twirling the mine.
	// Removes some Asserts....
	//		med_check_all_vertices();
		clear_editor_status();		// if enough time elapsed, clear editor status message
	}

	gr_set_current_canvas( GameViewBox->canvas );
	
	// Remove keys used for slew
	switch(keypress)
	{
		case KEY_PAD9:
		case KEY_PAD7:
		case KEY_PADPLUS:
		case KEY_PADMINUS:
		case KEY_PAD8:
		case KEY_PAD2:
		case KEY_LBRACKET:
		case KEY_RBRACKET:
		case KEY_PAD1:
		case KEY_PAD3:
		case KEY_PAD6:
		case KEY_PAD4:
			keypress = 0;
	}
	if ((keypress&0xff)==KEY_LSHIFT) keypress=0;
	if ((keypress&0xff)==KEY_RSHIFT) keypress=0;
	if ((keypress&0xff)==KEY_LCTRL) keypress=0;
	if ((keypress&0xff)==KEY_RCTRL) keypress=0;
	if ((keypress&0xff)==KEY_LMETA) keypress=0;
	if ((keypress&0xff)==KEY_RMETA) keypress=0;
//		if ((keypress&0xff)==KEY_LALT) keypress=0;
//		if ((keypress&0xff)==KEY_RALT) keypress=0;

	//=================== DO FUNCTIONS ====================

	if ( KeyFunction[ keypress ] != NULL )
	{
		KeyFunction[keypress]();
		keypress = 0;
		rval = window_event_result::handled;
	}

	switch (keypress)
	{
		case 0:
		case KEY_Z:
		case KEY_G:
		case KEY_LALT:
		case KEY_RALT:
		case KEY_LCTRL:
		case KEY_RCTRL:
		case KEY_LSHIFT:
		case KEY_RSHIFT:
		case KEY_LAPOSTRO:
			break;
		case KEY_SHIFTED + KEY_L:
#if !DXX_USE_OGL
			ToggleLighting();
#endif
			rval = window_event_result::handled;
			break;
		case KEY_F1:
			render_3d_in_big_window = !render_3d_in_big_window;
			Update_flags |= UF_ALL;
			rval = window_event_result::handled;
			break;			
		default:
			if (rval == window_event_result::ignored)
			{
				char kdesc[100];
				GetKeyDescription( kdesc, keypress );
				editor_status_fmt("Error: %s isn't bound to anything.", kdesc  );
			}
	}

	//================================================================

	if (ModeFlag)
	{
		return window_event_result::close;
	}

	new_cv = current_view;

	if (new_cv != current_view ) {
		current_view->ev_changed = 1;
		new_cv->ev_changed = 1;
		current_view = new_cv;
	}

	// DO TEXTURE STUFF
	if (texpage_do(event))
		rval = window_event_result::handled;
	
	if (objpage_do(event))
		rval = window_event_result::handled;


	// Process selection of Cursegp using mouse.
	if (GADGET_PRESSED(LargeViewBox.get()) && !render_3d_in_big_window) 
	{
		int	xcrd,ycrd;
		xcrd = LargeViewBox->b1_drag_x1;
		ycrd = LargeViewBox->b1_drag_y1;

		find_segments(xcrd,ycrd,LargeViewBox->canvas.get(),&LargeView,Cursegp,Big_depth);	// Sets globals N_found_segs, Found_segs

		// If shift is down, then add segment to found list
		if (keyd_pressed[ KEY_LSHIFT ] || keyd_pressed[ KEY_RSHIFT ])
			subtract_found_segments_from_selected_list();
		else
			add_found_segments_to_selected_list();

		Found_seg_index = 0;	
	
		if (!Found_segs.empty())
		{
			sort_seg_list(Found_segs,ConsoleObject->pos);
			Cursegp = imsegptridx(Found_segs[0]);
			med_create_new_segment_from_cursegp();
			if (Lock_view_to_cursegp)
			{
				auto &vcvertptr = Vertices.vcptr;
				set_view_target_from_segment(vcvertptr, Cursegp);
			}
		}

		Update_flags |= UF_ED_STATE_CHANGED | UF_VIEWPOINT_MOVED;
	}

	if (event.type == EVENT_UI_USERBOX_DRAGGED && &ui_event_get_gadget(event) == GameViewBox.get())
	{
		int	x, y;
		x = GameViewBox->b1_drag_x2;
		y = GameViewBox->b1_drag_y2;

		gr_set_current_canvas( GameViewBox->canvas );
		gr_rect(*grd_curcanv, x-1, y-1, x+1, y+1, 15);
	}
	
	// Set current segment and side by clicking on a polygon in game window.
	//	If ctrl pressed, also assign current texture map to that side.
	//if (GameViewBox->mouse_onme && (GameViewBox->b1_done_dragging || GameViewBox->b1_clicked)) {
	if ((GADGET_PRESSED(GameViewBox.get()) && !render_3d_in_big_window) ||
		(GADGET_PRESSED(LargeViewBox.get()) && render_3d_in_big_window))
	{
		int	xcrd,ycrd;
		int face;

		if (render_3d_in_big_window) {
			xcrd = LargeViewBox->b1_drag_x1;
			ycrd = LargeViewBox->b1_drag_y1;
		}
		else {
			xcrd = GameViewBox->b1_drag_x1;
			ycrd = GameViewBox->b1_drag_y1;
		}

		//Int3();

		segnum_t seg;
		objnum_t obj;
		sidenum_t side;
		if (find_seg_side_face(xcrd,ycrd,seg,obj,side,face))
		{

			if (obj != object_none) {							//found an object

				Cur_object_index = obj;
				editor_status_fmt("Object %d selected.",Cur_object_index);

				Update_flags |= UF_ED_STATE_CHANGED;
			}
			else {

				//	See if either shift key is down and, if so, assign texture map
				if (keyd_pressed[KEY_LSHIFT] || keyd_pressed[KEY_RSHIFT]) {
					Cursegp = imsegptridx(seg);
					Curside = side;
					AssignTexture();
					med_create_new_segment_from_cursegp();
					editor_status("Texture assigned");
				} else if (keyd_pressed[KEY_G])	{
					const unique_segment &us = vcsegptr(seg);
					const auto tmap = us.sides[side].tmap_num;
					texpage_grab_current(tmap);
					editor_status( "Texture grabbed." );
				} else if (keyd_pressed[ KEY_LAPOSTRO] ) {
					move_object_to_mouse_click();
				} else {
					Cursegp = imsegptridx(seg);
					Curside = side;
					med_create_new_segment_from_cursegp();
					editor_status("Curseg and curside selected");
				}
			}

			Update_flags |= UF_ED_STATE_CHANGED;
		}
		else 
			editor_status("Click on non-texture ingored");

	}

	// Allow specification of LargeView using mouse
	if (event.type == EVENT_MOUSE_MOVED && (keyd_pressed[ KEY_LCTRL ] || keyd_pressed[ KEY_RCTRL ]))
	{
		int dx, dy, dz;

		event_mouse_get_delta(event, &dx, &dy, &dz);
		if ((dx != 0) && (dy != 0))
		{
			vms_matrix	MouseRotMat;
			
			GetMouseRotation( dx, dy, &MouseRotMat );
			LargeView.ev_matrix = vm_matrix_x_matrix(LargeView.ev_matrix,MouseRotMat);
			LargeView.ev_changed = 1;
			Large_view_index = -1;			// say not one of the orthogonal views
			rval = window_event_result::handled;
		}
	}

	if (event.type == EVENT_MOUSE_MOVED)
	{
		int dx, dy, dz;

		event_mouse_get_delta(event, &dx, &dy, &dz);
		if (dz != 0)
		{
			current_view->ev_dist += dz*10000;
			current_view->ev_changed = 1;
		}
	}
	
	return rval;
}
