#include "PLCore.h"
#include "AppEventHandler.h"
#include "PLEventQueue.h"
#include "PLKeyEncoding.h"
#include "PLMovies.h"
#include "PLSysCalls.h"
#include "PLTimeTaggedVOSEvent.h"
#include "DisplayDeviceManager.h"
#include "GpVOSEvent.h"
#include "IGpDisplayDriver.h"
#include "IGpVOSEventQueue.h"
#include "InputManager.h"
#include "HostSuspendCallArgument.h"
#include "HostSuspendHook.h"
#include "MacRomanConversion.h"

#include "PLDrivers.h"
#include "CoreDefs.h"

#include <assert.h>
#include <setjmp.h>
#include <stdlib.h>

static void TranslateMouseInputEvent(const GpVOSEvent &vosEventBase, uint32_t timestamp, PortabilityLayer::EventQueue *queue)
{
	const GpMouseInputEvent &vosEvent = vosEventBase.m_event.m_mouseInputEvent;

	bool requeue = false;
	if (vosEvent.m_button == GpMouseButtons::kLeft)
	{
		if (vosEvent.m_eventType == GpMouseEventTypes::kDown)
			requeue = true;
		else if (vosEvent.m_eventType == GpMouseEventTypes::kUp)
			requeue = true;
	}
	else if (vosEvent.m_eventType == GpMouseEventTypes::kMove)
		requeue = true;

	if (requeue)
	{
		if (TimeTaggedVOSEvent *evt = queue->Enqueue())
			*evt = TimeTaggedVOSEvent::Create(vosEventBase, timestamp);
	}

	PortabilityLayer::InputManager *inputManager = PortabilityLayer::InputManager::GetInstance();

	inputManager->ApplyMouseEvent(vosEvent);
}

static void TranslateTouchInputEvent(const GpVOSEvent &vosEventBase, uint32_t timestamp, PortabilityLayer::EventQueue *queue)
{
	if (TimeTaggedVOSEvent *evt = queue->Enqueue())
		*evt = TimeTaggedVOSEvent::Create(vosEventBase, timestamp);
}

static void TranslateGamepadInputEvent(const GpGamepadInputEvent &vosEvent, PortabilityLayer::EventQueue *queue)
{
	PortabilityLayer::InputManager *inputManager = PortabilityLayer::InputManager::GetInstance();

	inputManager->ApplyGamepadEvent(vosEvent);

	PL_DEAD(queue);
}

static void TranslateVideoResolutionChangedEvent(const GpVideoResolutionChangedEvent &evt)
{
	PortabilityLayer::DisplayDeviceManager::IResolutionChangeHandler *chgHandler = PortabilityLayer::DisplayDeviceManager::GetInstance()->GetResolutionChangeHandler();
	if (chgHandler)
		chgHandler->OnResolutionChanged(evt.m_prevWidth, evt.m_prevHeight, evt.m_newWidth, evt.m_newHeight);
}

static void TranslateKeyboardInputEvent(const GpVOSEvent &vosEventBase, uint32_t timestamp, PortabilityLayer::EventQueue *queue)
{
	const GpKeyboardInputEvent &vosEvent = vosEventBase.m_event.m_keyboardInputEvent;

	GP_STATIC_ASSERT((1 << PL_INPUT_PLAYER_INDEX_BITS) >= PL_INPUT_MAX_PLAYERS);
	GP_STATIC_ASSERT((1 << PL_INPUT_TYPE_CODE_BITS) >= KeyEventType_Count);

	PortabilityLayer::InputManager *inputManager = PortabilityLayer::InputManager::GetInstance();

	if (vosEvent.m_eventType == GpKeyboardInputEventTypes::kUp || vosEvent.m_eventType == GpKeyboardInputEventTypes::kDown)
		inputManager->ApplyKeyboardEvent(vosEvent);

	// Special handling of alt-enter, redirect to display driver
	if (vosEventBase.m_event.m_keyboardInputEvent.m_eventType == GpKeyboardInputEventTypes::kDown &&
		vosEventBase.m_event.m_keyboardInputEvent.m_keyIDSubset == GpKeyIDSubsets::kSpecial &&
		vosEventBase.m_event.m_keyboardInputEvent.m_key.m_specialKey == GpKeySpecials::kEnter)
	{
		const KeyDownStates *keyStates = inputManager->GetKeys();
		if (keyStates->m_special.Get(GpKeySpecials::kLeftAlt) || keyStates->m_special.Get(GpKeySpecials::kRightAlt))
		{
			IGpDisplayDriver *dd = PLDrivers::GetDisplayDriver();
			if (dd)
				dd->RequestToggleFullScreen(timestamp);
		}
	}

	if (TimeTaggedVOSEvent *evt = queue->Enqueue())
		*evt = TimeTaggedVOSEvent::Create(vosEventBase, timestamp);
}

