// license:BSD-3-Clause
// copyright-holders:Aaron Giles, Brad Hughes
//============================================================
//
//  input_win32.cpp - Win32 input implementation
//
//============================================================

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

#if defined(OSD_WINDOWS)

// standard windows headers
#include <windows.h>
#include <tchar.h>
#undef interface

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

// MAMEOS headers
#include "winmain.h"
#include "window.h"

#include "input_common.h"
#include "input_windows.h"

namespace {

//============================================================
//  win32_keyboard_device
//============================================================

// This device is purely event driven so the implementation is in the module
class win32_keyboard_device : public event_based_device<KeyPressEventArgs>
{
public:
	keyboard_state keyboard;

	win32_keyboard_device(running_machine& machine, std::string &&name, std::string &&id, input_module &module)
		: event_based_device(machine, std::move(name), std::move(id), DEVICE_CLASS_KEYBOARD, module),
			keyboard({{0}})
	{
	}

	void reset() override
	{
		memset(&keyboard, 0, sizeof(keyboard));
	}

protected:
	void process_event(KeyPressEventArgs &args) override
	{
		keyboard.state[args.scancode] = args.event_id == INPUT_EVENT_KEYDOWN ? 0x80 : 0x00;
	}
};

//============================================================
//  keyboard_input_win32 - win32 keyboard input module
//============================================================

class keyboard_input_win32 : public wininput_module
{
private:

public:
	keyboard_input_win32()
		: wininput_module(OSD_KEYBOARDINPUT_PROVIDER, "win32")
	{
	}

