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


#ifndef CONTROLS_CONFIG_H
#define CONTROLS_CONFIG_H

#include "globalincs/pstypes.h"
#include "scripting/scripting.h"

#define CONTROL_CONFIG_XSTR	507

#define CCF_AXIS_BTN    0x40    //!< axis is a button on positive side. If also inverted, is on negative side instead
#define CCF_RELATIVE    0x20    //!< axis is relative
#define CCF_INVERTED    0x10    //!< axis is inverted
#define CCF_AXIS        0x08    //!< btn is an axis
#define CCF_HAT         0x04    //!< btn is a hat
#define CCF_BALL        0x02    //!< btn is a ball
#define CCF_BUTTON      0x01    //!< btn is actually a button
#define CCF_NONE        0x00    //!< No flags.  Alias is "NONE"

/*!
 * These are used to index a corresponding axis value from an array.
 * Currently only used by ::Axis_map_to[] and ::Axis_map_to_defaults[]
 */
enum Joy_axis_index : short {
	JOY_X_AXIS		=0,
	JOY_Y_AXIS,
	JOY_Z_AXIS,
	JOY_RX_AXIS,
	JOY_RY_AXIS,
	JOY_RZ_AXIS,
	JOY_U_AXIS,
	JOY_V_AXIS,

	JOY_NUM_AXES	//!< Max number of joy axes that FSO can handle. Must be last enum in Joy_axis_index.
};

// Aliases for mouse axes.  Really should unify this...
const short MOUSE_X_AXIS = JOY_X_AXIS;
const short MOUSE_Y_AXIS = JOY_Y_AXIS;
const short MOUSE_Z_AXIS = JOY_Z_AXIS;
const short MOUSE_NUM_AXES = 3;
const short MOUSE_NUM_BUTTONS = 9;	// Keep this up to date with mouse.h until z64555 stops being lazy and does it right

/*!
 * Controller index enumeration
 * @details For use with hardcoded bindings and scripting API to allow human-readable translation
 * @note These enums are hardcoded so that an int value of 0 would be JOY0
 */
enum CID : int8_t {
	CID_NONE     = -3,	//!< No device bound
	CID_KEYBOARD = -2,	//!< belongs to keyboard
	CID_MOUSE    = -1,  //!< to mouse
	CID_JOY0     =  0,  //!< to Joy0
	CID_JOY1     =  1,  //!< to Joy1 (Throttle?)
	CID_JOY2     =  2,  //!< to Joy2 (Pedals?)
	CID_JOY3     =  3,  //!< to Joy3 (Head tracker?)
	
	CID_JOY_MAX         //!< Maximum supported joysticks, must be last in the enum definition
};

/*!
 * Control Configuration Types. Namely differ in how the control is activated
 */
enum CC_type {
	CC_TYPE_TRIGGER = 0,    //!< A normal, one-shot type control that is activated when a key is or button is pressed
	CC_TYPE_CONTINUOUS,     //!< A continous control that is activated as long as the key or button is held down
	CC_TYPE_AXIS_ABS,       //!< Absolute Axis mode.  Axis position = output value
	CC_TYPE_AXIS_REL,       //!< Relative Axis mode.  Axis postion away from its center adds or subtracts an output value register
	CC_TYPE_AXIS_BTN_NEG,   //!< Axis Button mode, negative side.  Axis position in the negative side will trigger a button action
	CC_TYPE_AXIS_BTN_POS    //!< Axis Button mode, positive side.  Axis position in the positive side will trigger a button action
};

/*!
 * All available actions
 * This is the value of the id field in config_item
 * The first group of items are ship targeting.
 *
 * Note: Do not adjust the order or numeric value
 */
enum IoActionId : int {
	TARGET_NEXT										=0,		//!< target next
	TARGET_PREV										=1,		//!< target previous
	TARGET_NEXT_CLOSEST_HOSTILE						=2,		//!< target the next hostile target
	TARGET_PREV_CLOSEST_HOSTILE						=3,		//!< target the previous closest hostile
	TOGGLE_AUTO_TARGETING							=4,		//!< toggle auto-targeting
	TARGET_NEXT_CLOSEST_FRIENDLY					=5,		//!< target the next friendly ship
	TARGET_PREV_CLOSEST_FRIENDLY					=6,		//!< target the closest friendly ship
	TARGET_SHIP_IN_RETICLE							=7,		//!< target ship closest to center of reticle
	TARGET_CLOSEST_SHIP_ATTACKING_TARGET			=8,		//!< target the closest ship attacking current target
	TARGET_LAST_TRANMISSION_SENDER					=9,		//!< TARGET_LAST_TRANMISSION_SENDER
	STOP_TARGETING_SHIP								=10,	//!< stop targeting ship

	//!< @n
	//!< Targeting a Ship's Subsystem
	//!< ------------------------------
	TARGET_SUBOBJECT_IN_RETICLE						=11,	//!< target ships subsystem in reticle
	TARGET_NEXT_SUBOBJECT							=12,	//!< target next subsystem on current target
	TARGET_PREV_SUBOBJECT							=13,	//!< TARGET_PREV_SUBOBJECT
	STOP_TARGETING_SUBSYSTEM						=14,	//!< stop targeting subsystems on ship

	//!< @n
	//!< Speed Matching
	//!< ----------------
	MATCH_TARGET_SPEED								=15,	//!< match target speed
	TOGGLE_AUTO_MATCH_TARGET_SPEED					=16,	//!< toggle auto-match target speed

