/////////
//MOUSE//
/////////

#include "difunctions.h"
#include "DeviceStateManager.h"

extern void wlog(char* str, ...);

CDIFunctions::CDIFunctions(D3DApp* const pApp)
{
	m_pApp = pApp;

	m_pDeviceCursor = NULL;

	m_bMouseOver = false;

	m_MaxX=m_MaxY=0;
	m_DI=NULL;
	m_MouseDevice=NULL;
	m_KeyboardDevice=NULL;
	m_CursorScreen = DXVector2(0, 0);
	m_CursorWindow = DXVector2(0, 0);
	xbmp=0;
	ybmp=0;
	dx=0;
	dy=0;
	xl=0;
	yl=0;
	dx=0;
	dy=0;
//	art=TEXTCURSOR;
//	setcursor(CURSOR);
	m_bMouseVisibility=true;
//	cursors=new CImage;
//	cursors->SetValues(RGB(255,255,255),"cursors.bmp",TRUE,TRUE,0,0,20,20);
	m_MouseEnable=true;

	m_MouseSpeed = 1;

	//get default double click time from system
	m_DoubleClickTime = GetDoubleClickTime();

	SystemParametersInfo(SPI_GETKEYBOARDDELAY,sizeof(DWORD),&m_KeyboardDelay,0); //value from 0 to 3, 250ms step

	m_KeyboardDelay = (m_KeyboardDelay + 1) * 250;

	SystemParametersInfo(SPI_GETKEYBOARDSPEED,sizeof(DWORD),&m_KeyboardSpeed,0); //value from 0 to 31, 

	m_KeyboardSpeed = (32 - m_KeyboardSpeed) * 32;

	m_Surface = NULL;
	ZeroMemory(&m_MouseButtons,sizeof(BYTE)*MBTN_COUNT);

	ZeroMemory(m_Keys,256);
}

CDIFunctions::~CDIFunctions(void)
{
	Release();
//	if(cursors)
//		delete cursors;
}

void CDIFunctions::SetMouseVisible(bool vis)
{
	m_bMouseVisibility=vis;
	if (m_pApp && m_pDeviceCursor)
		m_pApp->GetDevice()->GetDevice()->ShowCursor(vis);
}

/*void CDIFunctions::setcursor(char cur)
{
	if(art!=cur)
	{	
		art=cur;
		switch(art)
		{
		case CURSOR:		yl=CURSOR_YL;
							xl=CURSOR_XL;
							xbmp=CURSOR_X;
							ybmp=CURSOR_Y;
							dx=-CURSOR_XL/2;
							dy=-CURSOR_YL/2;
							break;
		case TEXTCURSOR:	yl=TEXTCURSOR_YL;
							xl=TEXTCURSOR_XL;
							xbmp=TEXTCURSOR_X;
							ybmp=TEXTCURSOR_Y;
							dx=-TEXTCURSOR_XL/2;
							dy=-TEXTCURSOR_YL/2;
							break;
		}
	}
}*/

/*HRESULT CMouse::draw(LPDIRECTDRAWSURFACE7 sur)
{

	HRESULT hr=DD_OK;
	short	drawx,drawy;
	RECT	rect;

//	wlog("adfasdf");
	if(visibility&&enable)
	{
	//	wlog("aaaaaaa§");
		drawx=xpos+dx;
		drawy=ypos+dy;
		rect.left=drawx<0?xbmp-drawx:xbmp;
		rect.top=drawy<0?ybmp-drawy:ybmp;
		rect.right=drawx+xl>myoptions.screenx?xbmp+myoptions.screenx-drawx:xbmp+xl;
		rect.bottom=drawy+yl>myoptions.screeny?ybmp+myoptions.screeny-drawy:ybmp+yl;

//		wlog("??");
		//wlog("%d %d",x,y);
//		hr=cursors->draw(BackBuffer,x,y,0,0,19,19);		
		hr=cursors->draw(BackBuffer,drawx<0?0:drawx,drawy<0?0:drawy,rect);
		
	}
	return hr;
}*/

