// Emacs style mode select   -*- C++ -*-
//-----------------------------------------------------------------------------
//
// Copyright (C) 1998-2000 by DooM Legacy Team.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//-----------------------------------------------------------------------------
/// \file
/// \brief Win32 WinMain Entry Point
///
///	Win32 Sonic Robo Blast 2
///
///	NOTE:
///		To compile WINDOWS SRB2 version : define a '_WINDOWS' symbol.
///		to do this go to Project/Settings/ menu, click C/C++ tab, in
///		'Preprocessor definitions:' add '_WINDOWS'

#include "../doomdef.h"
#include <stdio.h>

#ifdef _WINDOWS

#include "../doomstat.h"  // netgame
#include "resource.h"

#include "../m_argv.h"
#include "../d_main.h"
#include "../i_system.h"

#include "../keys.h"    //hack quick test

#include "../console.h"

#include "fabdxlib.h"
#include "win_main.h"
#include "win_dbg.h"
#include "../i_sound.h" // midi pause/unpause
#include "../g_input.h" // KEY_MOUSEWHEELxxx
#include "../screen.h" // for BASEVID*

// MSWheel support for Win95/NT3.51
#include <zmouse.h>

#ifndef WM_XBUTTONDOWN
#define WM_XBUTTONDOWN 523
#endif
#ifndef WM_XBUTTONUP
#define WM_XBUTTONUP 524
#endif
#ifndef MK_XBUTTON1
#define MK_XBUTTON1 32
#endif
#ifndef MK_XBUTTON2
#define MK_XBUTTON2 64
#endif

typedef BOOL (WINAPI *p_IsDebuggerPresent)(VOID);

HWND hWndMain = NULL;
static HCURSOR windowCursor = NULL; // main window cursor

static LPCSTR wClassName = "SRB2WC";

INT appActive = false; // app window is active

#ifdef LOGMESSAGES
FILE *logstream;
#endif

BOOL nodinput = FALSE;

