// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.

#include "OculusXRMR_PlaneMeshComponent.h"
#include "RenderingThread.h"
#include "RenderResource.h"
#include "PrimitiveViewRelevance.h"
#include "PrimitiveSceneProxy.h"
#include "VertexFactory.h"
#include "Engine/CollisionProfile.h"
#include "Engine/TextureRenderTarget2D.h"
#include "Materials/Material.h"
#include "LocalVertexFactory.h"
#include "SceneManagement.h"
#include "DynamicMeshBuilder.h"
#include "EngineGlobals.h"
#include "Engine/Engine.h"
#include "MaterialShared.h"
#include "SceneInterface.h"
#include "TextureResource.h"
#include "MaterialDomain.h"
#include "Materials/MaterialRenderProxy.h"

/** Scene proxy */
class FOculusXRMR_PlaneMeshSceneProxy : public FPrimitiveSceneProxy
{
public:
	FOculusXRMR_PlaneMeshSceneProxy(UOculusXRMR_PlaneMeshComponent* Component, UTextureRenderTarget2D* RenderTarget)
		: FPrimitiveSceneProxy(Component)
		, MaterialRelevance(Component->GetMaterialRelevance(GetScene().GetFeatureLevel()))
		, PlaneRenderTarget(RenderTarget)
	{
		const FColor VertexColor(255, 255, 255);

		const int32 NumTris = Component->CustomMeshTris.Num();
		Vertices.AddUninitialized(NumTris * 3);
		Indices.AddUninitialized(NumTris * 3);
		// Add each triangle to the vertex/index buffer
		for (int32 TriIdx = 0; TriIdx < NumTris; TriIdx++)
		{
			FOculusXRMR_PlaneMeshTriangle& Tri = Component->CustomMeshTris[TriIdx];

			const FVector Edge01 = (Tri.Vertex1 - Tri.Vertex0);
			const FVector Edge02 = (Tri.Vertex2 - Tri.Vertex0);

			const FVector TangentX = Edge01.GetSafeNormal();
			const FVector TangentZ = (Edge02 ^ Edge01).GetSafeNormal();
			const FVector TangentY = (TangentX ^ TangentZ).GetSafeNormal();

			FDynamicMeshVertex Vert;

			Vert.Color = VertexColor;
			Vert.SetTangents((FVector3f)TangentX, (FVector3f)TangentY, (FVector3f)TangentZ);

			Vert.Position = (FVector3f)Tri.Vertex0;
			Vert.TextureCoordinate[0] = FVector2f(Tri.UV0); // LWC_TODO: Precision loss
			Vertices[TriIdx * 3 + 0] = Vert;
			Indices[TriIdx * 3 + 0] = TriIdx * 3 + 0;

			Vert.Position = (FVector3f)Tri.Vertex1;
			Vert.TextureCoordinate[0] = FVector2f(Tri.UV1); // LWC_TODO: Precision loss
			Vertices[TriIdx * 3 + 1] = Vert;
			Indices[TriIdx * 3 + 1] = TriIdx * 3 + 1;

			Vert.Position = (FVector3f)Tri.Vertex2;
			Vert.TextureCoordinate[0] = FVector2f(Tri.UV2); // LWC_TODO: Precision loss
			Vertices[TriIdx * 3 + 2] = Vert;
			Indices[TriIdx * 3 + 2] = TriIdx * 3 + 2;
		}

		// Grab material
		Material = Component->GetMaterial(0);
		if (Material == nullptr)
		{
			Material = UMaterial::GetDefaultMaterial(MD_Surface);
		}
	}

	virtual ~FOculusXRMR_PlaneMeshSceneProxy()
	{
	}

	SIZE_T GetTypeHash() const override
	{
		static size_t UniquePointer;
		return reinterpret_cast<size_t>(&UniquePointer);
	}