bool CDIFunctions::Update(void)
{
	HRESULT hr;
	DIMOUSESTATE2 dims;      // DirectInput mouse state structure

	if (NULL != m_MouseDevice) 
    {
		if(!m_MouseEnable)
		{
			ZeroMemory(&m_MouseButtons,sizeof(BYTE)*MBTN_COUNT);
			return TRUE;
		}
        
        hr = DIERR_INPUTLOST;

        // if input is lost then acquire and keep trying 
        while ( DIERR_INPUTLOST == hr ) 
        {
            // get the input's device state, and put the state in dims
            hr = m_MouseDevice->GetDeviceState( sizeof(DIMOUSESTATE), &dims );

            if ( hr == DIERR_INPUTLOST )
            {
                // DirectInput is telling us that the input stream has
                // been interrupted.  We aren't tracking any state
                // between polls, so we don't have any special reset
                // that needs to be done.  We just re-acquire and
                // try again.
                hr = m_MouseDevice->Acquire();
                if ( FAILED(hr) ) 
				{
//					wlog("Maus hin\0");
                    return FALSE;
				}
				hr = m_MouseDevice->GetDeviceState( sizeof(DIMOUSESTATE), &dims );
            }
        }

        if ( FAILED(hr) ) 
		{
//			wlog("Maus hin 2 %r",hr);
            return FALSE;
		}

		dx = (short)(dims.lX * m_MouseSpeed);
		dy = (short)(dims.lY * m_MouseSpeed);
		//m_CursorScreen.x += dx = (short)(dims.lX * m_MouseSpeed);
		//m_CursorScreen.y += dy = (short)(dims.lY * m_MouseSpeed);
		wheel = dims.lZ;
		/*if(xpos>=m_MaxX)
			xpos=m_MaxX;
		if(xpos<0)
			xpos=0;
		if(ypos>=m_MaxY)
			ypos=m_MaxY;
		if(ypos<0)
			ypos=0;*/

		for(int i=0;i<MBTN_COUNT;i++)
		{
			if(dims.rgbButtons[i] & 0x80)
			{
				if(!(m_MouseButtons[i] & BUTTONPRESSED))
				{
					m_MouseButtons[i] |= BUTTONPRESSED;
					m_MouseButtons[i] |= BUTTONDOWN;
				}
				else if(m_MouseButtons[i] & BUTTONDOWN)
				{
					m_MouseButtons[i] ^= BUTTONDOWN;
				}
			}
			else
			{
				if(m_MouseButtons[i] & BUTTONPRESSED)
				{
					m_MouseButtons[i] ^= BUTTONPRESSED;
					m_MouseButtons[i] |= BUTTONUP;
				}
				else if(m_MouseButtons[i] & BUTTONUP)
				{
					m_MouseButtons[i] ^= BUTTONUP;
				}

				if(m_MouseButtons[i] & BUTTONDOWN)
					m_MouseButtons[i] ^= BUTTONDOWN;
			}
		}

		POINT pnt;
		GetCursorPos(&pnt);
		m_CursorScreen.x = pnt.x;
		m_CursorScreen.y = pnt.y;

		if (m_pApp && m_pApp->GetCurrentMode().Windowed)
		{
			ScreenToClient(m_hWnd, &pnt);

			m_CursorWindow.x = pnt.x;
			m_CursorWindow.y = pnt.y;
			
			RECT clientrect;
			GetClientRect(m_hWnd, &clientrect);

			if (m_CursorWindow.x >= clientrect.left && m_CursorWindow.y >= clientrect.top && 
				m_CursorWindow.x < clientrect.right && m_CursorWindow.y < clientrect.bottom)
			{
				m_bMouseOver = true;
			}
			else
			{
				m_bMouseOver = false;
			}
		}
		else
		{
			m_CursorWindow.x = pnt.x;
			m_CursorWindow.y = pnt.y;

			m_bMouseOver = true;
		}

		/*RECT screenrect;
		GetWindowRect(GetDesktopWindow(), &screenrect);

		if (m_CursorScreen.x < 0)
			m_CursorScreen.x = 0;
		else if (m_CursorScreen.x > screenrect.right-1)
			m_CursorScreen.x = screenrect.right-1;

		if (m_CursorScreen.y < 0)
			m_CursorScreen.y = 0;
		else if (m_CursorScreen.y > screenrect.bottom-1)
			m_CursorScreen.y = screenrect.bottom-1;

		POINT pnt;
		pnt.x = (int)(m_CursorScreen.x + 0.5f);
		pnt.y = (int)(m_CursorScreen.y + 0.5f);

		if (m_hCursor)
		{
			SetCursorPos(pnt.x, pnt.y);
		}
		
		RECT clientrect;
		if (m_pApp && m_pApp->GetCurrentMode().Windowed)
		{
			ScreenToClient(m_hWnd, &pnt);
			GetClientRect(m_hWnd, &clientrect);
		}
		else
		{
			clientrect.left = clientrect.top = 0;
			clientrect.right = m_pApp->GetCurrentMode().Width;
			clientrect.bottom = m_pApp->GetCurrentMode().Height;
		}

		m_CursorWindow = DXVector2(pnt.x, pnt.y);
		//wlog("%f %f", m_CursorWindow.x, m_CursorWindow.y);
		
		if (m_CursorWindow.x >= clientrect.left && m_CursorWindow.y >= clientrect.top && 
			m_CursorWindow.x < clientrect.right && m_CursorWindow.y < clientrect.bottom)
		{
			m_bMouseOver = true;
		}
		else
		{
			m_bMouseOver = false;
		}*/
	}

	DIDEVICEOBJECTDATA didod[ KEYBOARDBUFFER_SIZE ];  // Receives buffered data 
    DWORD dwElements;
    hr = DIERR_INPUTLOST;
	DWORD i;

	if (m_KeyboardDevice) 
    {
        dwElements = KEYBOARDBUFFER_SIZE;
        hr = m_KeyboardDevice->GetDeviceData( sizeof(DIDEVICEOBJECTDATA),
                                            didod, 
                                            &dwElements, 
                                            0 );
        if (hr != DI_OK) 
        {
            // We got an error or we got DI_BUFFEROVERFLOW.
            //
            // Either way, it means that continuous contact with the
            // device has been lost, either due to an external
            // interruption, or because the buffer overflowed
            // and some events were lost.
            //
            // Consequently, if a button was pressed at the time
            // the buffer overflowed or the connection was broken,
            // the corresponding "up" message might have been lost.
            //
            // But since our simple sample doesn't actually have
            // any state associated with button up or down events,
            // there is no state to reset.  (In a real game, ignoring
            // the buffer overflow would result in the game thinking
            // a key was held down when in fact it isn't; it's just
            // that the "up" event got lost because the buffer
            // overflowed.)
            //
            // If we want to be cleverer, we could do a
            // GetDeviceState() and compare the current state
            // against the state we think the device is in,
            // and process all the states that are currently
            // different from our private state.

			hr = m_KeyboardDevice->Acquire();
			while( hr == DIERR_INPUTLOST )
	            hr = m_KeyboardDevice->Acquire();
			
			if ( FAILED(hr) ) 
			{
//				wlog("Keyboard hin\0");
				return hr;
			}

			for(i=0;i<256;i++) 
				m_Keys[i]=0;

			hr = m_KeyboardDevice->GetDeviceData( sizeof(DIDEVICEOBJECTDATA),didod,&dwElements,0);
        }

        if ( FAILED(hr) )  
		{
//			wlog("Keyboard hin \0");
            return hr;
		}
        // Study each of the buffer elements and process them.
        //
        // Since we really don't do anything, our "processing"
        // consists merely of squirting the name into our
        // local buffer.

		for(i=0;i<256;i++) 
		{
			if(m_Keys[i] == KEY_DOWN)
			{
				m_Keys[i] = KEY_PRESS;
			}
			else if(m_Keys[i] == KEY_UP)
			{
				m_Keys[i] = 0;
			}
		}

        for (i = 0; i < dwElements; i++) 
        {
            // this will display then scan code of the key
            // plus a 'D' - meaning the key was pressed 
            //   or a 'U' - meaning the key was released
			//wlog("Key Pressed %d",didod[i].dwOfs);
			if(didod[i].dwData & 0x80)
			{
				m_Keys[didod[i].dwOfs] = KEY_DOWN;

				OnKeyDown(didod[i].dwOfs);
			}
			else
			{
				m_Keys[didod[i].dwOfs] = KEY_UP;

				OnKeyUp(didod[i].dwOfs);
			}
        }

		for(i=0;i<256;i++) 
		{
			if (m_Keys[i] == KEY_PRESS)
			{
				OnKeyPress(i);
			}
		}
    }

	return TRUE;
}

