//
// Project UnReality: The First Nintendo 64 Emulator
// Copyright (C) 1997-1998, Cold Fusion Entertainment
// All rights reserved
//
// This source code is unpublished propietary information of Cold Fusion Entertainment.  No portion of this software
// may be disclosed to any third party or copied or duplicated in any form, in whole or in part, without the prior
// written consent of Cold Fusion Entertainment.
// 

//
// $Log: INPUT.CPP $
// Revision 1.1  1998/03/08 19:08:31  unknown
// Initial revision
//

static char *rcsid = "$Id: INPUT.CPP 1.1 1998/03/08 19:08:31 unknown Exp $";

//---------------------------------------------------------------------------------------------------------------------

#include <stdio.h>
#include <windows.h>
#include <dinput.h>
#include <types.h>

#include "unreal.h"
#include "emu.h"
#include "resource.h"
#include "heap.h"
#include "cfb.h"
#define NO_EXTERN_INPUT
#include "input.h"

IDirectInput *di = NULL;
IDirectInputDevice *di_keyb = NULL, *di_joy = NULL, *di_mouse = NULL;

HWND last_hwnd;
BYTE curr_keys[256];
DIJOYSTATE curr_dijs;
DIMOUSESTATE curr_dims;
int current_cont, keyb_count;
bool keyb_acquired, joy_acquired, mouse_acquired;
slong mouse_xpos, mouse_ypos;
HANDLE hInputThread;
bool Input_Exit = FALSE;

DWORD Input_ThreadFunc(LPDWORD lpdwParam);

bool Input_StartThread(void)
{
	DWORD ThreadID;

	Input_Exit = FALSE;

	// Kick off the thread.
	hInputThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Input_ThreadFunc, 0, 0, &ThreadID);
	if (hInputThread == NULL) return FALSE;

	return TRUE;
}

void Input_StopThread(void)
{
	Input_Exit = TRUE;

	// Wait until thread stops...
	WaitForSingleObject(hInputThread, INFINITE);
	CloseHandle(hInputThread);
	hInputThread = NULL;
}

bool InitDirectInput(void)
{
    HRESULT res;

	// Used for counting reentrancy to GetKeyboardState()...
	keyb_count = 0;

	keyb_acquired  = FALSE;
	joy_acquired   = FALSE;
	mouse_acquired = FALSE;

    res = DirectInputCreate(MainhInst, DIRECTINPUT_VERSION, &di, NULL);
    if (res != DI_OK) return FALSE;

	// Create keyboard object.
    res = di->CreateDevice(GUID_SysKeyboard, &di_keyb, NULL);
    if (res != DI_OK)
		di_keyb = NULL;
	else
	{
		res = di_keyb->SetDataFormat(&c_dfDIKeyboard);
		if (res != DI_OK)
			di_keyb = NULL;
		else
		{
			res = di_keyb->SetCooperativeLevel(MainhWnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND);
			if (res != DI_OK) di_keyb = NULL;
		}
	}

/*
	// Create joystick object.
    res = di->CreateDevice(GUID_SysJoystick, &di_joy, NULL);
    if (res != DI_OK)
		di_joy = NULL;
	else
	{
		res = di_joy->SetDataFormat(&c_dfDIKeyboard);
		if (res != DI_OK)
			di_joy = NULL;
		else
		{
			res = di_joy->SetCooperativeLevel(MainhWnd, DISCL_EXCLUSIVE | DISCL_FOREGROUND);
			if (res != DI_OK) di_joy = NULL;
		}
	}
*/

	// Create mouse object.
    res = di->CreateDevice(GUID_SysMouse, &di_mouse, NULL);
    if (res != DI_OK)
		di_mouse = NULL;
	else
	{
		res = di_mouse->SetDataFormat(&c_dfDIMouse);
		if (res != DI_OK)
			di_mouse = NULL;
		else
		{
			res = di_mouse->SetCooperativeLevel(MainhWnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND);
			if (res != DI_OK) di_mouse = NULL;
		}
	}

	if ((di_keyb == NULL) && (di_joy == NULL) && (di_mouse == NULL)) return FALSE;
    return TRUE;
}

