// Fill out your copyright notice in the Description page of Project Settings.


#include "ArriLiveLinkDevice.h"

#include "Async/Async.h"
#include "Common/UdpSocketBuilder.h"
#include "Sockets.h"
#include "JsonObjectConverter.h"
#include "SocketSubsystem.h"

#define LOCTEXT_NAMESPACE "FArriLiveLinkDevice"

#define RECV_BUFFER_SIZE 1024 * 1024
#define UNIT_FACTOR 100

FArriLiveLinkDevice::FArriLiveLinkDevice(const FArriLiveLinkConnectionSettings& ConnectionSettings)
{
	mSocket = UArriDevice::createSocket(ConnectionSettings, mSocket);
	mConnectionSettings = ConnectionSettings;
}

FArriLiveLinkDevice::~FArriLiveLinkDevice()
{

}

void FArriLiveLinkDevice::ReceiveClient(ILiveLinkClient* InClient, FGuid InSourceGuid)
{
	Client = InClient;
	SourceGuid = InSourceGuid;

	Start();
}

bool FArriLiveLinkDevice::IsSourceStillValid() const
{
	// Source is valid if we have a valid thread and socket
	bool bIsSourceValid = !mStopping && mThread != nullptr && mSocket != nullptr;;
	return bIsSourceValid;
}

bool FArriLiveLinkDevice::RequestSourceShutdown()
{
	Stop();
	if (mThread != nullptr)
	{
		// Verify if our thread has actually stopped processing incoming frames before telling LiveLink we can be shutdown
		if (mStopped == false)
		{
			return false;
		}

		delete mThread;
		mThread = nullptr;
	}
	if (mSocket != nullptr)
	{
		mSocket->Close();
		mSocket = nullptr;
		UE_LOG(LogArri, Log, TEXT("Socket closed"));
	}

	Client = nullptr;
	EncounteredSubjects.Empty();
	SourceGuid.Invalidate();
	mStopping = false;
	return true;
}

void FArriLiveLinkDevice::Start()
{
	mSocketSubsystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);
	mRecvBuffer.SetNumUninitialized(RECV_BUFFER_SIZE);

	mThreadName = "LiveLink Receiver";
	mThreadName.AppendInt(FAsyncThreadIndex::GetNext());
	mThread = FRunnableThread::Create(this, *mThreadName, 128 * 1024, TPri_AboveNormal, FPlatformAffinity::GetPoolThreadMask());

}

void FArriLiveLinkDevice::Stop()
{
	mStopping = true;
}

uint32 FArriLiveLinkDevice::Run()
{
	mStopped = false;
	TSharedRef<FInternetAddr> Sender = mSocketSubsystem->CreateInternetAddr();

	while (!mStopping && mSocket != nullptr)
	{
		if (mSocket->Wait(ESocketWaitConditions::WaitForRead, mWaitTime))
		{
			uint32 Size;
			while (mSocket->HasPendingData(Size))
			{
				int32 Read = 0;
				if (mSocket->RecvFrom(mRecvBuffer.GetData(), mRecvBuffer.Num(), Read, *Sender))
				{
					if (Read > 0)
					{
						if (!Client)
						{
							continue;
						}
						TArray<char> ReceivedData;
						ReceivedData.Reserve(Read);
						memcpy(ReceivedData.GetData(), mRecvBuffer.GetData(), Read);
						FString ReceivedBuf(Read, ReceivedData.GetData());

						FString Address = Sender->ToString(false);
						FString sCam = mConnectionSettings.SubjectName;
						sCam += "@";
						sCam += Address;
						FName SubjectNameCam(sCam);

						bool bCreateSubject = EncounteredSubjects.Contains(SubjectNameCam);

						if (!bCreateSubject)
						{
							mBufferMap.Add(SubjectNameCam, ReceivedBuf);
							EncounteredSubjects.Add(SubjectNameCam);
							bCreateSubject = false;

							FLiveLinkStaticDataStruct StaticDataStruct = FLiveLinkStaticDataStruct(FLiveLinkCameraStaticData::StaticStruct());
							FLiveLinkCameraStaticData& StaticData = *StaticDataStruct.Cast<FLiveLinkCameraStaticData>();

							StaticData.bIsFocalLengthSupported = true;
							StaticData.bIsFocusDistanceSupported = true;
							StaticData.bIsApertureSupported = true;

							Client->PushSubjectStaticData_AnyThread({ SourceGuid, SubjectNameCam }, ULiveLinkCameraRole::StaticClass(), MoveTemp(StaticDataStruct));
							UE_LOG(LogArri, Log, TEXT("Subject created with name: %s"), *FString(SubjectNameCam.ToString()));
						}
						else
						{
							mBufferMap[SubjectNameCam].Append(ReceivedBuf);
						}

						FArriCameraRawMatadata ArriRawMetadata;
						FJsonObjectConverter::JsonObjectStringToUStruct<FArriCameraRawMatadata>(ReceivedBuf, &ArriRawMetadata, 0, 0);
						
						mMetadata = UArriDevice::rawToUnrealMetadata(ArriRawMetadata);

						FLiveLinkFrameDataStruct CameraFrameDataStruct = FLiveLinkFrameDataStruct(FLiveLinkCameraFrameData::StaticStruct());
						FLiveLinkCameraFrameData& CameraFrameData = *CameraFrameDataStruct.Cast<FLiveLinkCameraFrameData>();

						FLiveLinkBaseDataStruct<FLiveLinkBaseBlueprintData> BaseDataStruct;

						frameTime = FQualifiedFrameTime(mMetadata.frameNumber, mMetadata.framerate);

						CameraFrameData.MetaData.SceneTime = frameTime;
						CameraFrameData.Aperture = mMetadata.aperture;
						CameraFrameData.FocalLength = mMetadata.focalLength;
						CameraFrameData.FocusDistance = mMetadata.focusDistance;

						FString cameraState = StaticEnum<CameraState>()->GetNameByValue((int64)mMetadata.cameraState).ToString();

						CameraFrameData.MetaData.StringMetaData.Add("CameraSate", cameraState);
						CameraFrameData.MetaData.StringMetaData.Add("ExposureIndex", FString::FromInt(mMetadata.exposureIndex));
						CameraFrameData.MetaData.StringMetaData.Add("ExposureTime", FString::SanitizeFloat(mMetadata.exposureTime));
						CameraFrameData.MetaData.StringMetaData.Add("ShutterAngle", FString::SanitizeFloat(mMetadata.shutterAngle));
						CameraFrameData.MetaData.StringMetaData.Add("NDDesnity", FString::SanitizeFloat(mMetadata.ndFilterDensity));
						CameraFrameData.MetaData.StringMetaData.Add("CCT", FString::FromInt(mMetadata.cct));
						CameraFrameData.MetaData.StringMetaData.Add("tint", FString::SanitizeFloat(mMetadata.tint));

						Client->PushSubjectFrameData_AnyThread({ SourceGuid, SubjectNameCam }, MoveTemp(CameraFrameDataStruct));
						mBufferMap[SubjectNameCam].Empty();
					}
				}
			}
		}
	}
	mStopped = true;
	return 0;
}

FText FArriLiveLinkDevice::GetSourceMachineName() const
{
	return FText().FromString(FPlatformProcess::ComputerName());
}

FText FArriLiveLinkDevice::GetSourceStatus() const
{
	return LOCTEXT("ArriLiveLinkStatus", "Active");
}

FText FArriLiveLinkDevice::GetSourceType() const
{
	return LOCTEXT("ArriLiveLinkSourceType", "Arri Live Link controller");
}

#undef LOCTEXT_NAMESPACE