// license:BSD-3-Clause
// copyright-holders:Brad Hughes
//============================================================
//
//  input_uwp.cpp - UWP input implementation
//
//============================================================

#include "input_module.h"
#include "modules/osdmodule.h"

#if defined(OSD_UWP)

#include <agile.h>
#include <ppltasks.h>
#include <collection.h>
#undef interface

// MAME headers
#include "emu.h"
#include "uiinput.h"
#include "strconv.h"

// MAMEOS headers
#include "winmain.h"
#include "input_common.h"
#include "input_windows.h"

#define UWP_BUTTON_COUNT 32

using namespace concurrency;
using namespace Windows::UI::Core;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::Gaming::Input;

//============================================================
//  UWP Base device/module implementation
//============================================================

//============================================================
//  UwpInputDevice - base class for implementing an input
//    device in C++/CX. To be used with uwp_input_device
//============================================================

private ref class UwpInputDevice
{
private:
	running_machine & m_machine;
	std::string m_name;
	std::string m_id;
	input_device_class m_devclass;
	input_module & m_module;
	input_device *m_inputdevice;

internal:
	UwpInputDevice(running_machine &machine, const char *name, const char *id, input_device_class deviceclass, input_module &module)
		: m_machine(machine),
		m_name(name),
		m_id(id),
		m_devclass(deviceclass),
		m_module(module),
		m_inputdevice(nullptr)
	{
	}

	property running_machine & Machine
	{
		running_machine & get() { return m_machine; }
	}

	property const std::string & Name
	{
		const std::string & get() { return m_name; }
	}

	property const std::string & Id
	{
		const std::string & get() { return m_id; }
	}

	property input_device_class DeviceClass
	{
		input_device_class get() { return m_devclass; }
	}

	property input_module & Module
	{
		input_module & get() { return m_module; }
	}

	property input_device* InputDevice
	{
		input_device* get() { return m_inputdevice; }
		void set(input_device* value) { m_inputdevice = value; }
	}

	virtual void Poll()
	{
	}

	virtual void Reset()
	{
	}
};

//============================================================
//  uwp_input_device - a device that can be used to wrap a
//    C++/CX ref class for an input device implementation
//============================================================

class uwp_input_device : public device_info
{
private:
	UwpInputDevice ^m_wrapped_device;

public:
	uwp_input_device(UwpInputDevice ^device)
		: device_info(device->Machine, device->Name.c_str(), device->Id.c_str(), device->DeviceClass, device->Module),
		m_wrapped_device(device)
	{
	}

	void poll() override
	{
		m_wrapped_device->Poll();
	}

	void reset() override
	{
		m_wrapped_device->Reset();
	}
};

//============================================================
//  UwpInputModule - a base class that can be used to
//    implement an input module with a C++/CX class.
//    normally used with uwp_wininput_module
//============================================================

class uwp_input_module;

private ref class UwpInputModule
{
private:
	const std::string m_type;
	const std::string m_name;
	uwp_input_module *m_module;

internal:
	UwpInputModule(const char *type, const char *name)
		: m_type(type),
		m_name(name),
		m_module(nullptr)
	{
	}

	property const std::string & Type
	{
		const std::string & get() { return m_type; }
	}

	property const std::string & Name
	{
		const std::string & get() { return m_name; }
	}

	property uwp_input_module * NativeModule
	{
		uwp_input_module * get() { return m_module; }
		void set(uwp_input_module * value) { m_module = value; }
	}

	virtual void input_init(running_machine &machine)
	{
	}
};

//============================================================
//  uwp_input_module - an input module that can be
//    used to create an input module with a C++/CX ref class
//============================================================

class uwp_input_module : public wininput_module
{
private:
	UwpInputModule^ m_refmodule;

public:
	uwp_input_module(UwpInputModule^ refmodule)
		: wininput_module(refmodule->Type.c_str(), refmodule->Name.c_str()),
		m_refmodule(refmodule)
	{
		refmodule->NativeModule = this;
	}

	void input_init(running_machine &machine) override
	{
		m_refmodule->input_init(machine);
	}
};

//============================================================
//  UWP Keyboard Implementation
//============================================================