static LRESULT CALLBACK MainWndproc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	event_t ev; //Doom input event
	int mouse_keys;

	// judgecutor:
	// Response MSH Mouse Wheel event

	if (message == MSHWheelMessage)
	{
		message = WM_MOUSEWHEEL;
		wParam <<= 16;
	}

	//I_OutputMsg("MainWndproc: %p,%i,%i,%i",hWnd, message, wParam, (UINT)lParam);

	switch (message)
	{
		case WM_CREATE:
			nodinput = M_CheckParm("-nodinput");
			if (!nodinput && !LoadDirectInput())
				nodinput = true;
			break;

		case WM_ACTIVATEAPP:           // Handle task switching
			appActive = (int)wParam;

			//coming back from alt-tab?  reset the palette.
			if (appActive)
				vid.recalc = true;

			// pause music when alt-tab
			if (appActive  && !paused)
				I_ResumeSong(0);
			else if (!paused)
				I_PauseSong(0);
			{
				HANDLE ci = GetStdHandle(STD_INPUT_HANDLE);
				DWORD mode;
				if (ci != INVALID_HANDLE_VALUE && GetFileType(ci) == FILE_TYPE_CHAR && GetConsoleMode(ci, &mode))
					appActive = true;
			}
			InvalidateRect (hWnd, NULL, TRUE);
			break;

		case WM_PAINT:
			if (!appActive && !bAppFullScreen && !netgame)
				// app becomes inactive (if windowed)
			{
				// Paint "Game Paused" in the middle of the screen
				PAINTSTRUCT ps;
				RECT        rect;
				HDC hdc = BeginPaint (hWnd, &ps);
				GetClientRect (hWnd, &rect);
				DrawText (hdc, TEXT("Game Paused"), -1, &rect,
					DT_SINGLELINE | DT_CENTER | DT_VCENTER);
				EndPaint (hWnd, &ps);
				return 0;
			}

			break;

		case WM_QUERYNEWPALETTE:
			RestoreDDPalette();
			return TRUE;

		case WM_PALETTECHANGED:
			if((HWND)wParam != hWnd)
				RestoreDDPalette();
			break;

		//case WM_RBUTTONDOWN:
		//case WM_LBUTTONDOWN:

		case WM_MOVE:
			if (bAppFullScreen)
			{
				SetWindowPos(hWnd, NULL, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
				return 0;
			}
			else
			{
				windowPosX = (SHORT) LOWORD(lParam);    // horizontal position
				windowPosY = (SHORT) HIWORD(lParam);    // vertical position
				break;
			}
			break;

			// This is where switching windowed/fullscreen is handled. DirectDraw
			// objects must be destroyed, recreated, and artwork reloaded.

		case WM_DISPLAYCHANGE:
		case WM_SIZE:
			break;

		case WM_SETCURSOR:
			if (bAppFullScreen)
				SetCursor(NULL);
			else
				SetCursor(windowCursor);
			return TRUE;

		case WM_KEYUP:
			ev.type = ev_keyup;
			goto handleKeyDoom;
			break;

		case WM_KEYDOWN:
			ev.type = ev_keydown;

	handleKeyDoom:
			ev.data1 = 0;
			if (wParam == VK_PAUSE)
			// intercept PAUSE key
			{
				ev.data1 = KEY_PAUSE;
			}
			else if (!keyboard_started)
			// post some keys during the game startup
			// (allow escaping from network synchronization, or pressing enter after
			//  an error message in the console)
			{
				switch (wParam)
				{
					case VK_ESCAPE: ev.data1 = KEY_ESCAPE;  break;
					case VK_RETURN: ev.data1 = KEY_ENTER;   break;
					case VK_SHIFT:  ev.data1 = KEY_LSHIFT;  break;
					default: ev.data1 = MapVirtualKey((DWORD)wParam,2); // convert in to char
				}
			}

			if (ev.data1)
				D_PostEvent (&ev);

			return 0;
			break;

		// judgecutor:
		// Handle mouse events
		case WM_LBUTTONDOWN:
		case WM_LBUTTONUP:
		case WM_RBUTTONDOWN:
		case WM_RBUTTONUP:
		case WM_MBUTTONDOWN:
		case WM_MBUTTONUP:
		case WM_MOUSEMOVE:
			if (nodinput)
			{
				mouse_keys = 0;
				if (wParam & MK_LBUTTON)
					mouse_keys |= 1;
				if (wParam & MK_RBUTTON)
					mouse_keys |= 2;
				if (wParam & MK_MBUTTON)
					mouse_keys |= 4;
				I_GetSysMouseEvents(mouse_keys);
			}
			break;

		case WM_XBUTTONUP:
			if (nodinput)
			{
				ev.type = ev_keyup;
				ev.data1 = KEY_MOUSE1 + 3 + HIWORD(wParam);
				D_PostEvent(&ev);
				return TRUE;
			}

		case WM_XBUTTONDOWN:
			if (nodinput)
			{
				ev.type = ev_keydown;
				ev.data1 = KEY_MOUSE1 + 3 + HIWORD(wParam);
				D_PostEvent(&ev);
				return TRUE;
			}

		case WM_MOUSEWHEEL:
			//I_OutputMsg("MW_WHEEL dispatched.\n");
			ev.type = ev_keydown;
			if ((INT16)HIWORD(wParam) > 0)
				ev.data1 = KEY_MOUSEWHEELUP;
			else
				ev.data1 = KEY_MOUSEWHEELDOWN;
			D_PostEvent(&ev);
			break;

		case WM_SETTEXT:
			COM_BufAddText((LPCSTR)lParam);
			return TRUE;
			break;

		case WM_CLOSE:
			PostQuitMessage(0);         //to quit while in-game
			ev.data1 = KEY_ESCAPE;      //to exit network synchronization
			ev.type = ev_keydown;
			D_PostEvent (&ev);
			return 0;
		case WM_DESTROY:
			//faB: main app loop will exit the loop and proceed with I_Quit()
			PostQuitMessage(0);
			break;

		case WM_SYSCOMMAND:
			// Don't allow the keyboard to activate the menu.
			if(wParam == SC_KEYMENU)
				return 0;

			break;

		default:
			break;
	}

	return DefWindowProc(hWnd, message, wParam, lParam);
}

