//////////////////////////////////////////////////////////////////////
// Tools_VolumeCylinderEdit_Object.cpp								//
// SJL 19/11/07														//
// Copyright (C) 2007 Rebellion, All Rights Reserved.			    //
//////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"

#include "Objects/Tools_VolumeCylinderEdit_Object.h"

#include "Camera/Asura_Camera.h"
#include "Core/Asura_Los.h"
#include "Core/Asura_UserInput.h"
#include "Core/Asura_Timers.h"
#include "Debugging/Asura_Assert.h"
#include "Interface/Asura_Interface.h"
#include "Interface/Asura_Overlay_System.h"
#include "Interface/Asura_Overlay.h"
#include "Interface/Asura_Dialog.h"
#include "Interface/Asura_Cursor.h"
#include "Mesh/Asura_Mesh_System.h"
#include "Mesh/Asura_Mesh.h"
#include "Rendering/Asura_Text_Output.h"
#include "Rendering/Asura_Module.h"
#include "Rendering/Asura_Render.h"

#include "Tools_Overlays.h"
#include "Misc/Tools_Render.h"
#include "Misc/Tools_LOS.h"
#include "Misc/Tools_Help.h"
#include "Misc/Tools_Timed_Messages.h"
#include "Misc/Tools_Modal_Overlay.h"
#include "Objects/Tools_Object_System.h"
#include "Objects/Tools_VolumeGroupEdit_Controller.h"

static const char* s_apszVolumeEditHelpStrings [] =
{
	"Esc :           End volume editing",
	"",
	"1 :             Select radius",
	"2 :             Select top end",
	"3 :             Select bottom end",
	"9 :             Decrease Face",
	"Shift + 9 :     Decrease Face (faster)",
	"0 :             Increase Face",
	"Shift + 0 :     Increase Face (faster)",
	"B :             Snap selected face to point under cursor",
	"G :             As above ,but also moves the opposite face in the opposite direction",
	"LMB :           Select nearest face under cursor",
	"Shift+LMB :     Select furthest face under cursor",
	0
};

Tools_VolumeCylinderEdit_Object::Tools_VolumeCylinderEdit_Object(const char* name, Asura_Volume_Cylinder* pxVolume,  bool* pbModifiedFlag /*=0*/)
:Tools_VolumeBaseEdit_Object(name, pxVolume, pbModifiedFlag)
{
	m_eSelectedFace = FACE_UNSET;

	m_pxPosition = pxVolume->GetPositionPtr();
	m_pfRadius = pxVolume->GetRadiusPtr();
	m_pfHeight = pxVolume->GetHeightPtr();
	
	m_bOverlayPositionChanged = false;
	m_bOverlayRadiusChanged = false;
	m_bOverlayHeightChanged = false;

	SetToolsDataFromVolumeData();
	m_pszHelpTitle = "Cylinder Volume";
	m_apszHelpTexts = s_apszVolumeEditHelpStrings;

}


Tools_VolumeCylinderEdit_Object::~Tools_VolumeCylinderEdit_Object()
{

}

void Tools_VolumeCylinderEdit_Object::UpdatePositionInAsuraData(bool /*updateLinkedObjects*/)
{
	SetVolumeDataFromToolsData();
}

void Tools_VolumeCylinderEdit_Object::SetToolsDataFromVolumeData()
{
	Position = *m_pxPosition;

	if(m_pxOrientMat)
	{
		Orientation = *m_pxOrientMat;
	}
	else if(m_pxOrientQuat)
	{
		m_pxOrientQuat->ToMatrix(Orientation);	
	}
	else
	{
		Orientation.Identity();
	}
}

void Tools_VolumeCylinderEdit_Object::SetVolumeDataFromToolsData()
{
	*m_pxPosition = Position;

	if(m_bUseOrientation)
	{
		if(m_pxOrientQuat)
		{
			m_pxOrientQuat->FromMatrix(Orientation);
		}
		else if(m_pxOrientMat)
		{
			*m_pxOrientMat = Orientation;
		}
	}
	else
	{
		if(m_pxOrientQuat)
		{
			*m_pxOrientQuat = Asura_Maths::IdentityQuat;
		}
		else if(m_pxOrientMat)
		{
			*m_pxOrientMat = Asura_Maths::IdentityMatrix3x3;
		}
	}

	if(m_pbModifiedFlag) *m_pbModifiedFlag = true;
}