	virtual void GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override
	{
		QUICK_SCOPE_CYCLE_COUNTER(STAT_OculusXRMR_PlaneMeshSceneProxy_GetDynamicMeshElements);

		// the mesh is only visible inside the CastingViewport, and the Full CastingLayer (the Composition mode)
		if (PlaneRenderTarget && ViewFamily.RenderTarget == PlaneRenderTarget->GetRenderTargetResource())
		{
			const bool bWireframe = AllowDebugViewmodes() && ViewFamily.EngineShowFlags.Wireframe;

			FMaterialRenderProxy* MaterialProxy = nullptr;
			if (bWireframe)
			{
				auto WireframeMaterialInstance = new FColoredMaterialRenderProxy(
					GEngine->WireframeMaterial->GetRenderProxy(),
					FLinearColor(0, 0.5f, 1.f));

				Collector.RegisterOneFrameMaterialProxy(WireframeMaterialInstance);
				MaterialProxy = WireframeMaterialInstance;
			}
			else
			{
				MaterialProxy = Material->GetRenderProxy();
			}

			for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
			{
				if (VisibilityMap & (1 << ViewIndex))
				{
					const FSceneView* View = Views[ViewIndex];

					FDynamicMeshBuilder DynamicMeshBuilder(View->GetFeatureLevel());
					DynamicMeshBuilder.AddVertices(Vertices);
					DynamicMeshBuilder.AddTriangles(Indices);

					DynamicMeshBuilder.GetMesh(GetLocalToWorld(), MaterialProxy, SDPG_World, true, false, ViewIndex, Collector);

					// -- Original draw code for reference --
					//FMeshBatch& Mesh = Collector.AllocateMesh();
					//FMeshBatchElement& BatchElement = Mesh.Elements[0];
					//BatchElement.IndexBuffer = &IndexBuffer;
					//Mesh.bWireframe = bWireframe;
					//Mesh.VertexFactory = &VertexFactory;
					//Mesh.MaterialRenderProxy = MaterialProxy;
					//BatchElement.PrimitiveUniformBuffer = CreatePrimitiveUniformBufferImmediate(GetLocalToWorld(), GetBounds(), GetLocalBounds(), true, DrawsVelocity());
					//BatchElement.FirstIndex = 0;
					//BatchElement.NumPrimitives = IndexBuffer.Indices.Num() / 3;
					//BatchElement.MinVertexIndex = 0;
					//BatchElement.MaxVertexIndex = VertexBuffer.Vertices.Num() - 1;
					//Mesh.ReverseCulling = IsLocalToWorldDeterminantNegative();
					//Mesh.Type = PT_TriangleList;
					//Mesh.DepthPriorityGroup = SDPG_World;
					//Mesh.bCanApplyViewModeOverrides = false;
					//Collector.AddMesh(ViewIndex, Mesh);
				}
			}
		}
	}

	virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override
	{
		FPrimitiveViewRelevance Result;
		Result.bDrawRelevance = IsShown(View);
		Result.bShadowRelevance = IsShadowCast(View);
		Result.bDynamicRelevance = true;
		Result.bRenderInMainPass = ShouldRenderInMainPass();
		Result.bUsesLightingChannels = GetLightingChannelMask() != GetDefaultLightingChannelMask();
		Result.bRenderCustomDepth = ShouldRenderCustomDepth();
		MaterialRelevance.SetPrimitiveViewRelevance(Result);
		return Result;
	}

	virtual bool CanBeOccluded() const override
	{
		return !MaterialRelevance.bDisableDepthTest;
	}

	virtual uint32 GetMemoryFootprint(void) const override { return (sizeof(*this) + GetAllocatedSize()); }

	uint32 GetAllocatedSize(void) const { return (FPrimitiveSceneProxy::GetAllocatedSize()); }

private:
	UMaterialInterface* Material;
	TArray<FDynamicMeshVertex> Vertices;
	TArray<uint32> Indices;
	FMaterialRelevance MaterialRelevance;
	UTextureRenderTarget2D* PlaneRenderTarget;
};