static inline VOID OpenTextConsole(VOID)
{
	HANDLE ci, co;
	BOOL console;

#ifdef _DEBUG
	console = M_CheckParm("-noconsole") == 0;
#else
	console = M_CheckParm("-console") != 0;
#endif

	dedicated = M_CheckParm("-dedicated") != 0;

	if (M_CheckParm("-detachconsole"))
	{
		if (FreeConsole())
		{
			I_OutputMsg("Detatched console.\n");
			console = TRUE; //lets get back a console
		}
		else
		{
			I_OutputMsg("No console to detatch.\n");
			I_ShowLastError(FALSE);
		}
	}

	if (dedicated || console)
	{
		if (AllocConsole()) //Let get the real console HANDLEs, because Mingw's Bash is bad!
		{
			SetConsoleTitleA("SRB2 Console");
			CONS_Printf(M_GetText("Hello, it's me, SRB2's Console Window\n"));
		}
		else
		{
			I_OutputMsg("We have a console already.\n");
			I_ShowLastError(FALSE);
			return;
		}
	}
	else
		return;

	ci = CreateFile(TEXT("CONIN$") ,               GENERIC_READ, FILE_SHARE_READ,  NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (ci != INVALID_HANDLE_VALUE)
	{
		HANDLE sih = GetStdHandle(STD_INPUT_HANDLE);
		if (sih != ci)
		{
			I_OutputMsg("Old STD_INPUT_HANDLE: %p\nNew STD_INPUT_HANDLE: %p\n", sih, ci);
			SetStdHandle(STD_INPUT_HANDLE,ci);
		}
		else
			I_OutputMsg("STD_INPUT_HANDLE already set at %p\n", ci);

		if (GetFileType(ci) == FILE_TYPE_CHAR)
		{
#if 0
			const DWORD CM = ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT|ENABLE_PROCESSED_INPUT; //default mode but no ENABLE_MOUSE_INPUT
			if (SetConsoleMode(ci,CM))
			{
				I_OutputMsg("Disabled mouse input on the console\n");
			}
			else
			{
				I_OutputMsg("Could not disable mouse input on the console\n");
				I_ShowLastError(FALSE);
			}
#endif
		}
		else
			I_OutputMsg("Handle CONIN$ in not a Console HANDLE\n");
	}
	else
	{
		I_OutputMsg("Could not get a CONIN$ HANDLE\n");
		I_ShowLastError(FALSE);
	}

	co = CreateFile(TEXT("CONOUT$"), GENERIC_WRITE|GENERIC_READ, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (co != INVALID_HANDLE_VALUE)
	{
		HANDLE soh = GetStdHandle(STD_OUTPUT_HANDLE);
		HANDLE seh = GetStdHandle(STD_ERROR_HANDLE);
		if (soh != co)
		{
			I_OutputMsg("Old STD_OUTPUT_HANDLE: %p\nNew STD_OUTPUT_HANDLE: %p\n", soh, co);
			SetStdHandle(STD_OUTPUT_HANDLE,co);
		}
		else
			I_OutputMsg("STD_OUTPUT_HANDLE already set at %p\n", co);
		if (seh != co)
		{
			I_OutputMsg("Old STD_ERROR_HANDLE: %p\nNew STD_ERROR_HANDLE: %p\n", seh, co);
			SetStdHandle(STD_ERROR_HANDLE,co);
		}
		else
			I_OutputMsg("STD_ERROR_HANDLE already set at %p\n", co);
	}
	else
		I_OutputMsg("Could not get a CONOUT$ HANDLE\n");
}


//
// Do that Windows initialization stuff...
//
static HWND OpenMainWindow (HINSTANCE hInstance, LPSTR wTitle)
{
	const LONG	styles = WS_CAPTION|WS_POPUP|WS_SYSMENU, exstyles = 0;
	HWND        hWnd;
	WNDCLASSEXA wc;
	RECT		bounds;

	// Set up and register window class
	ZeroMemory(&wc, sizeof(wc));
	wc.cbSize        = sizeof(wc);
	wc.style         = CS_HREDRAW | CS_VREDRAW /*| CS_DBLCLKS*/;
	wc.lpfnWndProc   = MainWndproc;
	wc.hInstance     = hInstance;
	wc.hIcon         = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_DLICON1));
	windowCursor     = LoadCursor(NULL, IDC_WAIT); //LoadCursor(hInstance, MAKEINTRESOURCE(IDC_DLCURSOR1));
	wc.hCursor       = windowCursor;
	wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
	wc.lpszClassName = wClassName;

	if (!RegisterClassExA(&wc))
	{
		I_OutputMsg("Error doing RegisterClassExA\n");
		I_ShowLastError(TRUE);
		return INVALID_HANDLE_VALUE;
	}

	// Create a window
	// CreateWindowEx - seems to create just the interior, not the borders

	bounds.left = 0;
	bounds.right = dedicated ? 0 : specialmodes[0].width;
	bounds.top = 0;
	bounds.bottom = dedicated ? 0 : specialmodes[0].height;

	AdjustWindowRectEx(&bounds, styles, FALSE, exstyles);

	hWnd = CreateWindowExA(
	       exstyles,                                 //ExStyle
	       wClassName,                        //Classname
	       wTitle,                            //Windowname
	       styles,    //dwStyle       //WS_VISIBLE|WS_POPUP for bAppFullScreen
	       0,
	       0,
	       bounds.right - bounds.left,        //GetSystemMetrics(SM_CXSCREEN),
	       bounds.bottom - bounds.top,        //GetSystemMetrics(SM_CYSCREEN),
	       NULL,                              //hWnd Parent
	       NULL,                              //hMenu Menu
	       hInstance,
	       NULL);

	if (hWnd == INVALID_HANDLE_VALUE)
	{
		I_OutputMsg("Error doing CreateWindowExA\n");
		I_ShowLastError(TRUE);
	}

	return hWnd;
}


