/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program 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 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#ifndef COMMON_HARDWARE_KEY_H
#define COMMON_HARDWARE_KEY_H

#include "common/scummsys.h"

#include "common/array.h"
#include "common/events.h"
#include "common/keyboard.h"
#include "common/str.h"

namespace Common {

typedef uint32 HardwareInputCode;

enum HardwareInputType {
	/** Empty / invalid input type */
	kHardwareInputTypeInvalid,
	/** Keyboard input that sends -up and -down events */
	kHardwareInputTypeKeyboard,
	/** Mouse input that sends -up and -down events */
	kHardwareInputTypeMouse,
	/** Joystick input that sends -up and -down events */
	kHardwareInputTypeJoystickButton,
	/** Joystick input that sends "analog" values */
	kHardwareInputTypeJoystickHalfAxis,
	/** Input that sends single events */
	kHardwareInputTypeCustom
};

/**
* Describes an available hardware input
*/
struct HardwareInput {
	/** unique id used for saving/loading to config */
	String id;

	/** Human readable description */
	U32String description;

	/** Type tag */
	HardwareInputType type;

	/**
	 * A platform specific unique identifier for an input event
	 * generated when this input is triggered.
	 * This is only relevant when type == kHardwareInputTypeGeneric
	 */
	HardwareInputCode inputCode;

	/**
	 * The KeyState that is generated by the back-end
	 * when this hardware key is pressed.
	 * This is only relevant when type == kHardwareInputTypeKeyboard
	 */
	KeyState key;

	HardwareInput()
		: inputCode(0), type(kHardwareInputTypeInvalid) { }

	static HardwareInput createCustom(const String &i, HardwareInputCode ic, const U32String &desc) {
		return createSimple(kHardwareInputTypeCustom, i, ic, desc);
	}

	static HardwareInput createKeyboard(const String &i, KeyState ky, const U32String &desc) {
		HardwareInput hardwareInput;
		hardwareInput.id = i;
		hardwareInput.description = desc;
		hardwareInput.type = kHardwareInputTypeKeyboard;
		hardwareInput.inputCode = 0;
		hardwareInput.key = ky;
		return hardwareInput;
	}

	static HardwareInput createJoystickButton(const String &i, uint8 button, const U32String &desc) {
		return createSimple(kHardwareInputTypeJoystickButton, i, button, desc);
	}

	static HardwareInput createJoystickHalfAxis(const String &i, uint8 axis, bool positiveHalf, const U32String &desc) {
		return createSimple(kHardwareInputTypeJoystickHalfAxis, i, axis * 2 + (positiveHalf ? 1 : 0), desc);
	}

	static HardwareInput createMouse(const String &i, uint8 button, const U32String &desc) {
		return createSimple(kHardwareInputTypeMouse, i, button, desc);
	}

private:
	static HardwareInput createSimple(HardwareInputType type, const String &i, HardwareInputCode ic, const U32String &desc) {
		HardwareInput hardwareInput;
		hardwareInput.id = i;
		hardwareInput.description = desc;
		hardwareInput.type = type;
		hardwareInput.inputCode = ic;
		return hardwareInput;
	}
};

/**
 * Entry in a static table of custom backend hardware inputs
 */
struct HardwareInputTableEntry {
	const char *hwId;
	HardwareInputCode code;
	const char *desc;

	static const HardwareInputTableEntry *findWithCode(const HardwareInputTableEntry *_entries, HardwareInputCode code) {
		for (const HardwareInputTableEntry *hw = _entries;  hw->hwId; hw++) {
			if (hw->code == code) {
				return hw;
			}
		}
		return nullptr;
	}

	static const HardwareInputTableEntry *findWithId(const HardwareInputTableEntry *_entries, const String &id) {
		for (const HardwareInputTableEntry *hw = _entries;  hw->hwId; hw++) {
			if (id.equals(hw->hwId)) {
				return hw;
			}
		}
		return nullptr;
	}
};

/**
 * Entry in a static table of available non-modifier keys
 */
struct KeyTableEntry {
	const char *hwId;
	KeyCode keycode;
	const char *desc;
};

/**
 * Entry in a static table of available key modifiers
 */
struct ModifierTableEntry {
	byte flag;
	const char *id;
	const char *desc;
};

enum AxisType {
	/** An axis that sends "analog" values from JOYAXIS_MIN to JOYAXIS_MAX. e.g. a gamepad stick axis */
	kAxisTypeFull,
	/** An axis that sends "analog" values from 0 to JOYAXIS_MAX. e.g. a gamepad trigger */
	kAxisTypeHalf
};

struct AxisTableEntry {
	const char *hwId;
	HardwareInputCode code;
	AxisType type;
	const char *desc;

