//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Implementation of the VGUI ISurface interface using the 
// material system to implement it
//
//===========================================================================//

#if defined( WIN32 ) && !defined( _X360 )
#include <windows.h>
#include <zmouse.h>
#endif
#include "inputsystem/iinputsystem.h"
#include "tier2/tier2.h"
#include "Input.h"
#include "vguimatsurface.h"
#include "../vgui2/src/VPanel.h"
#include <vgui/KeyCode.h>
#include <vgui/MouseCode.h>
#include <vgui/IVGui.h>
#include <vgui/IPanel.h>
#include <vgui/ISurface.h>
#include <vgui/IClientPanel.h>
#include "inputsystem/ButtonCode.h"
#include "Cursor.h"
#include "tier0/dbg.h"
#include "../vgui2/src/vgui_key_translation.h"
#include <vgui/IInputInternal.h>
#include "tier0/icommandline.h"
#ifdef _X360
#include "xbox/xbox_win32stubs.h"
#endif

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

using namespace vgui;

//-----------------------------------------------------------------------------
// Vgui input events
//-----------------------------------------------------------------------------
enum VguiInputEventType_t
{
	IE_Close = IE_FirstVguiEvent,
	IE_LocateMouseClick,
	IE_SetCursor,
	IE_KeyTyped,
	IE_KeyCodeTyped,
	IE_InputLanguageChanged,
	IE_IMESetWindow,
	IE_IMEStartComposition,
	IE_IMEComposition,
	IE_IMEEndComposition,
	IE_IMEShowCandidates,
	IE_IMEChangeCandidates,
	IE_IMECloseCandidates,
	IE_IMERecomputeModes,
};

void InitInput()
{
	EnableInput( true );
}


static bool s_bInputEnabled = true;
#ifdef WIN32
//-----------------------------------------------------------------------------
// Translates actual keys into VGUI ids
//-----------------------------------------------------------------------------
static WNDPROC s_ChainedWindowProc = NULL;
extern HWND thisWindow;


//-----------------------------------------------------------------------------
// Initializes the input system
//-----------------------------------------------------------------------------

static bool s_bIMEComposing = false;
static HWND s_hLastHWnd = 0;


