// Fill out your copyright notice in the Description page of Project Settings.


#include "KeyboardLayout.h"

#if PLATFORM_WINDOWS
// WindowsHWrapper et pas un <Windows.h> nu : il pose lui-meme les gardes
// Pre/PostWindowsApi qui neutralisent les macros et les typedefs de l'API Win32
// entrant en collision avec ceux du moteur (INT, FLOAT, TEXT...).
#include "Windows/WindowsHWrapper.h"
#endif

namespace KeyboardLayout
{
#if PLATFORM_WINDOWS

	/**
	 * Position physique de chaque touche de reference, en scancode PS/2 set 1.
	 *
	 * Ces codes decrivent l'EMPLACEMENT sur le clavier, pas le caractere grave
	 * dessus : c'est ce qui rend la table independante de la disposition. Ils sont
	 * figes depuis le clavier IBM de 1984 et ne bougeront plus.
	 *
	 * Table ecrite a la main plutot que deduite d'un HKL americain charge a la
	 * volee : LoadKeyboardLayout ajoute la disposition a celles du systeme, et un
	 * jeu n'a pas a toucher a la configuration clavier de la machine.
	 *
	 * Construite au PREMIER APPEL et pas en variable globale : les constantes de
	 * EKeys sont initialisees par le moteur au demarrage, et une table globale
	 * pourrait naitre avant elles.
	 */
	static const TMap<FName, uint32>& GetReferenceScanCodes()
	{
		static const TMap<FName, uint32> ScanCodes = []
		{
			TMap<FName, uint32> Map;
			Map.Reserve(48);

			// Rangee des chiffres
			Map.Add(EKeys::Tilde.GetFName(), 0x29);
			Map.Add(EKeys::One.GetFName(), 0x02);
			Map.Add(EKeys::Two.GetFName(), 0x03);
			Map.Add(EKeys::Three.GetFName(), 0x04);
			Map.Add(EKeys::Four.GetFName(), 0x05);
			Map.Add(EKeys::Five.GetFName(), 0x06);
			Map.Add(EKeys::Six.GetFName(), 0x07);
			Map.Add(EKeys::Seven.GetFName(), 0x08);
			Map.Add(EKeys::Eight.GetFName(), 0x09);
			Map.Add(EKeys::Nine.GetFName(), 0x0A);
			Map.Add(EKeys::Zero.GetFName(), 0x0B);
			Map.Add(EKeys::Hyphen.GetFName(), 0x0C);
			Map.Add(EKeys::Equals.GetFName(), 0x0D);

			// Rangee du haut
			Map.Add(EKeys::Q.GetFName(), 0x10);
			Map.Add(EKeys::W.GetFName(), 0x11);
			Map.Add(EKeys::E.GetFName(), 0x12);
			Map.Add(EKeys::R.GetFName(), 0x13);
			Map.Add(EKeys::T.GetFName(), 0x14);
			Map.Add(EKeys::Y.GetFName(), 0x15);
			Map.Add(EKeys::U.GetFName(), 0x16);
			Map.Add(EKeys::I.GetFName(), 0x17);
			Map.Add(EKeys::O.GetFName(), 0x18);
			Map.Add(EKeys::P.GetFName(), 0x19);
			Map.Add(EKeys::LeftBracket.GetFName(), 0x1A);
			Map.Add(EKeys::RightBracket.GetFName(), 0x1B);
			Map.Add(EKeys::Backslash.GetFName(), 0x2B);

			// Rangee de repos
			Map.Add(EKeys::A.GetFName(), 0x1E);
			Map.Add(EKeys::S.GetFName(), 0x1F);
			Map.Add(EKeys::D.GetFName(), 0x20);
			Map.Add(EKeys::F.GetFName(), 0x21);
			Map.Add(EKeys::G.GetFName(), 0x22);
			Map.Add(EKeys::H.GetFName(), 0x23);
			Map.Add(EKeys::J.GetFName(), 0x24);
			Map.Add(EKeys::K.GetFName(), 0x25);
			Map.Add(EKeys::L.GetFName(), 0x26);
			Map.Add(EKeys::Semicolon.GetFName(), 0x27);
			Map.Add(EKeys::Apostrophe.GetFName(), 0x28);

			// Rangee du bas
			Map.Add(EKeys::Z.GetFName(), 0x2C);
			Map.Add(EKeys::X.GetFName(), 0x2D);
			Map.Add(EKeys::C.GetFName(), 0x2E);
			Map.Add(EKeys::V.GetFName(), 0x2F);
			Map.Add(EKeys::B.GetFName(), 0x30);
			Map.Add(EKeys::N.GetFName(), 0x31);
			Map.Add(EKeys::M.GetFName(), 0x32);
			Map.Add(EKeys::Comma.GetFName(), 0x33);
			Map.Add(EKeys::Period.GetFName(), 0x34);
			Map.Add(EKeys::Slash.GetFName(), 0x35);

			return Map;
		}();

		return ScanCodes;
	}

#endif // PLATFORM_WINDOWS