void CDIFunctions::RestoreDeviceCursor(void)
{
	SetDeviceCursor(m_pDeviceCursor, m_DevCurHotX, m_DevCurHotY);
}

/*void CDIFunctions::DrawCursor(void)
{
	if(m_pApp && m_bMouseVisibility)
	{
		m_pApp->GetDevice()->GetDevice()->SetCursorPosition(m_CursorScreen.x, m_CursorScreen.y, D3DCURSOR_IMMEDIATE_UPDATE );
		//m_pApp->GetDevice()->GetDevice()->ShowCursor(true);
		//wlog("%f, %f", m_CursorWindow.x, m_CursorWindow.y);
	}
}*/

void CDIFunctions::SetMousePos(int x,int y)
{
	m_CursorScreen.x = x;
	dx = 0;
	m_CursorScreen.y = y;
	dy = 0;

	SetCursorPos(x, y);
}

HRESULT CDIFunctions::InitMouse(int hwnd,int hinst)
{
	HRESULT hr;

	m_hWnd = (HWND)hwnd;

	if (!m_DI)
	{
		hr = DirectInput8Create( (HINSTANCE)hinst , 0x0800,IID_IDirectInput8, (void**)&m_DI, NULL );
		if ( FAILED(hr) ) 
		{
			MessageBox(0,"Can't create DI!",XString(hinst).getbuffer(),0);
		   return hr;
		}
	}

	 // Obtain an interface to the system mouse device.
    hr = m_DI->CreateDevice( GUID_SysMouse, &m_MouseDevice, NULL );
    if ( FAILED(hr) ) 
        return hr;

    // Set the data format to "mouse format" - a predefined data format 
    //
    // A data format specifies which controls on a device we
    // are interested in, and how they should be reported.
    //
    // This tells DirectInput that we will be passing a
    // DIMOUSESTATE structure to IDirectInputDevice::GetDeviceState.
    hr = m_MouseDevice->SetDataFormat( &c_dfDIMouse );
    if ( FAILED(hr) ) 
        return hr;

    // Set the cooperativity level to let DirectInput know how
    // this device should interact with the system and with other
    // DirectInput applications.
	hr = m_MouseDevice->SetCooperativeLevel( (HWND)hwnd,DISCL_BACKGROUND);
    //hr = m_MouseDevice->SetCooperativeLevel( (HWND)hwnd,DISCL_EXCLUSIVE | DISCL_FOREGROUND | DISCL_NOWINKEY);

//	if(S_OK!=cursors->LoadBitmap(pDD))
//		wlog("scheiß cursor");

	return hr;
}

