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

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

#include "inferno.h"
#include "editor.h"
#include "editor/esegment.h"
#include "editor/kdefs.h"
#include "eswitch.h"
#include "segment.h"
#include "dxxerror.h"
#include "event.h"
#include "wall.h"
#include "medwall.h"
#include "textures.h"
#include "texmerge.h"
#include "medrobot.h"
#include "key.h"
#include "ehostage.h"
#include "centers.h"
#include "piggy.h"
#include "u_mem.h"

#include "compiler-range_for.h"
#include "d_underlying_value.h"
#include "partial_range.h"
#include <memory>

//-------------------------------------------------------------------------
// Variables for this module...
//-------------------------------------------------------------------------
#define NUM_TRIGGER_FLAGS 10

namespace dsx {

namespace {

struct trigger_dialog : UI_DIALOG
{
	using UI_DIALOG::UI_DIALOG;
	std::unique_ptr<UI_GADGET_USERBOX> wallViewBox;
	std::unique_ptr<UI_GADGET_BUTTON> quitButton, remove_trigger, bind_wall, bind_matcen, enable_all_triggers;
#if DXX_BUILD_DESCENT == 1
	std::array<std::unique_ptr<UI_GADGET_CHECKBOX>, NUM_TRIGGER_FLAGS> triggerFlag;
#endif
	trgnum_t old_trigger_num = trigger_none;
	virtual window_event_result callback_handler(const d_event &) override;
};

static trigger_dialog *MainWindow;

#if DXX_BUILD_DESCENT == 1
//-----------------------------------------------------------------
// Adds a trigger to wall, and returns the trigger number. 
// If there is a trigger already present, it returns the trigger number. (To be replaced)
static trgnum_t add_trigger(trigger_array &Triggers, fvcvertptr &vcvertptr, wall_array &Walls, const shared_segment &seg, const sidenum_t side)
{
	const std::underlying_type<trgnum_t>::type trigger_num = Triggers.get_count();

	if (trigger_num >= Triggers.size())
		return trigger_none;

	auto wall_num = seg.sides[side].wall_num;
	wall *wp;
	auto &vmwallptr = Walls.vmptr;
	if (wall_num == wall_none) {
		wall_add_to_markedside(vcvertptr, Walls, WALL_OPEN);
		wall_num = seg.sides[side].wall_num;
		wp = vmwallptr(wall_num);
		// Set default values first time trigger is added
	} else {
		auto &w = *vmwallptr(wall_num);
		if (w.trigger != trigger_none)
			return w.trigger;

		wp = &w;
		// Create new trigger.
	}
	const auto r = wp->trigger = trgnum_t{trigger_num};
	auto &t = *Triggers.vmptr(r);
	t.flags = {};
	t.value = F1_0*5;
	t.num_links = 0;
	Triggers.set_count(trigger_num + 1);
	return r;
}		

//-----------------------------------------------------------------
// Adds a specific trigger flag to Markedsegp/Markedside if it is possible.
// Automatically adds flag to Connectside if possible unless it is a control trigger.
// Returns 1 if trigger flag added.
// Returns 0 if trigger flag cannot be added.
static int trigger_flag_Markedside(const TRIGGER_FLAG flag, const int value)
{
	auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
	auto &Vertices = LevelSharedVertexState.get_vertices();
	if (!Markedsegp) {
		editor_status("No Markedside.");
		return 0;
	}

	// If no child on Markedside return
	shared_segment &markedseg = Markedsegp;
	if (!IS_CHILD(markedseg.children[Markedside])) return 0;

	// If no wall just return
	const auto wall_num = markedseg.sides[Markedside].wall_num;
	if (!value && wall_num == wall_none) return 0;
	auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
	auto &vcvertptr = Vertices.vcptr;
	auto &Walls = LevelUniqueWallSubsystemState.Walls;
	auto &vcwallptr = Walls.vcptr;
	const auto trigger_num = value ? add_trigger(Triggers, vcvertptr, Walls, markedseg, Markedside) : vcwallptr(wall_num)->trigger;

	if (trigger_num == trigger_none) {
		editor_status(value ? "Cannot add trigger at Markedside." : "No trigger at Markedside.");
		return 0;
	}

	auto &vmtrgptr = Triggers.vmptr;
	auto &flags = vmtrgptr(trigger_num)->flags;
 	if (value)
		flags |= flag;
	else
		flags &= ~flag;

	return 1;
}
#endif

static int bind_matcen_to_trigger() {

	if (!Markedsegp) {
		editor_status("No marked segment.");
		return 0;
	}

	const auto wall_num = Markedsegp->shared_segment::sides[Markedside].wall_num;
	if (wall_num == wall_none) {
		editor_status("No wall at Markedside.");
		return 0;
	}
	auto &Walls = LevelUniqueWallSubsystemState.Walls;
	auto &vcwallptr = Walls.vcptr;
	const auto trigger_num = vcwallptr(wall_num)->trigger;
	if (trigger_num == trigger_none) {
		editor_status("No trigger at Markedside.");
		return 0;
	}

	if (!(Cursegp->special == segment_special::robotmaker))
	{
		editor_status("No Matcen at Cursegp.");
		return 0;
	}

	auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
	auto &vmtrgptr = Triggers.vmptr;
	const auto &&t = vmtrgptr(trigger_num);
	const auto link_num = t->num_links;
	for (int i=0;i<link_num;i++)
		if (Cursegp == t->seg[i])
		{
			editor_status("Matcen already bound to Markedside.");
			return 0;
		}

	// Error checking completed, actual binding begins
	t->seg[link_num] = Cursegp;
	t->num_links++;

	editor_status("Matcen linked to trigger");

	return 1;
}

}

int bind_wall_to_trigger() {
	if (!Markedsegp) {
		editor_status("No marked segment.");
		return 0;
	}
	const auto wall_num = Markedsegp->shared_segment::sides[Markedside].wall_num;
	if (wall_num == wall_none) {
		editor_status("No wall at Markedside.");
		return 0;
	}
	auto &Walls = LevelUniqueWallSubsystemState.Walls;
	auto &vcwallptr = Walls.vcptr;
	const auto trigger_num = vcwallptr(wall_num)->trigger;
	if (trigger_num == trigger_none) {
		editor_status("No trigger at Markedside.");
		return 0;
	}

	if (Cursegp->shared_segment::sides[Curside].wall_num == wall_none)
	{
		editor_status("No wall at Curside.");
		return 0;
	}

	if ((Cursegp==Markedsegp) && (Curside==Markedside)) {
		editor_status("Cannot bind wall to itself.");
		return 0;
	}

	auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
	auto &vmtrgptr = Triggers.vmptr;
	const auto &&t = vmtrgptr(trigger_num);
	const auto link_num = t->num_links;
	for (int i=0;i<link_num;i++)
		if (Cursegp == t->seg[i] && Curside == t->side[i])
		{
			editor_status("Curside already bound to Markedside.");
			return 0;
		}

	// Error checking completed, actual binding begins
	t->seg[link_num] = Cursegp;
	t->side[link_num] = Curside;
	t->num_links++;

	editor_status("Wall linked to trigger");

	return 1;
}

int remove_trigger_num(trigger_array &Triggers, fvmwallptr &vmwallptr, const trgnum_t trigger_num)
{
		auto r = partial_range(Triggers, static_cast<unsigned>(trigger_num), Triggers.get_count());
		Triggers.set_count(Triggers.get_count() - 1);
		std::move(std::next(r.begin()), r.end(), r.begin());
		for (auto &w : vmwallptr)
		{
			auto &trigger{w.trigger};
			if (trigger == trigger_num)
				trigger = trigger_none;	// a trigger can be shared by multiple walls
			else if (trigger > trigger_num && trigger != trigger_none)
				trigger = static_cast<trgnum_t>(static_cast<unsigned>(trigger) - 1);
		}

		return 1;
}

unsigned remove_trigger(shared_segment &seg, const sidenum_t side)
{    	
	const auto wall_num = seg.sides[side].wall_num;
	if (wall_num == wall_none)
	{
		return 0;
	}

	auto &Walls = LevelUniqueWallSubsystemState.Walls;
	auto &vcwallptr = Walls.vcptr;
	const auto trigger = vcwallptr(wall_num)->trigger;
	if (trigger == trigger_none)
	{
		editor_status("No trigger to remove");
		return 0;
	}
	return remove_trigger_num(LevelUniqueWallSubsystemState.Triggers, Walls.vmptr, trigger);
}

static int trigger_remove()
{
	remove_trigger(Markedsegp, Markedside);
	Update_flags = UF_WORLD_CHANGED;
	return 1;
}

#if DXX_BUILD_DESCENT == 2
static int trigger_turn_all_ON()
{
	auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
	auto &vmtrgptr = Triggers.vmptr;
	for (auto &t : vmtrgptr)
		t.flags &= ~trigger_behavior_flags::disabled;
	return 1;
}
#endif

//-------------------------------------------------------------------------
// Called from the editor... does one instance of the trigger dialog box
//-------------------------------------------------------------------------
int do_trigger_dialog()
{
	if (!Markedsegp) {
		editor_status("Trigger requires Marked Segment & Side.");
		return 0;
	}

	// Only open 1 instance of this window...
	if ( MainWindow != NULL ) return 0;
	
	// Close other windows.	
	robot_close_window();
	close_wall_window();
	close_centers_window();
#if DXX_BUILD_DESCENT == 1
	hostage_close_window();
#endif

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

static window_event_result trigger_dialog_created(trigger_dialog *const t)
{
	// These are the checkboxes for each door flag.
	int i{44};
#if DXX_BUILD_DESCENT == 1
	t->triggerFlag[0] = ui_add_gadget_checkbox(*t, 22, i, 16, 16, 0, "Door Control");  	i+=22;
	t->triggerFlag[1] = ui_add_gadget_checkbox(*t, 22, i, 16, 16, 0, "Shield damage"); 	i+=22;
	t->triggerFlag[2] = ui_add_gadget_checkbox(*t, 22, i, 16, 16, 0, "Energy drain");		i+=22;
	t->triggerFlag[3] = ui_add_gadget_checkbox(*t, 22, i, 16, 16, 0, "Exit");					i+=22;
	t->triggerFlag[4] = ui_add_gadget_checkbox(*t, 22, i, 16, 16, 0, "One-shot");			i+=22;
	t->triggerFlag[5] = ui_add_gadget_checkbox(*t, 22, i, 16, 16, 0, "Illusion ON");		i+=22;
	t->triggerFlag[6] = ui_add_gadget_checkbox(*t, 22, i, 16, 16, 0, "Illusion OFF");		i+=22;
	t->triggerFlag[7] = ui_add_gadget_checkbox(*t, 22, i, 16, 16, 0, "Unused");			i+=22;
	t->triggerFlag[8] = ui_add_gadget_checkbox(*t, 22, i, 16, 16, 0, "Matcen Trigger"); 	i+=22;
	t->triggerFlag[9] = ui_add_gadget_checkbox(*t, 22, i, 16, 16, 0, "Secret Exit"); 		i+=22;
#endif

	t->quitButton = ui_add_gadget_button(*t, 20, i, 48, 40, "Done", nullptr);
																				 
	// The little box the wall will appear in.
	t->wallViewBox = ui_add_gadget_userbox(*t, 155, 5, 64, 64 );

	// A bunch of buttons...
	i = 80;
	t->remove_trigger = ui_add_gadget_button(*t, 155, i, 140, 26, "Remove Trigger", trigger_remove); i += 29;
	t->bind_wall = ui_add_gadget_button(*t, 155, i, 140, 26, "Bind Wall", bind_wall_to_trigger); i += 29;
	t->bind_matcen = ui_add_gadget_button(*t, 155, i, 140, 26, "Bind Matcen", bind_matcen_to_trigger); i += 29;
#if DXX_BUILD_DESCENT == 2
	t->enable_all_triggers = ui_add_gadget_button(*t, 155, i, 140, 26, "All Triggers ON", trigger_turn_all_ON); i += 29;
#endif
	return window_event_result::handled;
}

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

window_event_result trigger_dialog::callback_handler(const d_event &event)
{
	switch(event.type)
	{
		case event_type::window_created:
			return trigger_dialog_created(this);
		case event_type::window_close:
			MainWindow = NULL;
			return window_event_result::ignored;
		default:
			break;
	}
	int keypress{0};
	window_event_result rval = window_event_result::ignored;

	Assert(MainWindow != NULL);
	if (!Markedsegp) {
		return window_event_result::close;
	}

	//------------------------------------------------------------
	// Call the ui code..
	//------------------------------------------------------------
	ui_button_any_drawn = 0;
	if (event.type == event_type::key_command)
		keypress = event_key_get(event);
	//------------------------------------------------------------
	// If we change walls, we need to reset the ui code for all
	// of the checkboxes that control the wall flags.  
	//------------------------------------------------------------
	msmusegment &&markedseg = Markedsegp;
	const auto Markedwall = markedseg.s.sides[Markedside].wall_num;
	auto &Walls = LevelUniqueWallSubsystemState.Walls;
	auto &vcwallptr = Walls.vcptr;
	const auto trigger_num = (Markedwall != wall_none) ? vcwallptr(Markedwall)->trigger : trigger_none;

	if (old_trigger_num != trigger_num)
	{
		if (trigger_num != trigger_none)
		{
#if DXX_BUILD_DESCENT == 1
			auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
			auto &vctrgptr = Triggers.vcptr;
			const auto &&trig = vctrgptr(trigger_num);

  			ui_checkbox_check(triggerFlag[0].get(), trig->flags & TRIGGER_CONTROL_DOORS);
 			ui_checkbox_check(triggerFlag[1].get(), trig->flags & TRIGGER_SHIELD_DAMAGE);
 			ui_checkbox_check(triggerFlag[2].get(), trig->flags & TRIGGER_ENERGY_DRAIN);
 			ui_checkbox_check(triggerFlag[3].get(), trig->flags & TRIGGER_EXIT);
 			ui_checkbox_check(triggerFlag[4].get(), trig->flags & TRIGGER_ONE_SHOT);
 			ui_checkbox_check(triggerFlag[5].get(), trig->flags & TRIGGER_ILLUSION_ON);
 			ui_checkbox_check(triggerFlag[6].get(), trig->flags & TRIGGER_ILLUSION_OFF);
			ui_checkbox_check(triggerFlag[7].get(), 0);
 			ui_checkbox_check(triggerFlag[8].get(), trig->flags & TRIGGER_MATCEN);
 			ui_checkbox_check(triggerFlag[9].get(), trig->flags & TRIGGER_SECRET_EXIT);
#endif
		}
	}
	
	//------------------------------------------------------------
	// If any of the checkboxes that control the wallflags are set, then
	// update the cooresponding wall flag.
	//------------------------------------------------------------
	if (IS_CHILD(markedseg.s.children[Markedside]))
	{
#if DXX_BUILD_DESCENT == 1
		rval = window_event_result::handled;
		if (GADGET_PRESSED(triggerFlag[0].get())) 
			trigger_flag_Markedside(TRIGGER_CONTROL_DOORS, triggerFlag[0]->flag); 
		else if (GADGET_PRESSED(triggerFlag[1].get()))
			trigger_flag_Markedside(TRIGGER_SHIELD_DAMAGE, triggerFlag[1]->flag); 
		else if (GADGET_PRESSED(triggerFlag[2].get()))
			trigger_flag_Markedside(TRIGGER_ENERGY_DRAIN, triggerFlag[2]->flag); 
		else if (GADGET_PRESSED(triggerFlag[3].get()))
			trigger_flag_Markedside(TRIGGER_EXIT, triggerFlag[3]->flag); 
		else if (GADGET_PRESSED(triggerFlag[4].get()))
			trigger_flag_Markedside(TRIGGER_ONE_SHOT, triggerFlag[4]->flag); 
		else if (GADGET_PRESSED(triggerFlag[5].get()))
			trigger_flag_Markedside(TRIGGER_ILLUSION_ON, triggerFlag[5]->flag); 
		else if (GADGET_PRESSED(triggerFlag[6].get()))
			trigger_flag_Markedside(TRIGGER_ILLUSION_OFF, triggerFlag[6]->flag);
		else if (GADGET_PRESSED(triggerFlag[7].get()))
		{
		}
		else if (GADGET_PRESSED(triggerFlag[8].get())) 
			trigger_flag_Markedside(TRIGGER_MATCEN, triggerFlag[8]->flag);
		else if (GADGET_PRESSED(triggerFlag[9].get())) 
			trigger_flag_Markedside(TRIGGER_SECRET_EXIT, triggerFlag[9]->flag);
		else
#endif
			rval = window_event_result::ignored;

	} else
	{
#if DXX_BUILD_DESCENT == 1
		range_for (auto &i, triggerFlag)
			ui_checkbox_check(i.get(), 0);
#endif
	}

	//------------------------------------------------------------
	// Draw the wall in the little 64x64 box
	//------------------------------------------------------------
	if (event.type == event_type::ui_dialog_draw)
	{
		gr_set_current_canvas( wallViewBox->canvas );
		auto &canvas = *grd_curcanv;

		const auto wall_num = markedseg.s.sides[Markedside].wall_num;
		if (wall_num == wall_none || vcwallptr(wall_num)->trigger == trigger_none)
			gr_clear_canvas(canvas, CBLACK);
		else {
			auto &us = markedseg.u.sides[Markedside];
			if (us.tmap_num2 != texture2_value::None) 
			{
				gr_ubitmap(canvas, texmerge_get_cached_bitmap(GameBitmaps, Textures, us.tmap_num, us.tmap_num2));
			} else {
				const auto tmap_num = us.tmap_num;
				if (tmap_num != texture1_value::None)
				{
					auto &t = Textures[get_texture_index(tmap_num)];
					PIGGY_PAGE_IN(t);
					gr_ubitmap(canvas, GameBitmaps[t]);
				} else
					gr_clear_canvas(canvas, CGREY);
			}
		}
	}

	//------------------------------------------------------------
	// If anything changes in the ui system, redraw all the text that
	// identifies this robot.
	//------------------------------------------------------------
	if (event.type == event_type::ui_dialog_draw)
	{
		if (markedseg.s.sides[Markedside].wall_num != wall_none)
		{
			ui_dprintf_at(MainWindow, 12, 6, "Trigger: %-4d ", underlying_value(trigger_num));
		}	else {
			ui_dputs_at(MainWindow, 12, 6, "Trigger: none ");
		}
	}
	
	if (ui_button_any_drawn || (old_trigger_num != trigger_num) )
		Update_flags |= UF_WORLD_CHANGED;
	if (GADGET_PRESSED(quitButton.get()) || keypress == KEY_ESC)
	{
		return window_event_result::close;
	}		

	old_trigger_num = trigger_num;
	
	return rval;
}

}
