// ------------------------------------------------
// Copyright Joe Marshall 2024- All Rights Reserved
// ------------------------------------------------
//
// Attaches to a static mesh, and allows the vertex data
// to be passed into the vulkanimpl for rendering
// ------------------------------------------------
#pragma once

#include "Engine/StaticMesh.h"
#include "Engine/StaticMeshActor.h"
#include "IMediaEventSink.h"
#include "MediaObjectPool.h"
#include "MediaPlayer.h"
#include "MediaPlayerFacade.h"
#include "MediaSampleSink.h"

#include "Logging/StructuredLog.h"

#include "IVulkanVertexData.h"

#include "RendererInterface.h"

#include "DirectVideoMeshRenderer.generated.h"

class AndroidVulkanTextureSample;

class FRendererSceneViewExt;

class FRawStaticIndexBuffer;
struct FStaticMeshVertexBuffers;

DECLARE_LOG_CATEGORY_EXTERN(LogDirectVideoMeshRenderer, Log, All);

UCLASS(Blueprintable, ClassGroup = (Rendering, Common),
       hidecategories = (Object, Activation, "Components|Activation"), ShowCategories = (Mobility),
       editinlinenew, meta = (BlueprintSpawnableComponent))
class VULKANVIDEOMESHCOMPONENT_API UDirectVideoMeshRendererComponent : public UActorComponent
{
    friend class FRendererSceneViewExt;
    GENERATED_BODY()
  public:
    UDirectVideoMeshRendererComponent();
    ~UDirectVideoMeshRendererComponent();

    void HandlePlayerMediaEvent(EMediaSampleSinkEvent Event);

    void DeInit();

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Components|DirectVideo")
    UMediaPlayer *LinkedPlayer;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Components|DirectVideo")
    bool HideMeshWhileWeHaveVideo;

    // FMediaTextureSampleSink (called indirectly from _TextureSink)
    virtual bool Enqueue(const TSharedRef<IMediaTextureSample, ESPMode::ThreadSafe> &Sample);
    virtual int32 Num() const
    {
        if (CurrentSample.IsValid())
            return 1;
        return 0;
    }
    virtual void RequestFlush()
    {
        CurrentSample.Reset();
        PreparedSample.Reset();
    }

    virtual void BeginDestroy() override;

  protected:
    // draw to a command list (which is already in a render pass with outputs set up)
    void DrawToCommandList(FRHICommandList &RHICmdList, FSceneView &InView);
    // load mesh, update matrices, create vulkan images (must be outside render pass)
    void DoWorkOutsideRenderPass(FRHICommandList &RHICmdList, FSceneViewFamily &InViewFamily);

    void UpdateVisibility();

    class _TextureSink : public FMediaTextureSampleSink
    {
      public:
        _TextureSink(UDirectVideoMeshRendererComponent *InOwner)
        {
            Owner = InOwner;
            FlushCount = 0;
            EventDelegate =
                OnMediaSampleSinkEvent().AddRaw(this, &_TextureSink::ReceiveEventHandler);
        }

        virtual ~_TextureSink()
        {
            OnMediaSampleSinkEvent().Remove(EventDelegate);
        }
        virtual bool Enqueue(
            const TSharedRef<IMediaTextureSample, ESPMode::ThreadSafe> &Sample) override
        {
            UE_LOGFMT(LogDirectVideoMeshRenderer, Verbose, "Enqueue");
            auto POwner = Owner.Get();
            if (POwner != NULL)
            {
                UE_LOGFMT(LogDirectVideoMeshRenderer, Verbose, "Enqueue2");
                return POwner->Enqueue(Sample);
            }
            return false;
        }
        virtual int32 Num() const override
        {
            UE_LOGFMT(LogDirectVideoMeshRenderer, Verbose, "Num");
            auto POwner = Owner.Get();
            if (POwner != NULL)
            {
                UE_LOGFMT(LogDirectVideoMeshRenderer, Verbose, "Num2");
                return POwner->Num();
            }
            return 0;
        }
        virtual void RequestFlush() override
        {
            UE_LOGFMT(LogDirectVideoMeshRenderer, Verbose, "Flush");

            FlushCount += 1;
            auto POwner = Owner.Get();
            if (POwner != NULL)
            {
                UE_LOGFMT(LogDirectVideoMeshRenderer, Verbose, "Flush2");
                POwner->RequestFlush();
            }
        }

        void ReceiveEventHandler(EMediaSampleSinkEvent Event, const FMediaSampleSinkEventData &Data)
        {
            int iEvent = (int)Event;
            UE_LOGFMT(LogDirectVideoMeshRenderer, Verbose, "On event:{1}", iEvent);
            auto POwner = Owner.Get();
            if (POwner != NULL)
            {
                POwner->HandlePlayerMediaEvent(Event);
            }
        }

        TWeakObjectPtr<UDirectVideoMeshRendererComponent> Owner;
        int FlushCount;

        FDelegateHandle EventDelegate;
    };

    void Initialize();

    void GetMeshObject();
    bool ConstructMeshFromRenderData(const FStaticMeshVertexBuffers &VertexBuffers,
                                     const FRawStaticIndexBuffer &IndexBuffer);
    void GetShaderInfo(const FSceneView *InView, float *m44, float *vp44,
                       float *pre_view_translation);
    virtual void TickComponent(float DeltaTime, ELevelTick TickType,
                               FActorComponentTickFunction *ThisTickFunction) override;

    virtual void Serialize(FArchive &Ar) override;

    UPROPERTY()
    bool HasMesh;
    // raw mesh data as passed to RenderToMesh
    // we save this in UObject::Serialize so that
    // we don't need to load it from the mesh at
    // runtime
    UPROPERTY()
    TArray<float> PositionsAsFloats; // x,y,z,u,v (floats)

    // vertexData is not a UStruct because we pass it through to
    // native android code
    TArray<VertexData> Positions; // x,y,z,u,v (floats)

    UPROPERTY()
    TArray<uint32> Indices; // index buffer for mesh (uint32)

    TSharedPtr<_TextureSink> TextureSink;

    TSharedPtr<IMediaTextureSample, ESPMode::ThreadSafe> CurrentSample;
    TSharedPtr<IMediaTextureSample, ESPMode::ThreadSafe> PreparedSample;

    TWeakPtr<FMediaPlayerFacade, ESPMode::ThreadSafe> Facade;

    TObjectPtr<AStaticMeshActor> MeshActor;

    TSharedPtr<FRendererSceneViewExt, ESPMode::ThreadSafe> ExtensionHolder;

    bool Initialized;
};