	FKey RemapKeyToActiveLayout(const FKey& ReferenceKey)
	{
#if PLATFORM_WINDOWS
		const uint32* ScanCode = GetReferenceScanCodes().Find(ReferenceKey.GetFName());
		if (!ScanCode)
		{
			// Touche insensible a la disposition : espace, modificateur, manette.
			return ReferenceKey;
		}

		// GetKeyboardLayout(0) = la disposition du thread appelant, donc celle que
		// le joueur a reellement active -- et pas celle par defaut du systeme, qui
		// differe des qu'il en a plusieurs et bascule au clavier.
		const HKL ActiveLayout = ::GetKeyboardLayout(0);

		const UINT VirtualKey = ::MapVirtualKeyExW(*ScanCode, MAPVK_VSC_TO_VK_EX, ActiveLayout);
		if (VirtualKey == 0)
		{
			// La disposition n'a rien a cette position. Rare, mais possible sur les
			// dispositions non latines : on garde la touche d'origine.
			return ReferenceKey;
		}

		// On REPRODUIT ici, a l'identique, le couple que le moteur recevra au
		// runtime : FWindowsApplication passe le virtual key ET le caractere non
		// modifie rendu par MAPVK_VK_TO_CHAR. Deduire le CharCode du virtual key
		// serait faux des que la touche ne porte pas le caractere de son code --
		// et c'est precisement le cas de la rangee des chiffres en AZERTY, ou
		// Windows garde VK_1 pour une touche qui produit "&". Le moteur y repond
		// EKeys::Ampersand ; simuler EKeys::One nous ferait conclure qu'il n'y a
		// rien a convertir, et les dix touches de la barre resteraient muettes.
		//
		// Le bit de poids fort signale une touche morte (l'accent circonflexe d'un
		// AZERTY, par exemple) : on le masque, seul le caractere nous interesse.
		const UINT CharCode = ::MapVirtualKeyExW(VirtualKey, MAPVK_VK_TO_CHAR, ActiveLayout) & 0x7FFFFFFF;

		const FKey MappedKey = FInputKeyManager::Get().GetKeyFromCodes(VirtualKey, CharCode);

		// Une touche introuvable dans les deux tables est SYNTHETISEE par le moteur,
		// et elle sort marquee non bindable -- l'assigner donnerait une action
		// definitivement muette. C'est le seul cas ou l'on prefere une touche fausse
		// a une touche morte.
		if (!MappedKey.IsValid() || !MappedKey.IsBindableToActions())
		{
			return ReferenceKey;
		}

		return MappedKey;
#else
		return ReferenceKey;
#endif
	}

	FString GetActiveLayoutId()
	{
#if PLATFORM_WINDOWS
		// 16 et pas KL_NAMELENGTH : l'API en ecrit 9 au plus, et un buffer en dur
		// evite de dependre d'une macro que WIN32_LEAN_AND_MEAN pourrait retirer.
		WCHAR LayoutName[16] = {};
		if (::GetKeyboardLayoutNameW(LayoutName))
		{
			return FString(LayoutName);
		}

		return TEXT("inconnue");
#else
		return TEXT("non gere sur cette plateforme");
#endif
	}
}
