/*
** i_system.cpp
** Timers, pre-console output, IWAD selection, and misc system routines.
**
**---------------------------------------------------------------------------
** Copyright 1998-2009 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/

// HEADER FILES ------------------------------------------------------------

#include <stdlib.h>
#include <stdio.h>
#include <io.h>
#include <direct.h>
#include <string.h>
#include <process.h>
#include <time.h>

#include <stdarg.h>
#include <sys/types.h>
#include <sys/timeb.h>

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <mmsystem.h>
#include <richedit.h>
#include <wincrypt.h>

#define USE_WINDOWS_DWORD
#include "hardware.h"
#include "doomerrors.h"
#include <math.h>

#include "doomtype.h"
#include "version.h"
#include "doomdef.h"
#include "cmdlib.h"
#include "m_argv.h"
#include "m_misc.h"
#include "i_video.h"
#include "i_sound.h"
#include "i_music.h"
#include "resource.h"
#include "x86.h"
#include "stats.h"

#include "d_main.h"
#include "d_net.h"
#include "g_game.h"
#include "i_input.h"
#include "i_system.h"
#include "c_dispatch.h"
#include "templates.h"
#include "gameconfigfile.h"
#include "v_font.h"
#include "g_level.h"
#include "doomstat.h"
#include "v_palette.h"
#include "stats.h"
#include "textures/bitmap.h"
#include "textures/textures.h"

// MACROS ------------------------------------------------------------------

#ifdef _MSC_VER
// Turn off "conversion from 'LONG_PTR' to 'LONG', possible loss of data"
// generated by SetClassLongPtr().
#pragma warning(disable:4244)
#endif

// TYPES -------------------------------------------------------------------

// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------

extern void CheckCPUID(CPUInfo *cpu);
extern void LayoutMainWindow(HWND hWnd, HWND pane);

// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------

// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------

static void CalculateCPUSpeed();
static void I_SelectTimer();

static int I_GetTimePolled(bool saveMS);
static int I_WaitForTicPolled(int prevtic);
static void I_FreezeTimePolled(bool frozen);
static int I_GetTimeEventDriven(bool saveMS);
static int I_WaitForTicEvent(int prevtic);
static void I_FreezeTimeEventDriven(bool frozen);
static void CALLBACK TimerTicked(UINT id, UINT msg, DWORD_PTR user, DWORD_PTR dw1, DWORD_PTR dw2);

static HCURSOR CreateCompatibleCursor(FTexture *cursorpic);
static HCURSOR CreateAlphaCursor(FTexture *cursorpic);
static HCURSOR CreateBitmapCursor(int xhot, int yhot, HBITMAP and_mask, HBITMAP color_mask);
static void DestroyCustomCursor();

// EXTERNAL DATA DECLARATIONS ----------------------------------------------

EXTERN_CVAR(String, language);
EXTERN_CVAR (Bool, queryiwad);

extern HWND Window, ConWindow, GameTitleWindow;
extern HANDLE StdOut;
extern bool FancyStdOut;
extern HINSTANCE g_hInst;
extern FILE *Logfile;
extern bool NativeMouse;

// PUBLIC DATA DEFINITIONS -------------------------------------------------

CVAR (String, queryiwad_key, "shift", CVAR_GLOBALCONFIG|CVAR_ARCHIVE);

double PerfToSec, PerfToMillisec;
UINT TimerPeriod;
UINT TimerEventID;
UINT MillisecondsPerTic;
HANDLE NewTicArrived;
uint32 LanguageIDs[4];

int (*I_GetTime) (bool saveMS);
int (*I_WaitForTic) (int);
void (*I_FreezeTime) (bool frozen);

os_t OSPlatform;
bool gameisdead;

// PRIVATE DATA DEFINITIONS ------------------------------------------------

static ticcmd_t emptycmd;
static bool HasExited;

static DWORD basetime = 0;
// These are for the polled timer.
static DWORD TicStart;
static DWORD TicNext;
static int TicFrozen;

// These are for the event-driven timer.
static int tics;
static DWORD ted_start, ted_next;

static WadStuff *WadList;
static int NumWads;
static int DefaultWad;

static HCURSOR CustomCursor;

// CODE --------------------------------------------------------------------

//==========================================================================
//
// I_Tactile
//
// Doom calls it when you take damage, so presumably it could be converted
// to something compatible with force feedback.
//
//==========================================================================

void I_Tactile(int on, int off, int total)
{
  // UNUSED.
  on = off = total = 0;
}

//==========================================================================
//
// I_BaseTiccmd
//
// Returns an empty ticcmd. I have no idea why this should be system-
// specific.
//
//==========================================================================

ticcmd_t *I_BaseTiccmd()
{
	return &emptycmd;
}

// Stubs that select the timer to use and then call into it ----------------

//==========================================================================
//
// I_GetTimeSelect
//
//==========================================================================

static int I_GetTimeSelect(bool saveMS)
{
	I_SelectTimer();
	return I_GetTime(saveMS);
}

//==========================================================================
//
// I_WaitForTicSelect
//
//==========================================================================

static int I_WaitForTicSelect(int prevtic)
{
	I_SelectTimer();
	return I_WaitForTic(prevtic);
}

//==========================================================================
//
// I_SelectTimer
//
// Tries to create a timer event for efficent CPU use when the FPS is
// capped. Failing that, it sets things up for a polling timer instead.
//
//==========================================================================

static void I_SelectTimer()
{
	assert(basetime == 0);

	// Use a timer event if possible.
	NewTicArrived = CreateEvent(NULL, FALSE, FALSE, NULL);
	if (NewTicArrived)
	{
		UINT delay;
		const char *cmdDelay;

		cmdDelay = Args->CheckValue("-timerdelay");
		delay = 0;
		if (cmdDelay != 0)
		{
			delay = atoi(cmdDelay);
		}
		if (delay == 0)
		{
			delay = 1000/TICRATE;
		}
		MillisecondsPerTic = delay;
		TimerEventID = timeSetEvent(delay, 0, TimerTicked, 0, TIME_PERIODIC);
	}
	// Get the current time as the basetime.
	basetime = timeGetTime();
	// Set timer functions.
	if (TimerEventID != 0)
	{
		I_GetTime = I_GetTimeEventDriven;
		I_WaitForTic = I_WaitForTicEvent;
		I_FreezeTime = I_FreezeTimeEventDriven;
	}
	else
	{
		I_GetTime = I_GetTimePolled;
		I_WaitForTic = I_WaitForTicPolled;
		I_FreezeTime = I_FreezeTimePolled;
	}
}

//==========================================================================
//
// I_MSTime
//
// Returns the current time in milliseconds, where 0 is the first call
// to I_GetTime or I_WaitForTic.
//
//==========================================================================

unsigned int I_MSTime()
{
	assert(basetime != 0);
	return timeGetTime() - basetime;
}

//==========================================================================
//
// I_FPSTime
//
// Returns the current system time in milliseconds. This is used by the FPS
// meter of DFrameBuffer::DrawRateStuff(). Since the screen can display
// before the play simulation is ready to begin, this needs to be
// separate from I_MSTime().
//
//==========================================================================

unsigned int I_FPSTime()
{
	return timeGetTime();
}

//==========================================================================
//
// I_GetTimePolled
//
// Returns the current time in tics. If saveMS is true, then calls to
// I_GetTimeFrac() will use this tic as 0 and the next tic as 1.
//
//==========================================================================

static int I_GetTimePolled(bool saveMS)
{
	DWORD tm;

	if (TicFrozen != 0)
	{
		return TicFrozen;
	}

	tm = timeGetTime();
	if (basetime == 0)
	{
		basetime = tm;
	}
	if (saveMS)
	{
		TicStart = tm;
		TicNext = (tm * TICRATE / 1000 + 1) * 1000 / TICRATE;
	}

	return ((tm-basetime)*TICRATE)/1000;
}

//==========================================================================
//
// I_WaitForTicPolled
//
// Busy waits until the current tic is greater than prevtic. Time must not
// be frozen.
//
//==========================================================================

static int I_WaitForTicPolled(int prevtic)
{
	int time;

	assert(TicFrozen == 0);
	while ((time = I_GetTimePolled(false)) <= prevtic)
	{ }

	return time;
}

//==========================================================================
//
// I_FreezeTimePolled
//
// Freeze/unfreeze the timer.
//
//==========================================================================

static void I_FreezeTimePolled(bool frozen)
{
	if (frozen)
	{
		assert(TicFrozen == 0);
		TicFrozen = I_GetTimePolled(false);
	}
	else
	{
		assert(TicFrozen != 0);
		int froze = TicFrozen;
		TicFrozen = 0;
		int now = I_GetTimePolled(false);
		basetime += (now - froze) * 1000 / TICRATE;
	}
}

//==========================================================================
//
// I_GetTimeEventDriven
//
// Returns the current tick counter. This is incremented asynchronously as
// the timer event fires.
//
//==========================================================================

static int I_GetTimeEventDriven(bool saveMS)
{
	if (saveMS)
	{
		TicStart = ted_start;
		TicNext = ted_next;
	}
	return tics;
}

//==========================================================================
//
// I_WaitForTicEvent
//
// Waits on the timer event as long as the current tic is not later than
// prevtic.
//
//==========================================================================

static int I_WaitForTicEvent(int prevtic)
{
	assert(!TicFrozen);
	while (prevtic >= tics)
	{
		WaitForSingleObject(NewTicArrived, 1000/TICRATE);
	}
	return tics;
}

//==========================================================================
//
// I_FreezeTimeEventDriven
//
// Freeze/unfreeze the ticker.
//
//==========================================================================

static void I_FreezeTimeEventDriven(bool frozen)
{
	TicFrozen = frozen;
}

//==========================================================================
//
// TimerTicked
//
// Advance the tick count and signal the NewTicArrived event.
//
//==========================================================================

static void CALLBACK TimerTicked(UINT id, UINT msg, DWORD_PTR user, DWORD_PTR dw1, DWORD_PTR dw2)
{
	if (!TicFrozen)
	{
		tics++;
	}
	ted_start = timeGetTime ();
	ted_next = ted_start + MillisecondsPerTic;
	SetEvent(NewTicArrived);
}

//==========================================================================
//
// I_GetTimeFrac
//
// Returns the fractional amount of a tic passed since the most recently
// saved tic.
//
//==========================================================================

fixed_t I_GetTimeFrac(uint32 *ms)
{
	DWORD now = timeGetTime();
	if (ms != NULL)
	{
		*ms = TicNext;
	}
	DWORD step = TicNext - TicStart;
	if (step == 0)
	{
		return FRACUNIT;
	}
	else
	{
		fixed_t frac = clamp<fixed_t> ((now - TicStart)*FRACUNIT/step, 0, FRACUNIT);
		return frac;
	}
}

//==========================================================================
//
// I_WaitVBL
//
// I_WaitVBL is never used to actually synchronize to the vertical blank.
// Instead, it's used for delay purposes. Doom used a 70 Hz display mode,
// so that's what we use to determine how long to wait for.
//
//==========================================================================

void I_WaitVBL(int count)
{
	Sleep(1000 * count / 70);
}

//==========================================================================
//
// I_DetectOS
//
// Determine which version of Windows the game is running on.
//
//==========================================================================

void I_DetectOS(void)
{
	OSVERSIONINFOEX info;
	const char *osname;

	info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
	if (!GetVersionEx((OSVERSIONINFO *)&info))
	{
		// Retry with the older OSVERSIONINFO structure.
		info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
		GetVersionEx((OSVERSIONINFO *)&info);
	}

	switch (info.dwPlatformId)
	{
	case VER_PLATFORM_WIN32_WINDOWS:
		OSPlatform = os_Win95;
		if (info.dwMinorVersion < 10)
		{
			osname = "95";
		}
		else if (info.dwMinorVersion < 90)
		{
			osname = "98";
		}
		else
		{
			osname = "Me";
		}
		break;

	case VER_PLATFORM_WIN32_NT:
		OSPlatform = info.dwMajorVersion < 5 ? os_WinNT4 : os_Win2k;
		osname = "NT";
		if (info.dwMajorVersion == 5)
		{
			if (info.dwMinorVersion == 0)
			{
				osname = "2000";
			}
			if (info.dwMinorVersion == 1)
			{
				osname = "XP";
			}
			else if (info.dwMinorVersion == 2)
			{
				osname = "Server 2003";
			}
		}
		else if (info.dwMajorVersion == 6)
		{
			if (info.dwMinorVersion == 0)
			{
				if (info.wProductType == VER_NT_WORKSTATION)
				{
					osname = "Vista";
				}
				else
				{
					osname = "Server 2008";
				}
			}
			else if (info.dwMinorVersion == 1)
			{
				if (info.wProductType == VER_NT_WORKSTATION)
				{
					osname = "7";
				}
				else
				{
					osname = "Server 2008 R2";
				}
			}
			else if (info.dwMinorVersion == 2)	
			{
				// Microsoft broke this API for 8.1 so without jumping through hoops it won't be possible anymore to detect never versions aside from the build number, especially for older compilers.
				if (info.wProductType == VER_NT_WORKSTATION)
				{
					osname = "8 (or higher)";
				}
				else
				{
					osname = "Server 2012 (or higher)";
				}
			}
		}
		break;

	default:
		OSPlatform = os_unknown;
		osname = "Unknown OS";
		break;
	}

	if (OSPlatform == os_Win95)
	{
		Printf ("OS: Windows %s %lu.%lu.%lu %s\n",
				osname,
				info.dwMajorVersion, info.dwMinorVersion,
				info.dwBuildNumber & 0xffff, info.szCSDVersion);
	}
	else
	{
		Printf ("OS: Windows %s (NT %lu.%lu) Build %lu\n    %s\n",
				osname,
				info.dwMajorVersion, info.dwMinorVersion,
				info.dwBuildNumber, info.szCSDVersion);
	}

	if (OSPlatform == os_unknown)
	{
		Printf ("(Assuming Windows 2000)\n");
		OSPlatform = os_Win2k;
	}
}

//==========================================================================
//
// SubsetLanguageIDs
//
// Helper function for SetLanguageIDs.
//
//==========================================================================

static void SubsetLanguageIDs(LCID id, LCTYPE type, int idx)
{
	char buf[8];
	LCID langid;
	char *idp;

	if (!GetLocaleInfo(id, type, buf, 8))
		return;
	langid = MAKELCID(strtoul(buf, NULL, 16), SORT_DEFAULT);
	if (!GetLocaleInfo(langid, LOCALE_SABBREVLANGNAME, buf, 8))
		return;
	idp = (char *)(&LanguageIDs[idx]);
	memset (idp, 0, 4);
	idp[0] = tolower(buf[0]);
	idp[1] = tolower(buf[1]);
	idp[2] = tolower(buf[2]);
	idp[3] = 0;
}

//==========================================================================
//
// SetLanguageIDs
//
//==========================================================================

void SetLanguageIDs()
{
	size_t langlen = strlen(language);

	if (langlen < 2 || langlen > 3)
	{
		memset(LanguageIDs, 0, sizeof(LanguageIDs));
		SubsetLanguageIDs(LOCALE_USER_DEFAULT, LOCALE_ILANGUAGE, 0);
		SubsetLanguageIDs(LOCALE_USER_DEFAULT, LOCALE_IDEFAULTLANGUAGE, 1);
		SubsetLanguageIDs(LOCALE_SYSTEM_DEFAULT, LOCALE_ILANGUAGE, 2);
		SubsetLanguageIDs(LOCALE_SYSTEM_DEFAULT, LOCALE_IDEFAULTLANGUAGE, 3);
	}
	else
	{
		DWORD lang = 0;

		((BYTE *)&lang)[0] = (language)[0];
		((BYTE *)&lang)[1] = (language)[1];
		((BYTE *)&lang)[2] = (language)[2];
		LanguageIDs[0] = lang;
		LanguageIDs[1] = lang;
		LanguageIDs[2] = lang;
		LanguageIDs[3] = lang;
	}
}

//==========================================================================
//
// CalculateCPUSpeed
//
// Make a decent guess at how much time elapses between TSC steps. This can
// vary over runtime depending on power management settings, so should not
// be used anywhere that truely accurate timing actually matters.
//
//==========================================================================

void CalculateCPUSpeed()
{
	LARGE_INTEGER freq;

	QueryPerformanceFrequency (&freq);

	if (freq.QuadPart != 0 && CPU.bRDTSC)
	{
		LARGE_INTEGER count1, count2;
		cycle_t ClockCalibration;
		DWORD min_diff;

		ClockCalibration.Reset();

		// Count cycles for at least 55 milliseconds.
        // The performance counter may be very low resolution compared to CPU
        // speeds today, so the longer we count, the more accurate our estimate.
        // On the other hand, we don't want to count too long, because we don't
        // want the user to notice us spend time here, since most users will
        // probably never use the performance statistics.
        min_diff = freq.LowPart * 11 / 200;

		// Minimize the chance of task switching during the testing by going very
		// high priority. This is another reason to avoid timing for too long.
		SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
		SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);

		// Make sure we start timing on a counter boundary.
		QueryPerformanceCounter(&count1);
		do { QueryPerformanceCounter(&count2); } while (count1.QuadPart == count2.QuadPart);

		// Do the timing loop.
		ClockCalibration.Clock();
		do { QueryPerformanceCounter(&count1); } while ((count1.QuadPart - count2.QuadPart) < min_diff);
		ClockCalibration.Unclock();

		SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS);
		SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL);

		PerfToSec = double(count1.QuadPart - count2.QuadPart) / (double(ClockCalibration.GetRawCounter()) * freq.QuadPart);
		PerfToMillisec = PerfToSec * 1000.0;
	}

	Printf ("CPU Speed: %.0f MHz\n", 0.001 / PerfToMillisec);
}

//==========================================================================
//
// I_Init
//
//==========================================================================

void I_Init()
{
	CheckCPUID(&CPU);
	CalculateCPUSpeed();
	DumpCPUInfo(&CPU);

	I_GetTime = I_GetTimeSelect;
	I_WaitForTic = I_WaitForTicSelect;

	atterm (I_ShutdownSound);
	I_InitSound ();
}

//==========================================================================
//
// I_Quit
//
//==========================================================================

void I_Quit()
{
	HasExited = true;		/* Prevent infinitely recursive exits -- killough */

	if (TimerEventID != 0)
	{
		timeKillEvent(TimerEventID);
	}
	if (NewTicArrived != NULL)
	{
		CloseHandle(NewTicArrived);
	}
	timeEndPeriod(TimerPeriod);
	if (demorecording)
	{
		G_CheckDemoStatus();
	}

	C_DeinitConsole();
}