static inline BOOL tlErrorMessage(const TCHAR *err)
{
	/* make the cursor visible */
	SetCursor(LoadCursor(NULL, IDC_ARROW));

	//
	// warn user if there is one
	//
	printf("Error %Ts..\n", err);
	fflush(stdout);

	MessageBox(hWndMain, err, TEXT("ERROR"), MB_OK);
	return FALSE;
}


// ------------------
// Command line stuff
// ------------------
#define         MAXCMDLINEARGS          64
static  char *  myWargv[MAXCMDLINEARGS+1];
static  char    myCmdline[512];

static VOID     GetArgcArgv (LPSTR cmdline)
{
	LPSTR   tokenstr;
	size_t  i = 0, len;
	char    cSep = ' ';
	BOOL    bCvar = FALSE, prevCvar = FALSE;

	// split arguments of command line into argv
	strlcpy (myCmdline, cmdline, sizeof(myCmdline));      // in case window's cmdline is in protected memory..for strtok
	len = strlen (myCmdline);

	myargc = 0;
	while (myargc < MAXCMDLINEARGS)
	{
		// get token
		while (myCmdline[i] == cSep)
			i++;
		if (i >= len)
			break;
		tokenstr = myCmdline + i;
		if (myCmdline[i] == '"')
		{
			cSep = '"';
			i++;
			if (!prevCvar)    //cvar leave the "" in
				tokenstr++;
		}
		else
			cSep = ' ';

		//cvar
		if (myCmdline[i] == '+' && cSep == ' ')   //a + begins a cvarname, but not after quotes
			bCvar = TRUE;
		else
			bCvar = FALSE;

		while (myCmdline[i] &&
				myCmdline[i] != cSep)
			i++;

		if (myCmdline[i] == '"')
		{
			cSep = ' ';
			if (prevCvar)
				i++; // get ending " quote in arg
		}

		prevCvar = bCvar;

		if (myCmdline + i > tokenstr)
		{
			myWargv[myargc++] = tokenstr;
		}

		if (!myCmdline[i] || i >= len)
			break;

		myCmdline[i++] = '\0';
	}
	myWargv[myargc] = NULL;

	// m_argv.c uses myargv[], we used myWargv because we fill the arguments ourselves
	// and myargv is just a pointer, so we set it to point myWargv
	myargv = myWargv;
}

