// Project description
// -------------------
// Name: nJoy
// Description: A Dolphin Compatible Input Plugin
//
// Author: Falcon4ever (nJoy@falcon4ever.com)
// Site: www.multigesture.net
// Copyright (C) 2003 Dolphin Project.
//


// Copyright (C) 2003 Dolphin Project.

// 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, version 2.0.

// 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 2.0 for more details.

// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/

// Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/


#include "GCPad.h"
#include "Config.h"
#include "LogManager.h"
#if defined(HAVE_WX) && HAVE_WX
	#include "ConfigBox.h"
#endif

#ifdef _WIN32
#include "XInput.h"
#endif

// Declare config window so that we can write debugging info to it from functions in this file
#if defined(HAVE_WX) && HAVE_WX
	GCPadConfigDialog* m_ConfigFrame = NULL;
#endif


// Variables
// ---------
bool g_SearchDeviceDone = false;
CONTROLLER_MAPPING_GC GCMapping[4];
std::vector<InputCommon::CONTROLLER_INFO> joyinfo;
int NumPads = 0, NumGoodPads = 0, g_ID = 0;
#ifdef _WIN32
	HWND m_hWnd = NULL; // Handle to window
#endif
#if defined(HAVE_X11) && HAVE_X11
   Display* GCdisplay;
#endif
SPADInitialize *g_PADInitialize = NULL;
PLUGIN_GLOBALS* globals = NULL;


// Standard crap to make wxWidgets happy
#ifdef _WIN32
HINSTANCE g_hInstance;

#if defined(HAVE_WX) && HAVE_WX
class wxDLLApp : public wxApp
{
	bool OnInit()
	{
		return true;
	}
};
IMPLEMENT_APP_NO_MAIN(wxDLLApp) 
	WXDLLIMPEXP_BASE void wxSetInstance(HINSTANCE hInst);
#endif

BOOL APIENTRY DllMain(HINSTANCE hinstDLL,	// DLL module handle
	DWORD dwReason,		// reason called
	LPVOID lpvReserved)	// reserved
{
	switch (dwReason)
	{
	case DLL_PROCESS_ATTACH:
		{
#if defined(HAVE_WX) && HAVE_WX
			wxSetInstance((HINSTANCE)hinstDLL);
			int argc = 0;
			char **argv = NULL;
			wxEntryStart(argc, argv);
			if (!wxTheApp || !wxTheApp->CallOnInit())
				return FALSE;
#endif
		}
		break; 

	case DLL_PROCESS_DETACH:
#if defined(HAVE_WX) && HAVE_WX
		wxEntryCleanup();
#endif
		break;
	default:
		break;
	}

	g_hInstance = hinstDLL;
	return TRUE;
}
#endif

#if defined(HAVE_WX) && HAVE_WX
wxWindow* GetParentedWxWindow(HWND Parent)
{
#ifdef _WIN32
	wxSetInstance((HINSTANCE)g_hInstance);
#endif
	wxWindow *win = new wxWindow();
#ifdef _WIN32
	win->SetHWND((WXHWND)Parent);
	win->AdoptAttributesFromHWND();
#endif
	return win;
}
#endif


// Input Plugin Functions (from spec's)
// ------------------------------------

// Get properties of plugin
// ------------------------
void GetDllInfo(PLUGIN_INFO* _PluginInfo)
{
	_PluginInfo->Version = 0x0100;
	_PluginInfo->Type = PLUGIN_TYPE_PAD;

#ifdef DEBUGFAST
	sprintf(_PluginInfo->Name, "Dolphin GCPad Plugin (DebugFast)");
#else
#ifdef _DEBUG
	sprintf(_PluginInfo->Name, "Dolphin GCPad Plugin (Debug)");
#else
	sprintf(_PluginInfo->Name, "Dolphin GCPad Plugin");
#endif
#endif
}

void SetDllGlobals(PLUGIN_GLOBALS* _pPluginGlobals)
{
	globals = _pPluginGlobals;
	LogManager::SetInstance((LogManager *)globals->logManager);
}