//============================================================
//  UwpKeyboardDevice
//============================================================

private ref class UwpKeyboardDevice : public UwpInputDevice
{
private:
	keyboard_state keyboard;
	Platform::Agile<CoreWindow> m_coreWindow;
	std::mutex m_state_lock;

internal:
	UwpKeyboardDevice(Platform::Agile<CoreWindow> coreWindow, running_machine& machine, char *name, const char *id, input_module &module)
		: UwpInputDevice(machine, name, id, DEVICE_CLASS_KEYBOARD, module),
		keyboard({{0}}),
		m_coreWindow(coreWindow)
	{
		coreWindow->Dispatcher->AcceleratorKeyActivated += ref new TypedEventHandler<CoreDispatcher^, AcceleratorKeyEventArgs^>(this, &UwpKeyboardDevice::OnAcceleratorKeyActivated);
		coreWindow->KeyDown += ref new TypedEventHandler<CoreWindow^, KeyEventArgs^>(this, &UwpKeyboardDevice::OnKeyDown);
		coreWindow->KeyUp += ref new TypedEventHandler<CoreWindow^, KeyEventArgs^>(this, &UwpKeyboardDevice::OnKeyUp);
		coreWindow->CharacterReceived += ref new TypedEventHandler<CoreWindow^, CharacterReceivedEventArgs^>(this, &UwpKeyboardDevice::OnCharacterReceived);
	}

	void Reset() override
	{
		std::lock_guard<std::mutex> scope_lock(m_state_lock);
		memset(&keyboard, 0, sizeof(keyboard));
	}

	void Configure()
	{
		keyboard_trans_table &table = keyboard_trans_table::instance();

		// populate it indexed by the scancode
		for (int keynum = KEY_UNKNOWN + 1; keynum < MAX_KEYS; keynum++)
		{
			input_item_id itemid = table.map_di_scancode_to_itemid(keynum);
			const char *keyname = table.ui_label_for_mame_key(itemid);

			char temp[256];
			if (keyname == nullptr)
			{
				snprintf(temp, std::size(temp), "Scan%03d", keynum);
				keyname = temp;
			}

			// add the item to the device
			this->InputDevice->add_item(keyname, itemid, generic_button_get_state<std::uint8_t>, &keyboard.state[keynum]);
		}
	}

	void OnKeyDown(CoreWindow^ win, KeyEventArgs^ args)
	{
		std::lock_guard<std::mutex> scope_lock(m_state_lock);
		CorePhysicalKeyStatus status = args->KeyStatus;
		int discancode = (status.ScanCode & 0x7f) | (status.IsExtendedKey ? 0x80 : 0x00);
		keyboard.state[discancode] = 0x80;
	}

	void OnKeyUp(CoreWindow^ win, KeyEventArgs^ args)
	{
		std::lock_guard<std::mutex> scope_lock(m_state_lock);
		CorePhysicalKeyStatus status = args->KeyStatus;
		int discancode = (status.ScanCode & 0x7f) | (status.IsExtendedKey ? 0x80 : 0x00);
		keyboard.state[discancode] = 0;
	}

	void OnCharacterReceived(CoreWindow ^sender, CharacterReceivedEventArgs ^args)
	{
		this->Machine.ui_input().push_char_event(osd_common_t::s_window_list.front()->target(), args->KeyCode);
	}

	void OnAcceleratorKeyActivated(CoreDispatcher ^sender, AcceleratorKeyEventArgs ^args)
	{
		std::lock_guard<std::mutex> scope_lock(m_state_lock);
		auto eventType = args->EventType;
		if (eventType == CoreAcceleratorKeyEventType::SystemKeyDown ||
			eventType == CoreAcceleratorKeyEventType::SystemKeyUp)
		{
			CorePhysicalKeyStatus status = args->KeyStatus;
			int discancode = (status.ScanCode & 0x7f) | (status.IsExtendedKey ? 0x80 : 0x00);
			keyboard.state[discancode] =
				eventType == CoreAcceleratorKeyEventType::SystemKeyDown ? 0x80 : 0;
		}
	}
};

//============================================================
//  UwpKeyboardModule
//============================================================

