#pragma once
#include "input/keyboard_device.h"
#include "sdl.h"

#ifdef __APPLE__
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOTypes.h>
#include <stack>
// Rumbling Taptic Engine by Private MultitouchSupport.framework
extern "C" {
typedef void *MTDeviceRef;
bool MTDeviceIsAvailable(void);
MTDeviceRef MTDeviceCreateDefault(void);
OSStatus MTDeviceGetDeviceID(MTDeviceRef, uint64_t*) __attribute__ ((weak_import));
CFTypeRef MTActuatorCreateFromDeviceID(UInt64 deviceID);
IOReturn MTActuatorOpen(CFTypeRef actuatorRef);
IOReturn MTActuatorClose(CFTypeRef actuatorRef);
IOReturn MTActuatorActuate(CFTypeRef actuatorRef, SInt32 actuationID, UInt32 unknown1, Float32 unknown2, Float32 unknown3);
bool MTActuatorIsOpen(CFTypeRef actuatorRef);
enum ActuatePattern { minimal = 3, weak = 5, medium = 4, strong = 6 };
}
#endif

class SDLKeyboardDevice : public KeyboardDevice
{
public:
	SDLKeyboardDevice(int maple_port) : KeyboardDevice(maple_port, "SDL") {
		_unique_id = "sdl_keyboard";
		if (find_mapping())
		{
			if (input_mapper->version == 1)
			{
				// Convert keycodes to scancode
				SDL_Scancode scancodes[4][26] {};
				for (int i = 0; i < 26; i++)
				{
					DreamcastKey key = (DreamcastKey)(1 << i);
					for (int port = 0; port < 4; port++)
					{
						SDL_Keycode keycode = (SDL_Keycode)input_mapper->get_button_code(port, key);
						if ((int)keycode != -1)
							scancodes[port][i] = SDL_GetScancodeFromKey(keycode);
					}
				}
				for (int i = 0; i < 26; i++)
				{
					DreamcastKey key = (DreamcastKey)(1 << i);
					for (int port = 0; port < 4; port++)
						if (scancodes[port][i] != 0)
							input_mapper->set_button(port, key, (u32)scancodes[port][i]);
				}
				save_mapping();
			}
		}
		else
			input_mapper = getDefaultMapping();

#ifdef __APPLE__
		uint64_t deviceID;
		if ( MTDeviceIsAvailable() && MTDeviceGetDeviceID(MTDeviceCreateDefault(), &deviceID) == 0 && (vib_device = MTActuatorCreateFromDeviceID(deviceID)) != NULL && MTActuatorOpen(vib_device) == kIOReturnSuccess)
			rumbleEnabled = true;
#endif
	}

	const char *get_button_name(u32 code) override
	{
		const char *name = SDL_GetKeyName(SDL_GetKeyFromScancode((SDL_Scancode)code));
		if (name[0] == 0)
			return nullptr;
		return name;
	}

#ifdef __APPLE__
	void rumble(float power, float inclination, u32 duration_ms) override
	{
		if (rumbleEnabled)
		{
			vib_stop_time = os_GetSeconds() + duration_ms / 1000.0;
			
			__block int pattern;
			if (power >= 0.75)
				pattern = ActuatePattern::strong;
			else if (power >= 0.5)
				pattern = ActuatePattern::medium;
			else if (power >= 0.25)
				pattern = ActuatePattern::weak;
			else if (power > 0)
				pattern = ActuatePattern::minimal;
			else
			{
				while(!vib_timer_stack.empty())
				{
					dispatch_source_cancel(vib_timer_stack.top());
					vib_timer_stack.pop();
				}
				return;
			}
			// Since the Actuator API does not support duration
			// using a interval timer with `10ms * rumblePower percentage` to fake it
			__block dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
			vib_timer_stack.push(_timer);
			dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, 10 * NSEC_PER_MSEC * rumblePower / 100.f, 0);
			
			dispatch_source_set_event_handler(_timer, ^{
				if ( vib_stop_time - os_GetSeconds() < 0 )
				{
					dispatch_source_cancel(_timer);
					return;
				}
				MTActuatorActuate(vib_device, pattern, 0, 0.0, 0.0);
			});
			
			dispatch_resume(_timer);
		}
	}
	
	~SDLKeyboardDevice() {
		if (rumbleEnabled)
		{
			MTActuatorClose(vib_device);
			CFRelease(vib_device);
		}
	}
#endif

	void input(SDL_Scancode scancode, bool pressed)
	{
		u8 keycode;
		if (settings.input.keyboardLangId != KeyboardLayout::US && scancode == SDL_SCANCODE_BACKSLASH)
			keycode = (u8)SDL_SCANCODE_NONUSHASH;
		else
			keycode = (u8)scancode;
		KeyboardDevice::input(keycode, pressed, 0);
	}

#ifdef __APPLE__
private:
	std::stack<dispatch_source_t> vib_timer_stack;
	CFTypeRef vib_device = NULL;
	double vib_stop_time = 0;
#endif
};