//////////////////////////////////////////////////////////////////////////

UOculusXRMR_PlaneMeshComponent::UOculusXRMR_PlaneMeshComponent(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	PrimaryComponentTick.bCanEverTick = false;

	SetCollisionProfileName(UCollisionProfile::BlockAllDynamic_ProfileName);

	bRenderCustomDepth = true;
}

bool UOculusXRMR_PlaneMeshComponent::SetCustomMeshTriangles(const TArray<FOculusXRMR_PlaneMeshTriangle>& Triangles)
{
	CustomMeshTris = Triangles;

	// Need to recreate scene proxy to send it over
	MarkRenderStateDirty();

	return true;
}

void UOculusXRMR_PlaneMeshComponent::AddCustomMeshTriangles(const TArray<FOculusXRMR_PlaneMeshTriangle>& Triangles)
{
	CustomMeshTris.Append(Triangles);

	// Need to recreate scene proxy to send it over
	MarkRenderStateDirty();
}

void UOculusXRMR_PlaneMeshComponent::ClearCustomMeshTriangles()
{
	CustomMeshTris.Reset();

	// Need to recreate scene proxy to send it over
	MarkRenderStateDirty();
}

void UOculusXRMR_PlaneMeshComponent::Place(const FVector& Center, const FVector& Up, const FVector& Normal, const FVector2D& Size)
{
	FVector Right = FVector::CrossProduct(Up, Normal);

	FVector Up_N = Up.GetUnsafeNormal();
	FVector Right_N = Right.GetUnsafeNormal();

	FVector V0 = Center - Right_N * Size.X * 0.5f - Up_N * Size.Y * 0.5f;
	FVector2D UV0(1, 1);
	FVector V1 = Center + Right_N * Size.X * 0.5f - Up_N * Size.Y * 0.5f;
	FVector2D UV1(0, 1);
	FVector V2 = Center - Right_N * Size.X * 0.5f + Up_N * Size.Y * 0.5f;
	FVector2D UV2(1, 0);
	FVector V3 = Center + Right_N * Size.X * 0.5f + Up_N * Size.Y * 0.5f;
	FVector2D UV3(0, 0);

	FOculusXRMR_PlaneMeshTriangle Tri0, Tri1;
	Tri0.Vertex0 = V1;
	Tri0.UV0 = UV1;
	Tri0.Vertex1 = V0;
	Tri0.UV1 = UV0;
	Tri0.Vertex2 = V2;
	Tri0.UV2 = UV2;
	Tri1.Vertex0 = V1;
	Tri1.UV0 = UV1;
	Tri1.Vertex1 = V2;
	Tri1.UV1 = UV2;
	Tri1.Vertex2 = V3;
	Tri1.UV2 = UV3;

	SetCustomMeshTriangles({ Tri0, Tri1 });
}

FPrimitiveSceneProxy* UOculusXRMR_PlaneMeshComponent::CreateSceneProxy()
{
	FPrimitiveSceneProxy* Proxy = nullptr;
	if (CustomMeshTris.Num() > 0)
	{
		Proxy = new FOculusXRMR_PlaneMeshSceneProxy(this, PlaneRenderTarget);
	}
	return Proxy;
}

int32 UOculusXRMR_PlaneMeshComponent::GetNumMaterials() const
{
	return 1;
}

FBoxSphereBounds UOculusXRMR_PlaneMeshComponent::CalcBounds(const FTransform& LocalToWorld) const
{
	FBoxSphereBounds NewBounds;
	NewBounds.Origin = FVector::ZeroVector;
	NewBounds.BoxExtent = FVector(HALF_WORLD_MAX, HALF_WORLD_MAX, HALF_WORLD_MAX);
	NewBounds.SphereRadius = FMath::Sqrt(3.0f * FMath::Square(HALF_WORLD_MAX));
	return NewBounds;
}