//-----------------------------------------------------------------------------
// Handles input messages
//-----------------------------------------------------------------------------
static LRESULT CALLBACK MatSurfaceWindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
	if ( !s_bInputEnabled )
		goto chainWndProc;

	InputEvent_t event;
	memset( &event, 0, sizeof(event) );
	event.m_nTick = g_pInputSystem->GetPollTick();

	if ( hwnd != s_hLastHWnd )
	{
		s_hLastHWnd = hwnd;
		event.m_nType = IE_IMESetWindow;
		event.m_nData = (int)s_hLastHWnd;
		g_pInputSystem->PostUserEvent( event );
	}

	switch(uMsg)
	{
	case WM_QUIT:
		// According to windows docs, WM_QUIT should never be passed to wndprocs 
		Assert( 0 );
		break;

	case WM_CLOSE:
		// Handle close messages
		{
			LONG_PTR wndProc = GetWindowLongPtrW( hwnd, GWLP_WNDPROC );
			if ( wndProc == (LONG_PTR)MatSurfaceWindowProc )
			{
				event.m_nType = IE_Close;
				g_pInputSystem->PostUserEvent( event );
			}
		}
		return 0;

	// All mouse messages need to mark where the click occurred before chaining down
	case WM_LBUTTONDOWN:
	case WM_RBUTTONDOWN:
	case WM_MBUTTONDOWN:
	case MS_WM_XBUTTONDOWN:
	case WM_LBUTTONUP:
	case WM_RBUTTONUP:
	case WM_MBUTTONUP:
	case MS_WM_XBUTTONUP:
	case WM_LBUTTONDBLCLK:
	case WM_RBUTTONDBLCLK:
	case WM_MBUTTONDBLCLK:
	case MS_WM_XBUTTONDBLCLK:
		event.m_nType = IE_LocateMouseClick;
		event.m_nData = (short)LOWORD(lParam);
		event.m_nData2 = (short)HIWORD(lParam);
		g_pInputSystem->PostUserEvent( event );
		break;

	case WM_SETCURSOR:
		event.m_nType = IE_SetCursor;
		g_pInputSystem->PostUserEvent( event );
		break;

	case WM_XCONTROLLER_KEY:
		if ( IsX360() )
		{	
			// First have to insert the edge case event
			int nRetVal = 0;
			if ( s_ChainedWindowProc )
			{
				nRetVal = CallWindowProcW( s_ChainedWindowProc, hwnd, uMsg, wParam, lParam );
			}

			// xboxissue - as yet HL2 input hasn't been made aware of analog inputs or ports
			// so just digital step on the sample range
			int sample = LOWORD( lParam );
			if ( sample )
			{
				event.m_nType = IE_KeyCodeTyped;
				event.m_nData = (vgui::KeyCode)wParam;
				g_pInputSystem->PostUserEvent( event );
			}
		}
		break;

	// Need to deal with key repeat for keydown since inputsystem doesn't
	case WM_KEYDOWN:
	case WM_SYSKEYDOWN:
		{
			// First have to insert the edge case event
			int nRetVal = 0;
			if ( s_ChainedWindowProc )
			{
				nRetVal = CallWindowProcW( s_ChainedWindowProc, hwnd, uMsg, wParam, lParam );
			}

			int nKeyRepeat = LOWORD( lParam );
			for ( int i = 0; i < nKeyRepeat; ++i )
			{
				event.m_nType = IE_KeyCodeTyped;
				event.m_nData = KeyCode_VirtualKeyToVGUI( wParam );
				g_pInputSystem->PostUserEvent( event );
			}

			return nRetVal;
		}

	case WM_SYSCHAR:
	case WM_CHAR:
		if ( !s_bIMEComposing )
		{
			event.m_nType = IE_KeyTyped;
			event.m_nData = (int)wParam;
			g_pInputSystem->PostUserEvent( event );
		}
		break;

	case WM_INPUTLANGCHANGE:
		event.m_nType = IE_InputLanguageChanged;
		g_pInputSystem->PostUserEvent( event );
		break;

	case WM_IME_STARTCOMPOSITION:
		s_bIMEComposing = true;
		event.m_nType = IE_IMEStartComposition;
		g_pInputSystem->PostUserEvent( event );
		return TRUE;

	case WM_IME_COMPOSITION:
		event.m_nType = IE_IMEComposition;
		event.m_nData = (int)lParam;
		g_pInputSystem->PostUserEvent( event );
		return TRUE;

	case WM_IME_ENDCOMPOSITION:
		s_bIMEComposing = false;
		event.m_nType = IE_IMEEndComposition;
		g_pInputSystem->PostUserEvent( event );
		return TRUE;

	case WM_IME_NOTIFY:
		{
			switch (wParam)
			{
			default:
				break;

			case 14:  // Chinese Traditional IMN_PRIVATE...
				break; 

			case IMN_OPENCANDIDATE:
				event.m_nType = IE_IMEShowCandidates;
				g_pInputSystem->PostUserEvent( event );
				return 1;

			case IMN_CHANGECANDIDATE:
				event.m_nType = IE_IMEChangeCandidates;
				g_pInputSystem->PostUserEvent( event );
				return 0;

			case IMN_CLOSECANDIDATE:
				event.m_nType = IE_IMECloseCandidates;
				g_pInputSystem->PostUserEvent( event );
				break;

			// To detect the change of IME mode, or the toggling of Japanese IME 
			case IMN_SETCONVERSIONMODE:
			case IMN_SETSENTENCEMODE:
			case IMN_SETOPENSTATUS:   
				event.m_nType = IE_IMERecomputeModes;
				g_pInputSystem->PostUserEvent( event );
				if ( wParam == IMN_SETOPENSTATUS )
					return 0;
				break;

			case IMN_CLOSESTATUSWINDOW:   
			case IMN_GUIDELINE:   
			case IMN_OPENSTATUSWINDOW:   
			case IMN_SETCANDIDATEPOS:   
			case IMN_SETCOMPOSITIONFONT:   
			case IMN_SETCOMPOSITIONWINDOW:   
			case IMN_SETSTATUSWINDOWPOS:   
				break;
			}
		}

	case WM_IME_SETCONTEXT:
		// We draw all IME windows ourselves
		lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
		lParam &= ~ISC_SHOWUIGUIDELINE;
		lParam &= ~ISC_SHOWUIALLCANDIDATEWINDOW;
		break;

	case WM_IME_CHAR:
		// We need to process this message so that the IME doesn't double convert the unicode IME characters into garbage characters and post
		//  them to our window... (get ? marks after text entry ).
		return 0;
	}