static inline VOID MakeCodeWritable(VOID)
{
#ifdef USEASM // Disable write-protection of code segment
	DWORD OldRights;
	const DWORD NewRights = PAGE_EXECUTE_READWRITE;
	PBYTE pBaseOfImage = (PBYTE)GetModuleHandle(NULL);
	PIMAGE_DOS_HEADER dosH =(PIMAGE_DOS_HEADER)pBaseOfImage;
	PIMAGE_NT_HEADERS ntH = (PIMAGE_NT_HEADERS)(pBaseOfImage + dosH->e_lfanew);
	PIMAGE_OPTIONAL_HEADER oH = (PIMAGE_OPTIONAL_HEADER)
		((PBYTE)ntH + sizeof (IMAGE_NT_SIGNATURE) + sizeof (IMAGE_FILE_HEADER));
	LPVOID pA = pBaseOfImage+oH->BaseOfCode;
	SIZE_T pS = oH->SizeOfCode;
#if 1 // try to find the text section
	PIMAGE_SECTION_HEADER ntS = IMAGE_FIRST_SECTION (ntH);
	WORD s;
	for (s = 0; s < ntH->FileHeader.NumberOfSections; s++)
	{
		if (memcmp (ntS[s].Name, ".text\0\0", 8) == 0)
		{
			pA = pBaseOfImage+ntS[s].VirtualAddress;
			pS = ntS[s].Misc.VirtualSize;
			break;
		}
	}
#endif

	if (!VirtualProtect(pA,pS,NewRights,&OldRights))
		I_Error("Could not make code writable\n");
#endif
}

// -----------------------------------------------------------------------------
// HandledWinMain : called by exception handler
// -----------------------------------------------------------------------------
static int WINAPI HandledWinMain(HINSTANCE hInstance)
{
	LPSTR          args;

#ifdef LOGMESSAGES
	// DEBUG!!! - set logstream to NULL to disable debug log
	// Replace WIN32 filehandle with standard C calls, because WIN32 doesn't handle \n properly.
	logstream = fopen(va("%s"PATHSEP"%s", srb2home, "log.txt"), "wt");
#endif

	// fill myargc,myargv for m_argv.c retrieval of cmdline arguments
	args = GetCommandLineA();
	CONS_Printf("Command line arguments: '%s'\n", args);
	GetArgcArgv(args);
	// Create a text console window
	OpenTextConsole();

#ifdef _DEBUG
	{
		int i;
		CONS_Printf("Myargc: %d\n", myargc);
		for (i = 0; i < myargc; i++)
			CONS_Printf("myargv[%d] : '%s'\n", i, myargv[i]);
	}
#endif

	// open a dummy window, both OpenGL and DirectX need one.
	if ((hWndMain = OpenMainWindow(hInstance, va("SRB2 "VERSIONSTRING))) == INVALID_HANDLE_VALUE)
	{
		tlErrorMessage(TEXT("Couldn't open window"));
		return FALSE;
	}

	// currently starts DirectInput
	CONS_Printf("I_StartupSystem() ...\n");
	I_StartupSystem();
	MakeCodeWritable();

	// startup SRB2
	CONS_Printf("Setting up SRB2...\n");
	D_SRB2Main();
	CONS_Printf("Entering main game loop...\n");
	// never return
	D_SRB2Loop();

	// back to Windoze
	return 0;
}

// -----------------------------------------------------------------------------
// Exception handler calls WinMain for catching exceptions
// -----------------------------------------------------------------------------
int WINAPI WinMain (HINSTANCE hInstance,
                    HINSTANCE hPrevInstance,
                    LPSTR     lpCmdLine,
                    int       nCmdShow)
{
	int Result = -1;

#if 0
	// Win95 and NT <4 don't have this, so link at runtime.
	p_IsDebuggerPresent pfnIsDebuggerPresent = (p_IsDebuggerPresent)GetProcAddress(GetModuleHandleA("kernel32.dll"),"IsDebuggerPresent");
#endif

	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);
	UNREFERENCED_PARAMETER(nCmdShow);

#if 0
#ifdef BUGTRAP
	// Try BugTrap first.
	if((!pfnIsDebuggerPresent || !pfnIsDebuggerPresent()) && InitBugTrap())
		Result = HandledWinMain(hInstance);
	else
	{
#endif
		// Try Dr MinGW's exception handler.
		if (!pfnIsDebuggerPresent || !pfnIsDebuggerPresent())
#endif
			LoadLibraryA("exchndl.dll");

		prevExceptionFilter = SetUnhandledExceptionFilter(RecordExceptionInfo);

		Result = HandledWinMain(hInstance);
#ifdef BUGTRAP
	}	// BT failure clause.

	// This is safe even if BT didn't start.
	ShutdownBugTrap();
#endif

	return Result;
}
#endif //_WINDOWS