private ref class UwpKeyboardModule : public UwpInputModule
{
private:
	running_machine *m_machine;

internal:
	UwpKeyboardModule()
		: UwpInputModule(OSD_KEYBOARDINPUT_PROVIDER, "uwp")
	{
	}

	void input_init(running_machine &machine) override
	{
		auto first_window = std::static_pointer_cast<uwp_window_info>(osd_common_t::s_window_list.front());
		auto coreWindow = first_window->platform_window();

		// allocate the UWP implementation of the device object
		UwpKeyboardDevice ^refdevice = ref new UwpKeyboardDevice(coreWindow, machine, "UWP Keyboard 1", "UWP Keyboard 1", *this->NativeModule);

		// Allocate the wrapper and add it to the list
		auto created_devinfo = std::make_unique<uwp_input_device>(refdevice);
		uwp_input_device *devinfo = NativeModule->devicelist()->add_device<uwp_input_device>(machine, std::move(created_devinfo));

		// Give the UWP implementation a handle to the input_device
		refdevice->InputDevice = devinfo->device();

		// Configure the device
		refdevice->Configure();
	}
};

//============================================================
//  uwp_keyboard_module
//============================================================

class uwp_keyboard_module : public uwp_input_module
{
public:
	uwp_keyboard_module()
		: uwp_input_module(ref new UwpKeyboardModule())
	{
	}
};

// default axis names
static const char *const uwp_axis_name[] =
{
	"LSX",
	"LSY",
	"RSX",
	"RSY"
};

static const input_item_id uwp_axis_ids[] =
{
	ITEM_ID_XAXIS,
	ITEM_ID_YAXIS,
	ITEM_ID_RXAXIS,
	ITEM_ID_RYAXIS
};

struct gamepad_state
{
	BYTE    buttons[UWP_BUTTON_COUNT];
	LONG    left_trigger;
	LONG    right_trigger;
	LONG    left_thumb_x;
	LONG    left_thumb_y;
	LONG    right_thumb_x;
	LONG    right_thumb_y;
};