	//!< @n
	//!< Weapons
	//!< ---------
	FIRE_PRIMARY									=17,	//!< FIRE_PRIMARY
	FIRE_SECONDARY									=18,	//!< FIRE_SECONDARY
	CYCLE_NEXT_PRIMARY								=19,	//!< cycle to next primary weapon
	CYCLE_PREV_PRIMARY								=20,	//!< cycle to previous primary weapon
	CYCLE_SECONDARY									=21,	//!< cycle to next secondary weapon
	CYCLE_NUM_MISSLES								=22,	//!< cycle number of missiles fired from secondary bank
	LAUNCH_COUNTERMEASURE							=23,	//!< LAUNCH_COUNTERMEASURE

	//!< @n
	//!< Controls
	//!< ----------
	FORWARD_THRUST									=24,	//!< FORWARD_THRUST
	REVERSE_THRUST									=25,	//!< REVERSE_THRUST
	BANK_LEFT										=26,	//!< BANK_LEFT
	BANK_RIGHT										=27,	//!< BANK_RIGHT
	PITCH_FORWARD									=28,	//!< PITCH_FORWARD
	PITCH_BACK										=29,	//!< PITCH_BACK
	YAW_LEFT										=30,	//!< YAW_LEFT
	YAW_RIGHT										=31,	//!< YAW_RIGHT

	//!< @n
	//!< Throttle Control
	//!< ------------------
	ZERO_THROTTLE									=32,	//!< ZERO_THROTTLE
	MAX_THROTTLE									=33,	//!< MAX_THROTTLE
	ONE_THIRD_THROTTLE								=34,	//!< ONE_THIRD_THROTTLE
	TWO_THIRDS_THROTTLE								=35,	//!< TWO_THIRDS_THROTTLE
	PLUS_5_PERCENT_THROTTLE							=36,	//!< PLUS_5_PERCENT_THROTTLE
	MINUS_5_PERCENT_THROTTLE						=37,	//!< MINUS_5_PERCENT_THROTTLE

	//!< @n
	//!< Squadmate Messaging Keys
	//!< --------------------------
	ATTACK_MESSAGE									=38,	//!< wingman message: attack current target
	DISARM_MESSAGE									=39,	//!< wingman message: disarm current target
	DISABLE_MESSAGE									=40,	//!< wingman message: disable current target
	ATTACK_SUBSYSTEM_MESSAGE						=41,	//!< wingman message: disable current target
	CAPTURE_MESSAGE									=42,	//!< wingman message: capture current target
	ENGAGE_MESSAGE									=43,	//!< wingman message: engage enemy
	FORM_MESSAGE									=44,	//!< wingman message: form on my wing
	IGNORE_MESSAGE									=45,	//!< IGNORE_MESSAGE
	PROTECT_MESSAGE									=46,	//!< wingman message: protect current target
	COVER_MESSAGE									=47,	//!< wingman message: cover me
	WARP_MESSAGE									=48,	//!< wingman message: warp out
	REARM_MESSAGE									=49,	//!< REARM_MESSAGE
	TARGET_CLOSEST_SHIP_ATTACKING_SELF				=50,	//!< target closest ship that is attacking player

	//!< @n
	//!< Views
	//!< -------
	VIEW_CHASE										=51,	//!< VIEW_CHASE
	VIEW_EXTERNAL									=52,	//!< VIEW_EXTERNAL
	VIEW_EXTERNAL_TOGGLE_CAMERA_LOCK				=53,	//!< VIEW_EXTERNAL_TOGGLE_CAMERA_LOCK
	VIEW_SLEW										=54,	//!< VIEW_SLEW
	VIEW_OTHER_SHIP									=55,	//!< VIEW_OTHER_SHIP
	VIEW_DIST_INCREASE								=56,	//!< VIEW_DIST_INCREASE
	VIEW_DIST_DECREASE								=57,	//!< VIEW_DIST_DECREASE
	VIEW_CENTER										=58,	//!< VIEW_CENTER
	PADLOCK_UP										=59,	//!< PADLOCK_UP
	PADLOCK_DOWN									=60,	//!< PADLOCK_DOWN
	PADLOCK_LEFT									=61,	//!< PADLOCK_LEFT
	PADLOCK_RIGHT									=62,	//!< PADLOCK_RIGHT

	//!< @n
	//!< Misc Controls 1
	//!< -----------------
	RADAR_RANGE_CYCLE								=63,	//!< cycle to next radar range
	SQUADMSG_MENU									=64,	//!< toggle the squadmate messaging menu
	SHOW_GOALS										=65,	//!< show the mission goals screen
	END_MISSION										=66,	//!< end the mission
	TARGET_TARGETS_TARGET							=67,	//!< target your target's target
	AFTERBURNER										=68,	//!< AFTERBURNER

