/*
	This file is part of Warzone 2100.
	Copyright (C) 1999-2004  Eidos Interactive
	Copyright (C) 2005-2020  Warzone 2100 Project

	Warzone 2100 is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	Warzone 2100 is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with Warzone 2100; if not, write to the Free Software
	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include "lib/framework/frame.h"
#include "lib/ivis_opengl/bitimage.h"//bitmap routines

#include "hci.h"
#include "intdisplay.h"
#include "intorder.h"
#include "objects.h"
#include "order.h"

#include <set>
#include <algorithm>


#define ORDER_X			6
#define ORDER_Y			45
#define ORDER_WIDTH		RET_FORMWIDTH
#define ORDER_HEIGHT	273
#define ORDER_BUTX		8
#define ORDER_BUTY		16
#define ORDER_BUTGAP	4
#define ORDER_BOTTOMY	318	+ E_H

#define MAX_DISPLAYABLE_ORDERS 11	// Max number of displayable orders.
#define MAX_ORDER_BUTS 5		// Max number of buttons for a given order.
#define NUM_ORDERS 13			// Number of orders in OrderButtons list.

#define IDORDER_ATTACK_RANGE				8010
#define IDORDER_REPAIR_LEVEL				8020
#define IDORDER_ATTACK_LEVEL				8030
#define IDORDER_PATROL						8040
#define IDORDER_HALT_TYPE					8050
#define IDORDER_RETURN						8060
#define IDORDER_RECYCLE						8070
#define IDORDER_ASSIGN_PRODUCTION			8080
#define IDORDER_ASSIGN_CYBORG_PRODUCTION	8090
#define IDORDER_FIRE_DESIGNATOR				8100
#define IDORDER_ASSIGN_VTOL_PRODUCTION		8110
#define IDORDER_CIRCLE						8120
#define IDORDER_ACCREP						8130

enum ORDBUTTONTYPE
{
	ORD_BTYPE_RADIO,			// Only one state allowed.
	ORD_BTYPE_BOOLEAN,			// Clicking on a button toggles it's state.
	ORD_BTYPE_BOOLEAN_DEPEND,	// Clicking on a button toggles it's state, button
	// is only enabled if previous button is down.
	ORD_BTYPE_BOOLEAN_COMBINE,	// Clicking on a button toggles it's state, all
	// the buttons states are OR'ed together to get the order state
};

enum ORDBUTTONCLASS
{
	ORDBUTCLASS_NORMAL,			// A normal button, one order type per line.
	ORDBUTCLASS_FACTORY,		// A factory assignment button.
	ORDBUTCLASS_CYBORGFACTORY,	// A cyborg factory assignment button.
	ORDBUTCLASS_VTOLFACTORY, 	// A VTOL factory assignment button.
};


/*
 NOTE:
	ORD_BTYPE_BOOLEAN_DEPEND only supports two buttons
	ie button 1 = enable destruct, button 2 = destruct
*/

enum ORDBUTTONJUSTIFY
{
	ORD_JUSTIFY_LEFT,			// Pretty self explanatory really.
	ORD_JUSTIFY_RIGHT,
	ORD_JUSTIFY_CENTER,
	ORD_JUSTIFY_COMBINE,		// allow the button to be put on the same line
	// as other orders with the same justify type
	ORD_NUM_JUSTIFY_TYPES,
};

// maximum number of orders on one line
#define ORD_MAX_COMBINE_BUTS	3

#define ORD_JUSTIFY_MASK	0x0f
#define ORD_JUSTIFY_NEWLINE	0x10	// Or with ORDBUTTONJUSTIFY enum type to specify start on new line.

struct ORDERBUTTONS
{
	ORDBUTTONCLASS Class;					// The class of button.
	SECONDARY_ORDER Order;					// The droid order.
	UDWORD StateMask;						// It's state mask.
	ORDBUTTONTYPE ButType;					// The group type.
	unsigned ButJustify;                            // Button justification. Type ORDBUTTONJUSTIFY, possibly ored with ORD_JUSTIFY_NEWLINE.
	UDWORD ButBaseID;						// Starting widget ID for buttons
	UWORD NumButs;							// Number of buttons ( = number of states )
	UWORD AcNumButs;						// Actual bumber of buttons enabled.
	UWORD ButImageID[MAX_ORDER_BUTS];		// Image ID's for each button ( normal ).
	UWORD ButGreyID[MAX_ORDER_BUTS];		// Image ID's for each button ( greyed ).
	UWORD ButHilightID[MAX_ORDER_BUTS];		// Image ID's for each button ( hilight overlay ).
	UWORD ButTips[MAX_ORDER_BUTS];			// Tip string id for each button.
	unsigned States[MAX_ORDER_BUTS];                // Order state relating to each button, combination of SECONDARY_STATEs ored together.
};


struct AVORDER
{
	bool operator <(AVORDER const &b) const
	{
		return OrderIndex < b.OrderIndex;
	}
	bool operator ==(AVORDER const &b) const
	{
		return OrderIndex == b.OrderIndex;
	}

	UWORD OrderIndex;		// Index into ORDERBUTTONS array of available orders.
};


enum
{
	STR_DORD_RANGE1,
	STR_DORD_RANGE2,
	STR_DORD_RANGE3,
	STR_DORD_REPAIR1,
	STR_DORD_REPAIR2,
	STR_DORD_REPAIR3,
	STR_DORD_FIRE1,
	STR_DORD_FIRE2,
	STR_DORD_FIRE3,
	STR_DORD_PATROL,
	STR_DORD_PURSUE,
	STR_DORD_GUARD,
	STR_DORD_HOLDPOS,
	STR_DORD_RETREPAIR,
	STR_DORD_RETBASE,
	STR_DORD_EMBARK,
	STR_DORD_ARMRECYCLE,
	STR_DORD_RECYCLE,
	STR_DORD_FACTORY,
	STR_DORD_CYBORG_FACTORY,
	STR_DORD_FIREDES,
	STR_DORD_VTOL_FACTORY,
	STR_DORD_CIRCLE,
	STR_DORD_ACCREP,
};