// Call config dialog
// ------------------
void DllConfig(HWND _hParent)
{
	if (!g_SearchDeviceDone)
	{
		g_Config.Load();	// load settings
		// Init Joystick + Haptic (force feedback) subsystem on SDL 1.3
		// Populate joyinfo for all attached devices
		Search_Devices(joyinfo, NumPads, NumGoodPads);
		g_SearchDeviceDone = true;
	}

#if defined(HAVE_WX) && HAVE_WX
	if (!m_ConfigFrame)
	{
		wxWindow *frame = GetParentedWxWindow(_hParent);
		m_ConfigFrame = new GCPadConfigDialog(frame);
		m_ConfigFrame->ShowModal();
		m_ConfigFrame->Destroy();
		m_ConfigFrame = NULL;
#ifdef _WIN32
		frame->SetHWND(NULL);
#endif
		delete frame;
	}
#endif
}

void DllDebugger(HWND _hParent, bool Show)
{
}


// Init PAD (start emulation)
// --------------------------
void Initialize(void *init)
{
	INFO_LOG(PAD, "Initialize: %i", SDL_WasInit(0));
    g_PADInitialize = (SPADInitialize*)init;
	
#ifdef _WIN32
	m_hWnd = (HWND)g_PADInitialize->hWnd;
#endif
#if defined(HAVE_X11) && HAVE_X11
   GCdisplay = (Display*)g_PADInitialize->hWnd; 
#endif

	if (!g_SearchDeviceDone)
	{
		g_Config.Load();	// load settings
		// Populate joyinfo for all attached devices
		Search_Devices(joyinfo, NumPads, NumGoodPads);
		g_SearchDeviceDone = true;
	}
}

// Shutdown PAD (stop emulation)
// -----------------------------
void Shutdown()
{
	INFO_LOG(PAD, "Shutdown: %i", SDL_WasInit(0));

	Close_Devices();

	// Finally close SDL
	if (SDL_WasInit(0))
	{
		SDL_Quit();
	}

	// Remove the pointer to the initialize data
	g_PADInitialize = NULL;
	g_SearchDeviceDone = false;
}

// Save state
// --------------
void DoState(unsigned char **ptr, int mode)
{
#ifdef RERECORDING
	Recording::DoState(ptr, mode);
#endif
}

void EmuStateChange(PLUGIN_EMUSTATE newState)
{
}

// Set buttons status from keyboard input. Currently this is done from wxWidgets in the main application.
// --------------
void PAD_Input(u16 _Key, u8 _UpDown)
{
}