	//!< @n
	//!< ETS
	//!< -----
	INCREASE_WEAPON									=69,	//!< increase weapon recharge rate
	DECREASE_WEAPON									=70,	//!< decrease weapon recharge rate
	INCREASE_SHIELD									=71,	//!< increase shield recharge rate
	DECREASE_SHIELD									=72,	//!< decrease shield recharge rate
	INCREASE_ENGINE									=73,	//!< increase energy to engines
	DECREASE_ENGINE									=74,	//!< decrease energy to engines
	ETS_EQUALIZE									=75,	//!< equalize recharge rates
	SHIELD_EQUALIZE									=76,	//!< equalize shield energy to all quadrants
	SHIELD_XFER_TOP									=77,	//!< transfer shield energy to front
	SHIELD_XFER_BOTTOM								=78,	//!< transfer shield energy to rear
	SHIELD_XFER_LEFT								=79,	//!< transfer shield energy to left
	SHIELD_XFER_RIGHT								=80,	//!< transfer shield energy to right
	XFER_SHIELD										=81,	//!< transfer energy to shield from weapons
	XFER_LASER										=82,	//!< transfer energy to weapons from shield

	//!< @n
	//!< Misc Controls 2
	//!< -----------------
	GLIDE_WHEN_PRESSED								=83, 	//!< GLIDE_WHEN_PRESSED
													//!< @remark Backslash -- this was a convenient place for Glide When Pressed, since Show Damage Popup isn't used
	BANK_WHEN_PRESSED								=84,	//!< BANK_WHEN_PRESSED
	SHOW_NAVMAP										=85,	//!< SHOW_NAVMAP
	ADD_REMOVE_ESCORT								=86,	//!< ADD_REMOVE_ESCORT
	ESCORT_CLEAR									=87,	//!< ESCORT_CLEAR

	TARGET_NEXT_ESCORT_SHIP							=88,	//!< TARGET_NEXT_ESCORT_SHIP
	TARGET_CLOSEST_REPAIR_SHIP						=89,	//!< target the closest repair ship
	TARGET_NEXT_UNINSPECTED_CARGO					=90,	//!< TARGET_NEXT_UNINSPECTED_CARGO
	TARGET_PREV_UNINSPECTED_CARGO					=91,	//!< TARGET_PREV_UNINSPECTED_CARGO
	TARGET_NEWEST_SHIP								=92,	//!< TARGET_NEWEST_SHIP

	TARGET_NEXT_LIVE_TURRET							=93, 	//!< TARGET_NEXT_LIVE_TURRET
	TARGET_PREV_LIVE_TURRET							=94,	//!< TARGET_PREV_LIVE_TURRET

	TARGET_NEXT_BOMB								=95,	//!< TARGET_NEXT_BOMB
	TARGET_PREV_BOMB								=96,	//!< TARGET_PREV_BOMB

	//!< @n
	//!< Multiplayer messaging keys
	//!< ----------------------------
	MULTI_MESSAGE_ALL								=97,	//!< message all netplayers
	MULTI_MESSAGE_FRIENDLY							=98,	//!< message all friendlies
	MULTI_MESSAGE_HOSTILE							=99,	//!< message all hostiles
	MULTI_MESSAGE_TARGET							=100,	//!< message targeted ship (if player)

	//!< @n
	//!< Multiplayer misc keys
	//!< -----------------------
	MULTI_OBSERVER_ZOOM_TO							=101,	//!< if i'm an observer, zoom to my targeted object

	TIME_SPEED_UP									=102,	//!< TIME_SPEED_UP
	TIME_SLOW_DOWN									=103,	//!< TIME_SLOW_DOWN

	TOGGLE_HUD_CONTRAST								=104,	//!< toggle between high and low HUD contrast

	MULTI_TOGGLE_NETINFO							=105,	//!< toggle network info

	MULTI_SELF_DESTRUCT								=106,	//!< self destruct (multiplayer only)

	//!< @n
	//!< Misc Controls 3
	//!< -----------------
	TOGGLE_HUD										=107,	//!< TOGGLE_HUD
	RIGHT_SLIDE_THRUST								=108,	//!< RIGHT_SLIDE_THRUST
	LEFT_SLIDE_THRUST								=109,	//!< LEFT_SLIDE_THRUST
	UP_SLIDE_THRUST									=110,	//!< UP_SLIDE_THRUST
	DOWN_SLIDE_THRUST								=111,	//!< DOWN_SLIDE_THRUST
	HUD_TARGETBOX_TOGGLE_WIREFRAME					=112,	//!< HUD_TARGETBOX_TOGGLE_WIREFRAME
	VIEW_TOPDOWN									=113,	//!< VIEW_TOPDOWN
	VIEW_TRACK_TARGET								=114,	//!< VIEW_TRACK_TARGET (Switfty) Toggle for VM_Track

	//!< @n
	//!< AutoPilot - Kazan
	//!< -------------------
	AUTO_PILOT_TOGGLE								=115,	//!< Autopilot key control
	NAV_CYCLE										=116,	//!< NAV_CYCLE

	//!< @n
	//!< Gliding
	//!< ---------
	TOGGLE_GLIDING									=117,	//!< TOGGLE_GLIDING

	//!< @n
	//!< Additional weapon controls
	//!< ----------------------------
	CYCLE_PRIMARY_WEAPON_SEQUENCE					=118,	//!< cycle num primaries to fire at once

	//!< @n
	//!< Custom control slots
	//!< ----------------------------
	CUSTOM_CONTROL_1								= 119,
	CUSTOM_CONTROL_2								= 120,
	CUSTOM_CONTROL_3								= 121,
	CUSTOM_CONTROL_4								= 122,
	CUSTOM_CONTROL_5								= 123,