// return translated text
static const char *getDORDDescription(int id)
{
	switch (id)
	{
	case STR_DORD_RANGE1         : return _("Short Range");
	case STR_DORD_RANGE2         : return _("Long Range");
	case STR_DORD_RANGE3         : return _("Optimum Range");
	case STR_DORD_REPAIR1        : return _("Retreat at Medium Damage");
	case STR_DORD_REPAIR2        : return _("Retreat at Heavy Damage");
	case STR_DORD_REPAIR3        : return _("Do or Die!");
	case STR_DORD_FIRE1          : return _("Fire-At-Will");
	case STR_DORD_FIRE2          : return _("Return Fire");
	case STR_DORD_FIRE3          : return _("Hold Fire");
	case STR_DORD_PATROL         : return _("Patrol");
	case STR_DORD_PURSUE         : return _("Pursue");
	case STR_DORD_GUARD          : return _("Guard Position");
	case STR_DORD_HOLDPOS        : return _("Hold Position");
	case STR_DORD_RETREPAIR      : return _("Return For Repair");
	case STR_DORD_RETBASE        : return _("Return To HQ");
	case STR_DORD_EMBARK         : return _("Go to Transport");
	case STR_DORD_ARMRECYCLE     : return _("Return for Recycling");
	case STR_DORD_RECYCLE        : return _("Recycle");
	case STR_DORD_FACTORY        : return _("Assign Factory Production");
	case STR_DORD_CYBORG_FACTORY : return _("Assign Cyborg Factory Production");
	case STR_DORD_FIREDES        : return _("Assign Fire Support");
	case STR_DORD_VTOL_FACTORY   : return _("Assign VTOL Factory Production");
	case STR_DORD_CIRCLE         : return _("Circle");
	case STR_DORD_ACCREP		 : return _("Accept Retreating Units");

	default : return "";  // make compiler shut up
	}
}

