/*****************************************************************************

        GRAOUMF TRACKER 2
        Author: Laurent de Soras, 1996-2016

--- Legal stuff ---

This program is free software. It comes without any warranty, to
the extent permitted by applicable law. You can redistribute it
and/or modify it under the terms of the Do What The Fuck You Want
To Public License, Version 2, as published by Sam Hocevar. See
http://sam.zoy.org/wtfpl/COPYING for more details.

*****************************************************************************/



#define	DINP_CURRENT_MODULE
#define	MODULE_OS

#include	<stdio.h>

#if ! defined (DIRECTINPUT_VERSION)
	#define	DIRECTINPUT_VERSION	0x0800
#endif
#include	<dinput.h>

#include	"archi.h"
#include	"base.h"
#include	"base_ct.h"
#include	"log.h"
#include	"keyboard.h"
#include	"memory.h"
#include	"os.h"
#include	"os_di.h"



/* Constantes et marcos */

#define	DINP_KEYBOARD_DI_BUFFERSIZE	64	/* Nombre maximum d'evenements pour le buffer clavier DirectInput */
#define	DINP_KEYBOARD_BUFFERSIZE		64	/* Nombre maximum de touches dans le buffer */
enum
{
	DINP_KEYBOARD_AZERTY = 0
};

#define	DINP_BITSET(x,y,z)			((z) ? (x) |= (y) : (x) &= ~(y))



/* Types et structures */

typedef struct DINP_Pressed_key
{
	int		scan_code;		/* Code scan de la touche */
	struct DINP_Pressed_key	*next_ptr;	/* Pointeur sur la touche suivante (NULL si aucune suivante) */
} DINP_PRESSED_KEY;



/* Prototypes des fonctions privees */

void	DINP_clear_keyboard_buffer (void);
signed int	DINP_get_keyboard_envents (void);
void	DINP_put_key_in_buffer (int scan_code);



/* Variables reservees au module OS */

/* Permet de suspendre un thread qui utilise le clavier
	en prenant le mutex. Bien sur le thread ne se supend
	qu'a l'arrivee dans les routines du clavier. */
std::mutex	DINP_keyboard_mutex;



/* Variables privees */

::LPDIRECTINPUTDEVICE8W	DINP_keyboard_device_ptr = NULL;
LWORD	DINP_keyboard_shift = 0;	/* Flags des touches Shift, Alt, Ctrl, etc. */
DINP_PRESSED_KEY	*DINP_pressed_key_list_ptr = NULL;	/* Liste chainee des touches enfoncees */
unsigned long	DINP_keyboard_single_time = 500;	/* Nombre de ms entre l'appui de la touche et les repetitions */
unsigned long	DINP_keyboard_repeat_time = 50;	/* Nombre de ms entre deux repetitions */
unsigned long	DINP_pressed_key_time = 0;		/* Instant (ms) de l'activation de la touche courante */
bool	DINP_pressed_key_repeat_flag = false;	/* Indique si on est en cours de repetition de touche */
LWORD	DINP_keyboard_buffer [DINP_KEYBOARD_BUFFERSIZE];
int	DINP_keyboard_write_pos = 0;	/* Position d'ecriture dans le buffer clavier */
int	DINP_keyboard_read_pos = 0;	/* Position de lecture dans le buffer clavier */
char	DINP_keyboard_mapping_0 [4] [47+1] =
{
	/* Azerty */
	"&é\"\'(-è_çà)=azertyuiop^$qsdfghjklmù*<wxcvbn,;:!",
	"1234567890°+AZERTYUIOP¨£QSDFGHJKLM%µ>WXCVBN\?./§",
	"\0~#{[|`\\^@]}\0\0\0\0\0\0\0\0\0\0\0¤\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
	"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
};
char	DINP_keyborad_mapping_special [] =
{
	'\0',
	'\0',	// Esc
	'\0',	// Backspace
	KEYB_ASCIICODE_TAB, // Tab
	KEYB_ASCIICODE_RETURN, // Return
	'`',	// Backquote
	' ',	// Space
	'\0',	// Clr home
	'\0',	// Up
	'\0',	// Page Up
	'-',	// Pad minus
	'\0',	// Left
	'\0',	// Right
	'+',	// Pad plus
	'\0',	// End
	'\0',	// Down
	'\0',	// Page down
	'\0',	// Insert
	'\0',	// Delete
	'\0',	// Numlock
	'/',	// Pad slash
	'*',	// Pad star
	'7',	// Pad 7
	'8',	// Pad 8
	'9',	// Pad 9
	'1',	// Pad 1
	'2',	// Pad 2
	'3',	// Pad 3
	'0',	// Pad 0
	'.',	// Pad .
	KEYB_ASCIICODE_RETURN,	// Pad Enter
	'4',	// Pad 4
	'5',	// Pad 5
	'6'	// Pad 6
};