void AcquireDevices(HWND hwnd)
{
	HRESULT res;

	last_hwnd = hwnd;

    if (di_keyb != NULL)
	{
		res = di_keyb->SetCooperativeLevel(hwnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND);
		if (res == DI_OK)
		{
			res = di_keyb->Acquire();
			if (res == DI_OK) keyb_acquired = TRUE;
		}
	}

	if (di_joy != NULL)
	{
		res = di_joy->SetCooperativeLevel(hwnd, DISCL_EXCLUSIVE | DISCL_FOREGROUND);
		if (res == DI_OK)
		{
			res = di_joy->Acquire();
			if (res == DI_OK) joy_acquired = TRUE;
		}
	}

	if (di_mouse != NULL)
	{
		res = di_mouse->SetCooperativeLevel(hwnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND);
		if (res == DI_OK)
		{
			res = di_mouse->Acquire();
			if (res == DI_OK) mouse_acquired = TRUE;
		}
	}

	ResetControllers();
}

void UnAcquireDevices(void)
{
    if (di_keyb  != NULL && keyb_acquired)  { di_keyb->Unacquire();  keyb_acquired  = FALSE; }
	if (di_joy   != NULL && joy_acquired)   { di_joy->Unacquire();   joy_acquired   = FALSE; }
	if (di_mouse != NULL && mouse_acquired) { di_mouse->Unacquire(); mouse_acquired = FALSE; }
}

void DeinitDirectInput(void)
{
	// Unacquire first!
	if (keyb_acquired || joy_acquired || mouse_acquired) UnAcquireDevices();

	// Then release interfaces...
    if (di_keyb  != NULL) di_keyb->Release();
    if (di_joy   != NULL) di_joy->Release();
    if (di_mouse != NULL) di_mouse->Release();

    if (di != NULL) di->Release();
}

// Can't call this function 'GetKeyboardState'... used in DInput's dllimport.
void GetKeybState(BYTE *keys)
{
	HRESULT res;

	// Check for reentrancy.
	if (keyb_count)
	{
		memset(keys, 0, 256);
		return;
	}

	keyb_count++;

	if ((di_keyb == NULL) || (keyb_acquired == FALSE))
	{
		if (keyb_acquired == FALSE)
		{
			// Attempt to reacquire.
			AcquireDevices(last_hwnd);
		}
		else
		{
			memset(keys, 0, 256);
			return;
		}
	}

    do
    {
        res = di_keyb->GetDeviceState(256, keys);
        if (res == DIERR_INPUTLOST) di_keyb->Acquire();
    } while (res == DIERR_INPUTLOST);

	// Check for failure.
	if (res != DI_OK)
	{
		// Attempt to reacquire.
		AcquireDevices(last_hwnd);

		// Indicate failure... hopefully all went well and we can poll next time around.
		memset(keys, 0, 256);
	}

	keyb_count--;
}

void GetJoystickState(DIJOYSTATE *dijs)
{
	if ((di_joy == NULL) || (joy_acquired == FALSE))
	{
		if (joy_acquired == FALSE)
		{
			// Attempt to reacquire.
			AcquireDevices(last_hwnd);
		}
		else
		{
			memset(dijs, 0, sizeof(DIJOYSTATE));
			return;
		}
	}

	// do nothing... for now.
}

void GetMouseState(DIMOUSESTATE *dims)
{
	HRESULT res;

	if ((di_mouse == NULL) || (mouse_acquired == FALSE))
	{
		if (mouse_acquired == FALSE)
		{
			// Attempt to reacquire.
			AcquireDevices(last_hwnd);
		}
		else
		{
			memset(dims, 0, sizeof(DIMOUSESTATE));
			return;
		}
	}

    do
    {
        res = di_mouse->GetDeviceState(sizeof(DIMOUSESTATE), dims);
        if (res == DIERR_INPUTLOST) di_mouse->Acquire();
    } while (res == DIERR_INPUTLOST);

	// Check for failure.
	if (res != DI_OK)
	{
		// Attempt to reacquire.
		AcquireDevices(last_hwnd);

		// Indicate failure... hopefully all went well and we can poll next time around.
		memset(dims, 0, sizeof(DIMOUSESTATE));
	}
}