// Maps different UWP GameControllerButtonLabels to a halfway-sane input_item_id in many cases
static input_item_id buttonlabel_to_itemid[] =
{
	input_item_id::ITEM_ID_INVALID,       // GameControllerButtonLabel::None
	input_item_id::ITEM_ID_SELECT,        // GameControllerButtonLabel::XboxBack
	input_item_id::ITEM_ID_START,         // GameControllerButtonLabel::XboxStart
	input_item_id::ITEM_ID_START,         // GameControllerButtonLabel::XboxMenu
	input_item_id::ITEM_ID_SELECT,        // GameControllerButtonLabel::XboxView
	input_item_id::ITEM_ID_HAT1UP,        // GameControllerButtonLabel::XboxUp
	input_item_id::ITEM_ID_HAT1DOWN,      // GameControllerButtonLabel::XboxDown
	input_item_id::ITEM_ID_HAT1LEFT,      // GameControllerButtonLabel::XboxLeft
	input_item_id::ITEM_ID_HAT1RIGHT,     // GameControllerButtonLabel::XboxRight
	input_item_id::ITEM_ID_BUTTON1,       // GameControllerButtonLabel::XboxA
	input_item_id::ITEM_ID_BUTTON2,       // GameControllerButtonLabel::XboxB
	input_item_id::ITEM_ID_BUTTON3,       // GameControllerButtonLabel::XboxX
	input_item_id::ITEM_ID_BUTTON4,       // GameControllerButtonLabel::XboxY
	input_item_id::ITEM_ID_BUTTON5,       // GameControllerButtonLabel::XboxLeftBumper
	input_item_id::ITEM_ID_ZAXIS,         // GameControllerButtonLabel::XboxLeftTrigger
	input_item_id::ITEM_ID_BUTTON7,       // GameControllerButtonLabel::XboxLeftStickButton
	input_item_id::ITEM_ID_BUTTON6,       // GameControllerButtonLabel::XboxRightBumper
	input_item_id::ITEM_ID_RZAXIS,        // GameControllerButtonLabel::XboxRightTrigger
	input_item_id::ITEM_ID_BUTTON8,       // GameControllerButtonLabel::XboxRightStickButton
	input_item_id::ITEM_ID_BUTTON9,       // GameControllerButtonLabel::XboxPaddle1
	input_item_id::ITEM_ID_BUTTON10,      // GameControllerButtonLabel::XboxPaddle2
	input_item_id::ITEM_ID_BUTTON11,      // GameControllerButtonLabel::XboxPaddle3
	input_item_id::ITEM_ID_BUTTON12,      // GameControllerButtonLabel::XboxPaddle4
	input_item_id::ITEM_ID_INVALID,       // GameControllerButtonLabel_Mode
	input_item_id::ITEM_ID_SELECT,        // GameControllerButtonLabel_Select
	input_item_id::ITEM_ID_START,         // GameControllerButtonLabel_Menu
	input_item_id::ITEM_ID_SELECT,        // GameControllerButtonLabel_View
	input_item_id::ITEM_ID_SELECT,        // GameControllerButtonLabel_Back
	input_item_id::ITEM_ID_START,         // GameControllerButtonLabel_Start
	input_item_id::ITEM_ID_OTHER_SWITCH,  // GameControllerButtonLabel_Options
	input_item_id::ITEM_ID_OTHER_SWITCH,  // GameControllerButtonLabel_Share
	input_item_id::ITEM_ID_OTHER_SWITCH,  // GameControllerButtonLabel_Up
	input_item_id::ITEM_ID_OTHER_SWITCH,  // GameControllerButtonLabel_Down
	input_item_id::ITEM_ID_OTHER_SWITCH,  // GameControllerButtonLabel_Left
	input_item_id::ITEM_ID_OTHER_SWITCH,  // GameControllerButtonLabel_Right
	input_item_id::ITEM_ID_BUTTON1,       // GameControllerButtonLabel_LetterA
	input_item_id::ITEM_ID_BUTTON2,       // GameControllerButtonLabel_LetterB
	input_item_id::ITEM_ID_OTHER_SWITCH,  // GameControllerButtonLabel_LetterC
	input_item_id::ITEM_ID_OTHER_SWITCH,  // GameControllerButtonLabel_LetterL
	input_item_id::ITEM_ID_OTHER_SWITCH,  // GameControllerButtonLabel_LetterR
	input_item_id::ITEM_ID_BUTTON3,       // GameControllerButtonLabel_LetterX
	input_item_id::ITEM_ID_BUTTON4,       // GameControllerButtonLabel_LetterY
	input_item_id::ITEM_ID_OTHER_SWITCH,  // GameControllerButtonLabel_LetterZ
	input_item_id::ITEM_ID_OTHER_SWITCH,  // GameControllerButtonLabel_Cross
	input_item_id::ITEM_ID_OTHER_SWITCH,  // GameControllerButtonLabel_Circle
	input_item_id::ITEM_ID_OTHER_SWITCH,  // GameControllerButtonLabel_Square
	input_item_id::ITEM_ID_OTHER_SWITCH,  // GameControllerButtonLabel_Triangle
	input_item_id::ITEM_ID_BUTTON5,       // GameControllerButtonLabel_LeftBumper
	input_item_id::ITEM_ID_ZAXIS,         // GameControllerButtonLabel_LeftTrigger
	input_item_id::ITEM_ID_BUTTON7,       // GameControllerButtonLabel_LeftStickButton
	input_item_id::ITEM_ID_OTHER_SWITCH,  // GameControllerButtonLabel_Left1
	input_item_id::ITEM_ID_OTHER_SWITCH,  // GameControllerButtonLabel_Left2
	input_item_id::ITEM_ID_OTHER_SWITCH,  // GameControllerButtonLabel_Left3
	input_item_id::ITEM_ID_BUTTON6,       // GameControllerButtonLabel_RightBumper
	input_item_id::ITEM_ID_RZAXIS,        // GameControllerButtonLabel_RightTrigger
	input_item_id::ITEM_ID_BUTTON8,       // GameControllerButtonLabel_RightStickButton
	input_item_id::ITEM_ID_OTHER_SWITCH,  // GameControllerButtonLabel_Right1
	input_item_id::ITEM_ID_OTHER_SWITCH,  // GameControllerButtonLabel_Right2
	input_item_id::ITEM_ID_OTHER_SWITCH,  // GameControllerButtonLabel_Right3
	input_item_id::ITEM_ID_BUTTON9,       // GameControllerButtonLabel_Paddle1
	input_item_id::ITEM_ID_BUTTON10,      // GameControllerButtonLabel_Paddle2
	input_item_id::ITEM_ID_BUTTON11,      // GameControllerButtonLabel_Paddle3
	input_item_id::ITEM_ID_BUTTON12,      // GameControllerButtonLabel_Paddle4
	input_item_id::ITEM_ID_OTHER_SWITCH,  // GameControllerButtonLabel_Plus
	input_item_id::ITEM_ID_OTHER_SWITCH,  // GameControllerButtonLabel_Minus
	input_item_id::ITEM_ID_OTHER_SWITCH,  // GameControllerButtonLabel_DownLeftArrow
	input_item_id::ITEM_ID_OTHER_SWITCH,  // GameControllerButtonLabel_DialLeft
	input_item_id::ITEM_ID_OTHER_SWITCH,  // GameControllerButtonLabel_DialRight
	input_item_id::ITEM_ID_OTHER_SWITCH,  // GameControllerButtonLabel_Suspension
};