/* Fonctions */

signed int	DINP_init_direct_input (::HINSTANCE hInstance)
{
	int		scan_gt;
	int		scan_win;
	int		vk;
	int		res;
	int		shift_mode;
	HKL		kbd_layout;
	unsigned char	ascii [2];
	UBYTE		kbd_state [256];
	UBYTE		scan_get_to_scan_win [12+12+12+11] =
	{
		0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
		0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B,
		0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x2B,
		0x56, 0x2C, 0x2D, 0X2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35
	};

	/* Initialisation de Direct Input */
	if (FAILED (DirectInput8Create (
		hInstance,
		DIRECTINPUT_VERSION,
		IID_IDirectInput8W,
		reinterpret_cast <void **> (&OS_di_object_ptr),
		NULL
	)))
	{
		LOG_printf ("DINP_init_direct_input: Error: Couldn't initialize Direct Input.\n");
		return (-1);
	}

	/* Cree la table de correspondance Scan -> ASCII en fonction
		du clavier de la machine hote. */
	kbd_layout = ::GetKeyboardLayout (0);
	memset (kbd_state, 0, 256);
	for (shift_mode = 0; shift_mode < 4; shift_mode ++)
	{
		for (scan_gt = 0; scan_gt < 12+12+12+11; scan_gt ++)
		{
			kbd_state [VK_SHIFT] = (shift_mode & 1) ? 0x80 : 0;
			kbd_state [VK_MENU] = (shift_mode & 2) ? 0x80 : 0;
			kbd_state [VK_CONTROL] = (shift_mode & 2) ? 0x80 : 0;

			scan_win = scan_get_to_scan_win [scan_gt];
			vk = ::MapVirtualKeyExW (scan_win, 1, kbd_layout);
			res = ::ToAsciiEx (vk, 0, kbd_state, (WORD *) ascii, 0, kbd_layout);

			/* Touche morte: on la combine avec un espace */
			if (res == -1)
			{
				kbd_state [VK_SHIFT] = 0;
				kbd_state [VK_MENU] = 0;
				kbd_state [VK_CONTROL] = 0;
				vk = ' ';
				scan_win = MapVirtualKeyEx (vk, 0, kbd_layout);
				res = ToAsciiEx (vk, 0, kbd_state, (WORD *) ascii, 0, kbd_layout);
			}

			/* Conversion reussie */
			if (res == 1)
			{
				DINP_keyboard_mapping_0 [shift_mode] [scan_gt] = ascii [0];
			}
		}
	}

	return (0);
}



void	DINP_restore_direct_input (void)
{
	/* Rien a faire */
}