void CDIFunctions::Release()
{
	if (m_MouseDevice) 
    {
        // Unacquire the device one last time just in case 
        // the app tried to exit while the device is still acquired.
        m_MouseDevice->Unacquire();
        m_MouseDevice->Release();
        m_MouseDevice = NULL;
    }

	if (m_KeyboardDevice) 
    {
        // Unacquire the device one last time just in case 
        // the app tried to exit while the device is still acquired.
        m_KeyboardDevice->Unacquire();
        m_KeyboardDevice->Release();
        m_KeyboardDevice = NULL;
    }
}

void CDIFunctions::SetDeviceCursor(DXTexture* const cursor, const int hotx, const int hoty)
{
	m_pDeviceCursor = cursor;
	m_DevCurHotX = hotx;
	m_DevCurHotY = hoty;
	
	if (m_pDeviceCursor && m_pDeviceCursor->GetTextureSurface())
	{
		LPDIRECT3DSURFACE9 curs;
		m_pDeviceCursor->GetTextureSurface()->GetSurfaceLevel(0, &curs);

		HRESULT hr = m_pApp->GetDevice()->GetDevice()->SetCursorProperties(hotx, hoty, curs);
		if (FAILED(hr))
		{
			wlog("cursor could not be set: %r; maybe not pow2 or A8R8G8B8", hr);
		}

		curs->Release();
	}

	/*m_pApp->GetDevice()->GetDevice()->CreateOffscreenPlainSurface(16,16,D3DFMT_A8R8G8B8,D3DPOOL_DEFAULT,&curs,NULL);
	D3DXLoadSurfaceFromFile(curs,NULL,NULL,"cursor.bmp",NULL, D3DX_DEFAULT, 0xffffffff, NULL);
	m_pApp->GetDevice()->GetDevice()->SetCursorProperties(6,6,curs);*/

	m_pApp->GetDevice()->GetDevice()->ShowCursor(m_bMouseVisibility);
}
//HRESULT CDIFunctions::Restore(void)
//{
//	//HRESULT hr;
//
//	//if (!m_pD3DDevice)
//	//	return E_FAIL;
//
//	//if(FAILED(hr=m_pD3DDevice->CreateOffscreenPlainSurface(16,16,D3DFMT_A8R8G8B8,D3DPOOL_DEFAULT,&m_Surface,NULL)))
//	//	return hr;
//
//	//if(FAILED(hr=D3DXLoadSurfaceFromFile(m_Surface,NULL,NULL,"cursor.bmp",NULL, D3DX_DEFAULT, 0xffffffff, NULL)))
//	//	return hr;
//
//	//if(FAILED(hr=m_pD3DDevice->SetCursorProperties(6,6,m_Surface)))
//	//	return hr;
//
//	//return m_pD3DDevice->ShowCursor( m_bMouseVisibility );
//
//	return 0;
//}
//
//HRESULT CDIFunctions::Invalidate(void)
//{
//	if(m_Surface)
//		m_Surface->Release();
//	m_Surface=NULL;
//	return S_OK;
//}

