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

        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.

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



/*
Trucs rajoutes dans le project pour Visual C++ 5.0:

Settings for All Configurations:
	o General:
      - Intermediate files: debug
		- Output files: plus rien.
	o C/C++:
		- General:
			. Generate browse info
		- Code Generation:
			. Alignement: 1 Byte
			. Run-time: (Debug) Multi-Threaded
	o Link:
		- Object/Library modules: dinput.lib dsound.lib mixer_a.obj splhan_a.obj os_a.obj
	o Browse:
      - Build browse info file

Settings en mode Release:
	o C/C++:
		- Optimizations:
			. Customize: Global, intrinsic, fast code, full opt
			. Inline: any suitable
*/


#define	OS_CURRENT_MODULE
#define	MODULE_OS



/*\\\ FICHIERS INCLUDE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

#include	<assert.h>
#include	<stdio.h>
#include	<time.h>
#include	<process.h>
//#include	<math.h>	// Uniquement pour le test du module ReconstructionFilter
//#include	<math.h>	// Uniquement pour le test du module filter.cpp

/* Le label suivant ne doit etre defini que dans un seul module pour
   DirectDraw (pour pouvoir utiliser QueryInterface), et que dans un
   seul module pour DirectInput. */
#define	INITGUID
#define	WIN32_LEAN_AND_MEAN
#define	NOMINMAX
#if ! defined (DIRECTINPUT_VERSION)
	#define	DIRECTINPUT_VERSION	0x0800
#endif
#include <windows.h>
#include <windowsx.h>
#include	<shellapi.h>
#include	<dinput.h>

#include	"archi.h"
#include	"base.h"
#include	"base_ct.h"
//#include	"ConfigFile.h"	// Uniquement pour le test du module de lecture de fichier de config
#include	"file.h"
#include	"fnames.h"
#include	"gtracker.h"
#include	"intrface.h"
//#include	"List.h"					// Uniquement pour le test du module de lecture de fichier de config
#include	"log.h"
#include	"mixer.h"
#include	"os.h"
#include	"os_di.h"
#include	"player.h"
//#include	"ReconstructionFilter.h"	// Uniquement pour le test du module ReconstructionFilter
#include	"sdrv_dx.h"
#include	"SoundDriver.h"
#include	"Thread.h"
#include	"windows_resources/resource.h"

// Provisoire
#include	"memory.h"



/*\\\ CONSTANTES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

#define	OS_WINDOW_NAME_MAXLEN		255
#define	OS_WINDOW_TITLE_MAXLEN		255
#define	OS_MAX_COMMAND_LINE_ARGC	1023


/*\\\ TYPES & STRUCTURES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ PROTOTYPES DES FONCTIONS PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

signed int OS_create_window (::HINSTANCE hInstance, int nCmdShow);
::LRESULT CALLBACK	OS_window_proc (::HWND hWnd, ::UINT message, ::WPARAM wParam, ::LPARAM lParam);
int	OS_get_real_mouse_button_state (void);
void	OS_main_thread_routine (void);

inline void	OS_get_cpu_clock_time (__int64 *time_64_ptr);



/*\\\ VARIABLES PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

/* Parametres transmis a GTK_main () */
int	OS_application_argc;
char	*OS_application_argv [OS_MAX_COMMAND_LINE_ARGC+1];

wchar_t	OS_window_name_0 [OS_WINDOW_NAME_MAXLEN+1] = L"GTracker";

int	OS_graph_mode_state = 0;	/* 0 = ancien mode, 1 = mode graphique, 2 = changement */

clock_t	OS_clock_ticks_per_sec_beg_time;	/* Horraire de depart du comptage de cycles d'horloge par seconde */

::HMENU	OS_window_menu = NULL;

static const wchar_t OS_registry_root_0 [] = L"Graoumf Tracker 2";
static const wchar_t OS_registry_mru_mod_0 [] = L"Recent Modules";



/*\\\ VARIABLES EXTERNES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

/* Handle de la fenetre, pour Windows. Reserve en fait
   uniquement aux sous-modules de OS. */
::HINSTANCE	OS_application_instance;	/* Instance de l'application Graoumf Tracker */
::HWND	OS_window_handle;
bool	OS_active_application_flag = true;	/* Indique que l'application est active */

::IDirectInput8W *	OS_di_object_ptr = NULL;



class OS_ThreadStub
:	public ThreadCbInterface
{
public:
	// ThreadCbInterface
	virtual void	do_run_thread ()
	{
		OS_main_thread_routine ();
	}
};

OS_ThreadStub	OS_main_thread_stub;
Thread	OS_main_thread (OS_main_thread_stub);