signed int	DINP_init_keyboard (void)
{
	::DIPROPDWORD	dipdw =
	{
		{
			sizeof (::DIPROPDWORD),
			sizeof (::DIPROPHEADER),
			0,
			DIPH_DEVICE
		},
		DINP_KEYBOARD_DI_BUFFERSIZE	/* Buffer de 16 evenements maximum */
	};

	/* Creation d'un object Direct Input pour le clavier */
	if (FAILED (OS_di_object_ptr->CreateDevice (GUID_SysKeyboard, &DINP_keyboard_device_ptr, NULL)))
	{
		LOG_printf ("DINP_init_keyboard: Error: Couldn't initialize keyboard.\n");
		return (-1);
	}

	/* Fixe le niveau de cooperation */
	if (FAILED (DINP_keyboard_device_ptr->SetCooperativeLevel (
		OS_window_handle,
		DISCL_NONEXCLUSIVE | DISCL_FOREGROUND
	)))
	{
		LOG_printf ("DINP_init_keyboard: Error: couldn't set keyboard cooperative level.\n");
		return (-1);
	}

	/* Fixe le format de donnees */
	if (FAILED (DINP_keyboard_device_ptr->SetDataFormat (&c_dfDIKeyboard)))
	{
		LOG_printf ("DINP_init_keyboard: Error: Couldn't set data format for keyboard.\n");
		return (-1);
	}
	
	/* Prepare le buffer */
	if (FAILED (DINP_keyboard_device_ptr->SetProperty (DIPROP_BUFFERSIZE, &dipdw.diph)))
	{
		LOG_printf ("DINP_init_keyboard: Error: couldn't set property for keyboard.\n");
		return (-1);
	}

// For some reason, it doesn't work when the exe is located in the
// program files directory and makes graoumf tracker close immediately.
// Not a big deal, we can acquire the device later.
// https://www.gamedev.net/topic/126210-directinputdevice8acquire-returns-e_accessdenied/
#if 0
	/* Demande l'acces aux donnees */
	if (FAILED (DINP_keyboard_device_ptr->Acquire ()))
	{
		LOG_printf ("DINP_init_keyboard: Error: Couldn't acquire data from keyboard.\n");
		return (-1);
	}
#endif

	return (0);
}



void	DINP_restore_keyboard (void)
{
	if (DINP_keyboard_device_ptr != NULL)
	{
		DINP_keyboard_device_ptr->Unacquire ();
		DINP_clear_keyboard_buffer ();
		DINP_keyboard_write_pos = 0;
		DINP_keyboard_read_pos = 0;
	}
}



void	DINP_clear_keyboard_buffer (void)
{
	DINP_PRESSED_KEY	*temp_ptr;

	/* Retire toutes les touches enfoncees du buffer */
	while (DINP_pressed_key_list_ptr != NULL)
	{
		temp_ptr = DINP_pressed_key_list_ptr->next_ptr;
		FREE (DINP_pressed_key_list_ptr);
		DINP_pressed_key_list_ptr = temp_ptr;
	}
	DINP_pressed_key_time = 0;
	DINP_pressed_key_repeat_flag = false;

	/* Efface l'etat de toutes les touches de modification */
	DINP_keyboard_shift = 0;
}



/* Cette routine doit etre une section critique */