// Define the order button groups.
static ORDERBUTTONS OrderButtons[NUM_ORDERS] =
{
	{
		ORDBUTCLASS_NORMAL,
		DSO_ATTACK_RANGE,
		DSS_ARANGE_MASK,
		ORD_BTYPE_RADIO,
		ORD_JUSTIFY_CENTER | ORD_JUSTIFY_NEWLINE,
		IDORDER_ATTACK_RANGE,
		3,0,
		{IMAGE_ORD_RANGE3UP,	IMAGE_ORD_RANGE1UP,	IMAGE_ORD_RANGE2UP},
		{IMAGE_ORD_RANGE3UP,	IMAGE_ORD_RANGE1UP,	IMAGE_ORD_RANGE2UP},
		{IMAGE_DES_HILIGHT,		IMAGE_DES_HILIGHT,	IMAGE_DES_HILIGHT},
		{STR_DORD_RANGE3,	STR_DORD_RANGE1,	STR_DORD_RANGE2},
		{DSS_ARANGE_OPTIMUM,	DSS_ARANGE_SHORT,	DSS_ARANGE_LONG}
	},
	{
		ORDBUTCLASS_NORMAL,
		DSO_REPAIR_LEVEL,
		DSS_REPLEV_MASK,
		ORD_BTYPE_RADIO,
		ORD_JUSTIFY_CENTER | ORD_JUSTIFY_NEWLINE,
		IDORDER_REPAIR_LEVEL,
		3, 0,
		{IMAGE_ORD_REPAIR3UP,	IMAGE_ORD_REPAIR2UP,	IMAGE_ORD_REPAIR1UP},
		{IMAGE_ORD_REPAIR3UP,	IMAGE_ORD_REPAIR2UP,	IMAGE_ORD_REPAIR1UP},
		{IMAGE_DES_HILIGHT,		IMAGE_DES_HILIGHT,	IMAGE_DES_HILIGHT},
		{STR_DORD_REPAIR3,	STR_DORD_REPAIR2,	STR_DORD_REPAIR1},
		{DSS_REPLEV_NEVER,	DSS_REPLEV_HIGH,	DSS_REPLEV_LOW}
	},
	{
		ORDBUTCLASS_NORMAL,
		DSO_ATTACK_LEVEL,
		DSS_ALEV_MASK,
		ORD_BTYPE_RADIO,
		ORD_JUSTIFY_CENTER | ORD_JUSTIFY_NEWLINE,
		IDORDER_ATTACK_LEVEL,
		3, 0,
		{IMAGE_ORD_FATWILLUP,	IMAGE_ORD_RETFIREUP,	IMAGE_ORD_HOLDFIREUP},
		{IMAGE_ORD_FATWILLUP,	IMAGE_ORD_RETFIREUP,	IMAGE_ORD_HOLDFIREUP},
		{IMAGE_DES_HILIGHT,		IMAGE_DES_HILIGHT,	IMAGE_DES_HILIGHT},
		{STR_DORD_FIRE1,	STR_DORD_FIRE2,	STR_DORD_FIRE3},
		{DSS_ALEV_ALWAYS,	DSS_ALEV_ATTACKED,	DSS_ALEV_NEVER}
	},
	{
		ORDBUTCLASS_NORMAL,
		DSO_FIRE_DESIGNATOR,
		DSS_FIREDES_MASK,
		ORD_BTYPE_BOOLEAN,
		ORD_JUSTIFY_COMBINE,
		IDORDER_FIRE_DESIGNATOR,
		1, 0,
		{IMAGE_ORD_FIREDES_UP,	0,	0},
		{IMAGE_ORD_FIREDES_UP,	0,	0},
		{IMAGE_DES_HILIGHT,	0,	0},
		{STR_DORD_FIREDES,	0,	0},
		{DSS_FIREDES_SET,	0,	0}
	},
	{
		ORDBUTCLASS_NORMAL,
		DSO_ACCEPT_RETREP,
		DSS_ACCREP_MASK,
		ORD_BTYPE_BOOLEAN,
		ORD_JUSTIFY_COMBINE,
		IDORDER_ACCREP,
		1, 0,
		{IMAGE_ORD_ACCREP_UP,	0,	0},
		{IMAGE_ORD_ACCREP_UP,	0,	0},
		{IMAGE_DES_HILIGHT,	0,	0},
		{STR_DORD_ACCREP,	0,	0},
		{DSS_ACCREP_SET,	0,	0}
	},
	{
		ORDBUTCLASS_NORMAL,
		DSO_PATROL,
		DSS_PATROL_MASK,
		ORD_BTYPE_BOOLEAN,
		ORD_JUSTIFY_COMBINE,
		IDORDER_PATROL,
		1, 0,
		{IMAGE_ORD_PATROLUP,	0,	0},
		{IMAGE_ORD_PATROLUP,	0,	0},
		{IMAGE_DES_HILIGHT,	0,	0},
		{STR_DORD_PATROL,	0,	0},
		{DSS_PATROL_SET,	0,	0}
	},
	{
		ORDBUTCLASS_NORMAL,
		DSO_CIRCLE,
		DSS_CIRCLE_MASK,
		ORD_BTYPE_BOOLEAN,
		ORD_JUSTIFY_COMBINE,
		IDORDER_CIRCLE,
		1, 0,
		{IMAGE_ORD_CIRCLEUP,	0,	0},
		{IMAGE_ORD_CIRCLEUP,	0,	0},
		{IMAGE_DES_HILIGHT,	0,	0},
		{STR_DORD_CIRCLE,	0,	0},
		{DSS_CIRCLE_SET,	0,	0}
	},
	{
		ORDBUTCLASS_NORMAL,
		DSO_HALTTYPE,
		DSS_HALT_MASK,
		ORD_BTYPE_RADIO,
		ORD_JUSTIFY_CENTER | ORD_JUSTIFY_NEWLINE,
		IDORDER_HALT_TYPE,
		3,0,
		{IMAGE_ORD_PURSUEUP,	IMAGE_ORD_GUARDUP,	IMAGE_ORD_HALTUP},
		{IMAGE_ORD_PURSUEUP,	IMAGE_ORD_GUARDUP,	IMAGE_ORD_HALTUP},
		{IMAGE_DES_HILIGHT,		IMAGE_DES_HILIGHT,	IMAGE_DES_HILIGHT},
		{STR_DORD_PURSUE,	STR_DORD_GUARD,	STR_DORD_HOLDPOS},
		{DSS_HALT_PURSUE,	DSS_HALT_GUARD,	DSS_HALT_HOLD}
	},
	{
		ORDBUTCLASS_NORMAL,
		DSO_RETURN_TO_LOC,
		DSS_RTL_MASK,
		ORD_BTYPE_RADIO,
		ORD_JUSTIFY_CENTER | ORD_JUSTIFY_NEWLINE,
		IDORDER_RETURN,
		3, 0,

		{IMAGE_ORD_RTRUP,	IMAGE_ORD_GOTOHQUP,	IMAGE_ORD_EMBARKUP},
		{IMAGE_ORD_RTRUP,	IMAGE_ORD_GOTOHQUP,	IMAGE_ORD_EMBARKUP},

		{IMAGE_DES_HILIGHT,		IMAGE_DES_HILIGHT,	IMAGE_DES_HILIGHT},
		{STR_DORD_RETREPAIR,	STR_DORD_RETBASE,	STR_DORD_EMBARK},
		{DSS_RTL_REPAIR,	DSS_RTL_BASE,	DSS_RTL_TRANSPORT},
	},
	{
		ORDBUTCLASS_NORMAL,
		DSO_RECYCLE,
		DSS_RECYCLE_MASK,
		ORD_BTYPE_BOOLEAN_DEPEND,
		ORD_JUSTIFY_CENTER | ORD_JUSTIFY_NEWLINE,
		IDORDER_RECYCLE,
		2, 0,
		{IMAGE_ORD_DESTRUCT1UP,	IMAGE_ORD_DESTRUCT2UP,	0},
		{IMAGE_ORD_DESTRUCT1UP,	IMAGE_ORD_DESTRUCT2GREY,	0},
		{IMAGE_DES_HILIGHT,	IMAGE_DES_HILIGHT,	0},
		{STR_DORD_ARMRECYCLE,	STR_DORD_RECYCLE,	0},
		{DSS_RECYCLE_SET,	DSS_RECYCLE_SET,	0}
	},
	{
		ORDBUTCLASS_FACTORY,
		DSO_ASSIGN_PRODUCTION,
		DSS_ASSPROD_MASK,
		ORD_BTYPE_BOOLEAN_COMBINE,
		ORD_JUSTIFY_CENTER | ORD_JUSTIFY_NEWLINE,
		IDORDER_ASSIGN_PRODUCTION,
		5, 0,
		{IMAGE_ORD_FAC1UP,		IMAGE_ORD_FAC2UP,	IMAGE_ORD_FAC3UP,	IMAGE_ORD_FAC4UP,	IMAGE_ORD_FAC5UP	},
		{IMAGE_ORD_FAC1UP,		IMAGE_ORD_FAC2UP,	IMAGE_ORD_FAC3UP,	IMAGE_ORD_FAC4UP,	IMAGE_ORD_FAC5UP	},
		{IMAGE_ORD_FACHILITE,	IMAGE_ORD_FACHILITE, IMAGE_ORD_FACHILITE, IMAGE_ORD_FACHILITE, IMAGE_ORD_FACHILITE	},
		{STR_DORD_FACTORY,	STR_DORD_FACTORY,	STR_DORD_FACTORY,	STR_DORD_FACTORY,	STR_DORD_FACTORY},
		{
			0x01 << DSS_ASSPROD_SHIFT, 0x02 << DSS_ASSPROD_SHIFT, 0x04 << DSS_ASSPROD_SHIFT,
			0x08 << DSS_ASSPROD_SHIFT, 0x10 << DSS_ASSPROD_SHIFT
		}
	},
	{
		ORDBUTCLASS_CYBORGFACTORY,
		DSO_ASSIGN_CYBORG_PRODUCTION,
		DSS_ASSPROD_MASK,
		ORD_BTYPE_BOOLEAN_COMBINE,
		ORD_JUSTIFY_CENTER | ORD_JUSTIFY_NEWLINE,
		IDORDER_ASSIGN_CYBORG_PRODUCTION,
		5, 0,
		{IMAGE_ORD_FAC1UP,		IMAGE_ORD_FAC2UP,	IMAGE_ORD_FAC3UP,	IMAGE_ORD_FAC4UP,	IMAGE_ORD_FAC5UP	},
		{IMAGE_ORD_FAC1UP,		IMAGE_ORD_FAC2UP,	IMAGE_ORD_FAC3UP,	IMAGE_ORD_FAC4UP,	IMAGE_ORD_FAC5UP	},
		{IMAGE_ORD_FACHILITE,	IMAGE_ORD_FACHILITE, IMAGE_ORD_FACHILITE, IMAGE_ORD_FACHILITE, IMAGE_ORD_FACHILITE	},
		{STR_DORD_CYBORG_FACTORY,	STR_DORD_CYBORG_FACTORY,	STR_DORD_CYBORG_FACTORY,	STR_DORD_CYBORG_FACTORY,	STR_DORD_CYBORG_FACTORY},
		{
			0x01 << DSS_ASSPROD_CYBORG_SHIFT, 0x02 << DSS_ASSPROD_CYBORG_SHIFT, 0x04 << DSS_ASSPROD_CYBORG_SHIFT,
			0x08 << DSS_ASSPROD_CYBORG_SHIFT, 0x10 << DSS_ASSPROD_CYBORG_SHIFT
		}
	},
	{
		ORDBUTCLASS_VTOLFACTORY,
		DSO_ASSIGN_VTOL_PRODUCTION,
		DSS_ASSPROD_MASK,
		ORD_BTYPE_BOOLEAN_COMBINE,
		ORD_JUSTIFY_CENTER | ORD_JUSTIFY_NEWLINE,
		IDORDER_ASSIGN_VTOL_PRODUCTION,
		5, 0,
		{IMAGE_ORD_FAC1UP,		IMAGE_ORD_FAC2UP,	IMAGE_ORD_FAC3UP,	IMAGE_ORD_FAC4UP,	IMAGE_ORD_FAC5UP	},
		{IMAGE_ORD_FAC1UP,		IMAGE_ORD_FAC2UP,	IMAGE_ORD_FAC3UP,	IMAGE_ORD_FAC4UP,	IMAGE_ORD_FAC5UP	},
		{IMAGE_ORD_FACHILITE,	IMAGE_ORD_FACHILITE, IMAGE_ORD_FACHILITE, IMAGE_ORD_FACHILITE, IMAGE_ORD_FACHILITE	},
		{STR_DORD_VTOL_FACTORY,	STR_DORD_VTOL_FACTORY,	STR_DORD_VTOL_FACTORY,	STR_DORD_VTOL_FACTORY,	STR_DORD_VTOL_FACTORY},
		{
			0x01 << DSS_ASSPROD_VTOL_SHIFT, 0x02 << DSS_ASSPROD_VTOL_SHIFT, 0x04 << DSS_ASSPROD_VTOL_SHIFT,
			0x08 << DSS_ASSPROD_VTOL_SHIFT, 0x10 << DSS_ASSPROD_VTOL_SHIFT
		}
	},
};