/* Donnees concernant la carte son */
SoundDriver	OS_sound_driver;	// Le driver du son
LWORD	OS_min_replay_freq;	/* Frequence minimum de replay */
LWORD	OS_max_replay_freq;	/* Frequence maximum de replay */
int	OS_nbr_play_config;	/* Nombre de configurations de replay */
LWORD	OS_play_config [OS_MAX_PLAY_CONFIG];	/* Configurations de replay */

QWORD	OS_clock_ticks_per_sec = 1;	/* Nombre de cycles d'horloge par seconde */
bool	OS_clock_ticks_per_sec_counted_flag = false;	/* Indique si on a mesure le nombre de cycles d'horloge par seconde */

int	OS_cpu_time = 0;	/* Estimation du temps CPU utilise, en % */



/*\\\ FONCTIONS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

/*==========================================================================*/
/*      Nom: WinMain                                                        */
/*      Description: C'est la fonction par laquelle on rentre dans le       */
/*                   programme.                                             */
/*==========================================================================*/

int CALLBACK	WinMain (::HINSTANCE hInstance, ::HINSTANCE hPrevInstance, ::LPSTR lpCmdLine, int nCmdShow)
{

/*______________________________________________
 *
 * Version normale
 *______________________________________________
 */

#if 1

	::MSG		msg;
	bool		exit_flag;

#if defined (OS_USE_CPU_FREQUENCY)
	/* On commence deja a regarder le nombre de cycles par seconde du processeur */
	OS_get_cpu_clock_time (&OS_clock_ticks_per_sec);
	OS_clock_ticks_per_sec_beg_time = clock ();
#endif

	OS_application_instance = hInstance;

	/* Recuperation de la ligne de commandes au format standard */
	const int      max_cl_len  = 32767;
	const std::string cmd_line = BASE_conv_utf16_to_utf8 (GetCommandLineW ());
	char          cmd_line_0 [max_cl_len + 1];
	strncpy (cmd_line_0, cmd_line.c_str (), max_cl_len);
	cmd_line_0 [max_cl_len] = '\0';
	OS_application_argc = BASE_parse_command_line (
		cmd_line_0,
		OS_application_argv,
		OS_MAX_COMMAND_LINE_ARGC,
		NULL
	);

	/* Choisit le bon driver pour le son */
	OS_sound_driver._init          = SDRVDX_init;
	OS_sound_driver._restore       = SDRVDX_restore;
	OS_sound_driver._set_config    = SDRVDX_set_config;
	OS_sound_driver._get_config    = SDRVDX_get_config;
	OS_sound_driver._start_replay  = SDRVDX_start_replay;
	OS_sound_driver._stop_replay   = SDRVDX_stop_replay;
	OS_sound_driver._set_new_frame = SDRVDX_set_new_frame;
	OS_sound_driver._set_data      = SDRVDX_set_data;
	OS_sound_driver._get_data      = SDRVDX_get_data;
	OS_sound_driver._is_playing    = SDRVDX_is_playing;
	OS_sound_driver._disconnect    = SDRVDX_disconnect;
	OS_sound_driver._connect       = SDRVDX_connect;
	OS_sound_driver._is_connected  = SDRVDX_is_connected;
	OS_sound_driver._get_message   = SDRVDX_get_message;
	OS_sound_driver._get_latency   = SDRVDX_get_latency;
	OS_sound_driver._set_latency   = SDRVDX_set_latency;

#if 0 && defined (NDEBUG)
	/* Recupere le handle du process */
	::DWORD        process_id = _getpid ();
	::HANDLE       process_handle =
		::OpenProcess (STANDARD_RIGHTS_REQUIRED, false, process_id);
	if (process_handle != NULL)
	{
		/* Passe le processus en mode de priorite haute */
		if (::SetPriorityClass (process_handle, HIGH_PRIORITY_CLASS) == 0)
		{
			LOG_printf ("Warning: Couldn't set High Priority for process.\n");
		}
		::CloseHandle (process_handle);
	}
	else
	{
		LOG_printf ("Warning: Couldn't get process handle to set High Priority.\n");
	}
#endif

	/* Creation de la fenetre de travail */
	if (::OS_create_window (hInstance, nCmdShow))
	{
		LOG_printf ("WinMain: Error: Couldn't create application window.\n");
		return (-1);
	}

	/* Genere un thread pour le main */
	if (::OS_main_thread.spawn ())
	{
		LOG_printf ("WinMain: Error: Couldn't spawn main thread.\n");
		return (-1);
	}

	/* Boucle de la fenetre */
	exit_flag = false;
	while (! exit_flag)
	{
		/* Traitement d'un message */
		while (   ::PeekMessageW (&msg, NULL, 0, 0, PM_NOREMOVE) != 0
		       && ! exit_flag)
		{
			if (::GetMessageW (&msg, NULL, 0, 0) == 0)
			{
				exit_flag = true;
			}

			else
			{
				::TranslateMessage (&msg);
				::DispatchMessageW (&msg);
			}
		}

		/* Comme on n'a rien a faire pour l'instant, on attend seulement
		   qu'il y ait un message qui arrive. */
		if (! exit_flag)
		{
			::WaitMessage ();
		}
	}

	return (int (msg.wParam));

/*______________________________________________
 *
 * Version de test de la classe
 * ReconstructionFilter
 *______________________________________________
 */

#elif 0

	ReconstructionFilter	rf_test (128);
	SWORD		source [1000];
	SLWORD	dest [20000] [2];
	int		i;
	int		length;
	double	invr;
	int		len_1;
	int		len_2;
	double	s_pos;
	double	temp_dbl;
	int		tot_len;

	memset (source, 0, 1000 * sizeof (*source));
	memset (dest, 0, 20000 * 2 * sizeof (**dest));
	tot_len = 1000;	// <= 1000

	freopen ("sample1.txt", "w", stdout);
	for (i = 0; i < tot_len; i ++)
	{
		source [i] = (SWORD) (sin ((double)i * 2 * PI / 20) * 32767);
//		source [i] = (i >= 100 ? 20000: 0);
//		source [i] = 10000;
		printf ("%d\n", source [i]);
	}

	invr = 0.25;	// < 1: sur-echantillonne
	length = (int) ceil (tot_len / invr);
	len_1 = length / 2;
	rf_test.resample_and_mix
	(
		source,		// sptr
		dest [0],	// dptr
		len_1,		// dlen
		1,				// schn
		2,				// dchn
		0x8000,		// lvol
		0x8000,		// rvol
		false,		// Courbe non cassee
		0x00000000,	// posfrac
		invr,			// invresampfactor
		16				// sbits
	);

	s_pos = len_1 * invr;
	len_2 = length - len_1;
	rf_test.resample_and_mix
	(
		source + (int)s_pos,		// sptr
		dest [len_1],	// dptr
		len_2,		// dlen
		1,				// schn
		2,				// dchn
		0x8000,		// lvol
		0x8000,		// rvol
		false,		// Courbe non cassee
		(ULWORD) (modf (s_pos, &temp_dbl) * 4294967296.0),	// posfrac
		invr,			// invresampfactor
		16				// sbits
	);

	freopen ("sample2.txt", "w", stdout);
	for (i = 0; i < length; i ++)
	{
		printf ("%d\n", MAX (MIN (dest [i] [1] / 256, 32767), -32768));
	}

	return (0);

/*______________________________________________
 *
 * Version de test du module filter
 *______________________________________________
 */

#elif 0

	#include	"filter.h"

	double	l_num [10];
	double	l_den [10];
	double	z_num [10];
	double	z_den [10];

	double	buf_y [16];
	SLWORD	source [44100];
	SLWORD	dest [44100];
	SLWORD	buf_x [16];
	int		nbr_coef;
	int		buffer_pos;
	long		i;
	double	src_power;
	double	dest_power;
	int		test;
	clock_t	init_time;
	clock_t	final_time;
	double	seconds;

	l_num [0] = 1;
	l_den [0] = 1;
	nbr_coef = 1;
	FILT_add_biquad (l_num, l_den, nbr_coef, 44100, 1000, 1000, 0.5, FILT_BIQUAD_LOWP);
	FILT_laplace_to_z (l_num, l_den, nbr_coef, 44100, 1000, z_num, z_den);
	FILT_z_to_diff_eq (z_num, z_den, nbr_coef, z_num, z_den);

	for (i = 0; i < 44100; i ++)
	{
		source [i] = (SLWORD) (sin (i * 2 * PI * 100 / 44100) * 0x7FFFFF);
	}
	memset (buf_x, 0, 16 * sizeof (*buf_x));
	memset (buf_y, 0, 16 * sizeof (*buf_y));

	buffer_pos = 0;
	init_time = clock ();
	for (test = 0; test < 1000; test ++)
	{
		FILT_do_iir_filtering (source, dest, 1, 1, z_num, z_den, nbr_coef,
		                       buf_x, buf_y, buffer_pos, 16, 44100);
	}
	final_time = clock ();
	seconds = (double) (final_time - init_time) / CLOCKS_PER_SEC;	// 34.510 pour un ordre 4

	src_power = 0;
	dest_power = 0;
	for (i = 0; i < 44100; i ++)
	{
		src_power += (double)source [i] * source [i];
		dest_power += (double)dest [i] * dest [i];
	}
	src_power /= 44100;
	dest_power /= 44100;

	return (0);

/*______________________________________________
 *
 * Autre
 *______________________________________________
 */

#else







	return (0);

#endif
}