signed int	DINP_get_keyboard_events (void)
{
	int		scan_code;
	bool		down_flag;
	unsigned long	current_time;
	unsigned long	delay;
	DWORD		count;
	DWORD		dwItems = DINP_KEYBOARD_DI_BUFFERSIZE;
	HRESULT	hr;
	DINP_PRESSED_KEY	*temp_ptr;
	DINP_PRESSED_KEY	**temp_ptr_ptr;
	::DIDEVICEOBJECTDATA	rgdod [DINP_KEYBOARD_DI_BUFFERSIZE];

	bool           acquired_flag = false;
	do
	{
		hr = DINP_keyboard_device_ptr->GetDeviceData (
			sizeof (rgdod [0]),
			rgdod,
			&dwItems,
			0
		);

		/* On a perdu le controle du clavier, il faut le recuperer
		   avant de pouvoir acquerir les donnees */
		if (   hr == DIERR_INPUTLOST
		    || hr == DIERR_NOTACQUIRED)
		{
			DINP_clear_keyboard_buffer ();	// On vide le buffer car on a pu manquer des evenements (touches relachees)
			if (FAILED (DINP_keyboard_device_ptr->Acquire ()))
			{
				::Sleep (50);
			}
		}
		else
		{
			acquired_flag = true;
		}
	}
	while (! acquired_flag);

	/* Le buffer est sature, on repart alors de 0 */
	if (hr == DI_BUFFEROVERFLOW)
	{
		DINP_clear_keyboard_buffer ();
	}

	/* Analyse les donnees du buffer une par une, dans l'ordre chronologique */
	for (count = 0; count < dwItems; count ++)
	{
		down_flag = ((rgdod [count].dwData & 0x80) != 0);
		scan_code = rgdod [count].dwOfs;
		switch (scan_code)
		{
		/* Touches de modification */
		case	DIK_LMENU:
		case	DIK_RMENU:
			DINP_BITSET (DINP_keyboard_shift, KEYB_ALT, down_flag);
			break;
		case	DIK_LSHIFT:
			DINP_BITSET (DINP_keyboard_shift, KEYB_LSHIFT, down_flag);
			DINP_BITSET (DINP_keyboard_shift, KEYB_SHIFT, DINP_keyboard_shift & (KEYB_LSHIFT | KEYB_RSHIFT));
			break;
		case	DIK_RSHIFT:
			DINP_BITSET (DINP_keyboard_shift, KEYB_RSHIFT, down_flag);
			DINP_BITSET (DINP_keyboard_shift, KEYB_SHIFT, DINP_keyboard_shift & (KEYB_LSHIFT | KEYB_RSHIFT));
			break;
		case	DIK_RCONTROL:
		case	DIK_LCONTROL:
			DINP_BITSET (DINP_keyboard_shift, KEYB_CTRL, down_flag);
			break;
		case	DIK_CAPITAL:
			DINP_BITSET (DINP_keyboard_shift, KEYB_CAPSLOCK, down_flag);
			break;
		case	DIK_SCROLL:
			DINP_BITSET (DINP_keyboard_shift, KEYB_SCROLLLOCK, down_flag);
			break;

		/* Touches enfoncees */
		default:
			switch (scan_code)
			{
			case	DIK_GRAVE:			scan_code = KEYB_SCANCODE_BCKQUOTE;		break;
			case	DIK_ESCAPE:			scan_code = KEYB_SCANCODE_ESC;			break;
			case	DIK_BACK:			scan_code = KEYB_SCANCODE_BACKSPACE;	break;
			case	DIK_TAB:				scan_code = KEYB_SCANCODE_TAB;			break;
			case	DIK_RETURN:			scan_code = KEYB_SCANCODE_RETURN;		break;
			case	DIK_SPACE:			scan_code = KEYB_SCANCODE_SPACE;			break;
			case	DIK_INSERT:			scan_code = KEYB_SCANCODE_INSERT;		break;
			case	DIK_DELETE:			scan_code = KEYB_SCANCODE_DELETE;		break;
			case	DIK_HOME:			scan_code = KEYB_SCANCODE_CLRHOME;		break;
			case	DIK_END:				scan_code = KEYB_SCANCODE_END;			break;
			case	DIK_PRIOR:			scan_code = KEYB_SCANCODE_PAGEUP;		break;
			case	DIK_NEXT:			scan_code = KEYB_SCANCODE_PAGEDOWN;		break;
			case	DIK_LEFT:			scan_code = KEYB_SCANCODE_LEFT;			break;
			case	DIK_RIGHT:			scan_code = KEYB_SCANCODE_RIGHT;			break;
			case	DIK_UP:				scan_code = KEYB_SCANCODE_UP;				break;
			case	DIK_DOWN:			scan_code = KEYB_SCANCODE_DOWN;			break;
			case	DIK_NUMLOCK:		scan_code = KEYB_SCANCODE_NUMLOCK;		break;
			case	DIK_DIVIDE:			scan_code = KEYB_SCANCODE_PADSLASH;		break;
			case	DIK_MULTIPLY:		scan_code = KEYB_SCANCODE_PADSTAR;		break;
			case	DIK_SUBTRACT:		scan_code = KEYB_SCANCODE_PADMINUS;		break;
			case	DIK_NUMPAD7:		scan_code = KEYB_SCANCODE_PAD7;			break;
			case	DIK_NUMPAD8:		scan_code = KEYB_SCANCODE_PAD8;			break;
			case	DIK_NUMPAD9:		scan_code = KEYB_SCANCODE_PAD9;			break;
			case	DIK_ADD:				scan_code = KEYB_SCANCODE_PADPLUS;		break;
			case	DIK_NUMPAD4:		scan_code = KEYB_SCANCODE_PAD4;			break;
			case	DIK_NUMPAD5:		scan_code = KEYB_SCANCODE_PAD5;			break;
			case	DIK_NUMPAD6:		scan_code = KEYB_SCANCODE_PAD6;			break;
			case	DIK_NUMPAD1:		scan_code = KEYB_SCANCODE_PAD1;			break;
			case	DIK_NUMPAD2:		scan_code = KEYB_SCANCODE_PAD2;			break;
			case	DIK_NUMPAD3:		scan_code = KEYB_SCANCODE_PAD3;			break;
			case	DIK_NUMPADENTER:	scan_code = KEYB_SCANCODE_PADENTER;		break;
			case	DIK_NUMPAD0:		scan_code = KEYB_SCANCODE_PAD0;			break;
			case	DIK_NUMPADPERIOD:	scan_code = KEYB_SCANCODE_PADPOINT;		break;

			default:
				/* Touches normales du clavier */
				if (scan_code >= DIK_1 && scan_code <= DIK_EQUALS)
				{
					scan_code += KEYB_SCANCODE_ROW1 - DIK_1;
				}
				else if (scan_code >= DIK_Q && scan_code <= DIK_RBRACKET)
				{
					scan_code += KEYB_SCANCODE_ROW2 - DIK_Q;
				}
				else if (scan_code >= DIK_A && scan_code <= DIK_APOSTROPHE)
				{
					scan_code += KEYB_SCANCODE_ROW3 - DIK_A;
				}
				else if (scan_code >= DIK_BACKSLASH && scan_code <= DIK_SLASH)
				{
					scan_code += KEYB_SCANCODE_ROW4 - DIK_BACKSLASH;
				}
				else if (scan_code >= DIK_F1 && scan_code <= DIK_F10)
				{
					scan_code += KEYB_SCANCODE_FUNC - DIK_F1;
				}
				else if (scan_code >= DIK_F11 && scan_code <= DIK_F15)
				{
					scan_code += KEYB_SCANCODE_FUNC + 10 - DIK_F11;
				}

				/* Touche non geree */
				else
				{
					continue;
				}

				break;
			}

			/* Cherche si cette touche existe deja */
			temp_ptr_ptr = &DINP_pressed_key_list_ptr;
			temp_ptr = *temp_ptr_ptr;
			while (temp_ptr != NULL)
			{
				if (temp_ptr->scan_code == scan_code)
				{
					break;
				}
				temp_ptr_ptr = &temp_ptr->next_ptr;
				temp_ptr = *temp_ptr_ptr;
			}

			/* Detruit la touche si elle existait deja. */
			if (temp_ptr != NULL)
			{
				*temp_ptr_ptr = temp_ptr->next_ptr;
				FREE (temp_ptr);
			}

			/* Insere la touche dans la liste des touches
			   enfoncees si elle a ete enfoncee. */
			if (down_flag)
			{
				temp_ptr = (DINP_PRESSED_KEY *) MALLOC (sizeof (*temp_ptr));
				if (temp_ptr == NULL)
				{
					LOG_printf ("DINP_get_keyboard_events: Error: not enough memory to add a new key to the pressed key list.\n");
					return (-1);
				}	
				temp_ptr->scan_code = scan_code;
				temp_ptr->next_ptr = DINP_pressed_key_list_ptr;
				DINP_pressed_key_list_ptr = temp_ptr;
				DINP_pressed_key_time = GetTickCount ();
				DINP_pressed_key_repeat_flag = false;
				DINP_put_key_in_buffer (scan_code);
			}
			break;
		}	/* Fin du switch */
	}	/* Fin du for */

	/* Si une touche est maintenue appuyee, on regarde s'il faut
	   l'inserer dans le buffer (repetitions). */
	if (DINP_pressed_key_list_ptr != NULL)
	{
		current_time = GetTickCount ();
		delay = DINP_pressed_key_repeat_flag ? DINP_keyboard_repeat_time
									                : DINP_keyboard_single_time;
		while (current_time >= DINP_pressed_key_time + delay)
		{
			DINP_put_key_in_buffer (DINP_pressed_key_list_ptr->scan_code);
			DINP_pressed_key_time += delay;
			DINP_pressed_key_repeat_flag = true;
			delay = DINP_keyboard_repeat_time;
		}
	}
	
	return (0);
}