// Set PAD status
// --------------
// Called from: SI_DeviceGCController.cpp
// Function: Gives the current pad status to the Core
void PAD_GetStatus(u8 _numPAD, SPADStatus* _pPADStatus)
{
	// Check if all is okay
	if (_pPADStatus == NULL) return;

	// Clear pad
	memset(_pPADStatus, 0, sizeof(SPADStatus));
	const int base = 0x80;
	_pPADStatus->stickX = base;
	_pPADStatus->stickY = base;
	_pPADStatus->substickX = base;
	_pPADStatus->substickY = base;
	_pPADStatus->button |= PAD_USE_ORIGIN;
	_pPADStatus->err = PAD_ERR_NONE;

	// Check that Dolphin is in focus, otherwise don't update the pad status
	if (!IsFocus()) return;

	g_ID = _numPAD;

	if (NumGoodPads && NumPads > GCMapping[_numPAD].ID)
		UpdatePadState(GCMapping[_numPAD]);

	if (IsKey(EGC_A))
	{
		_pPADStatus->button |= PAD_BUTTON_A;
		_pPADStatus->analogA = DEF_BUTTON_FULL;
	}
	if (IsKey(EGC_B))
	{
		_pPADStatus->button |= PAD_BUTTON_B;
		_pPADStatus->analogB = DEF_BUTTON_FULL;
	}
	if (IsKey(EGC_X))	_pPADStatus->button |= PAD_BUTTON_X;
	if (IsKey(EGC_Y))	_pPADStatus->button |= PAD_BUTTON_Y;
	if (IsKey(EGC_Z))	_pPADStatus->button |= PAD_TRIGGER_Z;
	if (IsKey(EGC_START))	_pPADStatus->button |= PAD_BUTTON_START;
	if (IsKey(EGC_DPAD_UP))	_pPADStatus->button |= PAD_BUTTON_UP;
	if (IsKey(EGC_DPAD_DOWN))	_pPADStatus->button |= PAD_BUTTON_DOWN;
	if (IsKey(EGC_DPAD_LEFT))	_pPADStatus->button |= PAD_BUTTON_LEFT;
	if (IsKey(EGC_DPAD_RIGHT))	_pPADStatus->button |= PAD_BUTTON_RIGHT;

	if (GCMapping[_numPAD].Stick.Main == FROM_KEYBOARD)
	{
		int iMagnitude = DEF_STICK_FULL;
		bool bUp = false;
		bool bDown = false;
		bool bLeft = false;
		bool bRight = false;
		if (IsKey(EGC_STICK_SEMI)) iMagnitude = GCMapping[_numPAD].Pressure.Main;
		if (IsKey(EGC_STICK_UP)) bUp = true;
		else if (IsKey(EGC_STICK_DOWN)) bDown = true;
		if (IsKey(EGC_STICK_LEFT)) bLeft = true;
		else if (IsKey(EGC_STICK_RIGHT)) bRight = true;
		EmulateAnalogStick(_pPADStatus->stickX, _pPADStatus->stickY, bUp, bDown, bLeft, bRight, iMagnitude);
	}
	else if (GCMapping[_numPAD].Stick.Main == FROM_ANALOG1)
	{
		_pPADStatus->stickX = GCMapping[_numPAD].AxisState.Lx;
		// Y-axis is inverted
		_pPADStatus->stickY = 0xFF - (u8)GCMapping[_numPAD].AxisState.Ly;
	}
	else if (GCMapping[_numPAD].Stick.Main == FROM_ANALOG2)
	{
		_pPADStatus->stickX = GCMapping[_numPAD].AxisState.Rx;
		// Y-axis is inverted
		_pPADStatus->stickY = 0xFF - (u8)GCMapping[_numPAD].AxisState.Ry;
	}
	else
	{
		_pPADStatus->stickX = GCMapping[_numPAD].AxisState.Tl;
		_pPADStatus->stickY = GCMapping[_numPAD].AxisState.Tr;
	}

	if (GCMapping[_numPAD].Stick.Sub == FROM_KEYBOARD)
	{
		int iMagnitude = DEF_STICK_FULL;
		bool bUp = false;
		bool bDown = false;
		bool bLeft = false;
		bool bRight = false;
		if (IsKey(EGC_CSTICK_SEMI)) iMagnitude = GCMapping[_numPAD].Pressure.Sub;
		if (IsKey(EGC_CSTICK_UP)) bUp = true;
		else if (IsKey(EGC_CSTICK_DOWN)) bDown = true;
		if (IsKey(EGC_CSTICK_LEFT)) bLeft = true;
		else if (IsKey(EGC_CSTICK_RIGHT)) bRight = true;
		EmulateAnalogStick(_pPADStatus->substickX, _pPADStatus->substickY, bUp, bDown, bLeft, bRight, iMagnitude);
	}
	else if (GCMapping[_numPAD].Stick.Sub == FROM_ANALOG1)
	{
		_pPADStatus->substickX = GCMapping[_numPAD].AxisState.Lx;
		// Y-axis is inverted
		_pPADStatus->substickY = 0xFF - (u8)GCMapping[_numPAD].AxisState.Ly;
	}
	else if (GCMapping[_numPAD].Stick.Sub == FROM_ANALOG2)
	{
		_pPADStatus->substickX = GCMapping[_numPAD].AxisState.Rx;
		// Y-axis is inverted
		_pPADStatus->substickY = 0xFF - (u8)GCMapping[_numPAD].AxisState.Ry;
	}
	else
	{
		_pPADStatus->substickX = GCMapping[_numPAD].AxisState.Tl;
		_pPADStatus->substickY = GCMapping[_numPAD].AxisState.Tr;
	}

	if (GCMapping[_numPAD].Stick.Shoulder == FROM_KEYBOARD)
	{
		if (IsKey(EGC_TGR_L))
		{
			_pPADStatus->triggerLeft = DEF_TRIGGER_FULL;
			_pPADStatus->button |= PAD_TRIGGER_L;
		}
		else if (IsKey(EGC_TGR_SEMI_L))
		{
			_pPADStatus->triggerLeft = GCMapping[_numPAD].Pressure.Shoulder;
			if (_pPADStatus->triggerLeft > DEF_TRIGGER_THRESHOLD)
				_pPADStatus->button |= PAD_TRIGGER_L;
		}

		if (IsKey(EGC_TGR_R))
		{
			_pPADStatus->triggerRight = DEF_TRIGGER_FULL;
			_pPADStatus->button |= PAD_TRIGGER_R;
		}
		else if (IsKey(EGC_TGR_SEMI_R))
		{
			_pPADStatus->triggerRight = GCMapping[_numPAD].Pressure.Shoulder;
			if (_pPADStatus->triggerRight > DEF_TRIGGER_THRESHOLD)
				_pPADStatus->button |= PAD_TRIGGER_R;
		}
	}
	else if (GCMapping[_numPAD].Stick.Shoulder == FROM_ANALOG1)
	{
		_pPADStatus->triggerLeft = GCMapping[_numPAD].AxisState.Lx;
		_pPADStatus->triggerRight = GCMapping[_numPAD].AxisState.Ly;
		EmulateAnalogTrigger(_pPADStatus->triggerLeft, _pPADStatus->triggerRight);
		if (_pPADStatus->triggerLeft > DEF_TRIGGER_THRESHOLD)
			_pPADStatus->button |= PAD_TRIGGER_L;
		if (_pPADStatus->triggerRight > DEF_TRIGGER_THRESHOLD)
			_pPADStatus->button |= PAD_TRIGGER_R;
	}
	else if (GCMapping[_numPAD].Stick.Shoulder == FROM_ANALOG2)
	{
		_pPADStatus->triggerLeft = GCMapping[_numPAD].AxisState.Rx;
		_pPADStatus->triggerRight = GCMapping[_numPAD].AxisState.Ry;
		EmulateAnalogTrigger(_pPADStatus->triggerLeft, _pPADStatus->triggerRight);
		if (_pPADStatus->triggerLeft > DEF_TRIGGER_THRESHOLD)
			_pPADStatus->button |= PAD_TRIGGER_L;
		if (_pPADStatus->triggerRight > DEF_TRIGGER_THRESHOLD)
			_pPADStatus->button |= PAD_TRIGGER_R;
	}
	else
	{
		_pPADStatus->triggerLeft = GCMapping[_numPAD].AxisState.Tl;
		_pPADStatus->triggerRight = GCMapping[_numPAD].AxisState.Tr;
		EmulateAnalogTrigger(_pPADStatus->triggerLeft, _pPADStatus->triggerRight);
		if (_pPADStatus->triggerLeft > DEF_TRIGGER_THRESHOLD)
			_pPADStatus->button |= PAD_TRIGGER_L;
		if (_pPADStatus->triggerRight > DEF_TRIGGER_THRESHOLD)
			_pPADStatus->button |= PAD_TRIGGER_R;
	}

}