signed int OS_create_window (HINSTANCE hInstance, int nCmdShow)
{
	::WNDCLASSEXW  wc;

	/* Fabrique le menu */
	OS_window_menu = ::CreateMenu ();
	if (! OS_window_menu)
	{
		LOG_printf ("OS_create_window: Error: menu creation failed.\n");
		return (-1);
	}

	/* Met en place une nouvelle classe de fenetre */
	wc.cbSize        = sizeof (wc);
	wc.style         = CS_BYTEALIGNCLIENT | CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc   = OS_window_proc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hInstance;
	wc.hIcon         = LoadIconA (NULL, IDI_APPLICATION);
	wc.hCursor       = NULL;	// LoadCursor (NULL, IDC_ARROW);
	wc.hbrBackground = NULL;
	wc.lpszMenuName  = OS_window_name_0;
	wc.lpszClassName = OS_window_name_0;
	wc.hIconSm       = LoadIconA (NULL, IDI_APPLICATION);
	::RegisterClassExW (&wc);

	/* Cree la fenetre */
	OS_window_handle = ::CreateWindowExW (
		WS_EX_APPWINDOW | WS_EX_OVERLAPPEDWINDOW | WS_EX_CONTEXTHELP,
		OS_window_name_0,
		L"Graoumf Tracker",
	   WS_OVERLAPPEDWINDOW | WS_VISIBLE,
	   CW_USEDEFAULT, 0,
	   0, 0,
	   NULL, NULL, hInstance, NULL
	);
	if (! OS_window_handle)
	{
		LOG_printf ("OS_create_window: Error: window creation failed.\n");
		return (-1);
	}

	/* Accepte les fichiers en Drag and Drop */
	::DragAcceptFiles (OS_window_handle, TRUE);

	::ShowWindow (OS_window_handle, nCmdShow);
	::UpdateWindow (OS_window_handle);

	return (0);
}