void Tools_VolumeCylinderEdit_Object::AddOverlayControls(class Asura_Overlay* pxOverlay,Asura_Vector_2& xOffset)
{
	PARENT::AddOverlayControls(pxOverlay, xOffset);

	Asura_Dialog* pxDialog;

	const char* pszExtentText = "Volume parameters (metres)";

	pxDialog = new Asura_Numerical_Dialog("Radius",xOffset,m_pfRadius,0.f,10000.f,0.1f,"%.3f",&m_bOverlayRadiusChanged);
	pxDialog->SetHelpText(pszExtentText);
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Numerical_Dialog("Height",xOffset,m_pfHeight,0.f,10000.f,0.1f,"%.3f",&m_bOverlayHeightChanged);
	pxDialog->SetHelpText(pszExtentText);
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	// Padding so  dialog boxes match up with other types.
	//xOffset.y+=Asura_Dialog::DefaultSpacing();
	//xOffset.y+=Asura_Dialog::DefaultSpacing();
	//xOffset.y+=Asura_Dialog::DefaultSpacing();
	//xOffset.y+=Asura_Dialog::DefaultSpacing();
}


void Tools_VolumeCylinderEdit_Object::Render()
{
	//render the flyable bit
	Tools_Object::Render();

	Asura_Render::CurrentStates.RequestWireFrameMode(false);

	u_int uColour;
	u_int uSelectedFaceColour;
	u_int uWireframeColour;	//colour to use as additional highlight when doing solid volumes
	if(IsSelected())
	{
		uColour = 0xafafff;
		uSelectedFaceColour = 0xff0000;
		uWireframeColour = 0x0000ff;
	}
	else if(GetController() && (GetController()->IsSelected() || GetController()->IsHighlighted()))
	{
		uColour = 0xafffaf;
		uSelectedFaceColour = uColour;
		uWireframeColour = 0x00ff00;
	}
	else
	{
		uColour = 0xafafaf;
		uSelectedFaceColour = uColour;
		uWireframeColour = 0xffffff;
	}
	if(s_bWireframe)
	{
		uColour |= 0xff000000;
		uSelectedFaceColour |= 0xff000000;
	}
	else
	{
		uColour |= 0x70000000;
		uSelectedFaceColour |= 0x70000000;
	}

	Asura_Matrix_3x3 xOrient = m_bUseOrientation ? Orientation : Asura_Maths::IdentityMatrix3x3;

	Asura_Vector_3 xExtents(*m_pfRadius, *m_pfRadius, *m_pfRadius);

	Asura_Vector_3 xPos1 = Position;
	Asura_Vector_3 xPos2 = xPos1 + Asura_Vector_3(0.f,-*m_pfHeight, 0.f) * xOrient;


	switch (m_eSelectedFace)
	{
		case FACE_UNSET://none
			Tools_Render::RenderCylinder(xPos1,xPos2,*m_pfRadius, uColour, s_bWireframe);
			break;
		case FACE_TOP://top
			Tools_Render::RenderCylinder(xPos1,xPos2,*m_pfRadius, uColour, uSelectedFaceColour, uColour, s_bWireframe);
			break;
		case FACE_BOTTOM://bot
			Tools_Render::RenderCylinder(xPos1,xPos2,*m_pfRadius, uSelectedFaceColour, uColour, uColour, s_bWireframe);
			break;
		case FACE_SIDE://side
			Tools_Render::RenderCylinder(xPos1,xPos2,*m_pfRadius, uColour, uColour, uSelectedFaceColour, s_bWireframe);
			break;
		default:
			Asura_Assert(0,"bad bad enum!");
			break;
	}


	if(!s_bWireframe)
	{
		//do a wireframe outline as well
		Tools_Render::RenderCylinder(xPos1,xPos2,*m_pfRadius, uWireframeColour|0xff000000, true);
	}

	Asura_Render::CurrentStates.RequestWireFrameMode(Asura_Render::GetWireframe());
}