//==========================================================================
//
// I_FatalError
//
// Throw an error that will end the game.
//
//==========================================================================

void STACK_ARGS I_FatalError(const char *error, ...)
{
	static BOOL alreadyThrown = false;
	gameisdead = true;

	if (!alreadyThrown)		// ignore all but the first message -- killough
	{
		alreadyThrown = true;
		char errortext[MAX_ERRORTEXT];
		va_list argptr;
		va_start(argptr, error);
		myvsnprintf(errortext, MAX_ERRORTEXT, error, argptr);
		va_end(argptr);

		// Record error to log (if logging)
		if (Logfile)
		{
			fprintf(Logfile, "\n**** DIED WITH FATAL ERROR:\n%s\n", errortext);
			fflush(Logfile);
		}

		throw CFatalError(errortext);
	}

	if (!HasExited)		// If it hasn't exited yet, exit now -- killough
	{
		HasExited = 1;	// Prevent infinitely recursive exits -- killough
		exit(-1);
	}
}

//==========================================================================
//
// I_Error
//
// Throw an error that will send us to the console if we are far enough
// along in the startup process.
//
//==========================================================================

void STACK_ARGS I_Error(const char *error, ...)
{
	va_list argptr;
	char errortext[MAX_ERRORTEXT];

	va_start(argptr, error);
	myvsnprintf(errortext, MAX_ERRORTEXT, error, argptr);
	va_end(argptr);

	throw CRecoverableError(errortext);
}