::LRESULT CALLBACK	OS_window_proc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	::LRESULT      ret_val;
	::PAINTSTRUCT  ps;
	::HDC          hdc;
	Mouse_CURRENT_STATE	mouse;

	switch (message)
	{
	/* Activation ou desactivation de la fenetre */
	case	WM_ACTIVATEAPP:
		::UpdateWindow (OS_window_handle);

		/* Desactivation */
		if (wParam == 0)
		{
			if (OS_active_application_flag)
			{
				OS_active_application_flag = false;

				/* On stoppe le thread qui s'occupe de gerer l'interface */
				DINP_keyboard_mutex.lock ();

				/* Si on n'est pas en mode play, on libere le peripherique son */
				Player &			player = Player::use_instance ();
				if (   (   player.get_play_mode () != Player::MODE_PATTERN
				        && player.get_play_mode () != Player::MODE_SONG)
					 || ! player.get_player_started_flag ())
				{
					OS_sound_driver.disconnect ();
				}
			}
		}
		
		/* Activation */
		else
		{
			ret_val = ::DefWindowProcW (hWnd, message, wParam, lParam);

			if (! OS_active_application_flag)
			{
				/* Reactive le son si on l'avait desactive */
				OS_sound_driver.connect ();

				/* Restoration du thread de gestion de l'interface */
				DINP_keyboard_mutex.unlock ();

				OS_active_application_flag = true;
			}

			return (ret_val);
		}
		return (0);

	/* Redraw du bitmap */
	case	WM_PAINT:
		hdc = ::BeginPaint (OS_window_handle, &ps);
		if (hdc == NULL)
		{
			LOG_printf ("OS_window_proc: Error: Couldn't get the display context.\n");
		}
		if (INTR_graph_ptr != NULL)
		{
			INTR_graph_ptr->paint (hdc);
		}
		::EndPaint (OS_window_handle, &ps);
		return (0);

	/* Fermeture normale */
	case	WM_CLOSE:
		GTK_ask_for_exit_flag = true;
		return (0);

	/* Fermeture forcee */
	case	WM_DESTROY:
		GTK_exit_now_flag = true;
		return (0);

	/* Gestion de la souris */
	case	WM_MOUSEMOVE:
		if (INTR_graph_ptr != NULL)
		{
			INTR_graph_ptr->set_mouse (LOWORD (lParam), HIWORD (lParam),
			                             (((wParam & MK_LBUTTON) != 0) ? 1 : 0)
			                           + (((wParam & MK_RBUTTON) != 0) ? 2 : 0)
			                           + (((wParam & MK_MBUTTON) != 0) ? 4 : 0));
		}
		return (0);

	case	WM_LBUTTONUP:
		::ReleaseCapture ();
		if (INTR_graph_ptr != NULL)
		{
			INTR_graph_ptr->get_mouse (&mouse);
			INTR_graph_ptr->set_mouse (LOWORD (lParam), HIWORD (lParam),
												mouse.k & 0x6);	// OS_get_real_mouse_button_state ());
		}
		return (0);

	case	WM_RBUTTONUP:
		::ReleaseCapture ();
		if (INTR_graph_ptr != NULL)
		{
			INTR_graph_ptr->get_mouse (&mouse);
			INTR_graph_ptr->set_mouse (LOWORD (lParam), HIWORD (lParam),
			                           mouse.k & 0x5);	// OS_get_real_mouse_button_state ());
		}
		return (0);

	case	WM_MBUTTONUP:
		::ReleaseCapture ();
		if (INTR_graph_ptr != NULL)
		{
			INTR_graph_ptr->get_mouse (&mouse);
			INTR_graph_ptr->set_mouse (LOWORD (lParam), HIWORD (lParam),
			                           mouse.k & 0x3);	// OS_get_real_mouse_button_state ());
		}
		return (0);

	case	WM_LBUTTONDOWN:
		::SetCapture (OS_window_handle);
		if (INTR_graph_ptr != NULL)
		{
			INTR_graph_ptr->get_mouse (&mouse);
			INTR_graph_ptr->set_mouse (LOWORD (lParam), HIWORD (lParam),
			                           (mouse.k & 0x6) + 1);	// OS_get_real_mouse_button_state ());
		}
		return (0);

	case	WM_RBUTTONDOWN:
		::SetCapture (OS_window_handle);
		if (INTR_graph_ptr != NULL)
		{
			INTR_graph_ptr->get_mouse (&mouse);
			INTR_graph_ptr->set_mouse (LOWORD (lParam), HIWORD (lParam),
			                           (mouse.k & 0x5) + 2);	// OS_get_real_mouse_button_state ());
		}
		return (0);

	case	WM_MBUTTONDOWN:
		::SetCapture (OS_window_handle);
		if (INTR_graph_ptr != NULL)
		{
			INTR_graph_ptr->get_mouse (&mouse);
			INTR_graph_ptr->set_mouse (LOWORD (lParam), HIWORD (lParam),
			                           (mouse.k & 0x3) + 4);	// OS_get_real_mouse_button_state ());
		}
		return (0);

	/* La fenetre a change de taille */
	case	WM_SIZE:
		if (wParam != SIZE_MINIMIZED)
		{
			GTK_new_res_width  = LOWORD (lParam);
			GTK_new_res_height = HIWORD (lParam);
			GTK_ask_for_resolution_change_flag = true;
		}
		return (0);

	/* Simple redraw */
	case	WM_MOVE:
		::UpdateWindow (OS_window_handle);
		return (0);

	/* On doit redonner la forme du curseur */
	case	WM_SETCURSOR:
		if (INTR_graph_ptr != NULL)
		{
			::SetCursor (INTR_graph_ptr->get_current_cursor_shape ());
		}
		else
		{
			::SetCursor (::LoadCursorA (NULL, IDC_APPSTARTING));
		}
		break;

	/* Un fichier a ete traine sur la fenetre */
	case	WM_DROPFILES:
		if (! GTK_ask_for_loading_module_flag)
		{
			const ::UINT   name_len =
				::DragQueryFileW ((::HDROP) wParam, 0, 0, 0);
			if (name_len > 0)
			{
				std::vector <wchar_t> buf (name_len + 1);
				::DragQueryFileW ((::HDROP) wParam, 0, &buf [0], name_len + 1);
				buf [name_len] = L'\0';
				const std::string name_utf8 = BASE_conv_utf16_to_utf8 (&buf [0]);
				strncpy (
					GTK_drag_and_drop_filename_0,
					name_utf8.c_str (),
					FNAM_PATHNAME_MAXLEN
				);
				GTK_drag_and_drop_filename_0 [FNAM_PATHNAME_MAXLEN] = '\0';

				::SetForegroundWindow (OS_window_handle);
				GTK_ask_for_loading_module_flag = true;
			}
		}
		return (0);
	}

	return (::DefWindowProcW (hWnd, message, wParam, lParam));
}



