//=================================================================================================
//
// Jolt Debug Renderer Implementation
//
//=================================================================================================

#include "cbase.h"

#ifdef JPH_DEBUG_RENDERER

#include <Jolt/Renderer/DebugRenderer.h>

#include "materialsystem/imaterialsystem.h"
#include "materialsystem/imesh.h"

#include "vjolt_debugrender.h"

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

// Slart: Not sure if this is still relevant,
// it's the amount of time a debugoverlay element should stay on-screen
#define ONE_SINGLE_FRAME 0.0f

#define JOLT_VERTEX_BUFFER_NAME		"Jolt Debug Renderer Vertices"
#define JOLT_INDEX_BUFFER_NAME		"Jolt Debug Renderer Indices"

static ConVar vjolt_debugrender( "vjolt_debugrender", "0", FCVAR_CHEAT );
#ifndef VJOLT_USE_PHYSICS_DEBUG_OVERLAY
static ConVar vjolt_debugrender_picture_in_picture( "vjolt_debugrender_picture_in_picture", "1" );
static ConVar vjolt_debugrender_clear_rt( "vjolt_debugrender_clear_rt", "1" );
static ConVar vjolt_debugrender_clear_depth( "vjolt_debugrender_clear_depth", "1" );
static ConVar vjolt_debugrender_wireframe( "vjolt_debugrender_wireframe", "0" );
static ConVar vjolt_debugrender_color_mode( "vjolt_debugrender_color_mode", "instance", 0, "One of instance, shape_type, motion_type, sleep, island, material." );
#endif

//-------------------------------------------------------------------------------------------------

JoltPhysicsDebugRenderer::JoltPhysicsDebugRenderer()
{
	DebugRenderer::Initialize();
}

JoltPhysicsDebugRenderer::~JoltPhysicsDebugRenderer()
{
}

void JoltPhysicsDebugRenderer::DrawLine( JPH::Vec3Arg inFrom, JPH::Vec3Arg inTo, JPH::ColorArg inColor )
{
	Vector v1 = JoltToSource::Distance( inFrom );
	Vector v2 = JoltToSource::Distance( inTo );

#ifdef VJOLT_USE_PHYSICS_DEBUG_OVERLAY
	GetDebugOverlay()->AddLineOverlay( v1, v2, inColor.r, inColor.g, inColor.b, true, ONE_SINGLE_FRAME );
#else
	GetDebugOverlay()->AddLineOverlay( v1, v2, inColor.r, inColor.g, inColor.b, inColor.a, true, 0.2f );
#endif
}

void JoltPhysicsDebugRenderer::DrawTriangle( JPH::Vec3Arg inV1, JPH::Vec3Arg inV2, JPH::Vec3Arg inV3, JPH::ColorArg inColor )
{
	//DrawTriangle_Internal( JPH::Float3( inV1.GetX(), inV1.GetY(), inV1.GetZ() ), JPH::Float3( inV2.GetX(), inV2.GetY(), inV2.GetZ() ), JPH::Float3( inV3.GetX(), inV3.GetY(), inV3.GetZ() ), inColor );
}

JoltPhysicsDebugRenderer::Batch JoltPhysicsDebugRenderer::CreateTriangleBatch( const Triangle* inTriangles, int inTriangleCount )
{
#ifndef VJOLT_USE_PHYSICS_DEBUG_OVERLAY
	const Vertex* inVertices = reinterpret_cast<const Vertex*>( inTriangles );
	int inVertexCount = inTriangleCount * 3;

	CMatRenderContextPtr pRenderContext( g_pMaterialSystem );

	constexpr VertexFormat_t fmt = VERTEX_POSITION | VERTEX_NORMAL | VERTEX_COLOR | VERTEX_TEXCOORD_SIZE(0, 2);

	IMesh* pMesh = pRenderContext->CreateStaticMesh( fmt, JOLT_VERTEX_BUFFER_NAME );

	CMeshBuilder meshBuilder;
	meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, inTriangleCount );
	{
		for (int i = 0; i < inVertexCount; ++i)
		{
			meshBuilder.Position3f  ( inVertices[i].mPosition.x * JoltToSource::Factor, inVertices[i].mPosition.y * JoltToSource::Factor, inVertices[i].mPosition.z * JoltToSource::Factor );
			meshBuilder.Normal3f    ( inVertices[i].mNormal.x, inVertices[i].mNormal.y, inVertices[i].mNormal.z );
			meshBuilder.TexCoord2f  ( 0, inVertices[i].mUV.x, inVertices[i].mUV.y );
			meshBuilder.Color4Packed( inVertices[i].mColor.mU32 );
			meshBuilder.AdvanceVertex();
		}
	}
	meshBuilder.End();

	return new BatchImpl( pMesh );
#else
	return nullptr;
#endif
}