BOOL APIENTRY WaitForInputProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_INITDIALOG:
            return TRUE;
    }

    return FALSE;
}

void ScanDeviceInput(uchar *type, uchar *val, uchar scan_type)
{
	static bool help_digital = FALSE, help_analog = FALSE;
    FARPROC waitproc;
    HWND waitdlg;
	BYTE init_keys[256];
	DIJOYSTATE init_dijs;
	DIMOUSESTATE init_dims;
	slong xdiff, ydiff;
	bool done;
	int t;

	if (scan_type == TYPE_INPUT_DIGITAL)
	{
		if (help_digital == FALSE)
		{
			MessageBox(MainhWnd, "To define an input for this control, prepare the device you want to use (keyboard, joystick button,\n"
				"or mouse button), then click the OK button below.  Project UnReality will wait a half second, then\n"
				"popup a box that will wait for the control input.  The small delay is to avoid any potential\n"
				"spurious messages from any device.\n\n"
				"This message will not appear again.  Please consult UNREAL.DOC if you have any questions.\n"
				"When you are ready, click OK.", "Configuration Info", MB_OK);

			help_digital = TRUE;
		}
	}
	else
	{
		if (help_analog == FALSE)
		{
			MessageBox(MainhWnd, "To define an input for the analog stick, prepare the device you want to use (joystick or mouse),\n"
				"then click the OK button below.  Project UnReality will wait a half second, then popup a box that\n"
				"will wait for the control input.  The small delay is to avoid any potential spurious messages\n"
				"from any device.\n\n"
				"This message will not appear again.  Please consult UNREAL.DOC if you have any questions.\n"
				"When you are ready, click OK.", "Configuration Info", MB_OK);

			help_analog = TRUE;
		}
	}

	Sleep(500);

    // Open 'Waiting for input' modeless dialog...
    waitproc = MakeProcInstance((FARPROC)WaitForInputProc, MainhInst);
    waitdlg  = CreateDialog(MainhInst, MAKEINTRESOURCE(IDD_INPUT), MainhWnd, waitproc);

	// Acquire devices with new hWnd of dialog.
	AcquireDevices(waitdlg);

	// Get all initial device states.
	GetKeybState(init_keys);
	GetJoystickState(&init_dijs);
	GetMouseState(&init_dims);

	// Poll all devices looking for any changes in states.
	done = FALSE;
	while (!done)
	{
		// Sleep a bit so we don't starve the CPU.
		Sleep(10);

		// Scan current device states...
		GetKeybState(curr_keys);
		GetJoystickState(&curr_dijs);
		GetMouseState(&curr_dims);

		// Now check for changes in states.  Start with the keyboard...
		if (scan_type == TYPE_INPUT_DIGITAL)
		{
			for (t = 0; t < 256; t++)
			{
				if (curr_keys[t] & 0x80)
				{
					// Check for keys that we don't allow (Alt and Windoze keys).
					if (t == 0x38 || t == 0xB8 || t == 0xDB || t == 0xDC || t == 0xDD)
					{
						UnAcquireDevices();
						MessageBox(MainhWnd, "Sorry, that key can not be used!  Try again.", NULL, MB_OK);
						done = TRUE;
					}
					else
					{
						done  = TRUE;
						*type = TYPE_KEYBOARD;
						*val  = t;
					}
				}
			}
		}

		// Next, check the joystick.
		if (!done)
		{
			// nothing for now...
		}

		// Finally, check the mouse.
		if (!done)
		{
			if (scan_type == TYPE_INPUT_ANALOG)
			{
				xdiff = curr_dims.lX - init_dims.lX;
				ydiff = curr_dims.lY - init_dims.lY;

				// Ignore small movements.
				if (xdiff < -32 || xdiff > 32 || ydiff < -32 || ydiff > 32)
				{
					done  = TRUE;
					*type = TYPE_MOUSE_MOVE;
					*val  = 0;
				}
			}

			// Check mouse buttons.
			if (!done && (scan_type == TYPE_INPUT_DIGITAL))
			{
				for (t = 0; t < 4; t++)
				{
					if (curr_dims.rgbButtons[t] & 0x80)
					{
						done  = TRUE;
						*type = TYPE_MOUSE_BUTTON;
						*val  = t;
					}
				}
			}
		}
	}

	UnAcquireDevices();
    DestroyWindow(waitdlg);
    FreeProcInstance(waitproc);
}