//============================================================
//  UwpJoystickDevice
//============================================================

private ref class UwpJoystickDevice : public UwpInputDevice
{
private:
	Gamepad ^m_pad;
	bool m_configured;
	gamepad_state state;

internal:
	UwpJoystickDevice(Gamepad^ pad, running_machine &machine, const char *name, const char *id, input_module &module)
		: UwpInputDevice(machine, name, id, DEVICE_CLASS_JOYSTICK, module),
		m_pad(pad),
		m_configured(false),
		state({0})
	{}

	void Poll() override
	{
		// If the device hasn't been configured, don't poll
		if (!m_configured)
			return;

		GamepadReading reading = m_pad->GetCurrentReading();

		for (int butnum = 0; butnum < UWP_BUTTON_COUNT; butnum++)
		{
			GamepadButtons currentButton = GamepadButtons(1 << butnum);
			state.buttons[butnum] = (reading.Buttons & currentButton) != GamepadButtons::None ? 0xFF : 0;
		}

		// Now grab the axis values
		// Each of the thumbstick axis members is a signed value between -32768 and 32767 describing the position of the thumbstick
		// However, the Y axis values are inverted from what MAME expects, so negate the value
		state.left_thumb_x = normalize_absolute_axis(reading.LeftThumbstickX, -1, 1);
		state.left_thumb_y = -normalize_absolute_axis(reading.LeftThumbstickY, -1, 1);
		state.right_thumb_x = normalize_absolute_axis(reading.RightThumbstickX, -1, 1);
		state.right_thumb_y = -normalize_absolute_axis(reading.RightThumbstickY, -1, 1);

		// Get the trigger values
		state.left_trigger = normalize_absolute_axis(reading.LeftTrigger, 0.0, 1.0);
		state.right_trigger = normalize_absolute_axis(reading.RightTrigger, 0.0, 1.0);

		// For the UI, triggering UI_CONFIGURE is odd. It requires a EVENT_CHAR first
		static constexpr int menuhotkey = (int)(GamepadButtons::View | GamepadButtons::X);
		if (((int)reading.Buttons & menuhotkey) == menuhotkey)
		{
			ui_event uiev;
			memset(&uiev, 0, sizeof(uiev));
			uiev.event_type = ui_event::IME_CHAR;
			this->Machine.ui_input().push_event(uiev);
		}
	}

	void Reset() override
	{
		memset(&state, 0, sizeof(state));
	}

	void Configure()
	{
		// If the device has already been configured, don't do it again
		if (m_configured)
			return;

		GamepadReading r = m_pad->GetCurrentReading();

		// Add the axes
		for (int axisnum = 0; axisnum < 4; axisnum++)
		{
			this->InputDevice->add_item(
				uwp_axis_name[axisnum],
				uwp_axis_ids[axisnum],
				generic_axis_get_state<LONG>,
				&state.left_thumb_x + axisnum);
		}

		// populate the buttons
		for (int butnum = 0; butnum < UWP_BUTTON_COUNT; butnum++)
		{
			GamepadButtons button = GamepadButtons(1 << butnum);
			auto label = m_pad->GetButtonLabel(button);
			if (label != GameControllerButtonLabel::None)
			{
				std::string desc = osd::text::from_wstring(label.ToString()->Data());
				this->InputDevice->add_item(
					desc.c_str(),
					buttonlabel_to_itemid[static_cast<int>(label)],
					generic_button_get_state<BYTE>,
					&state.buttons[butnum]);
			}
		}

		this->InputDevice->add_item(
			"Left Trigger",
			ITEM_ID_ZAXIS,
			generic_axis_get_state<LONG>,
			&state.left_trigger);

		this->InputDevice->add_item(
			"Right Trigger",
			ITEM_ID_RZAXIS,
			generic_axis_get_state<LONG>,
			&state.right_trigger);

		m_configured = true;
	}
};