static std::vector<DROID *> SelectedDroids;
static STRUCTURE *psSelectedFactory = nullptr;
static std::vector<AVORDER> AvailableOrders;


bool OrderUp = false;

// Build a list of currently selected droids.
// Returns true if droids were selected.
//
static bool BuildSelectedDroidList()
{
	if (selectedPlayer >= MAX_PLAYERS)
	{
		return false;
	}

	for (DROID *psDroid : apsDroidLists[selectedPlayer])
	{
		if (psDroid->selected)
		{
			SelectedDroids.push_back(psDroid);
		}
	}

	return !SelectedDroids.empty();
}

// Build a list of orders available for the selected group of droids.
//
static std::vector<AVORDER> buildDroidOrderList()
{
	std::set<AVORDER> orders;

	for (auto &SelectedDroid : SelectedDroids)
	{
		for (unsigned OrdIndex = 0; OrdIndex < NUM_ORDERS; ++OrdIndex)
		{
			// Is the order available?
			if (secondarySupported(SelectedDroid, OrderButtons[OrdIndex].Order))
			{
				AVORDER avOrder;
				avOrder.OrderIndex = OrdIndex;
				// Sort by Order index, A bubble sort? I know but it's only
				// a small list so what the hell.
				// Herein lies the remains of a bubble sort.
				orders.insert(avOrder);
			}
		}
	}

	return std::vector<AVORDER>(orders.begin(), orders.end());
}

// Build a list of orders available for the selected structure.
static std::vector<AVORDER> buildStructureOrderList(STRUCTURE *psStructure)
{
	ASSERT_OR_RETURN(std::vector<AVORDER>(), psStructure && psStructure->isFactory(), "BuildStructureOrderList: structure is not a factory");

	//this can be hard-coded!
	std::vector<AVORDER> orders(4);
	orders[0].OrderIndex = 0;//DSO_ATTACK_RANGE;
	orders[1].OrderIndex = 1;//DSO_REPAIR_LEVEL;
	orders[2].OrderIndex = 2;//DSO_ATTACK_LEVEL;
	orders[3].OrderIndex = 6;//DSO_HALTTYPE;

	return orders;
}

// return the state for an order for all the units selected
// if there are multiple states then don't return a state
static SECONDARY_STATE GetSecondaryStates(SECONDARY_ORDER sec)
{
	SECONDARY_STATE state, currState;
	bool	bFirst;

	state = (SECONDARY_STATE)0;
	bFirst = true;
	if (psSelectedFactory)
	{
		if (getFactoryState(psSelectedFactory, sec, &currState))
		{
			state = currState;
		}
	}
	else //droids
	{
		for (auto &SelectedDroid : SelectedDroids)
		{
			currState = secondaryGetState(SelectedDroid, sec, ModeQueue);
			if (bFirst)
			{
				state = currState;
				bFirst = false;
			}
			else if (state != currState)
			{
				state = (SECONDARY_STATE)0;
			}
		}
	}

	return state;
}


static UDWORD GetImageWidth(IMAGEFILE *ImageFile, UDWORD ImageID)
{
	return iV_GetImageWidth(ImageFile, (UWORD)ImageID);
}