JoltPhysicsDebugRenderer::Batch JoltPhysicsDebugRenderer::CreateTriangleBatch( const Vertex* inVertices, int inVertexCount, const uint32* inIndices, int inIndexCount )
{
#ifndef VJOLT_USE_PHYSICS_DEBUG_OVERLAY
	CMatRenderContextPtr pRenderContext( g_pMaterialSystem );

	constexpr VertexFormat_t fmt = VERTEX_POSITION | VERTEX_NORMAL | VERTEX_COLOR | VERTEX_TEXCOORD_SIZE(0, 2);

	IMesh* pMesh = pRenderContext->CreateStaticMesh( fmt, JOLT_VERTEX_BUFFER_NAME );

	CMeshBuilder meshBuilder;
	meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, inVertexCount, inIndexCount );
	{
		for (int i = 0; i < inVertexCount; ++i)
		{
			meshBuilder.Position3f  ( inVertices[i].mPosition.x * JoltToSource::Factor, inVertices[i].mPosition.y * JoltToSource::Factor, inVertices[i].mPosition.z * JoltToSource::Factor );
			meshBuilder.Normal3f    ( inVertices[i].mNormal.x, inVertices[i].mNormal.y, inVertices[i].mNormal.z );
			meshBuilder.TexCoord2f  ( 0, inVertices[i].mUV.x, inVertices[i].mUV.y );
			meshBuilder.Color4Packed( inVertices[i].mColor.mU32 );
			meshBuilder.AdvanceVertex();
		}

		for (int i = 0; i < inIndexCount; ++i)
		{
			meshBuilder.Index( static_cast<unsigned short>( inIndices[i] ) );
			meshBuilder.AdvanceIndex();
		}
	}
	meshBuilder.End();

	return new BatchImpl( pMesh );
#else
	return nullptr;
#endif
}

#ifndef VJOLT_USE_PHYSICS_DEBUG_OVERLAY
MaterialCullMode_t ConvertCullMode( JPH::DebugRenderer::ECullMode mode )
{
	switch (mode)
	{
	case JPH::DebugRenderer::ECullMode::CullFrontFace:
		return MATERIAL_CULLMODE_CCW;
	case JPH::DebugRenderer::ECullMode::CullBackFace:
		return MATERIAL_CULLMODE_CW;
	default: // case JPH::DebugRenderer::ECullMode::Off:
		return MATERIAL_CULLMODE_NONE;
	}
}
#endif

void JoltPhysicsDebugRenderer::DrawGeometry( JPH::Mat44Arg inModelMatrix, const JPH::AABox& inWorldSpaceBounds, float inLODScaleSq, JPH::ColorArg inModelColor, const GeometryRef& inGeometry, ECullMode inCullMode, ECastShadow inCastShadow, EDrawMode inDrawMode )
{
#ifndef VJOLT_USE_PHYSICS_DEBUG_OVERLAY
	matrix3x4_t sourceMatrix = JoltToSource::Matrix( inModelMatrix );

	const BatchImpl* batch = reinterpret_cast< const BatchImpl * >( inGeometry->mLODs[0].mTriangleBatch.GetPtr() );

	color32 clearColor = { .r = 20, .g = 20, .b = 20, .a = 255 };

	color32 modulateColor = JoltToSource::Color( inModelColor ).ToColor32();

	bool bForceWireFrame = vjolt_debugrender_wireframe.GetInt() == 2;

	DebugOverlayMeshDesc_t desc =
	{
		.pMesh				= batch->GetMesh(),
		.matTransform		= sourceMatrix,
		.flDuration			= -1.0f,
		.bIgnoreZ			= false,
		.bWireframe			= inDrawMode == EDrawMode::Wireframe || bForceWireFrame,
		.bClearRT			= vjolt_debugrender_clear_rt.GetBool(),
		.bClearDepth		= vjolt_debugrender_clear_depth.GetBool(),
		.colClearColor		= clearColor,
		.colModulateColor	= modulateColor,
		.eCullMode			= ConvertCullMode( inCullMode ),
		.bPip				= vjolt_debugrender_picture_in_picture.GetBool()
	};

	GetDebugOverlay()->DrawMesh( desc );
#endif
}

void JoltPhysicsDebugRenderer::DrawText3D( JPH::Vec3Arg inPosition, const std::string_view &inString, JPH::ColorArg inColor, float inHeight )
{
	// Josh:
	// Doing a copy of 1024, the max size allowed by a debug overlay
	// because AddTextOverlayRGB takes in a c_str and a string view
	// is not necessarily null terminated.
	char text[1024];
	V_strncpy( text, inString.data(), sizeof( text ) );

	Vector origin = JoltToSource::Distance( inPosition );

	GetDebugOverlay()->AddTextOverlayRGB( origin, 0, 0.5f, inColor.r, inColor.g, inColor.b, inColor.a, "%s", text );
}

void JoltPhysicsDebugRenderer::DrawJoltTVText()
{
	GetDebugOverlay()->AddScreenTextOverlay( 0.75f, 0.55f, 0.5f, 255, 0, 255, 255, "Live Jolt Reaction" ); // "Jolt TV"
}