void CreateDeviceString(char *buf, uchar type, uchar val)
{
	switch (type)
	{
		case TYPE_DISABLED:
			buf[0] = 0;
			break;

		case TYPE_KEYBOARD:
			strcpy(buf, DIK_Codes[val]);
			break;

		case TYPE_JOYSTICK_BUTTON:
			sprintf(buf, "JoystickBtn %d", val);
			break;

		case TYPE_JOYSTICK_MOVE:
			strcpy(buf, "Joystick");
			break;

		case TYPE_MOUSE_BUTTON:
			switch (val)
			{
				case 0: strcpy(buf, "Left MouseBtn"); break;
				case 1: strcpy(buf, "Right MouseBtn"); break;
				case 2: strcpy(buf, "Middle MouseBtn"); break;
				default: sprintf(buf, "MouseBtn %d", val); break;
			}
			break;

		case TYPE_MOUSE_MOVE:
			strcpy(buf, "Mouse");
			break;
	}
}

bool ScanControllersForDupes(void)
{
	char buf[256];
	int scont, sbtn, dcont, dbtn;

	// Scan for button conflicts.
	for (scont = 0; scont < 4; scont++)
	{
		if (cfg.Cont[scont].Controller_Active)
		{
			for (sbtn = 0; sbtn < MAX_BUTTONS; sbtn++)
			{
				for (dcont = 0; dcont < 4; dcont++)
				{
					if (cfg.Cont[dcont].Controller_Active)
					{
						for (dbtn = 0; dbtn < MAX_BUTTONS; dbtn++)
						{
							if (!((scont == dcont) && (sbtn == dbtn)))
							{
								if ((cfg.Cont[scont].Type[sbtn] != TYPE_DISABLED) && (cfg.Cont[dcont].Type[dbtn] != TYPE_DISABLED))
								{
									if ((cfg.Cont[scont].Type[sbtn] == cfg.Cont[dcont].Type[dbtn]) &&
										(cfg.Cont[scont].Val[sbtn]  == cfg.Cont[dcont].Val[dbtn]))
									{
										sprintf(buf, "There is a conflict between %s on controller %d and %s on controller %d.\n\nClick OK, then correct this conflict.",
											btn_table[sbtn], scont + 1, btn_table[dbtn], dcont + 1);

										MessageBox(MainhWnd, buf, NULL, MB_OK);
										return TRUE;
									}
								}
							}
						}
					}
				}
			}
		}
	}

	// Scan for toggle conflicts.
	for (scont = 0; scont < 4; scont++)
	{
		if (cfg.Cont[scont].Controller_Active && cfg.Cont[scont].Toggle_Active)
		{
			for (dcont = 0; dcont < 4; dcont++)
			{
				if ((scont != dcont) && (cfg.Cont[dcont].Controller_Active) && (cfg.Cont[dcont].Toggle_Active))
				{
					if ((cfg.Cont[scont].Type_Toggle != TYPE_DISABLED) && (cfg.Cont[dcont].Type_Toggle != TYPE_DISABLED))
					{
						if ((cfg.Cont[scont].Type_Toggle == cfg.Cont[dcont].Type_Toggle) &&
							(cfg.Cont[scont].Val_Toggle  == cfg.Cont[dcont].Val_Toggle))
						{
							sprintf(buf, "There is a conflict between the D-Pad/Analog toggle on controller %d and the D-Pad/Analog toggle on controller %d.\n\nClick OK, then correct this conflict.",
								scont + 1, dcont + 1);

							MessageBox(MainhWnd, buf, NULL, MB_OK);
							return TRUE;
						}
					}
				}
			}
		}
	}

	// Scan for analog conflicts.
	for (scont = 0; scont < 4; scont++)
	{
		if (cfg.Cont[scont].Controller_Active && !cfg.Cont[scont].Toggle_Active)
		{
			for (dcont = 0; dcont < 4; dcont++)
			{
				if ((scont != dcont) && (cfg.Cont[dcont].Controller_Active) && (!cfg.Cont[dcont].Toggle_Active))
				{
					if ((cfg.Cont[scont].Type_Analog != TYPE_DISABLED) && (cfg.Cont[dcont].Type_Analog != TYPE_DISABLED))
					{
						if (cfg.Cont[scont].Type_Analog == cfg.Cont[dcont].Type_Analog)
						{
							sprintf(buf, "There is a conflict between the analog input on controller %d and the analog input on controller %d.\n\nClick OK, then correct this conflict.",
								scont + 1, dcont + 1);

							MessageBox(MainhWnd, buf, NULL, MB_OK);
							return TRUE;
						}
					}
				}
			}
		}
	}

	return FALSE;
}