//============================================================
//  UwpJoystickModule
//============================================================

private ref class UwpJoystickModule : public UwpInputModule
{
private:
	boolean m_joysticks_discovered;

internal:
	UwpJoystickModule()
		: UwpInputModule(OSD_JOYSTICKINPUT_PROVIDER, "uwp"),
		m_joysticks_discovered(false)
	{
	}

	void input_init(running_machine &machine) override
	{
		PerformGamepadDiscovery();

		auto pads = Gamepad::Gamepads;

		int padindex = 0;
		std::for_each(begin(pads), end(pads), [&](Gamepad^ pad)
		{
			uwp_input_device *devinfo;

			std::ostringstream namestream;
			namestream << "UWP Gamepad " << (padindex + 1);

			auto name = namestream.str();

			// allocate the UWP implementation of the device object
			UwpJoystickDevice ^refdevice = ref new UwpJoystickDevice(pad, machine, name.c_str(), name.c_str(), *this->NativeModule);

			// Allocate the wrapper and add it to the list
			auto created_devinfo = std::make_unique<uwp_input_device>(refdevice);
			devinfo = NativeModule->devicelist()->add_device<uwp_input_device>(machine, std::move(created_devinfo));

			// Give the UWP implementation a handle to the input_device
			refdevice->InputDevice = devinfo->device();

			// Configure the device
			refdevice->Configure();

			padindex++;
		});
	}

private:
	void PerformGamepadDiscovery()
	{
		Gamepad::GamepadAdded += ref new EventHandler<Gamepad ^>(this, &UwpJoystickModule::OnGamepadAdded);
		auto start = std::chrono::system_clock::now();

		// We need to pause a bit and pump events so gamepads get discovered
		while (std::chrono::system_clock::now() - start < std::chrono::milliseconds(1000))
			CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);

		m_joysticks_discovered = true;
	}

	void OnGamepadAdded(Platform::Object ^sender, Gamepad ^pad)
	{
		if (m_joysticks_discovered)
		{
			osd_printf_error("Input: UWP Compatible %s gamepad plugged in AFTER discovery complete!\n", pad->IsWireless ? "Wireless" : "Wired");
		}
		else
		{
			osd_printf_verbose("Input: UWP Compatible %s gamepad discovered.\n", pad->IsWireless ? "Wireless" : "Wired");
		}
	}
};

//============================================================
//  uwp_joystick_module
//============================================================

class uwp_joystick_module : public uwp_input_module
{
public:
	uwp_joystick_module()
		: uwp_input_module(ref new UwpJoystickModule())
	{
	}
};

#else
MODULE_NOT_SUPPORTED(uwp_keyboard_module, OSD_KEYBOARDINPUT_PROVIDER, "uwp")
MODULE_NOT_SUPPORTED(uwp_joystick_module, OSD_JOYSTICKINPUT_PROVIDER, "uwp")
#endif

MODULE_DEFINITION(KEYBOARDINPUT_UWP, uwp_keyboard_module)
MODULE_DEFINITION(JOYSTICKINPUT_UWP, uwp_joystick_module)