static UDWORD GetImageHeight(IMAGEFILE *ImageFile, UDWORD ImageID)
{
	return iV_GetImageHeight(ImageFile, (UWORD)ImageID);
}

std::vector<UWORD> intOrderGetButtonImageIDs(SECONDARY_ORDER secondaryOrder)
{
	size_t i = 0;
	for (; i < NUM_ORDERS; ++i)
	{
		if (OrderButtons[i].Order == secondaryOrder)
		{
			// found it
			break;
		}
	}
	if (i >= NUM_ORDERS)
	{
		return {};
	}
	std::vector<UWORD> result;
	result.reserve(OrderButtons[i].NumButs);
	for (size_t butNum = 0; butNum < OrderButtons[i].NumButs; ++butNum)
	{
		result.push_back(OrderButtons[i].ButImageID[butNum]);
	}
	return result;
}


// Add the droid order screen.
// Returns true if the form was displayed ok.
//
//changed to a BASE_OBJECT to accommodate the factories - AB 21/04/99
bool intAddOrder(BASE_OBJECT *psObj)
{
	bool Animate = true;
	SECONDARY_STATE State;
	UWORD OrdIndex;
	UWORD Height, NumDisplayedOrders;
	UWORD NumButs;
	UWORD NumJustifyButs, NumCombineButs, NumCombineBefore;
	bool  bLastCombine, bHidden;
	DROID *Droid;
	STRUCTURE *psStructure;

	// Is the form already up?
	if (widgGetFromID(psWScreen, IDORDER_FORM) != nullptr)
	{
		intRemoveOrderNoAnim();
		Animate = false;
	}
	// Is the stats window up?
	if (widgGetFromID(psWScreen, IDSTAT_FORM) != nullptr)
	{
		intRemoveStatsNoAnim();
		Animate = false;
	}

	if (psObj)
	{
		if (psObj->type == OBJ_DROID)
		{
			Droid = (DROID *)psObj;
			psStructure =  nullptr;
		}
		else if (psObj->type == OBJ_STRUCTURE)
		{
			Droid = nullptr;
			psStructure = (STRUCTURE *)psObj;
			psSelectedFactory = psStructure;
			ASSERT_OR_RETURN(false, psSelectedFactory->isFactory(), "Trying to select a %s as a factory!",
			                 objInfo((BASE_OBJECT *)psSelectedFactory));
		}
		else
		{
			ASSERT(false, "Invalid object type");
			Droid = nullptr;
			psStructure =  nullptr;
		}
	}
	else
	{
		Droid = nullptr;
		psStructure =  nullptr;
	}

	setWidgetsStatus(true);

	AvailableOrders.clear();
	SelectedDroids.clear();

	// Selected droid is a command droid?
	if ((Droid != nullptr) && (Droid->droidType == DROID_COMMAND))
	{
		// displaying for a command droid - ignore any other droids
		SelectedDroids.push_back(Droid);
	}
	else if (psStructure != nullptr)
	{
		AvailableOrders = buildStructureOrderList(psStructure);
		if (AvailableOrders.empty())
		{
			return false;
		}
	}
	// Otherwise build a list of selected droids.
	else if (!BuildSelectedDroidList())
	{
		// If no droids selected then see if we were given a specific droid.
		if (Droid != nullptr)
		{
			// and put it in the list.
			SelectedDroids.push_back(Droid);
		}
	}

	// Build a list of orders available for the list of selected droids. - if a factory has not been selected
	if (psStructure == nullptr)
	{
		AvailableOrders = buildDroidOrderList();
		if (AvailableOrders.empty())
		{
			// If no orders then return;
			return false;
		}
	}

	auto const &parent = psWScreen->psForm;

	/* Create the basic form */
	auto orderForm = std::make_shared<IntFormAnimated>(Animate);  // Do not animate the opening, if the window was already open.
	parent->attach(orderForm);
	orderForm->id = IDORDER_FORM;
	orderForm->setCalcLayout(LAMBDA_CALCLAYOUT_SIMPLE({
		psWidget->setGeometry(ORDER_X, ORDER_Y, ORDER_WIDTH, ORDER_HEIGHT);
	}));

	// Add the close button.
	W_BUTINIT sButInit;
	sButInit.formID = IDORDER_FORM;
	sButInit.id = IDORDER_CLOSE;
	sButInit.x = ORDER_WIDTH - CLOSE_WIDTH;
	sButInit.y = 0;
	sButInit.width = CLOSE_WIDTH;
	sButInit.height = CLOSE_HEIGHT;
	sButInit.pTip = _("Close");
	sButInit.pDisplay = intDisplayImageHilight;
	sButInit.UserData = PACKDWORD_TRI(0, IMAGE_CLOSEHILIGHT , IMAGE_CLOSE);
	if (!widgAddButton(psWScreen, &sButInit))
	{
		return false;
	}

	sButInit = W_BUTINIT();
	sButInit.formID = IDORDER_FORM;
	sButInit.id = IDORDER_CLOSE + 1;
	sButInit.pDisplay = intDisplayButtonHilight;
	sButInit.y = ORDER_BUTY;

	Height = 0;
	NumDisplayedOrders = 0;

	for (unsigned j = 0; j < AvailableOrders.size() && NumDisplayedOrders < MAX_DISPLAYABLE_ORDERS; ++j)
	{
		OrdIndex = AvailableOrders[j].OrderIndex;

		// Get current order state.
		State = GetSecondaryStates(OrderButtons[OrdIndex].Order);

		// Get number of buttons.
		NumButs = OrderButtons[OrdIndex].NumButs;
		// Set actual number of buttons.
		OrderButtons[OrdIndex].AcNumButs = NumButs;

		// Handle special case for factory -> command droid assignment buttons.
		switch (OrderButtons[OrdIndex].Class)
		{
		case ORDBUTCLASS_FACTORY:
			NumButs = countAssignableFactories((UBYTE)selectedPlayer, FACTORY_FLAG);
			break;
		case ORDBUTCLASS_CYBORGFACTORY:
			NumButs = countAssignableFactories((UBYTE)selectedPlayer, CYBORG_FLAG);
			break;
		case ORDBUTCLASS_VTOLFACTORY:
			NumButs = countAssignableFactories((UBYTE)selectedPlayer, VTOL_FLAG);
			break;
		default:
			break;
		}

		sButInit.id = OrderButtons[OrdIndex].ButBaseID;

		NumJustifyButs = NumButs;
		bLastCombine = false;

		switch (OrderButtons[OrdIndex].ButJustify & ORD_JUSTIFY_MASK)
		{
		case ORD_JUSTIFY_LEFT:
			sButInit.x = ORDER_BUTX;
			break;

		case ORD_JUSTIFY_RIGHT:
			sButInit.x = orderForm->width() - ORDER_BUTX -
			             (NumJustifyButs * GetImageWidth(IntImages, OrderButtons[OrdIndex].ButImageID[0]) +
			              (NumJustifyButs - 1) * ORDER_BUTGAP);
			break;

		case ORD_JUSTIFY_CENTER:
			sButInit.x = (orderForm->width() -
			              (NumJustifyButs * GetImageWidth(IntImages, OrderButtons[OrdIndex].ButImageID[0]) +
			               (NumJustifyButs - 1) * ORDER_BUTGAP)) / 2;
			break;

		case ORD_JUSTIFY_COMBINE:
			// see how many are on this line before the button
			NumCombineBefore = 0;
			for (unsigned i = 0; i < j; ++i)
			{
				if ((OrderButtons[AvailableOrders[i].OrderIndex].ButJustify & ORD_JUSTIFY_MASK)
				    == ORD_JUSTIFY_COMBINE)
				{
					NumCombineBefore += 1;
				}
			}
			NumCombineButs = (UWORD)(NumCombineBefore + 1);

			// now see how many in total
			for (unsigned i = j + 1; i < AvailableOrders.size(); ++i)
			{
				if ((OrderButtons[AvailableOrders[i].OrderIndex].ButJustify & ORD_JUSTIFY_MASK)
				    == ORD_JUSTIFY_COMBINE)
				{
					NumCombineButs += 1;
				}
			}

			// get position on line
			NumCombineButs = (UWORD)(NumCombineButs - (NumCombineBefore - (NumCombineBefore % ORD_MAX_COMBINE_BUTS)));

			if (NumCombineButs >= ORD_MAX_COMBINE_BUTS)
			{
				// the buttons will fill the line
				sButInit.x = (SWORD)(ORDER_BUTX +
				                     (GetImageWidth(IntImages, OrderButtons[OrdIndex].ButImageID[0]) + ORDER_BUTGAP) * NumCombineBefore);
			}
			else
			{
				// center the buttons
				sButInit.x = orderForm->width() / 2 -
				             (NumCombineButs * GetImageWidth(IntImages, OrderButtons[OrdIndex].ButImageID[0]) +
				              (NumCombineButs - 1) * ORDER_BUTGAP) / 2;
				sButInit.x = (SWORD)(sButInit.x +
				                     (GetImageWidth(IntImages, OrderButtons[OrdIndex].ButImageID[0]) + ORDER_BUTGAP) * NumCombineBefore);
			}

			// see if need to start a new line of buttons
			if ((NumCombineBefore + 1) == (NumCombineButs % ORD_MAX_COMBINE_BUTS))
			{
				bLastCombine = true;
			}

			break;
		}

		for (unsigned i = 0; i < OrderButtons[OrdIndex].AcNumButs; ++i)
		{
			sButInit.pTip = getDORDDescription(OrderButtons[OrdIndex].ButTips[i]);
			sButInit.width = (UWORD)GetImageWidth(IntImages, OrderButtons[OrdIndex].ButImageID[i]);
			sButInit.height = (UWORD)GetImageHeight(IntImages, OrderButtons[OrdIndex].ButImageID[i]);
			sButInit.UserData = PACKDWORD_TRI(OrderButtons[OrdIndex].ButGreyID[i],
			                                  OrderButtons[OrdIndex].ButHilightID[i],
			                                  OrderButtons[OrdIndex].ButImageID[i]);
			if (!widgAddButton(psWScreen, &sButInit))
			{
				return false;
			}

			// Set the default state for the button.
			switch (OrderButtons[OrdIndex].ButType)
			{
			case ORD_BTYPE_RADIO:
			case ORD_BTYPE_BOOLEAN:
			 {
				bool selected = (State & OrderButtons[OrdIndex].StateMask) == (UDWORD)OrderButtons[OrdIndex].States[i];
				widgSetButtonState(psWScreen, sButInit.id, selected? WBUT_CLICKLOCK : 0);
				break;
			 }
			case ORD_BTYPE_BOOLEAN_DEPEND:
			 {
				bool selected = (State & OrderButtons[OrdIndex].StateMask) == (UDWORD)OrderButtons[OrdIndex].States[i];
				bool first = i == 0;
				widgSetButtonState(psWScreen, sButInit.id, selected? WBUT_CLICKLOCK : first? 0 : WBUT_DISABLE);
				break;
			 }
			case ORD_BTYPE_BOOLEAN_COMBINE:
			 {
				bool selected = State & (UDWORD)OrderButtons[OrdIndex].States[i];
				widgSetButtonState(psWScreen, sButInit.id, selected? WBUT_CLICKLOCK : 0);
				break;
			 }
			}

			// may not add a button if the factory doesn't exist
			bHidden = false;
			switch (OrderButtons[OrdIndex].Class)
			{
			case ORDBUTCLASS_FACTORY:
				if (!checkFactoryExists(selectedPlayer, FACTORY_FLAG, i))
				{
					widgHide(psWScreen, sButInit.id);
					bHidden = true;
				}
				break;
			case ORDBUTCLASS_CYBORGFACTORY:
				if (!checkFactoryExists(selectedPlayer, CYBORG_FLAG, i))
				{
					widgHide(psWScreen, sButInit.id);
					bHidden = true;
				}
				break;
			case ORDBUTCLASS_VTOLFACTORY:
				if (!checkFactoryExists(selectedPlayer, VTOL_FLAG, i))
				{
					widgHide(psWScreen, sButInit.id);
					bHidden = true;
				}
				break;
			default:
				break;
			}

			if (!bHidden)
			{

				sButInit.x = (SWORD)(sButInit.x + sButInit.width + ORDER_BUTGAP);
			}
			sButInit.id++;
		}

		if (((OrderButtons[OrdIndex].ButJustify & ORD_JUSTIFY_MASK) != ORD_JUSTIFY_COMBINE) ||
		    bLastCombine)
		{
			sButInit.y = (SWORD)(sButInit.y + sButInit.height + ORDER_BUTGAP);
			Height = (UWORD)(Height + sButInit.height + ORDER_BUTGAP);
		}
		NumDisplayedOrders ++;
	}

	// Now we know how many orders there are we can resize the form accordingly.
	int newHeight = Height + CLOSE_HEIGHT + ORDER_BUTGAP;
	orderForm->setCalcLayout([newHeight](WIDGET *psWidget) {
		psWidget->setGeometry(psWidget->x(), ORDER_BOTTOMY - newHeight, psWidget->width(), newHeight);
	});

	OrderUp = true;
	setSecondaryWindowUp(true);

	return true;
}


