//
// Asura_Mouse.cpp
//
// Created by Kevin Lea - 05/10/2001 16:40:25
// 
// Copyright (C) 2001 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

#include "Input/Asura_Mouse.h"
#include "Input/Asura_DirectInput.h"
#include "Interface/Asura_Cursor.h"
#include "Core/Asura_Windows.h"

#include "Core/Asura_Timers.h"
#include "Input/Asura_PlatformInput.h"
#include "Debugging/Asura_Assert.h"


#define DINPUT_BUFFERSIZE  64

static HANDLE g_hMouseEvent = NULL;
static LPDIRECTINPUTDEVICE8 lpMouse = NULL; 

#ifdef ASURA_TOOLS
bool Asura_Mouse::s_bExclusive = false;
bool Asura_Mouse::s_bExclusiveInternal = false;
bool Asura_Mouse::s_bUseWindowsCursor = true;
#else
bool Asura_Mouse::s_bExclusive = true;
bool Asura_Mouse::s_bExclusiveInternal = true;
bool Asura_Mouse::s_bUseWindowsCursor = false;
#endif
bool Asura_Mouse::s_bRestrictWindowsCursorToWindow = false;
bool Asura_Mouse::s_bIgnoreNextFramesInput;

static ASURA_KEYID g_aeMouseButtonIDs[] =
{
	ASURA_KEYID_MOUSEBUTTON1,
	ASURA_KEYID_MOUSEBUTTON2,
	ASURA_KEYID_MOUSEBUTTON3,
	ASURA_KEYID_MOUSEBUTTON4,
	ASURA_KEYID_MOUSEBUTTON5,
};

bool Asura_Mouse::Initialise(void)
{
	// (AAM - 14.01.2004) moved Asura_DirectInput::Initialise() call into Asura_UserInput::Platform_Initialise()

	if (!Asura_DirectInput::DIObject)
	{
		Asura_Assert(false, "Tried to set up the mouse, but DirectInput has not been initialised.");
		return false;
	}

	if(IsDebuggerPresent() && s_bExclusive)
	{
		//force non-exclusive mouse when running in debugger to prevent the mouse from vanishing when hitting breakpoints.
		SetRestrictWindowsCursorToWindow(true);
		SetExclusive(false);
	}

	HRESULT	hr; 
 
	hr = Asura_DirectInput::DIObject->CreateDevice(GUID_SysMouse, &lpMouse, NULL);
	Asura_Assert( NULL != lpMouse, "Mouse Input has just failed to initialise.\n Close the editor, restart it and sit and wait for it to finished initialising and loading with out alt-tabing or loosing focus or anything else.\n");
 	if (FAILED(hr)) return false; 

	hr = lpMouse->SetDataFormat(&c_dfDIMouse2);
	if (FAILED(hr)) return false; 

	int iExclusiveFlag = s_bExclusiveInternal ? DISCL_EXCLUSIVE : DISCL_NONEXCLUSIVE;
	hr = lpMouse->SetCooperativeLevel(Asura_Windows::GetWindowHandle(),iExclusiveFlag | DISCL_FOREGROUND);
 	if (FAILED(hr)) return false; 

	g_hMouseEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
 	if (g_hMouseEvent == NULL) return false;
	 
	hr = lpMouse->SetEventNotification(g_hMouseEvent);
 	if (FAILED(hr)) return false; 

 
	DIPROPDWORD dipdw;
    // the header
    dipdw.diph.dwSize       = sizeof(DIPROPDWORD);
    dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
    dipdw.diph.dwObj        = 0;
    dipdw.diph.dwHow        = DIPH_DEVICE;
    // the data
    dipdw.dwData            = DINPUT_BUFFERSIZE;
 
	hr = lpMouse->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph);
 	if (FAILED(hr)) return false; 


	lpMouse->Acquire();

	DeltaX=0;
	DeltaY=0;
	HorizontalVelocity = 0;
	VerticalVelocity = 0;

	return true;
}

void Asura_Mouse::Shutdown(void)
{
    if( lpMouse )
    {
        lpMouse->SetEventNotification( NULL );
    }

    if( g_hMouseEvent )
    {
        CloseHandle( g_hMouseEvent );
        g_hMouseEvent = NULL;
    }

    if( lpMouse )
    {
        lpMouse->Unacquire();
        lpMouse->Release();
        lpMouse = NULL;
    }

	// (AAM - 14.01.2004) moved Asura_DirectInput::Shutdown() call into Asura_UserInput::Platform_Shutdown()
}

