//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//

#include <stdafx.h>
#include "MapWorld.h"
#include "GlobalFunctions.h"
#include "MainFrm.h"
#include "ToolOverlay.h"
#include "MapDoc.h"
#include "History.h"
#include "CollisionUtils.h"
#include "cmodel.h"
#include "MapView3D.h"
#include "MapView2D.h"
#include "MapSolid.h"
#include "Camera.h"
#include "ObjectProperties.h"  // FIXME: For ObjectProperties::RefreshData
#include "Selection.h"

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

#define OVERLAY_TOOL_SNAP_DISTANCE	35.0f

//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CToolOverlay::CToolOverlay()
{
	m_bDragging = false;
	m_pActiveOverlay = NULL;
}

//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
CToolOverlay::~CToolOverlay()
{
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CToolOverlay::OnActivate()
{
	m_bDragging = false;
	m_pActiveOverlay = NULL;
}
    
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CToolOverlay::OnDeactivate()
{
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CToolOverlay::OnLMouseUp3D( CMapView3D *pView, UINT nFlags, const Vector2D &vPoint )
{
	// Post drag events.
	PostDrag();

	// Update the entity properties dialog.
	GetMainWnd()->pObjectProperties->MarkDataDirty();

	return true;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CToolOverlay::OnLMouseDown3D( CMapView3D *pView, UINT nFlags, const Vector2D &vPoint )
{
	// Handle the overlay "handle" selection.
	if ( HandleSelection( pView, vPoint ) )
	{
		PreDrag();
		return true;
	}

	// Handle adding and removing overlay entities from the selection list.
	OverlaySelection( pView, nFlags, vPoint );

	// Handle the overlay creation and placement (if we hit a solid).
	ULONG ulFace;
	CMapClass *pObject = NULL;
	if ( ( pObject = pView->NearestObjectAt( vPoint, ulFace ) ) != NULL )
	{
		CMapSolid *pSolid = dynamic_cast<CMapSolid*>( pObject );
		if ( pSolid )
		{
			return CreateOverlay( pSolid, ulFace, pView, vPoint );
		}
	}

	return true;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CToolOverlay::OnMouseMove3D( CMapView3D *pView, UINT nFlags, const Vector2D &vPoint )
{
	if ( m_bDragging )
	{
		bool bShift = ( ( GetKeyState( VK_SHIFT ) & 0x8000 ) != 0 );

		// Build the ray and drag the overlay handle to the impact point.
		const CCamera *pCamera = pView->GetCamera();
		if ( pCamera )
		{
			Vector vecStart, vecEnd;
			pView->BuildRay( vPoint, vecStart, vecEnd );
			OnDrag( vecStart, vecEnd, bShift );
		}
	}

	return true;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CToolOverlay::CreateOverlay( CMapSolid *pSolid, ULONG iFace, CMapView3D *pView, Vector2D point )
{
	// Build a ray to trace against the face that they clicked on to
	// find the point of intersection.
	Vector vecStart, vecEnd;
	pView->BuildRay( point, vecStart, vecEnd );
	
	Vector vecHitPos, vecHitNormal;
	CMapFace *pFace = pSolid->GetFace( iFace );
	if( pFace->TraceLine( vecHitPos, vecHitNormal, vecStart, vecEnd ) )
	{
		// Create and initialize the "entity" --> "overlay."
		CMapEntity *pEntity = new CMapEntity;
		pEntity->SetKeyValue( "material", GetDefaultTextureName() );
		pEntity->SetPlaceholder( TRUE );
		pEntity->SetOrigin( vecHitPos );
		pEntity->SetClass( "info_overlay" );				
		
		// Add the entity to the world.
		m_pDocument->AddObjectToWorld( pEntity );
		
		// Setup "history."
		GetHistory()->MarkUndoPosition( NULL, "Create Overlay" );
		GetHistory()->KeepNew( pEntity );
				
		// Initialize the overlay.
		InitOverlay( pEntity, pFace );

		pEntity->CalcBounds( TRUE );
		
		// Add to selection list.
		m_pDocument->SelectObject( pEntity, scSelect );
		m_bEmpty = false;
		
		// Set modified and update views.
		m_pDocument->SetModifiedFlag();
		
		m_pShoreline = NULL;

		return true;
	}
	
	return false;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CToolOverlay::InitOverlay( CMapEntity *pEntity, CMapFace *pFace )
{
	// Valid face?
	if ( !pFace )
		return;

	const CMapObjectList *pChildren  = pEntity->GetChildren();

	FOR_EACH_OBJ( *pChildren, pos )
	{
		CMapOverlay *pOverlay = dynamic_cast<CMapOverlay*>( pChildren->Element(pos) );
		if ( pOverlay )
		{
			pOverlay->Basis_Init( pFace );
			pOverlay->Handles_Init( pFace );
			pOverlay->SideList_Init( pFace );
			pOverlay->SetOverlayType( OVERLAY_TYPE_GENERIC );
			pOverlay->SetLoaded( true );
			pOverlay->CalcBounds( true );
		}
	}
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CToolOverlay::OverlaySelection( CMapView3D *pView, UINT nFlags, const Vector2D &vPoint )
{
	CMapObjectList aSelectionList;
	m_pDocument->GetSelection()->ClearHitList();

	// Find out how many (and what) map objects are under the point clicked on.
	HitInfo_t Objects[MAX_PICK_HITS];
	int nHits = pView->ObjectsAt( vPoint, Objects, sizeof( Objects ) / sizeof( Objects[0] ) );
	if ( nHits != 0 )
	{
		// We now have an array of pointers to CMapAtoms. Any that can be upcast to CMapClass objects?
		for ( int iHit = 0; iHit < nHits; ++iHit )
		{
			CMapClass *pMapClass = dynamic_cast<CMapClass*>( Objects[iHit].pObject );
			if ( pMapClass )
			{
				aSelectionList.AddToTail( pMapClass );
			}
		}
	}

	// Did we hit anything?
	if ( !aSelectionList.Count() )
	{
		m_pDocument->SelectFace( NULL, 0, scClear );
		m_pDocument->SelectObject( NULL, scClear|scSaveChanges );
		SetEmpty();
		return;
	}

	// Find overlays.
	bool bUpdateViews = false;
	
	SelectMode_t eSelectMode = m_pDocument->GetSelection()->GetMode();
	
	FOR_EACH_OBJ( aSelectionList, pos )
	{
		CMapClass *pObject = aSelectionList.Element( pos );
		CMapClass *pHitObject = pObject->PrepareSelection( eSelectMode );
		if ( pHitObject )
		{
			if ( pHitObject->IsMapClass( MAPCLASS_TYPE( CMapEntity ) ) ) 
			{
				const CMapObjectList *pChildren = pHitObject->GetChildren();
				FOR_EACH_OBJ( *pChildren, pos2 )
				{
					CMapOverlay *pOverlay = dynamic_cast<CMapOverlay*>( pChildren->Element(pos2) );
					if ( pOverlay )
					{
						m_pDocument->GetSelection()->AddHit( pHitObject );
						m_bEmpty = false;

						UINT cmd = scClear | scSelect | scSaveChanges;
						if (nFlags & MK_CONTROL)
						{
							cmd = scToggle;
						}
						m_pDocument->SelectObject( pHitObject, cmd );
						bUpdateViews = true;
					}
				}
			}
		}
	}

	// Update the views.
	if ( bUpdateViews )
	{
		m_pDocument->UpdateAllViews( MAPVIEW_UPDATE_OBJECTS );		
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CToolOverlay::OnContextMenu2D( CMapView2D *pView, UINT nFlags, const Vector2D &vPoint )
{
	static CMenu menu, menuOverlay;
	static bool bInit = false;

	if ( !bInit )
	{
		// Create the menu.
		menu.LoadMenu( IDR_POPUPS );
		menuOverlay.Attach( ::GetSubMenu( menu.m_hMenu,  6 ) );
		bInit = true;
	}

	if ( !pView->PointInClientRect(vPoint) )
		return false;

	if (!IsEmpty())
	{
		if ( HitTest( pView, vPoint, false ) )
		{
			CPoint ptScreen( vPoint.x,vPoint.y);
			pView->ClientToScreen(&ptScreen);
			menuOverlay.TrackPopupMenu( TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_LEFTALIGN, ptScreen.x, ptScreen.y, pView );
		}
	}

	return true;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CToolOverlay::UpdateTranslation( const Vector &vUpdate, UINT nFlags)
{
//	if( m_bBoxSelecting )
//		return Box3D::UpdateTranslation( pt, nFlags );

	return true;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CToolOverlay::HandlesReset( void )
{
	// Go through selection list and reset overlay handles.
	const CMapObjectList *pSelection = m_pDocument->GetSelection()->GetList();
	for( int iSelection = 0; iSelection < pSelection->Count(); ++iSelection )
	{
		CMapClass *pMapClass = pSelection->Element( iSelection );
		if ( pMapClass && pMapClass->IsMapClass( MAPCLASS_TYPE( CMapEntity ) ) )
		{	
			const CMapObjectList *pChildren = pMapClass->GetChildren();
			FOR_EACH_OBJ( *pChildren, pos )
			{
				CMapOverlay *pOverlay = dynamic_cast<CMapOverlay*>( pChildren->Element(pos) );
				if ( pOverlay )
				{
					pOverlay->HandlesReset();
					break;
				}
			}
		}
	}
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CToolOverlay::HandleSelection( CMapView *pView, const Vector2D &vPoint )
{
	// Reset the hit overlay.
	m_pActiveOverlay = NULL;

	// Go through selection list and test all overlay's handles and set the
	// "hit" overlay current.
	const CMapObjectList *pSelection = m_pDocument->GetSelection()->GetList();
	for ( int iSelection = 0; iSelection < pSelection->Count(); ++iSelection )
	{
		CMapClass *pMapClass = pSelection->Element( iSelection );
		if ( pMapClass && pMapClass->IsMapClass( MAPCLASS_TYPE( CMapEntity ) ) )
		{
			const CMapObjectList *pChildren = pMapClass->GetChildren();
			FOR_EACH_OBJ( *pChildren, pos )
			{
				CMapOverlay *pOverlay = dynamic_cast<CMapOverlay*>( pChildren->Element(pos) );
				if ( pOverlay && pOverlay->IsSelected() )
				{
					if ( pOverlay->HandlesHitTest( pView, vPoint ) )
					{
						m_pActiveOverlay = pOverlay;
						break;
					}
				}
			}
		}
	}

	if ( !m_pActiveOverlay )
		return false;

	return true;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CToolOverlay::HandleSnap( CMapOverlay *pOverlay, Vector &vecHandlePt )
{
	Vector vecTmp;
	for ( int i = 0; i < OVERLAY_HANDLES_COUNT; i++ )
	{
		pOverlay->GetHandlePos( i, vecTmp );
		vecTmp -= vecHandlePt;
		float flDist = vecTmp.Length();

		if ( flDist < OVERLAY_TOOL_SNAP_DISTANCE )
		{
			// Snap!
			pOverlay->GetHandlePos( i, vecHandlePt );
			return true;
		}
	}

	return false;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CToolOverlay::HandleInBBox( CMapOverlay *pOverlay, Vector const &vecHandlePt )
{
	Vector vecMin, vecMax;
	pOverlay->GetCullBox( vecMin, vecMax );

	for ( int iAxis = 0; iAxis < 3; iAxis++ )
	{
		vecMin[iAxis] -= OVERLAY_TOOL_SNAP_DISTANCE;
		vecMax[iAxis] += OVERLAY_TOOL_SNAP_DISTANCE;

		if( ( vecHandlePt[iAxis] < vecMin[iAxis] ) || ( vecHandlePt[iAxis] > vecMax[iAxis] ) )
			return false;
	}

	return true;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CToolOverlay::SnapHandle( Vector &vecHandlePt )
{
	CMapWorld *pWorld = GetActiveWorld();
	if ( !pWorld )
		return;

	EnumChildrenPos_t posWorld;
	CMapClass *pChild = pWorld->GetFirstDescendent( posWorld );
	while ( pChild )
	{
		CMapEntity *pEntity = dynamic_cast<CMapEntity*>( pChild );
		if ( pEntity )
		{
			const CMapObjectList *pChildren = pEntity->GetChildren();
			FOR_EACH_OBJ( *pChildren, pos )
			{
				CMapOverlay *pOverlay = dynamic_cast<CMapOverlay*>( pChildren->Element(pos) );
				if ( pOverlay && pOverlay != m_pActiveOverlay && pOverlay->IsSelected() )
				{
					// Intersection test and attempt to snap
					if ( HandleInBBox( pOverlay, vecHandlePt ) )
					{
						if ( HandleSnap( pOverlay, vecHandlePt ) )
							return;
					}
				}
			}
		}

		pChild = pWorld->GetNextDescendent( posWorld );
	}
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CToolOverlay::OnDrag( Vector const &vecRayStart, Vector const &vecRayEnd, bool bShift )
{
	// Get the current overlay.
	CMapOverlay *pOverlay = m_pActiveOverlay;
	if ( !pOverlay )
		return;

	// Get a list of faces and test for "impact."
	Vector vecImpact( 0.0f, 0.0f, 0.0f );
	Vector vecImpactNormal( 0.0f, 0.0f, 0.0f );
	CMapFace *pFace = NULL;
		
	int nFaceCount = pOverlay->GetFaceCount();
	int iFace;
	for ( iFace = 0; iFace < nFaceCount; iFace++ )
	{
		pFace = pOverlay->GetFace( iFace );
		if ( pFace )
		{
			if ( pFace->TraceLineInside( vecImpact, vecImpactNormal, vecRayStart, vecRayEnd ) )
				break;
		}
	}

	// Test for impact (face index = count mean no impact).
	if ( iFace == nFaceCount )
		return;

	if ( bShift )
	{
		SnapHandle( vecImpact );
	}
	
	// Pass the new handle position to the overlay.
	pOverlay->HandlesDragTo( vecImpact, pFace );

	m_pDocument->UpdateAllViews( MAPVIEW_UPDATE_ONLY_3D );
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CToolOverlay::PreDrag( void )
{
	m_bDragging = true;
	SetupHandleDragUndo();
}


//-----------------------------------------------------------------------------
// Purpose: Renders the cordon tool in the 3D view.
//-----------------------------------------------------------------------------
void CToolOverlay::RenderTool3D(CRender3D *pRender)
{
	// TODO render tool handles here and not in overlay rendering code
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CToolOverlay::PostDrag( void )
{
	if ( !m_bDragging )
		return;

	m_bDragging = false;

	// Get the current overlay.
	CMapOverlay *pOverlay = m_pActiveOverlay;
	if ( pOverlay )
	{
		pOverlay->DoClip();
		pOverlay->CenterEntity();
		pOverlay->PostUpdate( Notify_Changed );
		m_pDocument->UpdateAllViews( MAPVIEW_UPDATE_OBJECTS );	
	}

	// Reset the overlay handles.
	HandlesReset();
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CToolOverlay::SetupHandleDragUndo( void )
{
	// Get the current overlay.
	CMapOverlay *pOverlay = m_pActiveOverlay;
	if ( pOverlay )
	{
		CMapEntity *pEntity = ( CMapEntity* )pOverlay->GetParent();
		if ( pEntity )
		{
			// Setup for drag undo.
			GetHistory()->MarkUndoPosition( m_pDocument->GetSelection()->GetList(), "Drag Overlay Handle" );
			GetHistory()->Keep( ( CMapClass* )pEntity );				
		}
	}
}