// Do any housekeeping for the droid order screen.
// Any droids that die now get set to NULL - John.
// No droids being selected no longer removes the screen,
// this lets the screen work with command droids - John.
void intRunOrder()
{
	// Check to see if there all dead or unselected.
	for (auto &SelectedDroid : SelectedDroids)
	{
		if (SelectedDroid->died)
		{
			SelectedDroid = nullptr;
		}
	}
	// Remove any NULL pointers from SelectedDroids.
	SelectedDroids.erase(std::remove(SelectedDroids.begin(), SelectedDroids.end(), (DROID *)nullptr), SelectedDroids.end());

	if (psSelectedFactory != nullptr && psSelectedFactory->died)
	{
		psSelectedFactory = nullptr;
	}

	// If all dead then remove the screen.
	// If droids no longer selected then remove screen.
	if (SelectedDroids.empty())
	{
		// might have a factory selected
		if (psSelectedFactory == nullptr)
		{
			intRemoveOrder();
			return;
		}
	}
}


// Set the secondary order state for all currently selected droids. And Factory (if one selected)
// Returns true if successful.
//
static bool SetSecondaryState(SECONDARY_ORDER sec, unsigned State)
{
	// This code is similar to kfsf_SetSelectedDroidsState() in keybind.cpp. Unfortunately, it seems hard to un-duplicate the code.
	for (auto &SelectedDroid : SelectedDroids)
	{
		if (SelectedDroid)
		{
			//Only set the state if it's not a transporter.
			if (!SelectedDroid->isTransporter())
			{
				if (!secondarySetState(SelectedDroid, sec, (SECONDARY_STATE)State))
				{
					return false;
				}
			}
		}
	}

	// set the Factory settings
	if (psSelectedFactory)
	{
		if (!setFactoryState(psSelectedFactory, sec, (SECONDARY_STATE)State))
		{
			return false;
		}
	}

	return true;
}