//-------------------------------------------------------------------------------------------------

#ifndef VJOLT_USE_PHYSICS_DEBUG_OVERLAY

static JPH::BodyManager::EShapeColor GetColorMode()
{
	const char *pszString = vjolt_debugrender_color_mode.GetString();

	if ( !V_stricmp( pszString, "instance" ) )
		return JPH::BodyManager::EShapeColor::InstanceColor;
	else if ( !V_stricmp( pszString, "shape_type" ) )
		return JPH::BodyManager::EShapeColor::ShapeTypeColor;
	else if ( !V_stricmp( pszString, "motion_type" ) )
		return JPH::BodyManager::EShapeColor::MotionTypeColor;
	else if ( !V_stricmp( pszString, "sleep" ) )
		return JPH::BodyManager::EShapeColor::SleepColor;
	else if ( !V_stricmp( pszString, "island" ) )
		return JPH::BodyManager::EShapeColor::IslandColor;
	else if ( !V_stricmp( pszString, "material" ) )
		return JPH::BodyManager::EShapeColor::MaterialColor;

	Log_Msg( LOG_VJolt, "Unknown color mode: %s\n", pszString );
	return JPH::BodyManager::EShapeColor::InstanceColor;
}

#endif

void JoltPhysicsDebugRenderer::RenderPhysicsSystem( JPH::PhysicsSystem &physicsSystem )
{
	if ( !GetDebugOverlay() || !vjolt_debugrender.GetBool())
		return;

#ifndef VJOLT_USE_PHYSICS_DEBUG_OVERLAY
	const bool wireframe = vjolt_debugrender_wireframe.GetBool();

	JPH::BodyManager::DrawSettings drawSettings
	{
		.mDrawGetSupportFunction	= false,			///< Draw the GetSupport() function, used for convex collision detection	
		.mDrawSupportDirection		= false,			///< When drawing the support function, also draw which direction mapped to a specific support point
		.mDrawGetSupportingFace		= false,			///< Draw the faces that were found colliding during collision detection
		.mDrawShape					= true,				///< Draw the shapes of all bodies
		.mDrawShapeWireframe		= wireframe,		///< When mDrawShape is true and this is true, the shapes will be drawn in wireframe instead of solid.
		.mDrawShapeColor			= GetColorMode(),	///< Coloring scheme to use for shapes
		.mDrawBoundingBox			= false,			///< Draw a bounding box per body
		.mDrawCenterOfMassTransform	= false,			///< Draw the center of mass for each body
		.mDrawWorldTransform		= false,			///< Draw the world transform (which can be different than the center of mass) for each body
		.mDrawVelocity				= false,			///< Draw the velocity vector for each body
		.mDrawMassAndInertia		= false,			///< Draw the mass and inertia (as the box equivalent) for each body
		.mDrawSleepStats			= false,			///< Draw stats regarding the sleeping algorithm of each body
	};
#else
	JPH::BodyManager::DrawSettings drawSettings
	{
		.mDrawGetSupportFunction	= false,			///< Draw the GetSupport() function, used for convex collision detection	
		.mDrawSupportDirection		= false,			///< When drawing the support function, also draw which direction mapped to a specific support point
		.mDrawGetSupportingFace		= false,			///< Draw the faces that were found colliding during collision detection
		.mDrawShape					= false,			///< Draw the shapes of all bodies
		.mDrawShapeWireframe		= false,			///< When mDrawShape is true and this is true, the shapes will be drawn in wireframe instead of solid.
		.mDrawBoundingBox			= true,				///< Draw a bounding box per body
		.mDrawCenterOfMassTransform	= false,			///< Draw the center of mass for each body
		.mDrawWorldTransform		= false,			///< Draw the world transform (which can be different than the center of mass) for each body
		.mDrawVelocity				= true,				///< Draw the velocity vector for each body
		.mDrawMassAndInertia		= false,			///< Draw the mass and inertia (as the box equivalent) for each body
		.mDrawSleepStats			= false,			///< Draw stats regarding the sleeping algorithm of each body
	};
#endif

	physicsSystem.DrawBodies( drawSettings, this );

#ifndef VJOLT_USE_PHYSICS_DEBUG_OVERLAY
	// :frog:
	if ( vjolt_debugrender_picture_in_picture.GetBool() )
		DrawJoltTVText();
#endif
}

//-------------------------------------------------------------------------------------------------

JoltPhysicsDebugRenderer &JoltPhysicsDebugRenderer::GetInstance()
{
	static JoltPhysicsDebugRenderer s_DebugRenderer;
	return s_DebugRenderer;
}

IVJoltDebugOverlay *JoltPhysicsDebugRenderer::GetDebugOverlay()
{
	return JoltPhysicsInterface::GetInstance().GetDebugOverlay();
}

//-------------------------------------------------------------------------------------------------

#endif // JPH_DEBUG_RENDERER