void Asura_Mouse::OncePerFrameUpdate(void)
{
	HorizontalVelocity = DeltaX*Asura_Timers::GetFrameRate();
	VerticalVelocity = DeltaY*Asura_Timers::GetFrameRate();
	DeltaX=0;
	DeltaY=0;
}
void Asura_Mouse::Update(float& deltaX, float& deltaY)
{
#if 01

	if (lpMouse==NULL)
	{
	 	return;
	}

    DIDEVICEOBJECTDATA od[DINPUT_BUFFERSIZE];
    DWORD dwElements = DINPUT_BUFFERSIZE;
	HRESULT hres;
	
	deltaX = 0;
	deltaY = 0;

	hres = lpMouse->GetDeviceData(sizeof(DIDEVICEOBJECTDATA),&od[0],&dwElements, 0);

    
    if (hres == DIERR_INPUTLOST || hres==DIERR_NOTACQUIRED)
	{
		// We had acquisition, but lost it.  Try to reacquire it.
		hres = lpMouse->Acquire();
		// No data this time
    	return;
    }

    if(hres == DI_BUFFEROVERFLOW)
	{
		// If the buffer overflows then our record of the button state built up from the messages will
		// no longer be reliable.
		// So , find out the current state of all thw buttons directly.
		// RAR 02/02/03
		DIMOUSESTATE2 xMouseState;
		hres = lpMouse->GetDeviceState(sizeof(xMouseState), &xMouseState);

		if(hres == DI_OK)
		{
			for(int i = 0; i < 5; i++)
			{
				if(xMouseState.rgbButtons[i] & 0x80)
					Asura_PlatformInput::KeyboardInput_AsciiKeyDown(g_aeMouseButtonIDs[i]);
				else
					Asura_PlatformInput::KeyboardInput_AsciiKeyUp(g_aeMouseButtonIDs[i]);
			}
		}
		else
		{
			Asura_Assertf(0,("Failed to get mouse state. hres = 0x%x",hres));
		}
		return;
	}
	if(s_bIgnoreNextFramesInput)
	{
		s_bIgnoreNextFramesInput = false;
		return;
	}
	
	// Unable to read data
	if (hres != DI_OK) return;

    // Check for any data being picked up
	if (dwElements == 0) return;
	
    // Process all recovered elements and
	// make appropriate modifications to mouse
	// status variables.

    for (unsigned int i=0; i<dwElements; i++)
	{
	// Look at the element to see what happened

		switch (od[i].dwOfs)
		{
			case DIMOFS_X:
				deltaX += (float)((int)od[i].dwData);
				break;

			case DIMOFS_Y: 
				deltaY += (float)((int)od[i].dwData);
			    break;

			case DIMOFS_Z:
			{
				int iDeltaZ = static_cast<int>(od[i].dwData);

				if (iDeltaZ > 0)
				{
					Asura_PlatformInput::KeyPressedThisFrameBuffer[ASURA_KEYID_MOUSEWHEELUP] = true;
				}
				else if (iDeltaZ < 0)
				{
					Asura_PlatformInput::KeyPressedThisFrameBuffer[ASURA_KEYID_MOUSEWHEELDOWN] = true;
				}
				
				break;
			}

			case DIMOFS_BUTTON0:
			{
				if (od[i].dwData & 0x80)
				{
					if(ShouldAcceptButtonPresses())
					{
						Asura_PlatformInput::KeyboardInput_AsciiKeyDown(g_aeMouseButtonIDs[0]);
						Asura_PlatformInput::KeyPressedThisFrameBuffer[g_aeMouseButtonIDs[0]]=true;
					}
				}
				else
				{
					Asura_PlatformInput::KeyboardInput_AsciiKeyUp(g_aeMouseButtonIDs[0]);
				}
				break;
			}
			case DIMOFS_BUTTON1:
			{
				if (od[i].dwData & 0x80)
				{
					if(ShouldAcceptButtonPresses())
					{
						Asura_PlatformInput::KeyboardInput_AsciiKeyDown(g_aeMouseButtonIDs[1]);
						Asura_PlatformInput::KeyPressedThisFrameBuffer[g_aeMouseButtonIDs[1]]=true;
					}
				}
				else
				{
					Asura_PlatformInput::KeyboardInput_AsciiKeyUp(g_aeMouseButtonIDs[1]);
				}
				break;
			}
			case DIMOFS_BUTTON2:
			{
				if (od[i].dwData & 0x80)
				{
					if(ShouldAcceptButtonPresses())
					{
						Asura_PlatformInput::KeyboardInput_AsciiKeyDown(g_aeMouseButtonIDs[2]);
						Asura_PlatformInput::KeyPressedThisFrameBuffer[g_aeMouseButtonIDs[2]]=true;
					}
				}
				else
				{
					Asura_PlatformInput::KeyboardInput_AsciiKeyUp(g_aeMouseButtonIDs[2]);
				}
				break;
			}
			case DIMOFS_BUTTON3:
			{
				if (od[i].dwData & 0x80)
				{
					if(ShouldAcceptButtonPresses())
					{
						Asura_PlatformInput::KeyboardInput_AsciiKeyDown(g_aeMouseButtonIDs[3]);
						Asura_PlatformInput::KeyPressedThisFrameBuffer[g_aeMouseButtonIDs[3]]=true;
					}
				}
				else
				{
					Asura_PlatformInput::KeyboardInput_AsciiKeyUp(g_aeMouseButtonIDs[3]);
				}
				break;
			}
			case DIMOFS_BUTTON4:
			{
				if (od[i].dwData & 0x80)
				{
					if(ShouldAcceptButtonPresses())
					{
						Asura_PlatformInput::KeyboardInput_AsciiKeyDown(g_aeMouseButtonIDs[4]);
						Asura_PlatformInput::KeyPressedThisFrameBuffer[g_aeMouseButtonIDs[4]]=true;
					}
				}
				else
				{
					Asura_PlatformInput::KeyboardInput_AsciiKeyUp(g_aeMouseButtonIDs[4]);
				}
				break;
			}
				
		
			default:
			  break;
		}
	}
	
	DeltaX += deltaX;
	DeltaY += deltaY;
			 
#endif
}