//******************************************************************************
// Supporting functions
//******************************************************************************

// for same displacement should be sqrt(2)/2 (in theory)
// 3/4 = 0.75 is a little faster than sqrt(2)/2 = 0.7071...
// In SMS, 17/20 = 0.85 is perfect; in WW, 7/10 = 0.70 is closer.
#define DIAGONAL_SCALE 0.70710678
void EmulateAnalogStick(unsigned char &stickX, unsigned char &stickY, bool buttonUp, bool buttonDown, bool buttonLeft, bool buttonRight, int magnitude)
{
	int mainX = 0;
	int mainY = 0;
	if (buttonUp)
		mainY = magnitude;
	else if (buttonDown)
		mainY = -magnitude;
	if (buttonLeft)
		mainX = -magnitude;
	else if (buttonRight)
		mainX = magnitude;

	if ((mainX == 0) || (mainY == 0))
	{
		stickX += mainX;
		stickY += mainY;
	}
	else
	{
		stickX += mainX * DIAGONAL_SCALE;
		stickY += mainY * DIAGONAL_SCALE;
	}
}

void EmulateAnalogTrigger(unsigned char &trL, unsigned char &trR)
{
	if (GCMapping[g_ID].TriggerType == InputCommon::CTL_TRIGGER_SDL)
	{
		int triggerL = abs((int)trL - 0x80) * 2;
		int triggerR = abs((int)trR - 0x80) * 2;
		trL = (triggerL > 0xFF) ? 0xFF : triggerL;
		trR = (triggerR > 0xFF) ? 0xFF : triggerR;
	}
}

