// Georgy Treshchev 2025.

#include "OVRLipSyncContextWrapper.h"
#include "RuntimeMetaHumanLipSyncModule.h"
#include "RuntimeMetaHumanLipSync_Standard.h"
#include "Interfaces/IPluginManager.h"
#include "Misc/Paths.h"
#include "HAL/PlatformProcess.h"

#if PLATFORM_WINDOWS || PLATFORM_ANDROID
UOVRLipSyncContextWrapper::UOVRLipSyncContextWrapper(ovrLipSyncContextProvider ProviderKind, int32 SampleRate,
                                                     int32 BufferSize, FString ModelPath, bool EnableAcceleration)
{
#if !PLATFORM_ANDROID
	const FString PluginDir = IPluginManager::Get().FindPlugin("RuntimeMetaHumanLipSync_Standard")->GetBaseDir();
	const FString LibraryDir = FPaths::Combine(PluginDir, TEXT("Source"), TEXT("RuntimeMetaHumanLipSync_Standard"), TEXT("ThirdParty"), TEXT("Lib"),
								  FPlatformProcess::GetBinariesSubdirectory());
	TArray<char> LibraryDirChar(LibraryDir.GetCharArray());
	auto LipSyncResult = ovrLipSync_InitializeEx(SampleRate, BufferSize, LibraryDirChar.GetData());
#else
	auto LipSyncResult = ovrLipSync_Initialize(SampleRate, BufferSize);
#endif
	if (LipSyncResult != ovrLipSyncSuccess)
	{
		UE_LOG(LogRuntimeMetaHumanLipSyncStandard, Error, TEXT("Can't initialize ovrLipSync: %d"), LipSyncResult);
		return;
	}
	LipSyncResult = ModelPath.IsEmpty()
			 ? ovrLipSync_CreateContextEx(&LipSyncContext, ProviderKind, SampleRate, EnableAcceleration)
			 : ovrLipSync_CreateContextWithModelFile(&LipSyncContext, ProviderKind, TCHAR_TO_ANSI(*ModelPath),
													 SampleRate, EnableAcceleration);
	if (LipSyncResult != ovrLipSyncSuccess)
	{
		UE_LOG(LogRuntimeMetaHumanLipSyncStandard, Error, TEXT("Can't create ovrLipSync context: %d"), LipSyncResult);
	}
}

UOVRLipSyncContextWrapper::~UOVRLipSyncContextWrapper() { ovrLipSync_DestroyContext(LipSyncContext); }

void UOVRLipSyncContextWrapper::ProcessFrame(const int16_t *AudioBuffer, int AudioBufferSize, TArray<float> &Visemes,
											 float &LaughterScore, int32_t &FrameDelay, bool Stereo)
{
	if (Visemes.Num() != ovrLipSyncViseme_Count)
	{
		Visemes.SetNumZeroed(ovrLipSyncViseme_Count);
	}
	ovrLipSyncFrame frame = {};
	frame.visemes = Visemes.GetData();
	frame.visemesLength = Visemes.Num();
	auto rc = ovrLipSync_ProcessFrameEx(LipSyncContext, AudioBuffer, AudioBufferSize,
										Stereo ? ovrLipSyncAudioDataType_S16_Stereo : ovrLipSyncAudioDataType_S16_Mono,
										&frame);
	if (rc != ovrLipSyncSuccess)
	{
		UE_LOG(LogRuntimeMetaHumanLipSyncStandard, Error, TEXT("Failed to process frame: %d"), rc);
		return;
	}
	LaughterScore = frame.laughterScore;
	FrameDelay = frame.frameDelay;
}

namespace
{
	void ProcessFrameCallback(void* opaque, const ovrLipSyncFrame* pFrame, ovrLipSyncResult result)
	{
		if (result != ovrLipSyncSuccess)
		{
			UE_LOG(LogRuntimeMetaHumanLipSyncStandard, Error, TEXT("Async prediction failed: %d"), result);
			return;
		}
		UOVRLipSyncContextWrapper* LipSyncContextWrapper = static_cast<UOVRLipSyncContextWrapper*>(opaque);
		TArray<float> Visemes(pFrame->visemes, pFrame->visemesLength);
		LipSyncContextWrapper->InvokeAsyncCallback(Visemes, pFrame->laughterScore);
	}
}

void UOVRLipSyncContextWrapper::SetAsyncCallback(const AsyncCallbackType &Callback) { AsyncCallback = Callback; }

void UOVRLipSyncContextWrapper::InvokeAsyncCallback(const TArray<float>& Visemes, float LaughterScore)
{
	if (!AsyncCallback)
	{
		UE_LOG(LogRuntimeMetaHumanLipSyncStandard, Error, TEXT("Trying invoke unintialized async callback"));
		return;
	}
	AsyncCallback(Visemes, LaughterScore);
}

void UOVRLipSyncContextWrapper::ProcessFrameAsync(const float* Data, int32 DataSize)
{
	auto LipSyncResult = ovrLipSync_ProcessFrameAsync(
		LipSyncContext, Data, DataSize, ovrLipSyncAudioDataType_F32_Mono, ProcessFrameCallback, this);
	if (LipSyncResult != ovrLipSyncSuccess)
	{
		UE_LOG(LogRuntimeMetaHumanLipSyncStandard, Error, TEXT("Failed to start async prediction: %d"), LipSyncResult);
		return;
	}
}
#endif
