#include "mungal4.h"
#pragma hdrstop

#include "l4padrio.h"
#include "l4keylight.h"

#include <XInput.h>
#pragma comment(lib, "xinput9_1_0.lib")

//########################################################################
// Input helpers; the binding tables live in bindings.txt now
// (l4padbindings.cpp writes and parses the vRIO-format profile)
//########################################################################

namespace
{
	Scalar StickValue(int raw, int dead_zone)
	{
		if (raw > -dead_zone && raw < dead_zone)
		{
			return (Scalar) 0;
		}
		Scalar value =
			(raw > 0)
				? (Scalar)(raw - dead_zone) / (Scalar)(32767 - dead_zone)
				: (Scalar)(raw + dead_zone) / (Scalar)(32768 - dead_zone);
		if (value > 1.0f)	value = 1.0f;
		if (value < -1.0f)	value = -1.0f;
		return value;
	}

	Scalar Clamp01(Scalar value)
	{
		if (value < 0.0f)	return 0.0f;
		if (value > 1.0f)	return 1.0f;
		return value;
	}

	Logical KeyDown(int virtual_key)
	{
		return (GetAsyncKeyState(virtual_key) & 0x8000) != 0;
	}

	void KeyLightLog(const char *line)
	{
		DEBUG_STREAM << line << "\n" << std::flush;
	}
}

//########################################################################
//############################### PadRIO #################################
//########################################################################

PadRIO *PadRIO::activeInstance = NULL;

void
	PadRIO::SetScreenButton(int unit, Logical pressed)
{
	if (activeInstance != NULL && unit >= 0 && unit < buttonUnits)
	{
		activeInstance->screenButton[unit] = pressed ? 1 : 0;
	}
}

int
	PadRIO::GetLampState(int unit)
{
	if (activeInstance != NULL && unit >= 0 && unit < lampCount)
	{
		return activeInstance->lampState[unit];
	}
	return 0;
}

PadRIO::PadRIO()
{
	Check_Pointer(this);

	queueHead = 0;
	queueTail = 0;
	lastPollTick = GetTickCount();
	lastPadCheckTick = 0;
	padIndex = -1;
	padReported = False;
	analogRequested = False;
	throttleAccum = (Scalar) 0;

	sentThrottle = sentLeftPedal = sentRightPedal = (Scalar) 0;
	sentJoystickX = sentJoystickY = (Scalar) 0;

	memset(buttonDown, 0, sizeof(buttonDown));
	memset(keypadDown, 0, sizeof(keypadDown));
	memset(lampState, 0, sizeof(lampState));
	memset(screenButton, 0, sizeof(screenButton));

	PadBindings_Load(&profile);

	//
	// RGB keyboard lamp mirror (Windows Dynamic Lighting): keys bound
	// to lamp addresses glow with the panel. Yellow = the Secondary /
	// Screen columns (0x10-0x1F), red = everything else, exactly like
	// the physical panel and vRIO. RP412KEYLIGHT=0 opts out.
	//
	keyLightActive = False;
	const char *keylight = getenv("RP412KEYLIGHT");
	if (keylight == NULL || atoi(keylight) != 0)
	{
		int light_keys[PadBindingProfile::maxKeyButtons];
		int light_addresses[PadBindingProfile::maxKeyButtons];
		unsigned char light_yellow[PadBindingProfile::maxKeyButtons];
		int light_count = 0;
		for (int i = 0; i < profile.keyButtonCount; ++i)
		{
			int address = profile.keyButtons[i].address;
			if (address >= buttonUnits)
			{
				continue;		// keypads have no lamps
			}
			Logical duplicate = False;
			for (int j = 0; j < light_count; ++j)
			{
				if (light_keys[j] == profile.keyButtons[i].virtualKey)
				{
					duplicate = True;	// first binding wins
					break;
				}
			}
			if (duplicate)
			{
				continue;
			}
			light_keys[light_count] = profile.keyButtons[i].virtualKey;
			light_addresses[light_count] = address;
			light_yellow[light_count] =
				(address >= 0x10 && address <= 0x1F) ? 1 : 0;
			++light_count;
		}
		if (light_count > 0)
		{
			KeyLight_SetLogger(&KeyLightLog);
			KeyLight_SetMap(light_keys, light_addresses, light_yellow, light_count);
			KeyLight_Start();
			keyLightActive = True;
		}
	}

	invertX = False;
	invertY = False;
	const char *flip = getenv("L4PADFLIP");
	if (flip != NULL)
	{
		if (strchr(flip, 'X') || strchr(flip, 'x'))
		{
			invertX = True;
		}
		if (strchr(flip, 'Y') || strchr(flip, 'y'))
		{
			invertY = True;
		}
	}

	// Report as a v4.2 board, like vRIO does
	MajorRevision = 4;
	MinorRevision = 2;

	activeInstance = this;

	DEBUG_STREAM << "PadRIO: virtual RIO active (XInput pad + keyboard)\n" << std::flush;
}