// Process the droid order screen.
//
void intProcessOrder(UDWORD id)
{
	UWORD i;
	UWORD OrdIndex;
	UDWORD BaseID;
	UDWORD StateIndex;
	UDWORD CombineState;

	if (id == IDORDER_CLOSE)
	{
		intRemoveOrder();
		if (intMode == INT_ORDER)
		{
			intMode = INT_NORMAL;
		}
		else
		{
			intMode = INT_OBJECT;
		}
		return;
	}

	for (OrdIndex = 0; OrdIndex < NUM_ORDERS; OrdIndex++)
	{
		BaseID = OrderButtons[OrdIndex].ButBaseID;

		switch (OrderButtons[OrdIndex].ButType)
		{
		case ORD_BTYPE_RADIO:
			if ((id >= BaseID) && (id < BaseID + OrderButtons[OrdIndex].AcNumButs))
			{
				StateIndex = id - BaseID;

				for (i = 0; i < OrderButtons[OrdIndex].AcNumButs; i++)
				{
					widgSetButtonState(psWScreen, BaseID + i, 0);
				}
				if (SetSecondaryState(OrderButtons[OrdIndex].Order,
				                      OrderButtons[OrdIndex].States[StateIndex] & OrderButtons[OrdIndex].StateMask))
				{
					widgSetButtonState(psWScreen, id, WBUT_CLICKLOCK);
				}
			}
			break;

		case ORD_BTYPE_BOOLEAN:
			if ((id >= BaseID) && (id < BaseID + OrderButtons[OrdIndex].AcNumButs))
			{
				StateIndex = id - BaseID;

				if (widgGetButtonState(psWScreen, id) & WBUT_CLICKLOCK)
				{
					widgSetButtonState(psWScreen, id, 0);
					SetSecondaryState(OrderButtons[OrdIndex].Order, 0);
				}
				else
				{
					widgSetButtonState(psWScreen, id, WBUT_CLICKLOCK);
					SetSecondaryState(OrderButtons[OrdIndex].Order,
					                  OrderButtons[OrdIndex].States[StateIndex] & OrderButtons[OrdIndex].StateMask);
				}

			}
			break;

		case ORD_BTYPE_BOOLEAN_DEPEND:
			// Toggle the state of this button.
			if (id == BaseID)
			{
				if (widgGetButtonState(psWScreen, id) & WBUT_CLICKLOCK)
				{
					widgSetButtonState(psWScreen, id, 0);
					// Disable the dependent button.
					widgSetButtonState(psWScreen, id + 1, WBUT_DISABLE);
				}
				else
				{
					widgSetButtonState(psWScreen, id, WBUT_CLICKLOCK);
					// Enable the dependent button.
					widgSetButtonState(psWScreen, id + 1, 0);
				}
			} if ((id > BaseID) && (id < BaseID + OrderButtons[OrdIndex].AcNumButs))
			{
				// If the previous button is down ( armed )..
				if (widgGetButtonState(psWScreen, id - 1) & WBUT_CLICKLOCK)
				{
					// Toggle the state of this button.
					if (widgGetButtonState(psWScreen, id) & WBUT_CLICKLOCK)
					{
						widgSetButtonState(psWScreen, id, 0);
						SetSecondaryState(OrderButtons[OrdIndex].Order, 0);
					}
					else
					{
						StateIndex = id - BaseID;

						widgSetButtonState(psWScreen, id, WBUT_CLICKLOCK);
						SetSecondaryState(OrderButtons[OrdIndex].Order,
						                  OrderButtons[OrdIndex].States[StateIndex] & OrderButtons[OrdIndex].StateMask);
					}
				}
			}
			break;
		case ORD_BTYPE_BOOLEAN_COMBINE:
			if ((id >= BaseID) && (id < BaseID + OrderButtons[OrdIndex].AcNumButs))
			{
				// Toggle the state of this button.
				if (widgGetButtonState(psWScreen, id) & WBUT_CLICKLOCK)
				{
					widgSetButtonState(psWScreen, id, 0);
				}
				else
				{
					widgSetButtonState(psWScreen, id, WBUT_CLICKLOCK);
				}

				// read the state of all the buttons to get the final state
				CombineState = 0;
				for (StateIndex = 0; StateIndex < OrderButtons[OrdIndex].AcNumButs; StateIndex++)
				{
					if (widgGetButtonState(psWScreen, BaseID + StateIndex) & WBUT_CLICKLOCK)
					{
						CombineState |= OrderButtons[OrdIndex].States[StateIndex];
					}
				}

				// set the final state
				SetSecondaryState(OrderButtons[OrdIndex].Order,
				                  CombineState & OrderButtons[OrdIndex].StateMask);
			}
			break;
		}
	}
}