chainWndProc:
	if ( s_ChainedWindowProc )
		return CallWindowProcW( s_ChainedWindowProc, hwnd, uMsg, wParam, lParam );

	// This means the application is driving the messages (calling our window procedure manually)
	// rather than us hooking their window procedure. The engine needs to do this in order for VCR 
	// mode to play back properly.
	return 0;	
}

#endif


//-----------------------------------------------------------------------------
// Enables/disables input (enabled by default)
//-----------------------------------------------------------------------------
void EnableInput( bool bEnable )
{
#if 0 // #ifdef BENCHMARK
	s_bInputEnabled = false;
#else
	s_bInputEnabled = bEnable;
#endif
}


#ifdef WIN32
//-----------------------------------------------------------------------------
// Hooks input listening up to a window
//-----------------------------------------------------------------------------
void InputAttachToWindow(void *hwnd)
{
#if !defined( USE_SDL )
	s_ChainedWindowProc = (WNDPROC)GetWindowLongPtrW( (HWND)hwnd, GWLP_WNDPROC );
	SetWindowLongPtrW( (HWND)hwnd, GWLP_WNDPROC, (LONG_PTR)MatSurfaceWindowProc );
#endif
}

void InputDetachFromWindow(void *hwnd)
{
	if (!hwnd)
		return;
	if ( s_ChainedWindowProc )
	{
		SetWindowLongPtrW( (HWND)hwnd, GWLP_WNDPROC, (LONG_PTR) s_ChainedWindowProc );
		s_ChainedWindowProc = NULL;
	}
}
#else
void InputAttachToWindow(void *hwnd)
{
#if !defined( OSX ) && !defined( LINUX )
	if ( hwnd && !HushAsserts() )
	{
		// under OSX we use the Cocoa mgr to route events rather than hooking winprocs
		// and under Linux we use SDL
		Assert( !"Implement me" );
	}
#endif
}

void InputDetachFromWindow(void *hwnd)
{
#if !defined( OSX ) && !defined( LINUX )
	if ( hwnd && !HushAsserts() )
	{
		// under OSX we use the Cocoa mgr to route events rather than hooking winprocs
		// and under Linux we use SDL
		Assert( !"Implement me" );
	}
#endif
}
#endif


//-----------------------------------------------------------------------------
// Converts an input system button code to a vgui key code
// FIXME: Remove notion of vgui::KeyCode + vgui::MouseCode altogether
//-----------------------------------------------------------------------------
static vgui::KeyCode ButtonCodeToKeyCode( ButtonCode_t buttonCode )
{
	return ( vgui::KeyCode )buttonCode;
}

static vgui::MouseCode ButtonCodeToMouseCode( ButtonCode_t buttonCode )
{
	return ( vgui::MouseCode )buttonCode;
}


