/*
 * 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.
*/

/*
 *
 * Dialog box to edit robot properties.
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string.h>

#include "inferno.h"
#include "segment.h"
#include "event.h"
#include "editor.h"
#include "editor/esegment.h"
#include "editor/medmisc.h"
#include "timer.h"
#include "objpage.h"
#include "maths.h"
#include "dxxerror.h"
#include "kdefs.h"
#include "object.h"
#include "robot.h"
#include "game.h"
#include "powerup.h"
#include "ai.h"
#include "eobject.h"
#include "medwall.h"
#include "medrobot.h"
#include "eswitch.h"
#include "ehostage.h"
#include "key.h"
#include "centers.h"
#include "bm.h"
#include "u_mem.h"

#include "compiler-range_for.h"
#include "d_levelstate.h"
#include "d_enumerate.h"
#include <memory>

static int GoodyNextID();
static int GoodyPrevID();

namespace {

static contained_object_parameters Cur_goody = {
	.type = contained_object_type::powerup,
	.id = contained_object_id{
		.powerup = powerup_type_t::POW_EXTRA_LIFE
	},
	.count = 0,
};

}

//-------------------------------------------------------------------------
// Variables for this module...
//-------------------------------------------------------------------------
namespace dsx {

namespace {

struct robot_dialog : UI_DIALOG
{
	using UI_DIALOG::UI_DIALOG;
	std::unique_ptr<UI_GADGET_USERBOX> robotViewBox, containsViewBox;
	std::unique_ptr<UI_GADGET_BUTTON> quitButton, prev_powerup_type, next_powerup_type, prev_powerup_id, next_powerup_id, prev_powerup_count, next_powerup_count, prev_robot_type, next_robot_type, next_segment, prev_object, next_object, delete_object, new_object, set_path;
	std::array<std::unique_ptr<UI_GADGET_RADIO>, 6> initialMode;			//	Number of boxes, AI modes
	fix64 time;
	vms_angvec angles{}, goody_angles{};
	int old_object;
	virtual window_event_result callback_handler(const d_event &) override;
};

static robot_dialog *MainWindow;

static void call_init_ai_object(const d_robot_info_array &Robot_info, vmobjptridx_t objp, ai_behavior behavior)
{
	segnum_t	hide_segment;

	if (behavior == ai_behavior::AIB_STATION)
		hide_segment = Cursegp;
	else {
		if (Markedsegp != segment_none)
			hide_segment = Markedsegp;
		else
			hide_segment = Cursegp;
	}

	init_ai_object(Robot_info, objp, behavior, hide_segment);
}

//-------------------------------------------------------------------------
// Called when user presses "Next Type" button.  This only works for polygon
// objects and it just selects the next polygon model for the current object.
//-------------------------------------------------------------------------
static int RobotNextType()
{
	auto &Objects = LevelUniqueObjectState.Objects;
	auto &vmobjptridx = Objects.vmptridx;
	auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
	if (Cur_object_index != object_none )	{
		const auto &&obj = vmobjptridx(Cur_object_index);
		if (obj->type == object_type::OBJ_ROBOT)
		{
			obj->id++;
			if (obj->id >= LevelSharedRobotInfoState.N_robot_types)
				obj->id = 0;

			//Set polygon-object-specific data
			obj->rtype.pobj_info.model_num.dsx = Robot_info[get_robot_id(obj)].model_num;
			obj->rtype.pobj_info.subobj_flags = 0;
			//set Physics info
			obj->mtype.phys_info.flags |= (PF_LEVELLING);
			obj->shields = Robot_info[get_robot_id(obj)].strength;
			call_init_ai_object(Robot_info, obj, ai_behavior::AIB_NORMAL);

			Cur_object_id = underlying_value(get_robot_id(obj));
		}
	}
	Update_flags |= UF_WORLD_CHANGED;
	return 1;
}

//-------------------------------------------------------------------------
// Called when user presses "Prev Type" button.  This only works for polygon
// objects and it just selects the prev polygon model for the current object.
//-------------------------------------------------------------------------
static int RobotPrevType()
{
	auto &Objects = LevelUniqueObjectState.Objects;
	auto &vmobjptridx = Objects.vmptridx;
	auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
	if (Cur_object_index != object_none )	{
		const auto &&obj = vmobjptridx(Cur_object_index);
		if (obj->type == object_type::OBJ_ROBOT)
		{
			if (obj->id == 0 ) 
				obj->id = LevelSharedRobotInfoState.N_robot_types - 1;
			else
				obj->id--;

			//Set polygon-object-specific data
			obj->rtype.pobj_info.model_num.dsx = Robot_info[get_robot_id(obj)].model_num;
			obj->rtype.pobj_info.subobj_flags = 0;
			//set Physics info
			obj->mtype.phys_info.flags |= (PF_LEVELLING);
			obj->shields = Robot_info[get_robot_id(obj)].strength;
			call_init_ai_object(Robot_info, obj, ai_behavior::AIB_NORMAL);

			Cur_object_id = underlying_value(get_robot_id(obj));
		}
	}
	Update_flags |= UF_WORLD_CHANGED;
	return 1;
}

}

}

//-------------------------------------------------------------------------
// Dummy function for Mike to write.
//-------------------------------------------------------------------------
static int med_set_ai_path()
{
	return 1;
}

// #define object_type::OBJ_NONE		255	//unused object
// #define object_type::OBJ_WALL		0		//A wall... not really an object, but used for collisions
// #define object_type::OBJ_FIREBALL	1		//a fireball, part of an explosion
// #define object_type::OBJ_ROBOT		2		//an evil enemy
// #define object_type::OBJ_HOSTAGE	3		//a hostage you need to rescue
// #define object_type::OBJ_PLAYER	4		//the player on the console
// #define object_type::OBJ_WEAPON	5		//a laser, missile, etc
// #define object_type::OBJ_CAMERA	6		//a camera to slew around with
// #define object_type::OBJ_POWERUP	7		//a powerup you can pick up
// #define object_type::OBJ_DEBRIS	8		//a piece of robot
// #define object_type::OBJ_CNTRLCEN	9		//the control center
// #define OBJ_FLARE		10		//the control center
// #define MAX_OBJECT_TYPES	11


#define	GOODY_TYPE_MAX	MAX_OBJECT_TYPES
#define	GOODY_X	6
#define	GOODY_Y	132

//#define	GOODY_ID_MAX_ROBOT	6
//#define	GOODY_ID_MAX_POWERUP	9
#define	GOODY_COUNT_MAX	4

static void update_goody_info(void)
{
	auto &Objects = LevelUniqueObjectState.Objects;
	auto &vmobjptr = Objects.vmptr;
	if (Cur_object_index != object_none )	{
		auto &obj = *vmobjptr(Cur_object_index);
		if (obj.type == object_type::OBJ_ROBOT)
		{
			obj.contains = Cur_goody;
		}
	}
}

// #define object_type::OBJ_WALL		0		//A wall... not really an object, but used for collisions
// #define object_type::OBJ_FIREBALL	1		//a fireball, part of an explosion
// #define object_type::OBJ_ROBOT		2		//an evil enemy
// #define object_type::OBJ_HOSTAGE	3		//a hostage you need to rescue
// #define object_type::OBJ_PLAYER	4		//the player on the console
// #define object_type::OBJ_WEAPON	5		//a laser, missile, etc
// #define object_type::OBJ_CAMERA	6		//a camera to slew around with
// #define object_type::OBJ_POWERUP	7		//a powerup you can pick up
// #define object_type::OBJ_DEBRIS	8		//a piece of robot
// #define object_type::OBJ_CNTRLCEN	9		//the control center
// #define OBJ_FLARE		10		//the control center
// #define MAX_OBJECT_TYPES	11

static void GoodyClampIDLower(const contained_object_type type)
{
	switch (type)
	{
		case contained_object_type::robot:
			if (underlying_value(Cur_goody.id.robot) >= LevelSharedRobotInfoState.N_robot_types)
				Cur_goody.id.robot = {};
			break;
		case contained_object_type::powerup:
			if (underlying_value(Cur_goody.id.powerup) >= N_powerup_types)
				Cur_goody.id.powerup = {};
			break;
		default:
			break;
	}
}

static void GoodyClampIDUpper(const contained_object_type type)
{
	switch (type)
	{
		case contained_object_type::robot:
			if (const auto N_robot_types{LevelSharedRobotInfoState.N_robot_types}; underlying_value(Cur_goody.id.robot) >= N_robot_types)
				Cur_goody.id.robot = static_cast<robot_id>(N_robot_types - 1);
			break;
		case contained_object_type::powerup:
			if (const auto n{N_powerup_types}; underlying_value(Cur_goody.id.powerup) >= n)
				Cur_goody.id.powerup = static_cast<powerup_type_t>(n - 1);
			break;
		default:
			break;
	}
}

static int GoodyToggleType()
{
	Cur_goody.type = (Cur_goody.type == contained_object_type::robot) ? contained_object_type::powerup : contained_object_type::robot;
	GoodyClampIDLower(Cur_goody.type);

	update_goody_info();
	return 1;
}

int GoodyNextID()
{
	Cur_goody.id.robot = static_cast<robot_id>(underlying_value(Cur_goody.id.robot) + 1);
	GoodyClampIDLower(Cur_goody.type);
	update_goody_info();
	return 1;
}

int GoodyPrevID()
{
	Cur_goody.id.robot = static_cast<robot_id>(underlying_value(Cur_goody.id.robot) - 1);
	GoodyClampIDUpper(Cur_goody.type);

	update_goody_info();
	return 1;
}

static int GoodyNextCount()
{
	Cur_goody.count = Cur_goody.count > GOODY_COUNT_MAX ? 0 : (Cur_goody.count + 1);
	update_goody_info();
	return 1;
}

static int GoodyPrevCount()
{
	Cur_goody.count = Cur_goody.count == 0 ? GOODY_COUNT_MAX : (Cur_goody.count - 1);
	update_goody_info();
	return 1;
}

static int is_legal_type(int the_type)
{
	return (the_type == object_type::OBJ_ROBOT) || (the_type == object_type::OBJ_CLUTTER);
}

static int is_legal_type_for_this_window(const imobjidx_t objnum)
{
	auto &Objects = LevelUniqueObjectState.Objects;
	auto &vmobjptr = Objects.vmptr;
	if (objnum == object_none)
		return 1;
	else
		return is_legal_type(vmobjptr(objnum)->type);
}

static int LocalObjectSelectNextinSegment(void)
{
	auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
	auto &Objects = LevelUniqueObjectState.Objects;
	auto &Vertices = LevelSharedVertexState.get_vertices();
	auto &vmobjptr = Objects.vmptr;
	int	rval, first_obj;

	rval = ObjectSelectNextinSegment();
	first_obj = Cur_object_index;

	if (Cur_object_index != object_none) {
		while (!is_legal_type_for_this_window(Cur_object_index)) {
			rval = ObjectSelectNextinSegment();
			if (first_obj == Cur_object_index)
				break;
		}

		const auto &&objp = vmobjptr(Cur_object_index);
		if (auto &c = objp->contains.count; c > GOODY_COUNT_MAX)
			c = 0;
		Cur_goody = objp->contains;
	}

	if (Cur_object_index != first_obj)
	{
		auto &vcvertptr = Vertices.vcptr;
		set_view_target_from_segment(vcvertptr, Cursegp);
	}

	return rval;
}

static int LocalObjectSelectNextinMine(void)
{
	auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
	auto &Objects = LevelUniqueObjectState.Objects;
	auto &Vertices = LevelSharedVertexState.get_vertices();
	auto &vmobjptr = Objects.vmptr;
	int	rval, first_obj;

	rval = ObjectSelectNextInMine();

	first_obj = Cur_object_index;

	if (Cur_object_index != object_none) {
		while (!is_legal_type_for_this_window(Cur_object_index)) {
			ObjectSelectNextInMine();
			if (Cur_object_index == first_obj)
				break;
		}

		const auto &&objp = vmobjptr(Cur_object_index);
		if (auto &c = objp->contains.count; c > GOODY_COUNT_MAX)
			c = 0;
		Cur_goody = objp->contains;
	}

	if (Cur_object_index != first_obj)
	{
		auto &vcvertptr = Vertices.vcptr;
		set_view_target_from_segment(vcvertptr, Cursegp);
	}

	return rval;
}

static int LocalObjectSelectPrevinMine(void)
{
	auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
	auto &Objects = LevelUniqueObjectState.Objects;
	auto &Vertices = LevelSharedVertexState.get_vertices();
	auto &vmobjptr = Objects.vmptr;
	int	rval, first_obj;

	rval = ObjectSelectPrevInMine();

	first_obj = Cur_object_index;

	if (Cur_object_index != object_none) {
		while (!is_legal_type_for_this_window(Cur_object_index)) {
			ObjectSelectPrevInMine();
			if (first_obj == Cur_object_index)
				break;
		}

		const auto &&objp = vmobjptr(Cur_object_index);
		if (auto &c = objp->contains.count; c > GOODY_COUNT_MAX)
			c = 0;
		Cur_goody = objp->contains;
	}

	if (Cur_object_index != first_obj)
	{
		auto &vcvertptr = Vertices.vcptr;
		set_view_target_from_segment(vcvertptr, Cursegp);
	}

	return rval;
}

static int LocalObjectDelete(void)
{
	auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
	auto &Objects = LevelUniqueObjectState.Objects;
	auto &Vertices = LevelSharedVertexState.get_vertices();
	auto &vcobjptr = Objects.vcptr;
	int	rval;

	rval = ObjectDelete();

	if (Cur_object_index != object_none) {
		auto &objp = *vcobjptr(Cur_object_index);
		Cur_goody = objp.contains;
	}

	auto &vcvertptr = Vertices.vcptr;
	set_view_target_from_segment(vcvertptr, Cursegp);

	return rval;
}

static int LocalObjectPlaceObject(void)
{
	auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
	auto &Objects = LevelUniqueObjectState.Objects;
	auto &Vertices = LevelSharedVertexState.get_vertices();
	auto &vmobjptr = Objects.vmptr;
	int	rval;

	if (Cur_object_type != object_type::OBJ_ROBOT)
	{
		Cur_object_type = object_type::OBJ_ROBOT;
		Cur_object_id = 3;	// class 1 drone
		Num_object_subtypes = LevelSharedRobotInfoState.N_robot_types;
	}

	Cur_goody.count = 0;

	rval = ObjectPlaceObject();
	if (rval == -1)
		return -1;

	const auto &&objp = vmobjptr(Cur_object_index);
	objp->contains = Cur_goody;

	auto &vcvertptr = Vertices.vcptr;
	set_view_target_from_segment(vcvertptr, Cursegp);

	return rval;
}

void close_all_windows(void)
{
	close_trigger_window();
	close_wall_window();
	close_centers_window();
#if DXX_BUILD_DESCENT == 1
	hostage_close_window();
#endif
	robot_close_window();
}


//-------------------------------------------------------------------------
// Called from the editor... does one instance of the robot dialog box
//-------------------------------------------------------------------------
int do_robot_dialog()
{
	// Only open 1 instance of this window...
	if ( MainWindow != NULL ) return 0;
	
	// Close other windows
	close_all_windows();
	Cur_goody.count = 0;

	// Open a window with a quit button
	MainWindow = window_create<robot_dialog>(TMAPBOX_X + 20, TMAPBOX_Y + 20, 765 - TMAPBOX_X, 545 - TMAPBOX_Y, DF_DIALOG);
	return 1;
}

static window_event_result robot_dialog_created(robot_dialog *const r)
{
	r->quitButton = ui_add_gadget_button(*r, 20, 286, 40, 32, "Done", NULL);
	r->prev_powerup_type = ui_add_gadget_button(*r, GOODY_X+50, GOODY_Y-3, 25, 22, "<<", GoodyToggleType);
	r->next_powerup_type = ui_add_gadget_button(*r, GOODY_X+80, GOODY_Y-3, 25, 22, ">>", GoodyToggleType);
	r->prev_powerup_id = ui_add_gadget_button(*r, GOODY_X+50, GOODY_Y+21, 25, 22, "<<", GoodyPrevID);
	r->next_powerup_id = ui_add_gadget_button(*r, GOODY_X+80, GOODY_Y+21, 25, 22, ">>", GoodyNextID);
	r->prev_powerup_count = ui_add_gadget_button(*r, GOODY_X+50, GOODY_Y+45, 25, 22, "<<", GoodyPrevCount);
	r->next_powerup_count = ui_add_gadget_button(*r, GOODY_X+80, GOODY_Y+45, 25, 22, ">>", GoodyNextCount);
	r->initialMode[0] = ui_add_gadget_radio(*r,  6, 58, 16, 16, 0, "Hover");
	r->initialMode[1] = ui_add_gadget_radio(*r, 76, 58, 16, 16, 0, "Normal");
	r->initialMode[2] = ui_add_gadget_radio(*r,  6, 78, 16, 16, 0, "(hide)");
	r->initialMode[3] = ui_add_gadget_radio(*r, 76, 78, 16, 16, 0, "Avoid");
	r->initialMode[4] = ui_add_gadget_radio(*r,  6, 98, 16, 16, 0, "Follow");
	r->initialMode[5] = ui_add_gadget_radio(*r, 76, 98, 16, 16, 0, "Station");
	// The little box the robots will spin in.
	r->robotViewBox = ui_add_gadget_userbox(*r, 155, 5, 150, 125);
	// The little box the robots will spin in.
	r->containsViewBox = ui_add_gadget_userbox(*r, 10, 202, 100, 80);
	// A bunch of buttons...
	int i{135};
	r->prev_robot_type = ui_add_gadget_button(*r, 190, i, 53, 26, "<<Typ", 			RobotPrevType);
	r->next_robot_type = ui_add_gadget_button(*r, 247, i, 53, 26, "Typ>>", 			RobotNextType);							i += 29;		
	r->next_segment = ui_add_gadget_button(*r, 190, i, 110, 26, "Next in Seg", LocalObjectSelectNextinSegment);	i += 29;		
	r->prev_object = ui_add_gadget_button(*r, 190, i, 53, 26, "<<Obj",		 	LocalObjectSelectPrevinMine);
	r->next_object = ui_add_gadget_button(*r, 247, i, 53, 26, ">>Obj",			LocalObjectSelectNextinMine); 		i += 29;		
	r->delete_object = ui_add_gadget_button(*r, 190, i, 110, 26, "Delete", 		LocalObjectDelete);						i += 29;		
	r->new_object = ui_add_gadget_button(*r, 190, i, 110, 26, "Create New", 	LocalObjectPlaceObject);				i += 29;		
	r->set_path = ui_add_gadget_button(*r, 190, i, 110, 26, "Set Path", 	med_set_ai_path);
	r->time = timer_query();
	r->old_object = -2;		// Set to some dummy value so everything works ok on the first frame.
	if ( Cur_object_index == object_none)
		LocalObjectSelectNextinMine();
	return window_event_result::handled;
}

void robot_close_window()
{
	if ( MainWindow!=NULL )	{
		ui_close_dialog(*std::exchange(MainWindow, nullptr));
	}

}

namespace dsx {

namespace {

window_event_result robot_dialog::callback_handler(const d_event &event)
{
	auto &Objects = LevelUniqueObjectState.Objects;
	auto &vcobjptr = Objects.vcptr;
	auto &vmobjptr = Objects.vmptr;
	auto &vmobjptridx = Objects.vmptridx;
	switch(event.type)
	{
		case event_type::window_created:
			return robot_dialog_created(this);
		case event_type::window_close:
			MainWindow = NULL;
			return window_event_result::ignored;
		default:
			break;
	}
	fix	DeltaTime;
	fix64	Temp;
	int	first_object_index;
	int keypress{0};
	window_event_result rval = window_event_result::ignored;
	if (event.type == event_type::key_command)
		keypress = event_key_get(event);
	Assert(MainWindow != NULL);

	first_object_index = Cur_object_index;
	while (!is_legal_type_for_this_window(Cur_object_index)) {
		LocalObjectSelectNextinMine();
		if (first_object_index == Cur_object_index) {
			break;
		}
	}

	//------------------------------------------------------------
	// Call the ui code..
	//------------------------------------------------------------
	ui_button_any_drawn = 0;

	//------------------------------------------------------------
	// If we change objects, we need to reset the ui code for all
	// of the radio buttons that control the ai mode.  Also makes
	// the current AI mode button be flagged as pressed down.
	//------------------------------------------------------------
	if (old_object != Cur_object_index )	{
		range_for (auto &i, initialMode)
			ui_radio_set_value(*i, 0);
		if ( Cur_object_index != object_none ) {
			auto &behavior = vmobjptr(Cur_object_index)->ctype.ai_info.behavior;
			switch (behavior)
			{
				case ai_behavior::AIB_STILL:
				case ai_behavior::AIB_NORMAL:
				case ai_behavior::AIB_RUN_FROM:
				case ai_behavior::AIB_STATION:
#if DXX_BUILD_DESCENT == 1
				case ai_behavior::AIB_HIDE:
				case ai_behavior::AIB_FOLLOW_PATH:
#elif DXX_BUILD_DESCENT == 2
				case ai_behavior::AIB_BEHIND:
				case ai_behavior::AIB_SNIPE:
				case ai_behavior::AIB_FOLLOW:
#endif
					break;
				default:
					behavior = ai_behavior::AIB_NORMAL;
					break;
			}
			ui_radio_set_value(*initialMode[static_cast<std::size_t>(behavior) - MIN_BEHAVIOR], 1);
		}
	}

	//------------------------------------------------------------
	// If any of the radio buttons that control the mode are set, then
	// update the cooresponding AI state.
	//------------------------------------------------------------
	for (auto &&[i, im] : enumerate(initialMode))
	{
		if (GADGET_PRESSED(im.get()))
		{
			const auto b = static_cast<ai_behavior>(MIN_BEHAVIOR + i);
			const auto &&objp = vmobjptridx(Cur_object_index);
			auto &behavior = objp->ctype.ai_info.behavior;
			if (behavior != b) {
				behavior = b;		// Set the ai_state to the cooresponding radio button
				call_init_ai_object(LevelSharedRobotInfoState.Robot_info, objp, b);
				rval = window_event_result::handled;
			}
		}
	}

	//------------------------------------------------------------
	// Redraw the object in the little 64x64 box
	//------------------------------------------------------------
	if (event.type == event_type::ui_dialog_draw)
	{
		// A simple frame time counter for spinning the objects...
		Temp = timer_query();
		DeltaTime = Temp - time;
		time = Temp;

		gr_set_current_canvas(robotViewBox->canvas);
		if (Cur_object_index != object_none )	{
			const auto &&obj = vmobjptr(Cur_object_index);

			draw_object_picture(*grd_curcanv, obj->id, angles, obj->type);
			angles.h += fixmul(0x1000, DeltaTime );
		} else {
			// no object, so just blank out
			gr_clear_canvas(*grd_curcanv, CGREY);

	//		LocalObjectSelectNextInMine();
		}

	//------------------------------------------------------------
	// Redraw the contained object in the other little box
	//------------------------------------------------------------
		gr_set_current_canvas(containsViewBox->canvas);
		if (Cur_object_index != object_none && Cur_goody.count > 0)
		{
			draw_object_picture(*grd_curcanv, underlying_value(Cur_goody.id.robot), goody_angles, underlying_value(Cur_goody.type));
			goody_angles.h += fixmul(0x1000, DeltaTime );
		} else {
			// no object, so just blank out
			gr_clear_canvas(*grd_curcanv, CGREY);

	//		LocalObjectSelectNextInMine();
		}
	//------------------------------------------------------------
	// If anything changes in the ui system, redraw all the text that
	// identifies this robot.
	//------------------------------------------------------------

		const char *id_text;
		const char *type_text;

		if (Cur_object_index != object_none) {
			auto &&obj = *vmobjptr(Cur_object_index);
			if (auto &c = obj.contains.count; c > GOODY_COUNT_MAX)
				c = 0;
			Cur_goody = obj.contains;
		}

		ui_dputs_at(MainWindow, GOODY_X, GOODY_Y,    " Type:");
		ui_dputs_at(MainWindow, GOODY_X, GOODY_Y+24, "   ID:");
		ui_dputs_at(MainWindow, GOODY_X, GOODY_Y+48, "Count:");

		switch (Cur_goody.type) {
			case contained_object_type::robot:
				type_text = "Robot  ";
				id_text = Robot_names[Cur_goody.id.robot].data();
				break;
			default:
				editor_status_fmt("Illegal contained object type (%i), changing to powerup.", underlying_value(std::exchange(Cur_goody.type, contained_object_type::powerup)));
				Cur_goody.id.powerup = {};
				[[fallthrough]];
			case contained_object_type::powerup:
				type_text = "Powerup";
				id_text = Powerup_names[Cur_goody.id.powerup].data();
				break;
		}

		ui_dputs_at( MainWindow, GOODY_X+108, GOODY_Y, type_text);
		ui_dprintf_at( MainWindow, GOODY_X+108, GOODY_Y+24, "%-8s", id_text);
		ui_dprintf_at(MainWindow, GOODY_X+108, GOODY_Y+48, "%u", Cur_goody.count);

		if ( Cur_object_index != object_none )	{
			const auto id = get_robot_id(vcobjptr(Cur_object_index));

			ui_dprintf_at( MainWindow, 12,  6, "Robot: %3d ", Cur_object_index );
			ui_dprintf_at( MainWindow, 12, 22, "   Id: %3d", underlying_value(id));
			ui_dprintf_at( MainWindow, 12, 38, " Name: %-8s", Robot_names[id].data());

		}	else {
			ui_dputs_at(MainWindow, 12,  6, "Robot: none");
			ui_dputs_at(MainWindow, 12, 22, " Type: ?");
			ui_dputs_at(MainWindow, 12, 38, " Name: ________");
		}
	}
	
	if (ui_button_any_drawn || (old_object != Cur_object_index) )
		Update_flags |= UF_WORLD_CHANGED;
	if (GADGET_PRESSED(quitButton.get()) || keypress == KEY_ESC)
	{
		return window_event_result::close;
	}		

	old_object = Cur_object_index;
	return rval;
}

}

}

//	--------------------------------------------------------------------------------------------------------------------------
#define	NUM_MATT_THINGS	2

#define	MATT_LEN				20

namespace {

struct object_dialog : UI_DIALOG
{
	explicit object_dialog(short x, short y, short w, short h, enum dialog_flags flags, object &obj);
	struct creation_context
	{
		vmobjptr_t obj;
		creation_context(vmobjptr_t o) :
			obj(o)
		{
		}
	};
	std::unique_ptr<UI_GADGET_INPUTBOX> xtext, ytext, ztext;
	std::array<std::unique_ptr<UI_GADGET_RADIO>, 2> initialMode;
	std::unique_ptr<UI_GADGET_BUTTON> quitButton;
	virtual window_event_result callback_handler(const d_event &) override;
};

static object_dialog *MattWindow;

}

void object_close_window()
{
	if ( MattWindow!=NULL )	{
		ui_close_dialog(*std::exchange(MattWindow, nullptr));
	}

}


//-------------------------------------------------------------------------
// Called from the editor... does one instance of the object dialog box
//-------------------------------------------------------------------------
int do_object_dialog()
{
	auto &Objects = LevelUniqueObjectState.Objects;
	auto &vmobjptr = Objects.vmptr;
	if (Cur_object_index == object_none)
		Cur_object_index = object_first;

	auto obj = vmobjptr(Cur_object_index);
	if (obj->type == object_type::OBJ_ROBOT)		//don't do this for robots
		return 0;

	// Only open 1 instance of this window...
	if ( MattWindow != NULL )
		return 0;
	
	Cur_goody.count = 0;

	// Open a window with a quit button
	MattWindow = window_create<object_dialog>(TMAPBOX_X + 20, TMAPBOX_Y + 20, 765 - TMAPBOX_X, 545 - TMAPBOX_Y, DF_DIALOG, *obj);
	return 1;
}

object_dialog::object_dialog(short x, short y, short w, short h, enum dialog_flags flags, object &obj) :
	UI_DIALOG(x, y, w, h, flags)
{
	quitButton = ui_add_gadget_button(*this, 20, 286, 40, 32, "Done", NULL );
	quitButton->hotkey = KEY_ENTER;
	// These are the radio buttons for each mode
	initialMode[0] = ui_add_gadget_radio(*this, 10, 50, 16, 16, 0, "None" );
	initialMode[1] = ui_add_gadget_radio(*this, 80, 50, 16, 16, 0, "Spinning" );
	initialMode[obj.movement_source == object::movement_type::spinning ? 1 : 0]->flag = 1;
	char message[MATT_LEN];
	snprintf(message, sizeof(message), "%.2f", f2fl(obj.mtype.spin_rate.x));
	xtext = ui_add_gadget_inputbox<MATT_LEN>(*this, 30, 132, message);
	snprintf(message, sizeof(message), "%.2f", f2fl(obj.mtype.spin_rate.y));
	ytext = ui_add_gadget_inputbox<MATT_LEN>(*this, 30, 162, message);
	snprintf(message, sizeof(message), "%.2f", f2fl(obj.mtype.spin_rate.z));
	ztext = ui_add_gadget_inputbox<MATT_LEN>(*this, 30, 192, message);
	ui_gadget_calc_keys(*this);
	keyboard_focus_gadget = initialMode[0].get();
}

window_event_result object_dialog::callback_handler(const d_event &event)
{
	auto &Objects = LevelUniqueObjectState.Objects;
	auto &vmobjptr = Objects.vmptr;
	switch(event.type)
	{
		case event_type::window_close:
			MattWindow = NULL;
			return window_event_result::ignored;
		default:
			break;
	}
	const auto &&obj = vmobjptr(Cur_object_index);
	int keypress{0};
	window_event_result rval = window_event_result::ignored;
	if (event.type == event_type::key_command)
		keypress = event_key_get(event);
	//------------------------------------------------------------
	// Call the ui code..
	//------------------------------------------------------------
	ui_button_any_drawn = 0;


	if (event.type == event_type::ui_dialog_draw)
	{
		ui_dputs_at(this, 10, 132, "&X:");
		ui_dputs_at(this, 10, 162, "&Y:");
		ui_dputs_at(this, 10, 192, "&Z:");
	}
	if (GADGET_PRESSED(quitButton.get()) || keypress == KEY_ESC)
	{
		if (initialMode[0]->flag)
			obj->movement_source = object::movement_type::None;
		if (initialMode[1]->flag)
			obj->movement_source = object::movement_type::spinning;

		obj->mtype.spin_rate.x = fl2f(atof(xtext->text.get()));
		obj->mtype.spin_rate.y = fl2f(atof(ytext->text.get()));
		obj->mtype.spin_rate.z = fl2f(atof(ztext->text.get()));
		return window_event_result::close;
	}
	return rval;
}