PadRIO::~PadRIO()
{
	Check_Pointer(this);
	if (keyLightActive)
	{
		KeyLight_Stop();
		keyLightActive = False;
	}
	if (activeInstance == this)
	{
		activeInstance = NULL;
	}
}

Logical
	PadRIO::TestInstance() const
{
	return True;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// The controls manager drains events every frame; sampling lives here so
// button latency does not depend on the analog request cadence (which is
// 15 s outside of missions).
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Logical
	PadRIO::GetNextEvent(RIOEvent *destinationPointer)
{
	Check_Pointer(this);
	Check_Pointer(destinationPointer);

	PollInputs();

	if (queueTail == queueHead)
	{
		return False;
	}
	*destinationPointer = eventQueue[queueTail];
	queueTail = (queueTail + 1) % queueSize;
	return True;
}

void
	PadRIO::RequestAnalogUpdate()
{
	Check_Pointer(this);
	analogRequested = True;
}

void
	PadRIO::GeneralReset()
{
	Check_Pointer(this);
	throttleAccum = (Scalar) 0;
	Throttle   = (Scalar) 0;
	LeftPedal  = (Scalar) 0;
	RightPedal = (Scalar) 0;
	JoystickX  = (Scalar) 0;
	JoystickY  = (Scalar) 0;
	analogRequested = True;
	memset(lampState, 0, sizeof(lampState));
	if (keyLightActive)
	{
		KeyLight_UpdateLamps(lampState, lampCount);
	}
	memset(keypadDown, 0, sizeof(keypadDown));
	for (int i = 0; i < profile.keyButtonCount; ++i)
	{
		profile.keyButtons[i].latched = False;
		profile.keyButtons[i].wasDown = False;
	}
	for (int i = 0; i < profile.padButtonCount; ++i)
	{
		profile.padButtons[i].latched = False;
		profile.padButtons[i].wasDown = False;
	}
}

void
	PadRIO::ResetThrottle()
{
	Check_Pointer(this);
	throttleAccum = (Scalar) 0;
	Throttle = (Scalar) 0;
	analogRequested = True;
}

void
	PadRIO::SetLamp(int lampNumber, int state)
{
	Check_Pointer(this);
	if (lampNumber >= 0 && lampNumber < lampCount)
	{
		lampState[lampNumber] = (unsigned char) state;
		if (keyLightActive)
		{
			KeyLight_UpdateLamps(lampState, lampCount);
		}
	}
}

void
	PadRIO::QueueEvent(const RIOEvent &an_event)
{
	int next = (queueHead + 1) % queueSize;
	if (next == queueTail)
	{
		// full: drop the oldest event
		queueTail = (queueTail + 1) % queueSize;
	}
	eventQueue[queueHead] = an_event;
	queueHead = next;
}

void
	PadRIO::PollInputs()
{
	unsigned long now = GetTickCount();
	if (now - lastPollTick < 10)
	{
		return;
	}
	Scalar delta_t = (Scalar)(now - lastPollTick) / 1000.0f;
	if (delta_t > 0.25f)
	{
		delta_t = 0.25f;
	}
	lastPollTick = now;

	//---------------------------------------------------------------
	// Find / keep the XInput pad. Probing empty slots is slow, so an
	// absent pad is only re-probed every 3 seconds.
	//---------------------------------------------------------------
	XINPUT_STATE pad;
	memset(&pad, 0, sizeof(pad));
	Logical pad_live = False;

	if (padIndex >= 0)
	{
		pad_live = (XInputGetState((DWORD) padIndex, &pad) == ERROR_SUCCESS);
		if (!pad_live)
		{
			DEBUG_STREAM << "PadRIO: controller " << padIndex << " disconnected\n" << std::flush;
			padIndex = -1;
		}
	}
	if (padIndex < 0 && (now - lastPadCheckTick) >= 3000)
	{
		lastPadCheckTick = now;
		for (DWORD i = 0; i < 4; ++i)
		{
			if (XInputGetState(i, &pad) == ERROR_SUCCESS)
			{
				padIndex = (int) i;
				pad_live = True;
				DEBUG_STREAM << "PadRIO: controller " << padIndex << " connected\n" << std::flush;
				break;
			}
		}
		if (padIndex < 0 && !padReported)
		{
			padReported = True;
			DEBUG_STREAM << "PadRIO: no controller found - keyboard only\n" << std::flush;
		}
	}

	//---------------------------------------------------------------
	// Buttons: build the desired state from the binding profile
	// (keyboard + pad, with toggle latches), merge the on-screen
	// cockpit buttons, then diff against what we last reported.
	// Keypad addresses (0x50-0x6F) collect separately - they become
	// arcade KeyEvents, not button events.
	//---------------------------------------------------------------
	unsigned char desired[buttonUnits];
	unsigned char keypadDesired[keypadUnits];
	memset(desired, 0, sizeof(desired));
	memset(keypadDesired, 0, sizeof(keypadDesired));

	for (int i = 0; i < profile.keyButtonCount; ++i)
	{
		PadKeyButtonBinding *binding = &profile.keyButtons[i];
		Logical down = KeyDown(binding->virtualKey);
		if (binding->toggle && down && !binding->wasDown)
		{
			binding->latched = !binding->latched;
		}
		binding->wasDown = down;

		if (binding->toggle ? binding->latched : down)
		{
			if (binding->address < buttonUnits)
			{
				desired[binding->address] = 1;
			}
			else if (binding->address >= 0x50 && binding->address < 0x50 + keypadUnits)
			{
				keypadDesired[binding->address - 0x50] = 1;
			}
		}
	}
	for (int i = 0; i < profile.padButtonCount; ++i)
	{
		PadPadButtonBinding *binding = &profile.padButtons[i];
		Logical down = pad_live &&
			(pad.Gamepad.wButtons & binding->padMask) != 0;
		if (binding->toggle && down && !binding->wasDown)
		{
			binding->latched = !binding->latched;
		}
		binding->wasDown = down;

		if (binding->toggle ? binding->latched : down)
		{
			if (binding->address < buttonUnits)
			{
				desired[binding->address] = 1;
			}
			else if (binding->address >= 0x50 && binding->address < 0x50 + keypadUnits)
			{
				keypadDesired[binding->address - 0x50] = 1;
			}
		}
	}
	for (int i = 0; i < buttonUnits; ++i)
	{
		if (screenButton[i])
		{
			desired[i] = 1;
		}
	}

	for (int unit = 0; unit < buttonUnits; ++unit)
	{
		if (desired[unit] != buttonDown[unit])
		{
			buttonDown[unit] = desired[unit];

			RIOEvent an_event;
			an_event.Type = desired[unit] ? ButtonPressedEvent : ButtonReleasedEvent;
			an_event.Data.Unit = unit;
			QueueEvent(an_event);
		}
	}

	//---------------------------------------------------------------
	// Keypads: presses become the arcade RIO KeyEvents. Unit 0 is the
	// pilot's internal keypad (0x50-0x5F), unit 1 the external
	// operator keypad (0x60-0x6F); the key is the hex digit 0-15.
	//---------------------------------------------------------------
	for (int pad_key = 0; pad_key < keypadUnits; ++pad_key)
	{
		if (keypadDesired[pad_key] != keypadDown[pad_key])
		{
			keypadDown[pad_key] = keypadDesired[pad_key];
			if (keypadDesired[pad_key])
			{
				RIOEvent an_event;
				an_event.Type = KeyEvent;
				an_event.Data.Keyboard.Unit = (pad_key >= 0x10) ? 1 : 0;
				an_event.Data.Keyboard.Key = pad_key & 0x0F;
				QueueEvent(an_event);
			}
		}
	}

	//---------------------------------------------------------------
	// Axes, from the profile. 'deflect' sources sum into a springy
	// position; 'rate' sources integrate the throttle (the pod's only
	// sticky axis) by value per second.
	//---------------------------------------------------------------
	Scalar deflect[BindAxisCount];
	Scalar rate[BindAxisCount];
	memset(deflect, 0, sizeof(deflect));
	memset(rate, 0, sizeof(rate));

	for (int i = 0; i < profile.keyAxisCount; ++i)
	{
		const PadKeyAxisBinding *binding = &profile.keyAxes[i];
		if (KeyDown(binding->virtualKey))
		{
			if (binding->mode == BindKeyRate)
			{
				rate[binding->axis] += binding->value;
			}
			else
			{
				deflect[binding->axis] += binding->value;
			}
		}
	}
	if (pad_live)
	{
		for (int i = 0; i < profile.padAxisCount; ++i)
		{
			const PadPadAxisBinding *binding = &profile.padAxes[i];
			Scalar value = (Scalar) 0;
			switch (binding->source)
			{
			case BindPadLeftStickX:
				value = StickValue(pad.Gamepad.sThumbLX, (int)(binding->deadzone * 32767.0f));
				break;
			case BindPadLeftStickY:
				value = StickValue(pad.Gamepad.sThumbLY, (int)(binding->deadzone * 32767.0f));
				break;
			case BindPadRightStickX:
				value = StickValue(pad.Gamepad.sThumbRX, (int)(binding->deadzone * 32767.0f));
				break;
			case BindPadRightStickY:
				value = StickValue(pad.Gamepad.sThumbRY, (int)(binding->deadzone * 32767.0f));
				break;
			case BindPadLeftTrigger:
				value = (Scalar)(pad.Gamepad.bLeftTrigger) / 255.0f;
				if (value <= binding->deadzone)	value = (Scalar) 0;
				break;
			case BindPadRightTrigger:
				value = (Scalar)(pad.Gamepad.bRightTrigger) / 255.0f;
				if (value <= binding->deadzone)	value = (Scalar) 0;
				break;
			}
			if (binding->invert)
			{
				value = -value;
			}
			if (binding->rate > 0.0f)
			{
				rate[binding->axis] += value * binding->rate;
			}
			else
			{
				deflect[binding->axis] += value;
			}
		}
	}

	throttleAccum = Clamp01(throttleAccum + rate[BindAxisThrottle] * delta_t);

	Scalar x = deflect[BindAxisJoystickX];
	Scalar y = deflect[BindAxisJoystickY];
	if (x > 1.0f)	x = 1.0f;
	if (x < -1.0f)	x = -1.0f;
	if (y > 1.0f)	y = 1.0f;
	if (y < -1.0f)	y = -1.0f;

	Throttle   = Clamp01(throttleAccum + deflect[BindAxisThrottle]);
	LeftPedal  = Clamp01(deflect[BindAxisLeftPedal]);
	RightPedal = Clamp01(deflect[BindAxisRightPedal]);
	// The profile encodes the pod's stick sign convention; L4PADFLIP
	// flips on top of it per axis.
	JoystickX  = invertX ? -x : x;
	JoystickY  = invertY ? -y : y;

	//---------------------------------------------------------------
	// Emit an analog event when asked to, or when anything moved
	//---------------------------------------------------------------
	Logical changed =
		(Throttle   != sentThrottle)   ||
		(LeftPedal  != sentLeftPedal)  ||
		(RightPedal != sentRightPedal) ||
		(JoystickX  != sentJoystickX)  ||
		(JoystickY  != sentJoystickY);

	if (analogRequested || changed)
	{
		analogRequested = False;
		sentThrottle   = Throttle;
		sentLeftPedal  = LeftPedal;
		sentRightPedal = RightPedal;
		sentJoystickX  = JoystickX;
		sentJoystickY  = JoystickY;

		RIOEvent an_event;
		an_event.Type = AnalogEvent;
		an_event.Data.Unit = 0;
		QueueEvent(an_event);
	}
}