	//!< @n
	//!< Analog controls
	//!<-----------------------------
	//!< All axis actions must be kept together
	JOY_HEADING_AXIS								= 124,
	JOY_PITCH_AXIS									= 125,
	JOY_BANK_AXIS									= 126,
	JOY_ABS_THROTTLE_AXIS							= 127,
	JOY_REL_THROTTLE_AXIS							= 128,

	//!< @n
	//! Communication menu controls
	//! ----------------------------
	COMMS_MENU_MOVE_UP,
	COMMS_MENU_MOVE_DOWN,
	COMMS_MENU_SELECT,

	TOGGLE_HUD_SHADOWS,

	CYCLE_PRIMARY_WEAPON_PATTERN,

	TOGGLE_PHOTO_MODE,
	PHOTO_MODE_FILTER_PREV,
	PHOTO_MODE_FILTER_NEXT,
	PHOTO_MODE_FILTER_RESET,
	PHOTO_MODE_PARAM_DECREASE,
	PHOTO_MODE_PARAM_INCREASE,

	/*!
	 * This must always be below the last defined item
	 */
	CCFG_MAX                                  //!<  The total number of defined control actions (or last define + 1)
};

// Constants for array-based checking (within Control_config) for the axis actions.
// z64: Yes, this is hackish. No I don't really like it.
const int JOY_AXIS_BEGIN = JOY_HEADING_AXIS;
const int JOY_AXIS_END = JOY_REL_THROTTLE_AXIS + 1;
const int NUM_JOY_AXIS_ACTIONS = JOY_AXIS_END - JOY_AXIS_BEGIN;

/*!
 * @brief Enum for indexing into axis action arrays.
 * @note Keep this up to date with JOY_HEADING_AXIS and friends.  Pay attention to their order and copy to here
 */
namespace Action {
enum {
	HEADING = 0,
	PITCH,
	BANK,
	ABS_THROTTLE,
	REL_THROTTLE,

	NUM_VALUES
};
}

/*!
 * Where the preset is located in memory
 */
enum class Preset_t {
	hardcode,	// Preset is defined hardcode
	tbl,		// Preset is defined in controlconfigdefaults.tbl
	pst			// Preset is defined in a .json
};

class CCB;

/*!
 *  A singular button binding
 */
class CC_bind {
public:
	CC_bind() = default;
	CC_bind(CID _cid, short _btn) : cid(_cid), btn(_btn) { validate(); };
	CC_bind(CID _cid, short _btn, char _flags) : cid(_cid), flags(_flags), btn(_btn) { validate(); };
	CC_bind(const CC_bind &A) = default;

	CC_bind& operator=(const CC_bind &A) = default;

	/*!
	 * Checks if this CC_bind is equal to the given CC_bind
	 *
	 * @note EQUAL means EQUAL, this will return FALSE if the flags differ by a single bit
	 */
	bool operator==(const CC_bind &B) const;

	/*!
	 * Checks if this CC_bind is not equal to the given CC_bind
	 */
	bool operator!=(const CC_bind &B) const;

	/*!
	 * Checks if this CC_bind is equal to the given CC_bind, disregarding the inversion flag
	 */
	bool invert_agnostic_equals(const CC_bind &B) const;

	/*!
	 * Clears the binding and flags.  Some flags are retained.
	 */
	void clear();

	/*!
	 * True if not bound
	 */
	bool empty() const;

	/**
	 * Takes the given binding.
	 */
	void take(CID _cid, short _btn, char _flags = 0);

	/**
	 * Validates the binding, clearing it if necassary
	 */
	void validate();

	/*!
	 * Returns a copy of ::btn
	 */
	short get_btn() const;

	/*!
	 * Returns a copy of ::cid
	 */
	CID get_cid() const;

	/*!
	 * Returns a copy of ::flags
	 */
	char get_flags() const;

	/*!
	 * Returns a human-readable string of this binding
	 */
	SCP_string textify() const;

	/*!
	 * Sets the inversion state of this binding according to the passed bool. True = inverted, false = normal
	 */
	void invert(bool);

	/*!
	 * Toggles the inversion state
	 */
	void invert_toggle();

	/*!
	 * Is true if inverted, false otherwise
	 */
	bool is_inverted() const;

	/*!
	 * Checks if this CC_bind conflicts with the given argument
	 *
	 * @note Similar to operator==, but ignores certain combinations of flags.
	 */
	bool conflicts_with(const CC_bind&) const;

private:
	CID cid = CID_NONE; //!< Which controller this belongs to
	char flags = 0;     //!< mask to determine various additional attributes of btn
	short btn = -1;     //!< The button, key combo, or axis that's bound.
};

/**
 * @struct cc_line
 * @brief Defines an interactable line to display control names and their bindings
 */
struct cc_line {
	const char* label;
	int cc_index;       // index into Control_config of item
	int y;              // Y coordinate of line
	int kx, kw, jx, jw; // x start and width of keyboard and joystick bound text
};

/**
 * @struct conflict
 * @brief Defines an conflict between bindings
 */
struct conflict {
	int first = -1;  // index of other control in conflict with this one
	int second = -1; // index of other control in conflict with this one
};

/*!
 * A pair of bindings.
 * @note Please don't set the bindings directly, use ::take() instead.
 */
class CCB {
public:
	CC_bind first;  // The primary binding
	CC_bind second; // The secondary binding

public:
	CCB() = default;
	CCB(const CCB& A) = default;

	/*!
	 * Returns true if nothing is bound
	 */
	bool empty() const;