int	OS_get_real_mouse_button_state (void)
{
	int		k;

	/* Recupere l'etat des boutons */
	if (::GetSystemMetrics (SM_SWAPBUTTON))
	{
		k =   (::GetAsyncKeyState (VK_LBUTTON) & 0x8000) ? 2 : 0
		    + (::GetAsyncKeyState (VK_RBUTTON) & 0x8000) ? 1 : 0;
	}
	else
	{
		k =   (::GetAsyncKeyState (VK_LBUTTON) & 0x8000) ? 1 : 0
		    + (::GetAsyncKeyState (VK_RBUTTON) & 0x8000) ? 2 : 0;
	}
	k += (::GetAsyncKeyState (VK_MBUTTON) & 0x8000) ? 4 : 0;

	return (k);
}



/*==========================================================================*/
/*      Nom: OS_main_thread_routine                                         */
/*      Description: Thread principal. Il execute la partie principale du   */
/*                   programme (init, boucles, etc.)                        */
/*==========================================================================*/

void	OS_main_thread_routine (void)
{
	/* Fonction principale */
	GTK_main (OS_application_argc, OS_application_argv);

	/* On demande au thread pere de se suicider */
	if (::PostMessageW (OS_window_handle, WM_QUIT, 0, 0) == 0)
	{
		LOG_printf ("OS_main_thread: Error: couldn't send WM_QUIT message to main window.\n");
	}
}