void	DINP_put_key_in_buffer (int scan_code)
{
	int		ascii_code;
	int		shift_mode;
	LWORD		key_code;

	/* Buffer sature ? */
	if ((DINP_keyboard_write_pos + 1) % DINP_KEYBOARD_BUFFERSIZE == DINP_keyboard_read_pos)
	{
		return;
	}

	/* Cherche le code ASCII qui correspond */
	shift_mode =   ((DINP_keyboard_shift & KEYB_SHIFT) != 0)
	             + (((DINP_keyboard_shift & KEYB_ALT) != 0) << 1);
	ascii_code = 0;
	if (scan_code >= KEYB_SCANCODE_ROW1)
	{
		ascii_code = DINP_keyboard_mapping_0 [shift_mode] [scan_code - KEYB_SCANCODE_ROW1];
	}
	else
	{
		ascii_code = DINP_keyborad_mapping_special [scan_code];
	}

	key_code = DINP_keyboard_shift | ((LWORD)scan_code << 16) | (ascii_code & 0xFF);

	DINP_keyboard_buffer [DINP_keyboard_write_pos] = key_code;
	DINP_keyboard_write_pos ++;
	DINP_keyboard_write_pos %= DINP_KEYBOARD_BUFFERSIZE;
}



bool	DINP_key_pressed (void)
{
	/*** Tant que la fenetre du GT n'est pas active, on bloque le thread. Le
		mutex aurait normalement servi a tous les coups mais DirectInput
		intercepte les Alt+Tab et le message de ->background n'est envoye que
		quand la souris passe au-dessus de GT (donc jamais si la nouvelle app
		est en fullscreen). Ca evite le "clavier musical". ***/
	while (::GetForegroundWindow () != OS_window_handle)
	{
		::Sleep (50);
	}

	{
		std::lock_guard <std::mutex>	lock (DINP_keyboard_mutex);
		DINP_get_keyboard_events ();
	}

	return (DINP_keyboard_read_pos != DINP_keyboard_write_pos);
}



LWORD	DINP_get_key (void)
{
	LWORD		ret_code;

	/* Attend qu'une donnee se presente au clavier */
	while (! DINP_key_pressed ())
	{
		::Sleep (50);
	}

	ret_code = DINP_keyboard_buffer [DINP_keyboard_read_pos];
	DINP_keyboard_read_pos ++;
	DINP_keyboard_read_pos %= DINP_KEYBOARD_BUFFERSIZE;

	return (ret_code);
}



/*==========================================================================*/
/*      Nom:                                                                */
/*      Description:                                                        */
/*      Parametres en entree:                                               */
/*      Parametres en sortie:                                               */
/*      Parametres en entree/sortie:                                        */
/*      Retour:                                                             */
/*==========================================================================*/