	/*!
	 * Takes a given binding
	 *
	 * @param[in] A     The bind to take
	 * @param[in] order 0 is primary, 1 is secondary, -1 is overwrite existing bind (if any)
	 *
	 * @note Should there be a binding to the same controller as the given binding, it is cleared
	 */
	void take(CC_bind A, int order);

	/*!
	 * Clears both bindings
	 */
	void clear();

	/*!
	 * Gets the value of btn if either CC_bind has the given CID
	 *
	 * @returns the btn of the bound controller, or
	 * @returns -1 if that controller is not bound
	 */
	short get_btn(CID) const;

	/*!
	 * Assigns the contents of the given CCB to this CCB
	 */
	CCB& operator=(const CCB&) = default;

	/*!
	 * Checks if the given CCB is exactly equal to this
	 */
	bool operator==(const CCB&) const;

	/*!
	 * Checks if the given CCB differs from this
	 */
	bool operator!=(const CCB&) const;

	/*!
	 * Returns True if this CCB's first isn't empty and the given CCB has a binding that can conflict with it
	 */
	bool has_first_conflict(const CCB&) const;

	/*!
	 * Returns True if this CCB's second isn't empty and the given CCB has a binding that can conflict with it
	 */
	bool has_second_conflict(const CCB&) const;

	/*!
	 * Returns a pointer to first, or second, whichever has a binding equal to the given CC_bind, or nullptr if neither
	 */
	CC_bind* find(const CC_bind&);

	/*!
	 * Returns a pointer to first, or second, whichever has a CID equal to the given CID, or nullptr if neither
	 */
	CC_bind* find(CID);

	/*!
	 * Returns a pointer to first, or second, whichever has *all* flags in the given mask
	 */
	CC_bind* find_flags(const char);

	/*!
	 * Sets the inversion state of both bindings
	 */
	void invert(bool);

	/*!
	 * Toggles the inversion state of Primary and copies it to Secondary
	 */
	void invert_toggle();

	/*!
	 * Is true if both bindings are inverted, false otherwise
	 */
	bool is_inverted() const;
};

/*!
 * A preset, a collection of bindings for use in Control_config with an associated name
 */
class CC_preset {
public:
	SCP_vector<CCB> bindings;
	SCP_string name;
	Preset_t type = Preset_t::hardcode;

public:
	CC_preset() = default;
	CC_preset(const CC_preset& A) = default;

	CC_preset& operator=(const CC_preset&);
	/*!
	 * Checks if the given preset is a duplicate of this one
	 */
	bool is_duplicate_of(CC_preset&);
};

/*!
 * Control configuration item type.
 * @detail Contains binding info, documentation, behavior, etc. for a single control
 */
// Conflict group bitmasks. Bindings only conflict if their groups overlap (bitwise AND != 0).
static const int CONFLICT_GROUP_DEFAULT    = (1 << 0);  //!< Standard gameplay bindings
static const int CONFLICT_GROUP_PHOTO_MODE = (1 << 1);  //!< Bindings used within photo mode

class CCI : public CCB {
public:
// Inherited from CCB
	// CC_bind first;
	// CC_bind second;

// Items Set in menu
	char tab;               //!< what tab (category) it belongs in
	int  conflict_groups = CONFLICT_GROUP_DEFAULT;  //!< Bitmask of conflict groups. Bindings only conflict with others sharing at least one group.
	int  indexXSTR;         //!< what string index we should use to translate this with an XSTR 0 = None, 1= Use item index + CONTROL_CONFIG_XSTR, 2 <= use CCI::indexXSTR directly
	SCP_string text;        //!< describes the action in the config screen

	CC_type type;           //!< manner control should be checked in

// Items used during gameplay
	TIMESTAMP digital_used;     //!< has control been used yet in mission?  If so, this is the timestamp.
	                            // Note this is not a UI timestamp because this is purely for mission timing purposes.
	int  analog_value;          // For axes, this denotes the last frame's value
	bool disabled = true;       //!< whether this action should be available at all
	bool locked = false;		//!< whether this action will be triggered by the respectively bound key
	bool scriptEnabledByDefault = false; //!< whether this binding will execute it's registered hooks if triggered. Resets each mission
	bool continuous_ongoing;    //!< whether this action is a continuous one and is currently ongoing

public:
	CCI() = default;
	CCI(const CCI& A) = default;
	
	/*!
	 * Assigns the contents of the given CCI to this CCI
	 */
	CCI& operator=(const CCI&);

	/*!
	 * @brief Takes the bindings of the given CCB, but leaves all other members alone
	 */
	CCI& operator=(const CCB&);

	/*!
	 * Returns true if this item is analogue or an axis
	 */
	bool is_axis();
};

/*!
 * Builder predicate to populate a ControlConfig vector with hardcoded default bindings.
 */
class CCI_builder {
public:
	/*!
	 * Initilizes the given ControlConfig vector
	 */
	CCI_builder(SCP_vector<CCI>& _ControlConfig);

	/*!
	 * Start a chain of factory methods. If there are any pre-init work to be done, that's done here.
	 */
	CCI_builder& start();

	/*!
	 * End a chain of factory methods.  If there any post-init work to be done, that's done here.
	 */
	void end();