void	OS_get_cpu_clock_time (__int64 *time_64_ptr)
{
	assert (time_64_ptr != 0);

	__asm
	{
		rdtsc
		mov				edi, time_64_ptr
		mov				[edi + 0], eax
		mov				[edi + 4], edx
	}
}



/****************************************************************************/
/*                                                                          */
/*      INITIALISATIONS                                                     */
/*                                                                          */
/****************************************************************************/



/*==========================================================================*/
/*      Nom: OS_first_init                                                  */
/*      Description: Premieres initialisations: recuperation du chemin      */
/*                   d'execution, initialisation du clavier...              */
/*      Retour: 0 si tout s'est bien passe.                                 */
/*==========================================================================*/

signed int	OS_first_init (void)
{
	/* Initialisation de Direct Input */
	if (DINP_init_direct_input (OS_application_instance))
	{
		LOG_printf ("OS_first_init: Error: DirectInput init failed.\n");
		return (-1);
	}

	/* Initialise le clavier */
	if (DINP_init_keyboard ())
	{
		LOG_printf ("OS_first_init: Error: keyboard init failed.\n");
		return (-1);
	}

	return (0);
}



void	OS_first_restore (void)
{
	DINP_restore_keyboard ();
	DINP_restore_direct_input ();
}



/*==========================================================================*/
/*      Nom: OS_init_sound_system                                           */
/*      Description: Initialise le systeme sonore.                          */
/*      Retour: 0 si tout va bien.                                          */
/*==========================================================================*/

signed int	OS_init_sound_system (void)
{
	SoundDriver_CONFIG	config;
	int		out_stereo [GTK_NBROUT_MAXI];
	int		in_stereo [GTK_NBRIN_MAXI];

	/* Initialise le driver */
	if (OS_sound_driver.init (OS_window_handle))
	{
		LOG_printf ("OS_init_sound_system: Error: couldn't init sound card.\n");
		return (-1);
	}

	/* C'est la configuration par defaut: 2 voies stereo en
		entree et en sortie. */
	config.sample_freq = 44100;
	config.nbr_buffers = 2;
	config.nbr_out = 2;
	config.nbr_in = 2;
	config.out_stereo_ptr = out_stereo;
	config.in_stereo_ptr = in_stereo;
	out_stereo [0] = 2;
	out_stereo [1] = 0;
	in_stereo [0] = 2;
	in_stereo [1] = 0;

	/* Fait evaluer la config */
	if (OS_sound_driver.set_config (&config))
	{
		LOG_printf ("OS_init_sound_system: Error: couldn't set configuration.\n");
		return (-1);
	}

	/* Verifie qu'il y a suffisamment de pistes de sortie et d'entree */
	if (config.nbr_out < 2)
	{
		LOG_printf ("OS_init_sound_system: Error: insufficient output tracks.\n");
		return (-1);
	}
	if (config.nbr_in < 2)
	{
		LOG_printf ("OS_init_sound_system: Error: insufficient input tracks.\n");
		return (-1);
	}

	/* La config est validee, on recupere les informations qui ont peut-etre
		ete corrigees. On ne s'occupe pas encore de la stereo de chaque piste
		car on la reglera plus tard (juste avant de demarrer le player) selon
		les preferences de l'utilisateur. */
	MIX_replay_freq = config.sample_freq;
	GTK_nbr_tracks [Pattern_TYPE_AIN] = config.nbr_in;
	GTK_nbr_tracks [GTK_TRACK_TYPE_AOU] = config.nbr_out;

	/* Maintenant, on reserve le peripherique */
	if (OS_sound_driver.connect ())
	{
		LOG_printf ("OS_init_sound_system: Warning: couldn't allocate sound device. Sound is disable for this session.\n");
		//return (-1);
	}

	return (0);
}