//==========================================================================
//
// ToEditControl
//
// Converts string to Unicode and inserts it into the control.
//
//==========================================================================

void ToEditControl(HWND edit, const char *buf, wchar_t *wbuf, int bpos)
{
	// Let's just do this ourself. It's not hard, and we can compensate for
	// special console characters at the same time.
#if 0
	MultiByteToWideChar(1252 /* Western */, 0, buf, bpos, wbuf, countof(wbuf));
	wbuf[bpos] = 0;
#else
	static wchar_t notlatin1[32] =		// code points 0x80-0x9F
	{
		0x20AC,		// Euro sign
		0x0081,		// Undefined
		0x201A,		// Single low-9 quotation mark
		0x0192,		// Latin small letter f with hook
		0x201E,		// Double low-9 quotation mark
		0x2026,		// Horizontal ellipsis
		0x2020,		// Dagger
		0x2021,		// Double dagger
		0x02C6,		// Modifier letter circumflex accent
		0x2030,		// Per mille sign
		0x0160,		// Latin capital letter S with caron
		0x2039,		// Single left-pointing angle quotation mark
		0x0152,		// Latin capital ligature OE
		0x008D,		// Undefined
		0x017D,		// Latin capital letter Z with caron
		0x008F,		// Undefined
		0x0090,		// Undefined
		0x2018,		// Left single quotation mark
		0x2019,		// Right single quotation mark
		0x201C,		// Left double quotation mark
		0x201D,		// Right double quotation mark
		0x2022,		// Bullet
		0x2013,		// En dash
		0x2014,		// Em dash
		0x02DC,		// Small tilde
		0x2122,		// Trade mark sign
		0x0161,		// Latin small letter s with caron
		0x203A,		// Single right-pointing angle quotation mark
		0x0153,		// Latin small ligature oe
		0x009D,		// Undefined
		0x017E,		// Latin small letter z with caron
		0x0178		// Latin capital letter Y with diaeresis
	};
	for (int i = 0; i <= bpos; ++i)
	{
		wchar_t code = (BYTE)buf[i];
		if (code >= 0x1D && code <= 0x1F)
		{ // The bar characters, most commonly used to indicate map changes
			code = 0x2550;	// Box Drawings Double Horizontal
		}
		else if (code >= 0x80 && code <= 0x9F)
		{
			code = notlatin1[code - 0x80];
		}
		wbuf[i] = code;
	}
#endif
	SendMessageW(edit, EM_REPLACESEL, FALSE, (LPARAM)wbuf); 
}