intptr_t PackVOSKeyCode(const GpKeyboardInputEvent &vosEvent)
{
	switch (vosEvent.m_keyIDSubset)
	{
	case GpKeyIDSubsets::kASCII:
		return PL_KEY_ASCII(vosEvent.m_key.m_asciiChar);
	case GpKeyIDSubsets::kFKey:
		return PL_KEY_FKEY(vosEvent.m_key.m_fKey);
	case GpKeyIDSubsets::kNumPadNumber:
		return PL_KEY_NUMPAD_NUMBER(vosEvent.m_key.m_numPadNumber);
	case GpKeyIDSubsets::kSpecial:
		return PL_KEY_SPECIAL_ENCODE(vosEvent.m_key.m_specialKey);
		break;
	case GpKeyIDSubsets::kNumPadSpecial:
		return PL_KEY_NUMPAD_SPECIAL_ENCODE(vosEvent.m_key.m_numPadSpecialKey);
		break;
	case GpKeyIDSubsets::kUnicode:
		for (int i = 128; i < 256; i++)
		{
			if (MacRoman::ToUnicode(i) == vosEvent.m_key.m_unicodeChar)
				return PL_KEY_MACROMAN(i);
		}
		break;
	case GpKeyIDSubsets::kGamepadButton:
		return PL_KEY_GAMEPAD_BUTTON_ENCODE(vosEvent.m_key.m_gamepadKey.m_button, vosEvent.m_key.m_gamepadKey.m_player);
	default:
		return 0;
	}

	return 0;
}

static void TranslateVOSEvent(const GpVOSEvent *vosEvent, uint32_t timestamp, PortabilityLayer::EventQueue *queue)
{
	switch (vosEvent->m_eventType)
	{
	case GpVOSEventTypes::kMouseInput:
		TranslateMouseInputEvent(*vosEvent, timestamp, queue);
		break;
	case GpVOSEventTypes::kTouchInput:
		TranslateTouchInputEvent(*vosEvent, timestamp, queue);
		break;
	case GpVOSEventTypes::kKeyboardInput:
		TranslateKeyboardInputEvent(*vosEvent, timestamp, queue);
		break;
	case GpVOSEventTypes::kGamepadInput:
		TranslateGamepadInputEvent(vosEvent->m_event.m_gamepadInputEvent, queue);
		break;
	case GpVOSEventTypes::kVideoResolutionChanged:
		TranslateVideoResolutionChangedEvent(vosEvent->m_event.m_resolutionChangedEvent);
		break;
	case GpVOSEventTypes::kQuit:
		if (TimeTaggedVOSEvent *evt = queue->Enqueue())
			*evt = TimeTaggedVOSEvent::Create(*vosEvent, timestamp);

		if (PortabilityLayer::IAppEventHandler *appHandler = PortabilityLayer::AppEventHandler::GetInstance())
			appHandler->OnQuit();

		break;
	}
}

static void ImportVOSEvents(uint32_t timestamp)
{
	PortabilityLayer::EventQueue *plQueue = PortabilityLayer::EventQueue::GetInstance();

	IGpVOSEventQueue *evtQueue = PLDrivers::GetVOSEventQueue();
	while (const GpVOSEvent *evt = evtQueue->GetNext())
	{
		TranslateVOSEvent(evt, timestamp, plQueue);
		evtQueue->DischargeOne();
	}
}

namespace PLSysCalls
{
	// Asyncify disarm checks are for manually checking that a stack has no indirect calls.
	// They should not be nested!
#if GP_DEBUG_CONFIG && GP_ASYNCIFY_PARANOID_VALIDATION
	static bool g_asyncifyParanoidDisarmed = false;

	void AsyncifyParanoidSetDisarmed(bool state)
	{
		assert(g_asyncifyParanoidDisarmed != state);
		g_asyncifyParanoidDisarmed = state;
	}

	AsyncifyDisarmScope::AsyncifyDisarmScope()
	{
		AsyncifyParanoidSetDisarmed(true);
	}

	AsyncifyDisarmScope::~AsyncifyDisarmScope()
	{
		AsyncifyParanoidSetDisarmed(false);
	}
#endif

	void Sleep(uint32_t ticks)
	{
#if GP_DEBUG_CONFIG && GP_ASYNCIFY_PARANOID_VALIDATION
		assert(g_asyncifyParanoidDisarmed);
#endif
		if (ticks > 0)
		{
			PortabilityLayer::RenderFrames(ticks);

			ImportVOSEvents(PortabilityLayer::DisplayDeviceManager::GetInstance()->GetTickCount());

			AnimationManager::GetInstance()->TickPlayers(ticks);
		}
	}

	static jmp_buf gs_mainExitWrapper;
	static int gs_exitCode = 0;

	void Exit(int exitCode)
	{
#if GP_ASYNCIFY_PARANOID
		exit(exitCode);
#else
		gs_exitCode = exitCode;
		longjmp(gs_mainExitWrapper, 1);
#endif
	}

	int MainExitWrapper(int (*mainFunc)())
	{
		if (!setjmp(gs_mainExitWrapper))
		{
			int returnCode = mainFunc();
			return returnCode;
		}
		else
			return gs_exitCode;
	}
}