// check whether the order list has changed
static bool CheckObjectOrderList()
{
	std::vector<AVORDER> NewAvailableOrders;

	if (psSelectedFactory != nullptr)
	{
		NewAvailableOrders = buildStructureOrderList(psSelectedFactory);
	}
	else
	{
		NewAvailableOrders = buildDroidOrderList();
	}

	// now check that all the orders are the same
	return NewAvailableOrders == AvailableOrders;
}

static bool intRefreshOrderButtons()
{
	SECONDARY_STATE State;
	UWORD OrdIndex;
	UDWORD	id;

	for (unsigned j = 0; j < AvailableOrders.size() && j < MAX_DISPLAYABLE_ORDERS; ++j)
	{
		OrdIndex = AvailableOrders[j].OrderIndex;

		// Get current order state.
		State = GetSecondaryStates(OrderButtons[OrdIndex].Order);

		// Set actual number of buttons.
		OrderButtons[OrdIndex].AcNumButs = OrderButtons[OrdIndex].NumButs;

		id = OrderButtons[OrdIndex].ButBaseID;
		for (unsigned i = 0; i < OrderButtons[OrdIndex].AcNumButs; ++i)
		{
			// Set the state for the button.
			switch (OrderButtons[OrdIndex].ButType)
			{
			case ORD_BTYPE_RADIO:
			case ORD_BTYPE_BOOLEAN:
			 {
				bool selected = (State & OrderButtons[OrdIndex].StateMask) == (UDWORD)OrderButtons[OrdIndex].States[i];
				widgSetButtonState(psWScreen, id, selected? WBUT_CLICKLOCK : 0);
				break;
			 }
			case ORD_BTYPE_BOOLEAN_DEPEND:
			 {
				bool selected = (State & OrderButtons[OrdIndex].StateMask) == (UDWORD)OrderButtons[OrdIndex].States[i];
				bool first = i == 0;
				widgSetButtonState(psWScreen, id, selected? WBUT_CLICKLOCK : first? 0 : WBUT_DISABLE);
				break;
			 }
			case ORD_BTYPE_BOOLEAN_COMBINE:
			 {
				bool selected = State & (UDWORD)OrderButtons[OrdIndex].States[i];
				widgSetButtonState(psWScreen, id, selected? WBUT_CLICKLOCK : 0);
				break;
			 }
			}

			id ++;
		}
	}

	return true;
}


// Call to refresh the Order screen, ie when a droids boards it.
//
bool intRefreshOrder()
{
	// Is the Order screen up?
	if ((intMode == INT_ORDER) &&
	    (widgGetFromID(psWScreen, IDORDER_FORM) != nullptr))
	{
		bool Ret;

		SelectedDroids.clear();

		// check for factory selected first
		if (!psSelectedFactory)
		{
			if (!BuildSelectedDroidList())
			{
				// no units selected - quit
				intRemoveOrder();
				return true;
			}
		}

		// if the orders haven't changed, just reset the state
		if (CheckObjectOrderList())
		{
			Ret = intRefreshOrderButtons();
		}
		else
		{
			// Refresh it by re-adding it.
			Ret = intAddOrder(nullptr);
			if (Ret == false)
			{
				intMode = INT_NORMAL;
			}
		}
		return Ret;
	}

	return true;
}


// Remove the droids order screen with animation.
//
void intRemoveOrder()
{
	widgDelete(psWScreen, IDORDER_CLOSE);
	setSecondaryWindowUp(false);

	// Start the window close animation.
	IntFormAnimated *form = (IntFormAnimated *)widgGetFromID(psWScreen, IDORDER_FORM);
	if (form != nullptr)
	{
		form->closeAnimateDelete();
		OrderUp = false;
		SelectedDroids.clear();
		psSelectedFactory = nullptr;
	}
}


// Remove the droids order screen without animation.
//
void intRemoveOrderNoAnim()
{
	widgDelete(psWScreen, IDORDER_CLOSE);
	widgDelete(psWScreen, IDORDER_FORM);
	setSecondaryWindowUp(false);
	OrderUp = false;
	SelectedDroids.clear();
	psSelectedFactory = nullptr;
}

//new function added to bring up the RMB order form for Factories as well as droids
void intAddFactoryOrder(STRUCTURE *psStructure)
{
	if (!OrderUp)
	{
		intResetScreen(false);
		intAddOrder((BASE_OBJECT *)psStructure);
		intMode = INT_ORDER;
	}
	else
	{
		intAddOrder((BASE_OBJECT *)psStructure);
	}
}