void Tools_VolumeCylinderEdit_Object::UpdateWhileSelected()
{
	if(!Asura_Overlay_System::OverlaysHaveKeyboardFocus())
	{
		if(!Asura_UserInput::KeyPressed(ASURA_KEYID_ALT) && !Asura_UserInput::KeyPressed(ASURA_KEYID_CTRL))
		{
			const float fMultiplier = Asura_UserInput::KeyPressed( ASURA_KEYID_SHIFT ) ? 10.f : 1.f;
			const float fInc = fMultiplier * Asura_Timers::GetFrameTime();

			const Asura_Matrix_3x3& xOrient = m_bUseOrientation ? Orientation : Asura_Maths::IdentityMatrix3x3;

			if(Asura_UserInput::KeyPressed('9'))
			{
				switch (m_eSelectedFace)
				{
					case FACE_SIDE:
						*m_pfRadius -= fInc;
						if (*m_pfRadius < 0.f) *m_pfRadius = 0.f;
						break;
					case FACE_TOP:
						*m_pfHeight -= fInc;
						if (*m_pfHeight < 0.f) *m_pfHeight = 0.f;
						break;
					case FACE_BOTTOM:
						*m_pfHeight += fInc;
						*m_pxPosition += Asura_Vector_3(0.f,1.f,0.f) * xOrient * fInc;					
						Position = *m_pxPosition;
						break;
					default:
						break;
				}
			}

			if(Asura_UserInput::KeyPressed('0'))
			{
				switch (m_eSelectedFace)
				{
					case FACE_SIDE:
						*m_pfRadius += fInc;
						break;
					case FACE_TOP:
						*m_pfHeight += fInc;
						break;
					case FACE_BOTTOM:
					{
						const float fClampedInc = Asura_Maths::Min(*m_pfHeight, fInc);
						*m_pfHeight -= fClampedInc;
						*m_pxPosition -= Asura_Vector_3(0.f,1.f,0.f) * xOrient * fClampedInc;
						Position = *m_pxPosition;
						break;
					}
					default:
					break;
				}
			}

			if (Asura_UserInput::KeyPressed('1'))
			{
				m_eSelectedFace = FACE_SIDE;
			}
			if (Asura_UserInput::KeyPressed('2'))
			{
				m_eSelectedFace = FACE_TOP;
			}
			if (Asura_UserInput::KeyPressed('3'))
			{
				m_eSelectedFace = FACE_BOTTOM;
			}

		}
	}
	PARENT::UpdateWhileSelected();
}


bool Tools_VolumeCylinderEdit_Object::ObjectIsInLOS(const Asura_Vector_3& xStart, const Asura_Vector_3& xDir, float& fLowest)
{
	bool bInLOS = Tools_Object::ObjectIsInLOS(xStart,xDir,fLowest);
	
	//If selected , test against the volume as well.
	if(IsSelected())
	{
		const Asura_Matrix_3x3 xOrient = m_bUseOrientation ? Orientation : Asura_Maths::IdentityMatrix3x3;
		const Asura_Vector_3 xAxis = Asura_Vector_3(0.f,-*m_pfHeight, 0.f) * xOrient;
		float fIntersectionDistance = fLowest;
		if(Asura_Maths::RayIntersectsCylinder(xStart, xDir, Position, xAxis, *m_pfRadius, &fIntersectionDistance))
		{
			if(fIntersectionDistance < fLowest)
			{
				fLowest = fIntersectionDistance;
				bInLOS = true;
			}
		}
	}
	return bInLOS;
}

void Tools_VolumeCylinderEdit_Object::Displace(const Asura_Vector_3& xOffset)
{
	Position += xOffset;
	
}

void  Tools_VolumeCylinderEdit_Object::RotateAbout(const Asura_Vector_3& xCentre, const Asura_Matrix_3x3& xOri)
{
	m_bUseOrientation = true;
	Orientation = xOri * Orientation;
	Position -= xCentre;
	Position = Position * xOri;
	Position += xCentre;
}