	virtual void input_init(running_machine &machine) override
	{
		// Add a single win32 keyboard device that we'll monitor using Win32
		auto &devinfo = devicelist()->create_device<win32_keyboard_device>(machine, "Win32 Keyboard 1", "Win32 Keyboard 1", *this);

		keyboard_trans_table &table = keyboard_trans_table::instance();

		// populate it
		for (int keynum = 0; keynum < MAX_KEYS; keynum++)
		{
			input_item_id itemid = table.map_di_scancode_to_itemid(keynum);
			TCHAR keyname[100];

			// generate the name
			if (GetKeyNameText(((keynum & 0x7f) << 16) | ((keynum & 0x80) << 17), keyname, std::size(keyname)) == 0)
				_sntprintf(keyname, std::size(keyname), TEXT("Scan%03d"), keynum);
			std::string name = osd::text::from_tstring(keyname);

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

	bool handle_input_event(input_event eventid, void *eventdata) override
	{
		if (!input_enabled())
			return false;

		KeyPressEventArgs *args;

		switch (eventid)
		{
			case INPUT_EVENT_KEYDOWN:
			case INPUT_EVENT_KEYUP:
				args = static_cast<KeyPressEventArgs*>(eventdata);
				devicelist()->for_each_device([args](auto device)
				{
					auto keyboard = dynamic_cast<win32_keyboard_device*>(device);
					if (keyboard != nullptr)
						keyboard->queue_events(args, 1);
				});

				return true;

			default:
				return false;
		}
	}
};

//============================================================
//  win32_mouse_device
//============================================================

struct win32_mouse_state
{
	POINT last_point;
};

class win32_mouse_device : public event_based_device<MouseButtonEventArgs>
{
public:
	mouse_state         mouse;
	win32_mouse_state   win32_mouse;

	win32_mouse_device(running_machine& machine, std::string &&name, std::string &&id, input_module &module)
		: event_based_device(machine, std::move(name), std::move(id), DEVICE_CLASS_MOUSE, module),
			mouse({0}),
			win32_mouse({{0}})
	{
	}

	void poll() override
	{
		event_based_device::poll();

		CURSORINFO cursor_info = {0};
		cursor_info.cbSize = sizeof(CURSORINFO);
		GetCursorInfo(&cursor_info);

		// We only take over the mouse if the cursor isn't showing
		// This should happen anyway in mouse mode
		if (!(cursor_info.flags & CURSOR_SHOWING))
		{
			// We measure the position change from the previously set center position
			mouse.lX = (cursor_info.ptScreenPos.x - win32_mouse.last_point.x) * INPUT_RELATIVE_PER_PIXEL;
			mouse.lY = (cursor_info.ptScreenPos.y - win32_mouse.last_point.y) * INPUT_RELATIVE_PER_PIXEL;

			RECT window_pos = {0};
			GetWindowRect(std::static_pointer_cast<win_window_info>(osd_common_t::s_window_list.front())->platform_window(), &window_pos);

			// We reset the cursor position to the middle of the window each frame
			win32_mouse.last_point.x = window_pos.left + (window_pos.right - window_pos.left) / 2;
			win32_mouse.last_point.y = window_pos.top + (window_pos.bottom - window_pos.top) / 2;

			SetCursorPos(win32_mouse.last_point.x, win32_mouse.last_point.y);
		}
	}

	void reset() override
	{
		memset(&mouse, 0, sizeof(mouse));
		memset(&win32_mouse, 0, sizeof(win32_mouse));
	}

protected:
	void process_event(MouseButtonEventArgs &args) override
	{
		// set the button state
		mouse.rgbButtons[args.button] = args.keydown ? 0x80 : 0x00;

		// Make sure we have a fresh mouse position on button down
		if (args.keydown)
			module().poll_if_necessary(machine());
	}
};

//============================================================
//  mouse_input_win32 - win32 mouse input module
//============================================================

class mouse_input_win32 : public wininput_module
{
public:
	mouse_input_win32()
		: wininput_module(OSD_MOUSEINPUT_PROVIDER, "win32")
	{
	}

	virtual void input_init(running_machine &machine) override
	{
		if (!input_enabled() || !mouse_enabled())
			return;

		// allocate a device
		auto &devinfo = devicelist()->create_device<win32_mouse_device>(machine, "Win32 Mouse 1", "Win32 Mouse 1", *this);

		// populate the axes
		for (int axisnum = 0; axisnum < 2; axisnum++)
		{
			devinfo.device()->add_item(
				default_axis_name[axisnum],
				static_cast<input_item_id>(ITEM_ID_XAXIS + axisnum),
				generic_axis_get_state<LONG>,
				&devinfo.mouse.lX + axisnum);
		}

		// populate the buttons
		for (int butnum = 0; butnum < 2; butnum++)
		{
			devinfo.device()->add_item(
				default_button_name(butnum),
				static_cast<input_item_id>(ITEM_ID_BUTTON1 + butnum),
				generic_button_get_state<BYTE>,
				&devinfo.mouse.rgbButtons[butnum]);
		}
	}

	bool handle_input_event(input_event eventid, void *eventdata) override
	{
		if (!input_enabled() || !mouse_enabled() || eventid != INPUT_EVENT_MOUSE_BUTTON)
			return false;

		auto args = static_cast<MouseButtonEventArgs*>(eventdata);
		devicelist()->for_each_device([args](auto device)
		{
			auto mouse = dynamic_cast<win32_mouse_device*>(device);
			if (mouse != nullptr)
				mouse->queue_events(args, 1);
		});

		return true;
	}
};

//============================================================
//  win32_lightgun_device
//============================================================

class win32_lightgun_device : public event_based_device<MouseButtonEventArgs>
{
private:
	BOOL m_lightgun_shared_axis_mode;
	int m_gun_index;

public:
	mouse_state     mouse;

	win32_lightgun_device(running_machine& machine, std::string &&name, std::string &&id, input_module &module)
		: event_based_device(machine, std::move(name), std::move(id), DEVICE_CLASS_LIGHTGUN, module),
			m_lightgun_shared_axis_mode(FALSE),
			m_gun_index(0),
			mouse({0})
	{
		m_lightgun_shared_axis_mode = downcast<windows_options &>(machine.options()).dual_lightgun();

		// Since we are about to be added to the list, the current size is the zero-based index of where we will be
		m_gun_index = downcast<wininput_module&>(module).devicelist()->size();
	}

	void poll() override
	{
		event_based_device::poll();

		int32_t xpos = 0, ypos = 0;
		POINT mousepos;

		// if we are using the shared axis hack, the data is updated via Windows messages only
		if (m_lightgun_shared_axis_mode)
			return;

		// get the cursor position and transform into final results
		GetCursorPos(&mousepos);
		if (!osd_common_t::s_window_list.empty())
		{
			RECT client_rect;

			// get the position relative to the window
			HWND hwnd = std::static_pointer_cast<win_window_info>(osd_common_t::s_window_list.front())->platform_window();
			GetClientRect(hwnd, &client_rect);
			ScreenToClient(hwnd, &mousepos);

			// convert to absolute coordinates
			xpos = normalize_absolute_axis(mousepos.x, client_rect.left, client_rect.right);
			ypos = normalize_absolute_axis(mousepos.y, client_rect.top, client_rect.bottom);
		}

		// update the X/Y positions
		mouse.lX = xpos;
		mouse.lY = ypos;
	}

	void reset() override
	{
		memset(&mouse, 0, sizeof(mouse));
	}

protected:
	void process_event(MouseButtonEventArgs &args) override
	{
		// Are we in shared axis mode?
		if (m_lightgun_shared_axis_mode)
		{
			handle_shared_axis_mode(args);
		}
		else
		{
			// In non-shared axis mode, just update the button state
			mouse.rgbButtons[args.button] = args.keydown ? 0x80 : 0x00;
		}
	}

private:
	void handle_shared_axis_mode(MouseButtonEventArgs &args)
	{
		int button = args.button;

		// We only handle the first four buttons in shared axis mode
		if (button > 3)
			return;

		// First gun doesn't handle buttons 2 & 3
		if (button >= 2 && m_gun_index == 0)
			return;

		// Second gun doesn't handle buttons 0 & 1
		if (button < 2 && m_gun_index == 1)
			return;

		// Adjust the button if we're the second lightgun
		int logical_button = m_gun_index == 1 ? button - 2 : button;

		// set the button state
		mouse.rgbButtons[logical_button] = args.keydown ? 0x80 : 0x00;
		if (args.keydown)
		{
			RECT client_rect;
			POINT mousepos;

			// get the position relative to the window
			HWND hwnd = std::static_pointer_cast<win_window_info>(osd_common_t::s_window_list.front())->platform_window();
			GetClientRect(hwnd, &client_rect);
			mousepos.x = args.xpos;
			mousepos.y = args.ypos;
			ScreenToClient(hwnd, &mousepos);

			// convert to absolute coordinates
			mouse.lX = normalize_absolute_axis(mousepos.x, client_rect.left, client_rect.right);
			mouse.lY = normalize_absolute_axis(mousepos.y, client_rect.top, client_rect.bottom);
		}
	}
};

//============================================================
//  lightgun_input_win32 - win32 lightgun input module
//============================================================

class lightgun_input_win32 : public wininput_module
{
public:
	lightgun_input_win32()
		: wininput_module(OSD_LIGHTGUNINPUT_PROVIDER, "win32")
	{
	}

	int init_internal() override
	{
		return 0;
	}

	virtual void input_init(running_machine &machine) override
	{
		int max_guns = downcast<windows_options&>(machine.options()).dual_lightgun() ? 2 : 1;

		// allocate the lightgun devices
		for (int gunnum = 0; gunnum < max_guns; gunnum++)
		{
			static const char *const gun_names[] = { "Win32 Gun 1", "Win32 Gun 2" };

			// allocate a device
			auto &devinfo = devicelist()->create_device<win32_lightgun_device>(machine, gun_names[gunnum], gun_names[gunnum], *this);

			// populate the axes
			for (int axisnum = 0; axisnum < 2; axisnum++)
			{
				devinfo.device()->add_item(
					default_axis_name[axisnum],
					static_cast<input_item_id>(ITEM_ID_XAXIS + axisnum),
					generic_axis_get_state<LONG>,
					&devinfo.mouse.lX + axisnum);
			}

			// populate the buttons
			for (int butnum = 0; butnum < 2; butnum++)
			{
				devinfo.device()->add_item(
					default_button_name(butnum),
					static_cast<input_item_id>(ITEM_ID_BUTTON1 + butnum),
					generic_button_get_state<BYTE>,
					&devinfo.mouse.rgbButtons[butnum]);
			}
		}
	}

	bool handle_input_event(input_event eventid, void* eventdata) override
	{
		if (!input_enabled() || !lightgun_enabled() || eventid != INPUT_EVENT_MOUSE_BUTTON)
			return false;

		auto args = static_cast<MouseButtonEventArgs*>(eventdata);
		devicelist()->for_each_device([args](auto device)
		{
			auto lightgun = dynamic_cast<win32_lightgun_device*>(device);
			if (lightgun != nullptr)
				lightgun->queue_events(args, 1);
		});

		return true;
	}
};

} // anonymous namespace

#else // defined(OSD_WINDOWS)

MODULE_NOT_SUPPORTED(keyboard_input_win32, OSD_KEYBOARDINPUT_PROVIDER, "win32")
MODULE_NOT_SUPPORTED(mouse_input_win32, OSD_MOUSEINPUT_PROVIDER, "win32")
MODULE_NOT_SUPPORTED(lightgun_input_win32, OSD_LIGHTGUNINPUT_PROVIDER, "win32")

#endif // defined(OSD_WINDOWS)

MODULE_DEFINITION(KEYBOARDINPUT_WIN32, keyboard_input_win32)
MODULE_DEFINITION(MOUSEINPUT_WIN32, mouse_input_win32)
MODULE_DEFINITION(LIGHTGUNINPUT_WIN32, lightgun_input_win32)