	/*!
	 * Assigns the hardcoded binding to the given action
	 *
	 * @note This differs from the original hardcode from :V: in the hopes of it being more intuitive to future IoAction additions
	 *
	 * param[in] action_id  The IoActionId enum associated with the control
	 * param[in] primary    The btn, key combo, or axis for the default primary binding
	 * param[in] secondary  The btn, key combo, or axis for the default secondary binding
	 * param[in] tab        Category tab this control should show up under on the config menu
	 * param[in] indexXSTR  ID for the XSTR localization utility. 0 for no localization, 1 for automatic lookup, all other values are index for item in strings.tbl
	 * param[in] text       Label text displayed in the config menu.
	 * param[in] type       How the control should be treated. see doxy on CC_type for details
	 * param[in] disabled   If true, this control will not be available in the game.  Controlsconfigdefaults.tbl may override this.  Don't use this to temporarily disable controls in mission.
	 * 
	 * @details The CID for the default primary and secondary is determined by the type parameter.
	 *   If the type is CC_TYPE_TRIGGER or CC_TYPE_CONTINOUS, the primary is assumed to be a key combo and secondary is assumed to be a joy button.
	 *   If the type is CC_TYPE_AXIS_ABS, CC_TYPE_AXIS_REL, CC_TYPE_AXIS_BTN_POS, or CC_TYPE_AXIS_BTN_NEG, the primary is a Joy0 axis and the secondary is a Mouse axis
	 */
	CCI_builder& operator()(IoActionId action_id, short primary, short secondary, char tab, int indexXSTR, const char *text, CC_type type, bool disabled = false, int conflict_groups = CONFLICT_GROUP_DEFAULT);

private:
	CCI_builder();	// Only one builder per Control Config, so a default constructor is useless
	SCP_vector<CCI>& ControlConfig;
};

enum class selItem : int {
	selItem_REND, // Must be first to allow cycling

	None,
	Primary,
	Secondary,

	selItem_END // Must be last to allow cycling
};

extern int Failed_key_index;

extern int Joy_dead_zone_size;
extern int Joy_sensitivity;

extern int Control_config_overlay_id;

extern SCP_vector<cc_line> Cc_lines; //!< Stores the keyboard details for the UI
extern SCP_vector<conflict> Conflicts; //!< Stores conflict data for the keybinds

extern SCP_vector<CCI> Control_config;		//!< Stores the keyboard configuration
extern SCP_vector<CC_preset> Control_config_presets; // tabled control presets; pointers to config_item arrays

extern bool Generate_controlconfig_table;

// string table constants for labels and stuff.
#define NUM_AXIS_TEXT			JOY_NUM_AXES
#define NUM_MOUSE_TEXT			MOUSE_NUM_BUTTONS

extern char *Axis_text[NUM_AXIS_TEXT];			// String table of axis labels (joystick and mice).  XSTR'd on init.
extern char *Mouse_button_text[NUM_MOUSE_TEXT];	// String table of mouse button labels.  XSTR'd on init.

/*!
 * @brief Checks if either binding in the CCB has the given cid
 *
 * @returns  0  if its the first element, or
 * @returns  1  if its the second element, or
 * @returns -1  if neither
 */
int is_cid_either(CID, const CCB);

/*!
 * Allows CID to be assigned by a short.  Has Assert to check values
 */
void cid_assign(CID&, const short);

/*!
* @brief initialize common control config stuff - call at game startup after localization has been initialized
*/
void control_config_common_init();

/*!
 * @brief close common control config stuff - call at game shutdown
 */
void control_config_common_close();

/*!
 * @brief init config menu
 */
void control_config_init(bool API_Access = false);

/*!
 * @brief do a frame of the config menu
 */
void control_config_do_frame(float frametime);

/*!
 * @brief close config menu
 */
void control_config_close(bool API_Access = false);

/*!
 * @brief setup for binding a control
 */
void control_config_do_bind(bool API_Access = false);

/*!
 * @brief setup for searching a control
 */
void control_config_do_search(bool API_Access = false);

/**
 * @brief Unbinds the selected control
 */
bool control_config_remove_binding(int ctrl, selItem item, bool API_Access = false);

/**
 * @brief Clears all conflicting control bindings, except the selected control
 */
bool control_config_clear_other(int ctrl, bool API_Access = false);

/**
 * @brief Unbinds ALL controls
 * TODO: unbind axes and reset inversion
 */
bool control_config_clear_all(bool API_Access = false);

/**
 * @brief Reverts all bindings to their preset. If already default, cycle to the next presets.
 */
bool control_config_do_reset(bool cycle = true, bool API_Access = false);

/**
 * @brief Toggles a modifier on or off for a control binding
 */
bool control_config_toggle_modifier(int bit, int ctrl, bool API_Access = false);

/**
 * @brief Toggles inversion for the selected axis control
 */
bool control_config_toggle_invert(int ctrl, selItem item, bool API_Access = false);

/*!
 * @brief Performs a single undo opration, reverting the most recent change to bindings, if any
 */
void control_config_do_undo(bool API_Access = false);

/*!
 *@brief Runs every frame to check for input and binds the input if appropriate
 */
int control_config_bind_key_on_frame(int ctrl, selItem item, bool API_Access = false);

/*!
 *@brief Runs every frame to check for input. Returns the index of the item the key is bound to or -1 if nothing.
 */
int control_config_search_key_on_frame(bool API_Access = false);

/*!
 * Does a cursory conflict check, then accepts changes to the bindings, if any, and request the menu to close.
 */
bool control_config_accept(bool API_Access = false);