//-----------------------------------------------------------------------------
// Handles an input event, returns true if the event should be filtered
// from the rest of the game
//-----------------------------------------------------------------------------
bool InputHandleInputEvent( const InputEvent_t &event )
{
	switch( event.m_nType )
	{
	case IE_ButtonPressed:
		{
			// NOTE: data2 is the virtual key code (data1 contains the scan-code one)
			ButtonCode_t code = (ButtonCode_t)event.m_nData2;
			if ( IsKeyCode( code ) || IsJoystickCode( code ) )
			{
				vgui::KeyCode keyCode = ButtonCodeToKeyCode( code );
				return g_pIInput->InternalKeyCodePressed( keyCode );
			}

			if ( IsJoystickCode( code ) )
			{
				vgui::KeyCode keyCode = ButtonCodeToKeyCode( code );
				return g_pIInput->InternalKeyCodePressed( keyCode );
			}

			if ( IsMouseCode( code ) )
			{
				vgui::MouseCode mouseCode = ButtonCodeToMouseCode( code );
				return g_pIInput->InternalMousePressed( mouseCode );
			}
		}
		break;

	case IE_ButtonReleased:
		{
			// NOTE: data2 is the virtual key code (data1 contains the scan-code one)
			ButtonCode_t code = (ButtonCode_t)event.m_nData2;
			if ( IsKeyCode( code ) || IsJoystickCode( code ) )
			{
				vgui::KeyCode keyCode = ButtonCodeToKeyCode( code );
				return g_pIInput->InternalKeyCodeReleased( keyCode );
			}

			if ( IsJoystickCode( code ) )
			{
				vgui::KeyCode keyCode = ButtonCodeToKeyCode( code );
				return g_pIInput->InternalKeyCodeReleased( keyCode );
			}

			if ( IsMouseCode( code ) )
			{
				vgui::MouseCode mouseCode = ButtonCodeToMouseCode( code );
				return g_pIInput->InternalMouseReleased( mouseCode );
			}
		}
		break;

	case IE_ButtonDoubleClicked:
		{
			// NOTE: data2 is the virtual key code (data1 contains the scan-code one)
			ButtonCode_t code = (ButtonCode_t)event.m_nData2;
			if ( IsMouseCode( code ) )
			{
				vgui::MouseCode mouseCode = ButtonCodeToMouseCode( code );
				return g_pIInput->InternalMouseDoublePressed( mouseCode );
			}
		}
		break;

	case IE_AnalogValueChanged:
		{
			if ( event.m_nData == MOUSE_WHEEL )
				return g_pIInput->InternalMouseWheeled( event.m_nData3 );
			if ( event.m_nData == MOUSE_XY )
				return g_pIInput->InternalCursorMoved( event.m_nData2, event.m_nData3 );
		}
		break;

	case IE_KeyCodeTyped:
		{
			vgui::KeyCode code = (vgui::KeyCode)event.m_nData;
			g_pIInput->InternalKeyCodeTyped( code );
		}
		return true;

	case IE_KeyTyped:
		{
			vgui::KeyCode code = (vgui::KeyCode)event.m_nData;
			g_pIInput->InternalKeyTyped( code );
		}
		return true;

	case IE_Quit:
		g_pVGui->Stop();
#if defined( USE_SDL )
		return false; // also let higher layers consume it
#else
		return true;
#endif

	case IE_Close:
		// FIXME: Change this so we don't stop until 'save' occurs, etc.
		g_pVGui->Stop();
		return true;

	case IE_SetCursor:
		ActivateCurrentCursor();
		return true;

	case IE_IMESetWindow:
		g_pIInput->SetIMEWindow( (void *)event.m_nData );
		return true;

	case IE_LocateMouseClick:
		g_pIInput->InternalCursorMoved( event.m_nData, event.m_nData2 );
		return true;

	case IE_InputLanguageChanged:
		g_pIInput->OnInputLanguageChanged();
		return true;

	case IE_IMEStartComposition:
		g_pIInput->OnIMEStartComposition();
		return true;

	case IE_IMEComposition:
		g_pIInput->OnIMEComposition( event.m_nData );
		return true;

	case IE_IMEEndComposition:
		g_pIInput->OnIMEEndComposition();
		return true;

	case IE_IMEShowCandidates:
		g_pIInput->OnIMEShowCandidates();
		return true;

	case IE_IMEChangeCandidates:
		g_pIInput->OnIMEChangeCandidates();
		return true;

	case IE_IMECloseCandidates:
		g_pIInput->OnIMECloseCandidates();
		return true;

	case IE_IMERecomputeModes:
		g_pIInput->OnIMERecomputeModes();
		return true;
	}

	return false;
}