void Asura_Mouse::SetSwapMouseButtons(bool bSwap)
{
	if(GetSwapMouseButtons() == bSwap) return;

	g_aeMouseButtonIDs[0] = bSwap ? ASURA_KEYID_MOUSEBUTTON2 : ASURA_KEYID_MOUSEBUTTON1;
	g_aeMouseButtonIDs[1] = bSwap ? ASURA_KEYID_MOUSEBUTTON1 : ASURA_KEYID_MOUSEBUTTON2;

	//Since the mouse swap was probably done via a mouse click , raise both the buttons , 
	//to avoid them being stuck down
	Asura_PlatformInput::KeyboardInput_AsciiKeyUp(ASURA_KEYID_MOUSEBUTTON1);
	Asura_PlatformInput::KeyboardInput_AsciiKeyUp(ASURA_KEYID_MOUSEBUTTON2);
}

bool Asura_Mouse::GetSwapMouseButtons()
{
	return g_aeMouseButtonIDs[0] != ASURA_KEYID_MOUSEBUTTON1;
}

void Asura_Mouse::SetExclusive(bool bExclusive)
{
	s_bExclusive = bExclusive;

	//Update the actual exclusive setting based on this flag and whether the windows cursor is supposed to be viisble
	SetExclusiveInternal();
}

void Asura_Mouse::SetUseWindowsCursor(bool bUse)
{
	#ifdef ASURA_USE_CURSOR
	if(bUse && !s_bUseWindowsCursor)
	{
		//Set the windows cursor to match our cursor position to avoid it jumping
		RECT xWindowRect;
		Asura_Windows::GetWindowViewportCoords(&xWindowRect);

		int iX,iY;
		Asura_Cursor::GetScreenSpaceCoords(iX,iY);

		iX += xWindowRect.left;
		iY += xWindowRect.top;
		SetCursorPos(iX,iY);
	}
	#endif //ASURA_USE_CURSOR

	s_bUseWindowsCursor = bUse;
	SetExclusiveInternal();

	#ifdef ASURA_USE_CURSOR
	Asura_Cursor::SetHideCursor(s_bUseWindowsCursor);
	#endif
}

void Asura_Mouse::SetExclusiveInternal()
{
	bool bExclusive = s_bExclusive && !s_bUseWindowsCursor;

	if(s_bExclusiveInternal != bExclusive)
	{
		s_bExclusiveInternal = bExclusive;

		if(lpMouse)
		{
			int iExclusiveFlag = s_bExclusiveInternal ? DISCL_EXCLUSIVE : DISCL_NONEXCLUSIVE;
			lpMouse->Unacquire();
			lpMouse->SetCooperativeLevel(Asura_Windows::GetWindowHandle(),iExclusiveFlag | DISCL_FOREGROUND);
			lpMouse->Acquire();

		}
	}
}




bool Asura_Mouse::ShouldAcceptButtonPresses()
{
	if(s_bExclusive) return true;

	//Ignore any mouse button presses if the cursor is currently outside of the main viewport
	RECT xWindowRect;
	Asura_Windows::GetWindowViewportCoords(&xWindowRect);

	POINT xCursorPos;
	GetCursorPos(&xCursorPos);

	if(xCursorPos.x < xWindowRect.left) return false;
	if(xCursorPos.x >= xWindowRect.right) return false;
	if(xCursorPos.y < xWindowRect.top) return false;
	if(xCursorPos.y >= xWindowRect.bottom) return false;

	return true;

}


void Asura_Mouse::SetRestrictWindowsCursorToWindow(bool bSet)
{
	if(bSet != s_bRestrictWindowsCursorToWindow)
	{
		s_bRestrictWindowsCursorToWindow = bSet;

		#ifdef ASURA_USE_CURSOR
		if(!s_bRestrictWindowsCursorToWindow)
		{
			//Set the windows cursor to match our cursor position to avoid it jumping
			RECT xWindowRect;
			Asura_Windows::GetWindowViewportCoords(&xWindowRect);

			int iX,iY;
			Asura_Cursor::GetScreenSpaceCoords(iX,iY);

			iX += xWindowRect.left;
			iY += xWindowRect.top;
			SetCursorPos(iX,iY);
		}
		#endif //ASURA_USE_CURSOR
	}
}