/*!
 * @brief Cancel configuration of controls, revert any changes, return to previous menu/game state
 */
void control_config_cancel_exit(bool API_Access = false);

/*!
 * @brief Copies all bindings within preset into Control_config
 */
void control_config_use_preset(CC_preset &preset);

/*!
 * @brief Search for a given preset by name and use it, if it exists.
 *
 * @returns TRUE    if the preset was found and selected
 * @returns FALSE   if the preset was not found, and the defaults were selected
 *
 * @details If the preset does not exist, then defaults are used instead.
 * @see control_config_use_preset()
 */
bool control_config_use_preset_by_name(const SCP_string &name);

/**
 * @brief Gets the currently used preset
 *
 * @param[in] invert_agnostic  If true, check the bindings in Control_config against the preset, ignoring inversion flags
 * 
 * @returns an iterator to the current preset, or
 * @returns ::iterator Control_config_presets.end() if current bindings are not in a preset
 * 
 * @note Similar to preset_find_duplicate, this function has additional logic in its search to ignore disabled controls
 */

SCP_vector<CC_preset>::iterator control_config_get_current_preset(bool invert_agnostic = false);

/*!
 * @brief Deletes the preset
 * 
 * @returns TRUE if successful
 * @returns FALSE if the preset is default or currently selected
 */
bool control_config_delete_preset(CC_preset preset);

/*!
 * @brief Clones a preset
 *
 * @returns TRUE if successful
 * @returns FALSE if the preset already exists with that name
 */
bool control_config_clone_preset(const CC_preset& preset, const SCP_string& name, bool overwrite);

/*!
 * @brief Saves a preset with the current controls
 *
 * @returns TRUE if successful
 * @returns FALSE if the preset already exists with that name
 */
bool control_config_create_new_preset(const SCP_string& name, bool overwrite);

/*!
 * Returns the IoActionId (index within Control_config[]) of a control bound to the given key
 *
 * @param[in]   key           The key combo to look for
 * @param[in]   find_override If true, return the IoActionId of a control that has this key as its default
 *
 * @return  -1 if the given key is NULL or not bound.
 *
 * @details If find_override is set to true, then this returns the index of the action
 */
int translate_key_to_index(const char *key, bool find_override=true);


/*!
 * @brief Given the system default key 'key', return the current control input(s) that is bound to that function.
 *
 * @param[in]   key     The default key combo (as cstring) to a certain control
 *
 * @return  The key combo (as cstring) currently bound to the control, or
 * @return  nullptr if the given key is not a system default key, or
 * @return  "None" if there is nothing bound to the control
 *
 * @details Both the 'key' and the return value are descriptive strings that can be displayed
 * directly to the user.  If 'key' isn't a real key, is not normally bound to anything,
 * or there is no key currently bound to the function, nullptr is returned.
 *
 * @note Not thread safe.  Has an internal buffer for the return value which is overwritten on each call.
 *
 * @note Uses CC_bind::textify for translations
 */
const char *translate_key(char *key);

/**
 * @brief Converts the specified key code to a human readable string, according to the selected locale and keyboard layout
 *
 * @param[in]   code    The key scancode to convert
 *
 * @return  The text representation of the code.
 *
 * @note Not thread safe.  Has an internal buffer for the return value which is overwritten on each call.
 *
 * @note The return value is translated according to localization settings
 */
const char *textify_scancode(int code);

/**
 * @brief Converts the specified key code to a human readable string.  Uses the English locale and QWERTY layout.
 * 
 * @param[in]    code   The key scancode to convert
 * 
 * @return The text representation of the code
 * 
 * @note Not thread safe.  Has an internal buffer for the return value which is overwritten on each call.
 */
const char *textify_scancode_universal(int code);

/**
 * @brief Creates a button label based on btn index and locale settings.
 * 
 * @param[in]   btn The btn to textify
 * 
 * @return The text representation of the btn
 * 
 * @note Not thread safe.  Has an internal buffer for the return value which is overwritten on each call.
 */
const char* textify_button(int btn);

	/*!
 * @brief Checks how long a control has been active
 *
 * @param[in] id The IoActionId of the control to check
 *
 * @returns Time, in milliseconds, that the control has been active.
 *
 * @note This function is only to be used on CC_CONTINOUS type controls, or it will trigger an assertion
 */
float check_control_timef(int id);

/**
 * @brief Wrapper for check_control_used. Allows the game to ignore the key if told to do so by the ignore-key SEXP.
 *
 * @param[in] id    The IoActionId of the control to check
 * @param[in] key   DO NOT USE OUTSIDE OF THE MAIN LOOP.  Key combination acquired from game_poll(), or -1 to check against a previous poll
 *
 * @returns 0 If the control was disabled by a mod, or
 * @returns 0 If locked by a script, or
 * @returns 0 If controls need to be ignored for multiplayer, or
 * @returns 0 If a script is handling the control execution, or
 * @returns 0 If the control wasn't used, or
 * @returns 1 If the control was used
 *
 * @details If the given control is CC_CONTINOUS, param key is effectively ignored and instead keyd_press[] is checked.
 * This function also checks against joy and mouse presses.
 */
int check_control(int id, int key = -1);