void	OS_restore_sound_system (void)
{
	OS_sound_driver.restore ();
}



/****************************************************************************/
/*                                                                          */
/*      CLAVIER ET SOURIS                                                   */
/*                                                                          */
/****************************************************************************/



/*==========================================================================*/
/*      Nom: OS_key_pressed                                                 */
/*      Description: Interroge le calvier pour voir si une touche a ete     */
/*                   enfoncee (buffer clavier non vide).                    */
/*      Retour: true si une touche a ete pressee, false sinon.              */
/*==========================================================================*/

bool	OS_key_pressed (void)
{
	return (DINP_key_pressed ());
}



/*==========================================================================*/
/*      Nom: OS_get_key                                                     */
/*      Description: Renvoie le code d'une touche ayant ete pressee, avec   */
/*                   l'etat des touches "auxiliaires".                      */
/*      Retour: Mot long decoupe en bits:                                   */
/*              0-7  : Code ASCII                                           */
/*              8    : Au moins une des touche Shift appuyee                */
/*              12-15: Bit du groupe de touche auquel appartient la touche. */
/*                     Ce champ n'est pas rempli par cette routine.         */
/*              16-23: code SCAN                                            */
/*              24   : Shift droit enfonce                                  */
/*              25   : Shift gauche enfonce                                 */
/*              26   : Control enfonce                                      */
/*              27   : Alternate enfonce                                    */
/*              28   : Caps Lock active                                     */
/*              29   : Num Lock active                                      */
/*              30   : Scroll Lock active                                   */
/*==========================================================================*/

LWORD	OS_get_key (void)
{
	return (DINP_get_key ());
}



/****************************************************************************/
/*                                                                          */
/*      DIVERS                                                              */
/*                                                                          */
/****************************************************************************/



/*==========================================================================*/
/*      Nom: OS_get_time                                                    */
/*      Description: Renvoie le nombre de cycles du processeur effectues    */
/*                   depuis l'allumage. Cette fonction n'est disponible que */
/*                   quand le nombre de cycles par seconde a ete mesure.    */
/*                   Cette routine ne doit etre utilisee que pour les       */
/*                   benchs.                                                */
/*      Retour: Nombre de cycles                                            */
/*==========================================================================*/

QWORD	OS_get_time (void)
{
	QWORD		time_64;

	assert (OS_clock_ticks_per_sec_counted_flag);

#if defined (OS_USE_CPU_FREQUENCY)

	OS_get_cpu_clock_time (&time_64);

#else

	::QueryPerformanceCounter ((LARGE_INTEGER *) &time_64);
	if (time_64 == 0)
	{
		time_64 = (QWORD) clock ();
	}

#endif

	return (time_64);
}



/*==========================================================================*/
/*      Nom: OS_processor_frequency_measurement                             */
/*      Description: Mesure du nombre de cycles d'horloge du processeur par */
/*                   seconde.                                               */
/*==========================================================================*/

/*** Changer tout ca et utiliser QueryPerformanceFrequency et
     QueryPerformanceCounter, qui sont plus propres. ***/

void	OS_processor_frequency_measurement (void)
{

#if defined (OS_USE_CPU_FREQUENCY)

	__int64	nbr_clock_ticks;
	clock_t	duration;

	if (! OS_clock_ticks_per_sec_counted_flag)
	{
		duration = 0;
		while (duration < CLOCKS_PER_SEC)
		{
			OS_get_cpu_clock_time (&nbr_clock_ticks);
			duration = clock () - OS_clock_ticks_per_sec_beg_time;
			if (duration < CLOCKS_PER_SEC)
			{
				Sleep (100);
			}
		}

		OS_clock_ticks_per_sec = (__int64) (  (  (double)(  nbr_clock_ticks
		                                                  - OS_clock_ticks_per_sec)
		                                       * CLOCKS_PER_SEC)
		                                    / duration);
		OS_clock_ticks_per_sec_counted_flag = true;
		LOG_printf ("OS_processor_frequency_measurement: Estimated processor frequency: %d MHz\n",
					   (int) (OS_clock_ticks_per_sec / 1000000));
	}

#else

	::QueryPerformanceFrequency ((LARGE_INTEGER *) &OS_clock_ticks_per_sec);

	/* Si on ne supporte pas la mesure precise de frequence, tant pis
	   on utilise l'horloge standard. */
	if (OS_clock_ticks_per_sec == 0)
	{
		OS_clock_ticks_per_sec = CLOCKS_PER_SEC;
	}

	OS_clock_ticks_per_sec_counted_flag = true;

#endif

}