//==========================================================================
//
// I_PrintStr
//
// Send output to the list box shown during startup (and hidden during
// gameplay).
//
//==========================================================================

void I_PrintStr(const char *cp)
{
	if (ConWindow == NULL && StdOut == NULL)
		return;

	HWND edit = ConWindow;
	char buf[256];
	wchar_t wbuf[countof(buf)];
	int bpos = 0;
	CHARRANGE selection;
	CHARRANGE endselection;
	LONG lines_before = 0, lines_after;
	CHARFORMAT format;

	if (edit != NULL)
	{
		// Store the current selection and set it to the end so we can append text.
		SendMessage(edit, EM_EXGETSEL, 0, (LPARAM)&selection);
		endselection.cpMax = endselection.cpMin = GetWindowTextLength(edit);
		SendMessage(edit, EM_EXSETSEL, 0, (LPARAM)&endselection);

		// GetWindowTextLength and EM_EXSETSEL can disagree on where the end of
		// the text is. Find out what EM_EXSETSEL thought it was and use that later.
		SendMessage(edit, EM_EXGETSEL, 0, (LPARAM)&endselection);

		// Remember how many lines there were before we added text.
		lines_before = (LONG)SendMessage(edit, EM_GETLINECOUNT, 0, 0);
	}

	while (*cp != 0)
	{
		// 28 is the escape code for a color change.
		if ((*cp == 28 && bpos != 0) || bpos == 255)
		{
			buf[bpos] = 0;
			if (edit != NULL)
			{
				ToEditControl(edit, buf, wbuf, bpos);
			}
			if (StdOut != NULL)
			{
				DWORD bytes_written;
				WriteFile(StdOut, buf, bpos, &bytes_written, NULL);
			}
			bpos = 0;
		}
		if (*cp != 28)
		{
			buf[bpos++] = *cp++;
		}
		else
		{
			const BYTE *color_id = (const BYTE *)cp + 1;
			EColorRange range = V_ParseFontColor(color_id, CR_UNTRANSLATED, CR_YELLOW);
			cp = (const char *)color_id;

			if (range != CR_UNDEFINED)
			{
				// Change the color of future text added to the control.
				PalEntry color = V_LogColorFromColorRange(range);
				if (StdOut != NULL && FancyStdOut)
				{
					// Unfortunately, we are pretty limited here: There are only
					// eight basic colors, and each comes in a dark and a bright
					// variety.
					float h, s, v, r, g, b;
					WORD attrib = 0;

					RGBtoHSV(color.r / 255.f, color.g / 255.f, color.b / 255.f, &h, &s, &v);
					if (s != 0)
					{ // color
						HSVtoRGB(&r, &g, &b, h, 1, 1);
						if (r == 1)  attrib  = FOREGROUND_RED;
						if (g == 1)  attrib |= FOREGROUND_GREEN;
						if (b == 1)  attrib |= FOREGROUND_BLUE;
						if (v > 0.6) attrib |= FOREGROUND_INTENSITY;
					}
					else
					{ // gray
						     if (v < 0.33) attrib = FOREGROUND_INTENSITY;
						else if (v < 0.90) attrib = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
						else			   attrib = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY;
					}
					SetConsoleTextAttribute(StdOut, attrib);
				}
				if (edit != NULL)
				{
					// GDI uses BGR colors, but color is RGB, so swap the R and the B.
					swapvalues(color.r, color.b);
					// Change the color.
					format.cbSize = sizeof(format);
					format.dwMask = CFM_COLOR;
					format.dwEffects = 0;
					format.crTextColor = color;
					SendMessage(edit, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&format);
				}
			}
		}
	}
	if (bpos != 0)
	{
		buf[bpos] = 0;
		if (edit != NULL)
		{
			ToEditControl(edit, buf, wbuf, bpos);
		}
		if (StdOut != NULL)
		{
			DWORD bytes_written;
			WriteFile(StdOut, buf, bpos, &bytes_written, NULL);
		}
	}

	if (edit != NULL)
	{
		// If the old selection was at the end of the text, keep it at the end and
		// scroll. Don't scroll if the selection is anywhere else.
		if (selection.cpMin == endselection.cpMin && selection.cpMax == endselection.cpMax)
		{
			selection.cpMax = selection.cpMin = GetWindowTextLength (edit);
			lines_after = (LONG)SendMessage(edit, EM_GETLINECOUNT, 0, 0);
			if (lines_after > lines_before)
			{
				SendMessage(edit, EM_LINESCROLL, 0, lines_after - lines_before);
			}
		}
		// Restore the previous selection.
		SendMessage(edit, EM_EXSETSEL, 0, (LPARAM)&selection);
		// Give the edit control a chance to redraw itself.
		I_GetEvent();
	}
	if (StdOut != NULL && FancyStdOut)
	{ // Set text back to gray, in case it was changed.
		SetConsoleTextAttribute(StdOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
	}
}

//==========================================================================
//
// SetQueryIWAD
//
// The user had the "Don't ask again" box checked when they closed the
// IWAD selection dialog.
//
//==========================================================================

static void SetQueryIWad(HWND dialog)
{
	HWND checkbox = GetDlgItem(dialog, IDC_DONTASKIWAD);
	int state = (int)SendMessage(checkbox, BM_GETCHECK, 0, 0);
	bool query = (state != BST_CHECKED);

	if (!query && queryiwad)
	{
		MessageBox(dialog,
			"You have chosen not to show this dialog box in the future.\n"
			"If you wish to see it again, hold down SHIFT while starting " GAMENAME ".",
			"Don't ask me this again",
			MB_OK | MB_ICONINFORMATION);
	}

	queryiwad = query;
}

//==========================================================================
//
// IWADBoxCallback
//
// Dialog proc for the IWAD selector.
//
//==========================================================================

BOOL CALLBACK IWADBoxCallback(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	HWND ctrl;
	int i;

	switch (message)
	{
	case WM_INITDIALOG:
		// Add our program name to the window title
		{
			TCHAR label[256];
			FString newlabel;

			GetWindowText(hDlg, label, countof(label));
			newlabel.Format(GAMESIG " %s: %s", GetVersionString(), label);
			SetWindowText(hDlg, newlabel.GetChars());
		}
		// Populate the list with all the IWADs found
		ctrl = GetDlgItem(hDlg, IDC_IWADLIST);
		for (i = 0; i < NumWads; i++)
		{
			FString work;
			const char *filepart = strrchr(WadList[i].Path, '/');
			if (filepart == NULL)
				filepart = WadList[i].Path;
			else
				filepart++;
			work.Format("%s (%s)", WadList[i].Name.GetChars(), filepart);
			SendMessage(ctrl, LB_ADDSTRING, 0, (LPARAM)work.GetChars());
			SendMessage(ctrl, LB_SETITEMDATA, i, (LPARAM)i);
		}
		SendMessage(ctrl, LB_SETCURSEL, DefaultWad, 0);
		SetFocus(ctrl);
		// Set the state of the "Don't ask me again" checkbox
		ctrl = GetDlgItem(hDlg, IDC_DONTASKIWAD);
		SendMessage(ctrl, BM_SETCHECK, queryiwad ? BST_UNCHECKED : BST_CHECKED, 0);
		// Make sure the dialog is in front. If SHIFT was pressed to force it visible,
		// then the other window will normally be on top.
		SetForegroundWindow(hDlg);
		break;

	case WM_COMMAND:
		if (LOWORD(wParam) == IDCANCEL)
		{
			EndDialog (hDlg, -1);
		}
		else if (LOWORD(wParam) == IDOK ||
			(LOWORD(wParam) == IDC_IWADLIST && HIWORD(wParam) == LBN_DBLCLK))
		{
			SetQueryIWad(hDlg);
			ctrl = GetDlgItem (hDlg, IDC_IWADLIST);
			EndDialog(hDlg, SendMessage (ctrl, LB_GETCURSEL, 0, 0));
		}
		break;
	}
	return FALSE;
}

//==========================================================================
//
// I_PickIWad
//
// Open a dialog to pick the IWAD, if there is more than one found.
//
//==========================================================================

int I_PickIWad(WadStuff *wads, int numwads, bool showwin, int defaultiwad)
{
	int vkey;

	if (stricmp(queryiwad_key, "shift") == 0)
	{
		vkey = VK_SHIFT;
	}
	else if (stricmp(queryiwad_key, "control") == 0 || stricmp (queryiwad_key, "ctrl") == 0)
	{
		vkey = VK_CONTROL;
	}
	else
	{
		vkey = 0;
	}
	if (showwin || (vkey != 0 && GetAsyncKeyState(vkey)))
	{
		WadList = wads;
		NumWads = numwads;
		DefaultWad = defaultiwad;

		return (int)DialogBox(g_hInst, MAKEINTRESOURCE(IDD_IWADDIALOG),
			(HWND)Window, (DLGPROC)IWADBoxCallback);
	}
	return defaultiwad;
}

//==========================================================================
//
// I_SetCursor
//
// Returns true if the cursor was successfully changed.
//
//==========================================================================

bool I_SetCursor(FTexture *cursorpic)
{
	HCURSOR cursor;

	if (cursorpic != NULL && cursorpic->UseType != FTexture::TEX_Null &&
		(screen == NULL || !screen->Is8BitMode()))
	{
		// Must be no larger than 32x32.
		if (cursorpic->GetWidth() > 32 || cursorpic->GetHeight() > 32)
		{
			return false;
		}

		cursor = CreateAlphaCursor(cursorpic);
		if (cursor == NULL)
		{
			cursor = CreateCompatibleCursor(cursorpic);
		}
		if (cursor == NULL)
		{
			return false;
		}
		// Replace the existing cursor with the new one.
		DestroyCustomCursor();
		CustomCursor = cursor;
		atterm(DestroyCustomCursor);
	}
	else
	{
		DestroyCustomCursor();
		cursor = LoadCursor(NULL, IDC_ARROW);
	}
	SetClassLongPtr(Window, GCLP_HCURSOR, (LONG_PTR)cursor);
	if (NativeMouse)
	{
		POINT pt;
		RECT client;

		// If the mouse pointer is within the window's client rect, set it now.
		if (GetCursorPos(&pt) && GetClientRect(Window, &client) &&
			ClientToScreen(Window, (LPPOINT)&client.left) &&
			ClientToScreen(Window, (LPPOINT)&client.right))
		{
			if (pt.x >= client.left && pt.x < client.right &&
				pt.y >= client.top && pt.y < client.bottom)
			{
				SetCursor(cursor);
			}
		}
	}
	return true;
}

//==========================================================================
//
// CreateCompatibleCursor
//
// Creates a cursor with a 1-bit alpha channel.
//
//==========================================================================

static HCURSOR CreateCompatibleCursor(FTexture *cursorpic)
{
	int picwidth = cursorpic->GetWidth();
	int picheight = cursorpic->GetHeight();

	// Create bitmap masks for the cursor from the texture.
	HDC dc = GetDC(NULL);
	if (dc == NULL)
	{
		return false;
	}
	HDC and_mask_dc = CreateCompatibleDC(dc);
	HDC xor_mask_dc = CreateCompatibleDC(dc);
	HBITMAP and_mask = CreateCompatibleBitmap(dc, 32, 32);
	HBITMAP xor_mask = CreateCompatibleBitmap(dc, 32, 32);
	ReleaseDC(NULL, dc);

	SelectObject(and_mask_dc, and_mask);
	SelectObject(xor_mask_dc, xor_mask);

	// Initialize with an invisible cursor.
	SelectObject(and_mask_dc, GetStockObject(WHITE_PEN));
	SelectObject(and_mask_dc, GetStockObject(WHITE_BRUSH));
	Rectangle(and_mask_dc, 0, 0, 32, 32);
	SelectObject(xor_mask_dc, GetStockObject(BLACK_PEN));
	SelectObject(xor_mask_dc, GetStockObject(BLACK_BRUSH));
	Rectangle(xor_mask_dc, 0, 0, 32, 32);

	FBitmap bmp;
	const BYTE *pixels;

	bmp.Create(picwidth, picheight);
	cursorpic->CopyTrueColorPixels(&bmp, 0, 0);
	pixels = bmp.GetPixels();

	// Copy color data from the source texture to the cursor bitmaps.
	for (int y = 0; y < picheight; ++y)
	{
		for (int x = 0; x < picwidth; ++x)
		{
			const BYTE *bgra = &pixels[x*4 + y*bmp.GetPitch()];
			if (bgra[3] != 0)
			{
				SetPixelV(and_mask_dc, x, y, RGB(0,0,0));
				SetPixelV(xor_mask_dc, x, y, RGB(bgra[2], bgra[1], bgra[0]));
			}
		}
	}
	DeleteDC(and_mask_dc);
	DeleteDC(xor_mask_dc);

	// Create the cursor from the bitmaps.
	return CreateBitmapCursor(cursorpic->LeftOffset, cursorpic->TopOffset, and_mask, xor_mask);
}

//==========================================================================
//
// CreateAlphaCursor
//
// Creates a cursor with a full alpha channel.
//
//==========================================================================

static HCURSOR CreateAlphaCursor(FTexture *cursorpic)
{
	HDC dc;
	BITMAPV5HEADER bi;
	HBITMAP color, mono;
	void *bits;

	memset(&bi, 0, sizeof(bi));
	bi.bV5Size = sizeof(bi);
	bi.bV5Width = 32;
	bi.bV5Height = 32;
	bi.bV5Planes = 1;
	bi.bV5BitCount = 32;
	bi.bV5Compression = BI_BITFIELDS;
	bi.bV5RedMask   = 0x00FF0000;
	bi.bV5GreenMask = 0x0000FF00;
	bi.bV5BlueMask  = 0x000000FF;
	bi.bV5AlphaMask = 0xFF000000;

	dc = GetDC(NULL);
	if (dc == NULL)
	{
		return NULL;
	}

	// Create the DIB section with an alpha channel.
	color = CreateDIBSection(dc, (BITMAPINFO *)&bi, DIB_RGB_COLORS, &bits, NULL, 0);
	ReleaseDC(NULL, dc);

	if (color == NULL)
	{
		return NULL;
	}

	// Create an empty mask bitmap, since CreateIconIndirect requires this.
	mono = CreateBitmap(32, 32, 1, 1, NULL);
	if (mono == NULL)
	{
		DeleteObject(color);
		return NULL;
	}

	// Copy cursor to the color bitmap. Note that GDI bitmaps are upside down compared
	// to normal conventions, so we create the FBitmap pointing at the last row and use
	// a negative pitch so that CopyTrueColorPixels will use GDI's orientation.
	FBitmap bmp((BYTE *)bits + 31*32*4, -32*4, 32, 32);
	cursorpic->CopyTrueColorPixels(&bmp, 0, 0);

	return CreateBitmapCursor(cursorpic->LeftOffset, cursorpic->TopOffset, mono, color);
}

//==========================================================================
//
// CreateBitmapCursor
//
// Create the cursor from the bitmaps. Deletes the bitmaps before returning.
//
//==========================================================================

static HCURSOR CreateBitmapCursor(int xhot, int yhot, HBITMAP and_mask, HBITMAP color_mask)
{
	ICONINFO iconinfo =
	{
		FALSE,		// fIcon
		xhot,		// xHotspot
		yhot,		// yHotspot
		and_mask,	// hbmMask
		color_mask	// hbmColor
	};
	HCURSOR cursor = CreateIconIndirect(&iconinfo);

	// Delete the bitmaps.
	DeleteObject(and_mask);
	DeleteObject(color_mask);
	
	return cursor;
}

//==========================================================================
//
// DestroyCustomCursor
//
//==========================================================================

static void DestroyCustomCursor()
{
	if (CustomCursor != NULL)
	{
		DestroyCursor(CustomCursor);
		CustomCursor = NULL;
	}
}

//==========================================================================
//
// I_WriteIniFailed
//
// Display a message when the config failed to save.
//
//==========================================================================

bool I_WriteIniFailed()
{
	char *lpMsgBuf;
	FString errortext;

	FormatMessageA (FORMAT_MESSAGE_ALLOCATE_BUFFER | 
					FORMAT_MESSAGE_FROM_SYSTEM | 
					FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL,
		GetLastError(),
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
		(LPSTR)&lpMsgBuf,
		0,
		NULL 
	);
	errortext.Format ("The config file %s could not be written:\n%s", GameConfig->GetPathName(), lpMsgBuf);
	LocalFree (lpMsgBuf);
	return MessageBox(Window, errortext.GetChars(), GAMENAME " configuration not saved", MB_ICONEXCLAMATION | MB_RETRYCANCEL) == IDRETRY;
}

//==========================================================================
//
// I_FindFirst
//
// Start a pattern matching sequence.
//
//==========================================================================

void *I_FindFirst(const char *filespec, findstate_t *fileinfo)
{
	return FindFirstFileA(filespec, (LPWIN32_FIND_DATAA)fileinfo);
}

//==========================================================================
//
// I_FindNext
//
// Return the next file in a pattern matching sequence.
//
//==========================================================================

int I_FindNext(void *handle, findstate_t *fileinfo)
{
	return !FindNextFileA((HANDLE)handle, (LPWIN32_FIND_DATAA)fileinfo);
}

//==========================================================================
//
// I_FindClose
//
// Finish a pattern matching sequence.
//
//==========================================================================

int I_FindClose(void *handle)
{
	return FindClose((HANDLE)handle);
}

//==========================================================================
//
// QueryPathKey
//
// Returns the value of a registry key into the output variable value.
//
//==========================================================================

static bool QueryPathKey(HKEY key, const char *keypath, const char *valname, FString &value)
{
	HKEY steamkey;
	DWORD pathtype;
	DWORD pathlen;
	LONG res;

	if(ERROR_SUCCESS == RegOpenKeyEx(key, keypath, 0, KEY_QUERY_VALUE, &steamkey))
	{
		if (ERROR_SUCCESS == RegQueryValueEx(steamkey, valname, 0, &pathtype, NULL, &pathlen) &&
			pathtype == REG_SZ && pathlen != 0)
		{
			// Don't include terminating null in count
			char *chars = value.LockNewBuffer(pathlen - 1);
			res = RegQueryValueEx(steamkey, valname, 0, NULL, (LPBYTE)chars, &pathlen);
			value.UnlockBuffer();
			if (res != ERROR_SUCCESS)
			{
				value = "";
			}
		}
		RegCloseKey(steamkey);
	}
	return value.IsNotEmpty();
}

//==========================================================================
//
// I_GetSteamPath
//
// Check the registry for the path to Steam, so that we can search for
// IWADs that were bought with Steam.
//
//==========================================================================

FString I_GetSteamPath()
{
	FString path;

	if (QueryPathKey(HKEY_CURRENT_USER, "Software\\Valve\\Steam", "SteamPath", path))
	{
		return path;
	}
	if (QueryPathKey(HKEY_LOCAL_MACHINE, "Software\\Valve\\Steam", "InstallPath", path))
	{
		return path;
	}
	path = "";
	return path;
}

//==========================================================================
//
// I_MakeRNGSeed
//
// Returns a 32-bit random seed, preferably one with lots of entropy.
//
//==========================================================================

unsigned int I_MakeRNGSeed()
{
	unsigned int seed;

	// If RtlGenRandom is available, use that to avoid increasing the
	// working set by pulling in all of the crytographic API.
	HMODULE advapi = GetModuleHandle("advapi32.dll");
	if (advapi != NULL)
	{
		BOOLEAN (APIENTRY *RtlGenRandom)(void *, ULONG) =
			(BOOLEAN (APIENTRY *)(void *, ULONG))GetProcAddress(advapi, "SystemFunction036");
		if (RtlGenRandom != NULL)
		{
			if (RtlGenRandom(&seed, sizeof(seed)))
			{
				return seed;
			}
		}
	}

	// Use the full crytographic API to produce a seed. If that fails,
	// time() is used as a fallback.
	HCRYPTPROV prov;

	if (!CryptAcquireContext(&prov, NULL, MS_DEF_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
	{
		return (unsigned int)time(NULL);
	}
	if (!CryptGenRandom(prov, sizeof(seed), (BYTE *)&seed))
	{
		seed = (unsigned int)time(NULL);
	}
	CryptReleaseContext(prov, 0);
	return seed;
}

//==========================================================================
//
// I_GetLongPathName
//
// Returns the long version of the path, or the original if there isn't
// anything worth changing.
//
//==========================================================================

FString I_GetLongPathName(FString shortpath)
{
	DWORD buffsize = GetLongPathName(shortpath.GetChars(), NULL, 0);
	if (buffsize == 0)
	{ // nothing to change (it doesn't exist, maybe?)
		return shortpath;
	}
	TCHAR *buff = new TCHAR[buffsize];
	DWORD buffsize2 = GetLongPathName(shortpath.GetChars(), buff, buffsize);
	if (buffsize2 >= buffsize)
	{ // Failure! Just return the short path
		delete[] buff;
		return shortpath;
	}
	FString longpath(buff, buffsize2);
	delete[] buff;
	return longpath;
}