void Close_Devices()
{
	PAD_RumbleClose();

	if (SDL_WasInit(0))
	{
		for (int i = 0; i < NumPads; i++)
		{
			if (joyinfo.at(i).joy)
			{
				if(SDL_JoystickOpened(i))
				{
					INFO_LOG(WIIMOTE, "Shut down Joypad: %i", i);
					SDL_JoystickClose(joyinfo.at(i).joy);
				}
			}
		}
	}

	for (int i = 0; i < 4; i++)
		GCMapping[i].joy = NULL;

	// Clear the physical device info
	joyinfo.clear();
	NumPads = 0;
	NumGoodPads = 0;
}

// Search for SDL devices
// ----------------
bool Search_Devices(std::vector<InputCommon::CONTROLLER_INFO> &_joyinfo, int &_NumPads, int &_NumGoodPads)
{
	// Close opened devices first
	Close_Devices();

	bool success = InputCommon::SearchDevices(_joyinfo, _NumPads, _NumGoodPads);

	if (_NumGoodPads == 0)
		return false;

	for (int i = 0; i < 4; i++)
	{
		if (_NumPads > GCMapping[i].ID)
			if(joyinfo.at(GCMapping[i].ID).Good)
			{
				GCMapping[i].joy = joyinfo.at(GCMapping[i].ID).joy;
#ifdef _WIN32
				XINPUT_STATE xstate;
				DWORD xresult = XInputGetState(GCMapping[i].ID, &xstate);
				if (xresult == ERROR_SUCCESS)
					GCMapping[i].TriggerType = InputCommon::CTL_TRIGGER_XINPUT;
#endif
			}
	}

	return success;
}

void GetAxisState(CONTROLLER_MAPPING_GC &_GCMapping)
{
	// Update the gamepad status
	SDL_JoystickUpdate();

	// Update axis states. It doesn't hurt much if we happen to ask for nonexisting axises here.
	_GCMapping.AxisState.Lx = SDL_JoystickGetAxis(_GCMapping.joy, _GCMapping.AxisMapping.Lx);
	_GCMapping.AxisState.Ly = SDL_JoystickGetAxis(_GCMapping.joy, _GCMapping.AxisMapping.Ly);
	_GCMapping.AxisState.Rx = SDL_JoystickGetAxis(_GCMapping.joy, _GCMapping.AxisMapping.Rx);
	_GCMapping.AxisState.Ry = SDL_JoystickGetAxis(_GCMapping.joy, _GCMapping.AxisMapping.Ry);

	// Update the analog trigger axis values
#ifdef _WIN32
	if (_GCMapping.TriggerType == InputCommon::CTL_TRIGGER_SDL)
	{
#endif
		// If we are using SDL analog triggers the buttons have to be mapped as 1000 or up, otherwise they are not used
		// We must also check that we are not asking for a negative axis number because SDL_JoystickGetAxis() has
		// no good way of handling that
		if ((_GCMapping.AxisMapping.Tl - 1000) >= 0)
			_GCMapping.AxisState.Tl = SDL_JoystickGetAxis(_GCMapping.joy, _GCMapping.AxisMapping.Tl - 1000);
		if ((_GCMapping.AxisMapping.Tr - 1000) >= 0)
			_GCMapping.AxisState.Tr = SDL_JoystickGetAxis(_GCMapping.joy, _GCMapping.AxisMapping.Tr - 1000);
#ifdef _WIN32
	}
	else
	{
		_GCMapping.AxisState.Tl = XInput::GetXI(_GCMapping.ID, _GCMapping.AxisMapping.Tl - 1000);
		_GCMapping.AxisState.Tr = XInput::GetXI(_GCMapping.ID, _GCMapping.AxisMapping.Tr - 1000);
	}
#endif
}