/*==========================================================================*/
/*      Nom: OS_change_app_title                                            */
/*      Description: Change le titre de l'application (pour faire figurer   */
/*                   le nom du module par exemple).                         */
/*      Parametres en entree:                                               */
/*        - title_0: pointeur sur une chaine qui sera recopiee dans le      */
/*                   titre.                                                 */
/*==========================================================================*/

void	OS_change_app_title (const char *title_0)
{
	char		temp_0 [31+1];

	BASE_double_to_simple (temp_0, double (GTK_VERSION_NUMBER));

	std::wstring   title_utf16 = BASE_conv_utf8_to_utf16 (title_0);
	title_utf16 += L" - Graoumf Tracker r";
	BASE_push_back_utf8_to_utf16 (title_utf16, temp_0, false);
	
	::SetWindowTextW (OS_window_handle, title_utf16.c_str ());
}



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

signed int	OS_launch_html_file (const char *filename_0)
{
	const std::wstring   filename_utf16 = BASE_conv_utf8_to_utf16 (filename_0);
	const ::HINSTANCE result =
		::ShellExecuteW (NULL, L"open", filename_utf16.c_str (), NULL, L".", 0);
	if ((char *) result - (char *) 0 <= 32)
	{
		LOG_printf ("OS_launch_html_file: Error: couldn't open html browser.\n");
		return (-1);
	}

	return (0);
}



std::vector <std::string> OS_load_mru_mod_list ()
{
	std::vector <std::string>  mru_arr;

	std::wstring   path (L"Software\\");
	path += OS_registry_root_0;
	path += L"\\";
	path += OS_registry_mru_mod_0;

	::HKEY         key_mru;
	::LONG         res = ::RegOpenKeyExW (
		HKEY_CURRENT_USER,
		path.c_str (),
		0,
		KEY_READ,
		&key_mru
	);
	if (res == ERROR_SUCCESS)
	{
		std::vector <wchar_t>   pathname_utf16 (32767+1);
		int            pos = 0;
		do
		{
			const int      maxnamelen = 127;
			wchar_t        name_0 [maxnamelen + 1];
			swprintf (name_0, maxnamelen + 1, L"%d", pos + 1);

			::DWORD        buf_size =
				pathname_utf16.size () * sizeof (pathname_utf16 [0]);
			res = ::RegQueryValueExW (
				key_mru,
				name_0,
				0,
				0,
				reinterpret_cast <BYTE *> (&pathname_utf16 [0]),
				&buf_size
			);

			if (res == ERROR_SUCCESS)
			{
				const std::string pathname =
					BASE_conv_utf16_to_utf8 (&pathname_utf16 [0]);
				mru_arr.push_back (pathname);

				++ pos;
			}
		}
		while (res == ERROR_SUCCESS);

		::RegCloseKey (key_mru);
	}

	return mru_arr;
}



void OS_save_mru_mod_list (const std::vector <std::string> &mru)
{
	std::wstring   path (L"Software\\");
	path += OS_registry_root_0;

	::HKEY         key_root;
	::LONG         res = ::RegCreateKeyExW (
		HKEY_CURRENT_USER,
		path.c_str (),
		0,
		0,
		0,
		KEY_WRITE | DELETE | KEY_ENUMERATE_SUB_KEYS,
		0,
		&key_root,
		0
	);

	if (res == ERROR_SUCCESS)
	{
		// Erases all the previous values
		::RegDeleteTreeW (key_root, OS_registry_mru_mod_0);

		// Creates a new key and fills it with values
		if (res == ERROR_SUCCESS)
		{
			const int      nbr_files = std::min (int (mru.size ()), 20);
			for (int pos = 0; pos < nbr_files; ++ pos)
			{
				const int      maxnamelen = 127;
				wchar_t        name_0 [maxnamelen + 1];
				swprintf (name_0, maxnamelen + 1, L"%d", pos + 1);

				const std::wstring   filepath =
					BASE_conv_utf8_to_utf16 (mru [pos].c_str ());

				::RegSetKeyValueW (
					key_root,
					OS_registry_mru_mod_0,
					name_0,
					REG_SZ,
					filepath.c_str (),
					(filepath.length () + 1) * sizeof (wchar_t)
				);
			}
		}

		::RegCloseKey (key_root);
	}
}



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