/**
 * @brief Gets the scaled reading for all control axes.
 *
 * @param[in]   frametime   Frametime used to scale the mouse axes
 * @param[out]  axis_v      Output array of all control axes, must have size of JOY_NUM_AXIS_ACTIONS
 *
 * @details
 * * Clamps output to stay within +/-JOY_AXIS_RANGE.
 * * Should multiple axes be bound to a control, their values are blended with a simple sum.  This is to save a bit of
 *   processing for speed.
 */
void control_get_axes_readings(int *axis_v, float frame_time);

/**
 * @brief Marks the given control (by IoActionId) as used
 *
 * @details Updates the ::used timestamp, triggers a script hook, and marks ::continous_ongoing as true
 */
void control_used(int id);

/**
 * @brief Runs a given controlbindings lua hook added in ccd.tbl
 *
 * @return Whether the control should be overridden
 */
bool control_run_lua(IoActionId id, int value);

/**
 * @brief Resets the cache for script evaluations for continuous buttons. Should be called once per frame.
 *
*/
void control_reset_lua_cache();

/**
 * @brief Registers a new hook for the keybinding action system.
 * Parameters set if it is a normal or an override function, as well as if it is enabled by default or needs to be enabled manually each mission
 */
void control_register_hook(IoActionId id, const luacpp::LuaFunction& hook, bool is_override, bool enabledByDefault);

/**
 * @brief Enables or disables the respective Lua hook
 */
void control_enable_hook(IoActionId id, bool enable);

/**
 * @brief Resets every hooks enable state to the tabled value. Call on Mission start
 */
void control_reset_hook();

/**
 * @brief Clears the bindings of all controls
 */
void control_config_clear();

/**
 * @brief debug function used to indicate number of controls checked
 */
void control_check_indicate();

/**
 * @brief Clears the used timestamp of all controls
 */
void control_config_clear_used_status();

/**
 * @brief Applies sensitivity multiplier and deadzone histerisis to the raw axis value
 *
 * @param[in] raw  The raw axis value to transform
 *
 * @return The transformed value
 */
int joy_get_scaled_reading(int raw);

/**
 * Lookups the given stringified enum to find its value
 * @return -1 if not found, or
 * @return IoActionId if found
 */
int ActionToVal(const char * str);

/**
 * Reverse lookups the IoActionId to get its stringified name
 * @return  Pointer to the stringified name of the action, or
 * @return  nullptr if not found
 */
const char * ValToAction(IoActionId id);

/**
 * Reverse lookups the IoActionId to get its stringified name
 * @return Pointer to the stringified name of the action, or
 * @return nullptr if not found, or invalid id
 */
const char * ValToAction(int id);

/**
 * Lookups the given stringified enum to find its value
 */
CID CIDToVal(const char * str);

/**
 * Reverse lookups the CID to get its stringified name
 * @return  The stringified name of the CID, or
 * @return  "NONE" if not found
 */
SCP_string ValToCID(CID id);

/**
 * Reverse lookups the CID to get its stringified name
 * @return The stringified name of the CID, or
 * @return "NONE" if not found, or invalid id
 */
SCP_string ValToCID(int id);
/**
 * Lookups the given stringified enum to find its value
 */
char CCFToVal(const char * str);

/**
 * Constructs a enum string from the CCF_FLAGS
 * @return The stringified name of the CCF, or
 * @return "NONE" if not found, or invalid id
 */
SCP_string ValToCCF(char id);

/**
 * Lookups the given stringified enum to find its value
 */
short InputToVal(CID cid, const char * str);

/**
 * Constructs a enumstring from the input binding, depending on the CID
 *
 * @return The stringified name of the input, or
 * @return "NONE" if not found, or invalid CC_bind
 *
 * @note This requires a CCB due to the way things are encoded
 */
SCP_string ValToInput(const CC_bind &bind);

/**
 * Lookups the given stringified enum to find its btn value
 */
short MouseToVal(const char * str);

/**
 * Constructs a enum string from the mouse input
 *
 * @return The stringified name of the input, or
 * @return "NONE" if not found, or invalid CC_bind
 * TODO XSTR
 *
 * @note This requires a CCB due to the way things are encoded
 */
SCP_string ValToMouse(const CC_bind &bind);

/**
 * Lookups the given stringified enum to find its btn value
 */
short KeyboardToVal(const char * str);

/**
 * Constructs an enum string from the key binding
 *
 * @return The stringified name of the input, or
 * @return "NONE" f not found, or invalid CC_bind
 *
 * @note This requires a CCB due to the way things are encoded
 * TODO XSTR
 */
SCP_string ValToKeyboard(const CC_bind &bind);

/**
 * Lookups the given stringified enum to find its value
 */
short JoyToVal(const char * str);

/**
 * Constructs a enum string from the Joystick input
 *
 * @return The stringified name of the input, or
 * @return "NONE" if not found, or invalid CC_bind
 *
 * @note This requires a CCB due to the way things are encoded
 * TODO XSTR
 */
SCP_string ValToJoy(const CC_bind &bind);

/**
 * Lookups the given string to find its tab value
 */
char CCTabToVal(const char *str);

/**
 * Reverse lookups the given tab in mCCTabToVal to retrieve the string name
 *
 * @return The stringified name of the input, or
 * @return nullptr if not found, or invalid tab
 *
 */
SCP_string ValToCCTab(char tab);

/**
 * Lookups the given string to find its CC_type value. =
 */
CC_type CCTypeToVal(const char *str);

/**
 * Reverse lookups the given CC_type to retrieve the string name

 */
SCP_string ValToCCType(CC_type type);

#endif