void UpdatePadState(CONTROLLER_MAPPING_GC &_GCiMapping)
{
	// Return if we have no pads
	if (NumGoodPads == 0) return;

	GetAxisState(_GCiMapping);

	int &Lx = _GCiMapping.AxisState.Lx;
	int &Ly = _GCiMapping.AxisState.Ly;
	int &Rx = _GCiMapping.AxisState.Rx;
	int &Ry = _GCiMapping.AxisState.Ry;
	int &Tl = _GCiMapping.AxisState.Tl;
	int &Tr = _GCiMapping.AxisState.Tr;

	// Check the circle to square option
	if(_GCiMapping.bSquare2Circle)
	{
		InputCommon::Square2Circle(Lx, Ly, _GCiMapping.Diagonal, false);
		InputCommon::Square2Circle(Rx, Ry, _GCiMapping.Diagonal, false);
	}

	// Dead zone adjustment
	float DeadZoneLeft = (float)_GCiMapping.DeadZoneL / 100.0f;
	float DeadZoneRight = (float)_GCiMapping.DeadZoneR / 100.0f;
	if (InputCommon::IsDeadZone(DeadZoneLeft, Lx, Ly))
	{
		Lx = 0;
		Ly = 0;
	}
	if (InputCommon::IsDeadZone(DeadZoneRight, Rx, Ry))
	{
		Rx = 0;
		Ry = 0;
	}

	// Downsize the values from 0x8000 to 0x80
	Lx = InputCommon::Pad_Convert(Lx);
	Ly = InputCommon::Pad_Convert(Ly);
	Rx = InputCommon::Pad_Convert(Rx);
	Ry = InputCommon::Pad_Convert(Ry);
	// The XInput range is already 0 to 0x80
	if (_GCiMapping.TriggerType == InputCommon::CTL_TRIGGER_SDL)
	{
		Tl = InputCommon::Pad_Convert(Tl);
		Tr = InputCommon::Pad_Convert(Tr);
	}
}

// Multi System Input Status Check
bool IsKey(int Key)
{
	int Ret = NULL;
	int MapKey = GCMapping[g_ID].Button[Key];

#ifdef _WIN32
	if (MapKey < 256)
	{
		Ret = GetAsyncKeyState(MapKey);		// Keyboard (Windows)
	}
	else if (MapKey < 0x1100)
#elif defined HAVE_X11 && HAVE_X11
	if (MapKey < 256 || MapKey > 0xf000)
	{
		char keys[32];
		KeyCode keyCode;
		XQueryKeymap(GCdisplay, keys);
		keyCode = XKeysymToKeycode(GCdisplay, MapKey);
		Ret = (keys[keyCode/8] & (1 << (keyCode%8)));	// Keyboard (Linux)
	}
	else if (MapKey < 0x1100)
#else
	if (MapKey < 0x1100)
#endif
	{
		Ret = SDL_JoystickGetButton(GCMapping[g_ID].joy, MapKey - 0x1000);	// Pad button
	}
	else	// Pad hat
	{
		u8 HatCode, HatKey;
		HatCode = SDL_JoystickGetHat(GCMapping[g_ID].joy, (MapKey - 0x1100) / 0x0010);
		HatKey = (MapKey - 0x1100) % 0x0010;

		if (HatCode & HatKey)
			Ret = HatKey;
	}

	return (Ret) ? true : false;
}

// Check if Dolphin is in focus
// ----------------
bool IsFocus()
{
#ifdef _WIN32
	HWND RenderingWindow = (g_PADInitialize) ? g_PADInitialize->hWnd : NULL;
	HWND Parent = GetParent(RenderingWindow);
	HWND TopLevel = GetParent(Parent);

	if (GetForegroundWindow() == TopLevel || GetForegroundWindow() == RenderingWindow)
		return true;
	else
		return false;
#elif defined HAVE_X11 && HAVE_X11
	Window GLWin = *(Window *)g_PADInitialize->pXWindow;
	Window FocusWin;
	int Revert;
	XGetInputFocus(GCdisplay, &FocusWin, &Revert);
	return (GLWin != 0 && GLWin == FocusWin);
#else
	return true;
#endif
}