void PollDeviceData(int cont)
{
	current_cont = cont;

	GetKeybState(curr_keys);
	GetJoystickState(&curr_dijs);
	GetMouseState(&curr_dims);
}

bool DeviceButtonPressed(int btn)
{
	uchar type, val;

	type = cfg.Cont[current_cont].Type[btn];
	val  = cfg.Cont[current_cont].Val[btn];

	if (type == TYPE_KEYBOARD)
	{
		if (curr_keys[val] & 0x80) return TRUE;
	} else if (type == TYPE_JOYSTICK_BUTTON) {
		// do nothing for now...
	} else if (type == TYPE_MOUSE_BUTTON) {
		if (curr_dims.rgbButtons[val] & 0x80) return TRUE;
	}

	return FALSE;
}

void DevicePosition(schar *x, schar *y)
{
	if (cfg.Cont[current_cont].Type_Analog == TYPE_JOYSTICK_MOVE)
	{
		// do nothing for now...
		*x = 0;
		*y = 0;
	}
	else
	{
		// update internal variables with new mouse position...
		mouse_xpos += (curr_dims.lX >> 1);
		mouse_ypos -= (curr_dims.lY >> 1);

		if (mouse_xpos < -83)
			mouse_xpos = -83;
		else
		{
			if (mouse_xpos > 92)
				mouse_xpos = 92;
		}

//		if (mouse_ypos < 0)
//		{
//			SendMessage(MainhWnd, WM_COMMAND, ID_DEBUG_ENTERDEBUGGER, 0);
//		}

		if (mouse_ypos < -84)
			mouse_ypos = -84;
		else
		{
			if (mouse_ypos > 93)
				mouse_ypos = 93;
		}

		*x = (schar)mouse_xpos;
		*y = (schar)mouse_ypos;
	}
}

void ResetControllers(void)
{
	mouse_xpos = 0;
	mouse_ypos = 0;
}

//---------------------------------------------------------------------------------------------------------------------

DWORD Input_ThreadFunc(LPDWORD lpdwParam)
{
	BYTE keys[256];

	while (!Input_Exit)
	{
		Sleep(250);

		// Only process keys if the CFB has been initialized.
		if (CFB_BitDepth != 8)
		{
			GetKeybState(keys);

			// Check to see if Alt-Enter was pressed.
			if (((keys[0x38] & 0x80) || (keys[0xB8] & 0x80)) && (keys[0x1C] & 0x80))
			{
				EventHeapInsert(c0reg.Count, CFB_FlipMode);
				Sleep(1000);
			}
		}
	}

	return 0;
}