void CDIFunctions::SetAcquire(bool act)
{
	if(m_MouseDevice)
	{
		if (act)   
		{
			// acquire the input device 
			m_MouseDevice->Acquire();  

			//SetCursor( NULL );

			//if(m_pD3DDevice)
			//	Restore();
    
		}
		else 
		{
			// unacquire the input device 
			m_MouseDevice->Unacquire();
		}
	}
	
	if (m_KeyboardDevice)
	{
		if (act) 
		{
			// acquire the input device 
			m_KeyboardDevice->Acquire();
		} 
		else 
		{
			// unacquire the input device 
			m_KeyboardDevice->Unacquire();
		}
	}
}

HRESULT CDIFunctions::InitKeyboard(int hwnd,int hinst)
{
	HRESULT hr;
    DIPROPDWORD dipdw;

	if (!m_DI)
	{
		hr = DirectInput8Create( (HINSTANCE)hinst, 0x0800,IID_IDirectInput8, (void**)&m_DI, NULL );
		if ( FAILED(hr) ) 
		   return hr;
	}

	hr = m_DI->CreateDevice( GUID_SysKeyboard, &m_KeyboardDevice, NULL );
    if ( FAILED(hr) ) 
        return hr;

    // Set the data format to "keyboard format" - a predefined data format 
    //
    // A data format specifies which controls on a device we
    // are interested in, and how they should be reported.
    //
    // This tells DirectInput that we will be passing an array
    // of 256 bytes to IDirectInputDevice::GetDeviceState.
    hr = m_KeyboardDevice->SetDataFormat( &c_dfDIKeyboard );
    if ( FAILED(hr) ) 
        return hr;

    // Set the cooperativity level to let DirectInput know how
    // this device should interact with the system and with other
    // DirectInput applications.
	DWORD flags = DISCL_NONEXCLUSIVE | DISCL_FOREGROUND | DISCL_NOWINKEY;
	//DWORD flags = DISCL_NONEXCLUSIVE | DISCL_BACKGROUND | DISCL_NOWINKEY;
    hr = m_KeyboardDevice->SetCooperativeLevel( (HWND)hwnd, flags);
    if ( FAILED(hr) ) 
	{
        return hr;
	}

    // IMPORTANT STEP TO USE BUFFERED DEVICE DATA!
    //
    // DirectInput uses unbuffered I/O (buffer size = 0) by default.
    // If you want to read buffered data, you need to set a nonzero
    // buffer size.
    //
    // Set the buffer size to DINPUT_BUFFERSIZE (defined above) elements.
    //
    // The buffer size is a DWORD property associated with the device.

    dipdw.diph.dwSize = sizeof(DIPROPDWORD);
    dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
    dipdw.diph.dwObj = 0;
    dipdw.diph.dwHow = DIPH_DEVICE;
    dipdw.dwData = KEYBOARDBUFFER_SIZE;

    hr = m_KeyboardDevice->SetProperty( DIPROP_BUFFERSIZE, &dipdw.diph );

	return hr;
}

void CDIFunctions::SetMaxMousePos(int mx,int my)
{
	m_MaxX=mx;
	m_MaxY=my;
}

XString CDIFunctions::GetKeyName(unsigned char key)
{
	char keyname[20];
	keyname[0] = '\0';

	GetKeyNameText(key<<16,keyname,20);

    return XString(keyname);
}

unsigned CDIFunctions::GetVK(unsigned char key)
{
	if(key == DIK_DELETE)
		return VK_DELETE;

	static HKL Layout = GetKeyboardLayout(0);

	return MapVirtualKeyEx( key, 1, Layout ); // 1 = MAPVK_VSC_TO_VK;
}

XString CDIFunctions::KeyToXString(unsigned char key)
{
	if(m_KeyboardDevice)
	{
		BYTE   State[256];

		static HKL Layout = GetKeyboardLayout(0);

		if(m_KeyboardDevice->GetDeviceState(256, State) == S_OK)
		{
			if( (State[DIK_LSHIFT] & 0x80) || (State[DIK_RSHIFT] & 0x80) )
			{
				ZeroMemory( State, 256 );
				State[VK_SHIFT] = 128;
			}
			else
				ZeroMemory( State, 256 );

			unsigned int VK = MapVirtualKeyEx( key, 1, Layout ); // 1 = MAPVK_VSC_TO_VK

			if( VK )
			{
				char Buffer[2];

				if( ToAsciiEx( VK, key, State, reinterpret_cast<WORD*>( Buffer ), 0, Layout ) )
				{
					Buffer[1] = '\0';

					return XString(Buffer);
				}
			}
		}
	}

	return XString("");
}	