void Tools_VolumeCylinderEdit_Object::SelectFaceUnderCursor( const bool bGetFurthest )
{
	Asura_Vector_3 xRayDir;
	Asura_Vector_3 xRayOrigin;
	Asura_Cursor::GetLOSDirection( xRayDir );

	xRayOrigin = *Asura_Camera::GetPosition();
	const Asura_Matrix_3x3& xCamOrient = *Asura_Camera::GetOrientation();

	xRayDir = xRayDir * (xCamOrient);

	const Asura_Matrix_3x3 xOrient = m_bUseOrientation ? Orientation : Asura_Maths::IdentityMatrix3x3;
	const Asura_Vector_3 xAxis = Asura_Vector_3(0.f,-*m_pfHeight, 0.f) * xOrient;
	float fDistance = 0.f;
	int iHitFace = 0;
	if(Asura_UserInput::KeyPressed(ASURA_KEYID_SHIFT))
	{
		// Flip the ray
		const float fLargeDistance = 100000.f; // 100Km should do the trick, given we only usually render 2Km
		xRayOrigin = xRayOrigin + xRayDir * fLargeDistance;
		xRayDir = -xRayDir;
	}
	if(Asura_Maths::RayIntersectsCylinder(xRayOrigin, xRayDir, Position, xAxis, *m_pfRadius, &fDistance, &iHitFace))
	{
		switch(iHitFace)
		{
			case -1: m_eSelectedFace = FACE_BOTTOM; break;
			case  0: m_eSelectedFace = FACE_SIDE; break;
			case  1: m_eSelectedFace = FACE_TOP; break;
			default: Asura_Assert(0, "Unexpected cylinder face"); break;
		}
	}
	else
	{
		m_eSelectedFace = FACE_UNSET;
	}
}

void Tools_VolumeCylinderEdit_Object::SnapFaceToCursor(const bool bAlsoExpandOppositeFace)
{
	int iPolyIndex;
	int iModuleIndex;
	Asura_Vector_3 xPosition;

	//find the location of the cursor
	if(!Tools_LOS::FindPolyAndPositionUnderCursor(iPolyIndex,iModuleIndex,xPosition)) return;

	const Asura_Matrix_3x3& xOrient = m_bUseOrientation ? Orientation : Asura_Maths::IdentityMatrix3x3;
	const Asura_Vector_3 xAxis = Asura_Vector_3(0.f, -1.f, 0.f) * xOrient;
	switch (m_eSelectedFace)
	{
		case FACE_UNSET:
			break;
		case FACE_BOTTOM:
		{
			if(bAlsoExpandOppositeFace)
			{
				float fDistanceToMove = (xPosition - *m_pxPosition) * xAxis;
				fDistanceToMove = Asura_Maths::Min(fDistanceToMove, *m_pfHeight/2.0f);

				*m_pxPosition = *m_pxPosition + xAxis*fDistanceToMove;
				*m_pfHeight -= fDistanceToMove * 2.0f;
				if(*m_pfHeight<0.0f) *m_pfHeight = 0.0f;
			}
			else
			{
				const Asura_Vector_3 xTop = *m_pxPosition + xAxis * *m_pfHeight;
				const Asura_Vector_3 xTopToPositionUnderCursor = xPosition - xTop;
				// Clamp to 0
				*m_pfHeight = Asura_Maths::Max(-xAxis * xTopToPositionUnderCursor, 0.f);
				Position = xTop - xAxis * *m_pfHeight;
				*m_pxPosition = xTop - xAxis * *m_pfHeight;
			}
			break;
		}
		case FACE_TOP:
		{
			if(bAlsoExpandOppositeFace)
			{
				const Asura_Vector_3 xTop = *m_pxPosition + xAxis * *m_pfHeight;
				float fDistanceToMove = (xTop - xPosition) * xAxis;
				fDistanceToMove = Asura_Maths::Min(fDistanceToMove, *m_pfHeight/2.0f);

				*m_pxPosition = *m_pxPosition + xAxis*fDistanceToMove;
				*m_pfHeight -= fDistanceToMove * 2.0f;
				if(*m_pfHeight<0.0f) *m_pfHeight = 0.0f;
			}
			else
			{
				const Asura_Vector_3 xBottomToPositionUnderCursor = xPosition - *m_pxPosition;
				// Clamp to 0
				*m_pfHeight = Asura_Maths::Max(xAxis * xBottomToPositionUnderCursor, 0.f);
			}
			break;
		}
		case FACE_SIDE:
		{
			*m_pfRadius = (xPosition - *m_pxPosition).Magnitude();
			break;
		}
		default:
			break;
	}

	Position = *m_pxPosition;

}