	static const AxisTableEntry *findWithCode(const AxisTableEntry *_entries, HardwareInputCode code) {
		for (const AxisTableEntry *hw = _entries;  hw->hwId; hw++) {
			if (hw->code == code) {
				return hw;
			}
		}
		return nullptr;
	}

	static const AxisTableEntry *findWithId(const AxisTableEntry *_entries, const String &id) {
		for (const AxisTableEntry *hw = _entries;  hw->hwId; hw++) {
			if (id.equals(hw->hwId)) {
				return hw;
			}
		}
		return nullptr;
	}
};


/**
 * Interface for querying information about a hardware input device
 */
class HardwareInputSet {
public:
	virtual ~HardwareInputSet();

	/**
	 * Retrieve a hardware input description from an unique identifier
	 *
	 * In case no input was found with the specified id, an empty
	 * HardwareInput structure is return with the type set to
	 * kHardwareInputTypeInvalid.
	 */
	virtual HardwareInput findHardwareInput(const String &id) const = 0;

	/**
	 * Retrieve a hardware input description from one of the events
	 * produced when the input is triggered.
	 *
	 * In case the specified event is not produced by this device,
	 * an empty HardwareInput structure is return with the type set to
	 * kHardwareInputTypeInvalid.
	 */
	virtual HardwareInput findHardwareInput(const Event &event) const = 0;
};

/**
 * A keyboard input device
 *
 * Describes the keys and key + modifiers combinations as HardwareInputs
 */
class KeyboardHardwareInputSet : public HardwareInputSet {
public:
	KeyboardHardwareInputSet(const KeyTableEntry *keys, const ModifierTableEntry *modifiers);

	// HardwareInputSet API
	HardwareInput findHardwareInput(const String &id) const override;
	HardwareInput findHardwareInput(const Event &event) const override;

	/** Transform a keystate into a canonical form that can be used to unambiguously identify the keypress */
	static KeyState normalizeKeyState(const KeyState &keystate);

private:
	const KeyTableEntry *_keys;
	const ModifierTableEntry *_modifiers;
};

/**
 * A mouse input device
 *
 * Describes the mouse buttons
 */
class MouseHardwareInputSet : public HardwareInputSet {
public:
	MouseHardwareInputSet(const HardwareInputTableEntry *buttonEntries);

	// HardwareInputSet API
	HardwareInput findHardwareInput(const String &id) const override;
	HardwareInput findHardwareInput(const Event &event) const override;

private:
	const HardwareInputTableEntry *_buttonEntries;
};

/**
 * A joystick input device
 */
class JoystickHardwareInputSet : public HardwareInputSet {
public:
	JoystickHardwareInputSet(const HardwareInputTableEntry *buttonEntries, const AxisTableEntry *axisEntries);

	// HardwareInputSet API
	HardwareInput findHardwareInput(const String &id) const override;
	HardwareInput findHardwareInput(const Event &event) const override;

private:
	const HardwareInputTableEntry *_buttonEntries;
	const AxisTableEntry *_axisEntries;
};

/**
 * A custom backend input device
 *
 * @todo This is currently unused. Perhaps it should be removed.
 */
class CustomHardwareInputSet : public HardwareInputSet {
public:
	CustomHardwareInputSet(const HardwareInputTableEntry *hardwareEntries);

	// HardwareInputSet API
	HardwareInput findHardwareInput(const String &id) const override;
	HardwareInput findHardwareInput(const Event &event) const override;

private:
	const HardwareInputTableEntry *_hardwareEntries;
};

/**
 * A composite input device that delegates to a set of actual input devices.
 */
class CompositeHardwareInputSet : public HardwareInputSet {
public:
	~CompositeHardwareInputSet() override;

	// HardwareInputSet API
	HardwareInput findHardwareInput(const String &id) const override;
	HardwareInput findHardwareInput(const Event &event) const override;

	/**
	 * Add an input device to this composite device
	 *
	 * Takes ownership of the hardware input set
	 */
	void addHardwareInputSet(HardwareInputSet *hardwareInputSet);

private:
	Array<HardwareInputSet *> _inputSets;
};

/** A standard set of keyboard keys */
extern const KeyTableEntry defaultKeys[];

/** A standard set of keyboard modifiers */
extern const ModifierTableEntry defaultModifiers[];

/** A standard set of mouse buttons */
extern const HardwareInputTableEntry defaultMouseButtons[];

/** A standard set of joystick buttons based on the ScummVM event model */
extern const HardwareInputTableEntry defaultJoystickButtons[];

/** A standard set of joystick axes based on the ScummVM event model */
extern const AxisTableEntry defaultJoystickAxes[];

} // End of namespace Common

#endif // #ifndef COMMON_HARDWARE_KEY_H
