#include "ArriDevice.h"
#include "Common/UdpSocketBuilder.h"
#include "JsonObjectConverter.h"

//#include <string>

#define RECV_BUFFER_SIZE 1024 * 1024
#define UNIT_FACTOR 100

DEFINE_LOG_CATEGORY(LogArri);


UArriDevice::UArriDevice() 
{
}

UArriDevice::~UArriDevice()
{
	shutdownDevice();
}

TSharedPtr<FSocket> UArriDevice::createSocket(FArriLiveLinkConnectionSettings connectionSettings, TSharedPtr<FSocket> Socket)
{
	FSocket* SocketPtr = nullptr;
	bool createSocket = true;
	FString SocketAddr;

	FIPv4Endpoint endpoint;
	FIPv4Address endpointAddress;

	//Wildcard to find Ip Addresses in a deisgnated subnet
	FString Wildcard = "*";
	if (connectionSettings.IPAddress.Contains(Wildcard))
	{
		TArray<FString> IPAddrSegments;
		connectionSettings.IPAddress.ParseIntoArray(IPAddrSegments, TEXT("."), true);

		endpointAddress.A = FCString::Atoi(*IPAddrSegments[0]);
		endpointAddress.B = FCString::Atoi(*IPAddrSegments[1]);
		endpointAddress.C = FCString::Atoi(*IPAddrSegments[2]);

		TArray<TSharedPtr<FInternetAddr>> localAddresses;
		bool gotAddresses = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->GetLocalAdapterAddresses(localAddresses);
		bool foundAddress = false;

		if (gotAddresses)
		{
			int numOfAddresses = localAddresses.Num();
			TArray<FString> localAddressesAsString;

			for (int i = 0; i < numOfAddresses; i++)
			{
				localAddressesAsString.Add(*localAddresses[i].Get()->ToString(false));
				UE_LOG(LogArri, Log, TEXT("Found local Address %d : %s"), i, *localAddressesAsString[i]);
			}

			int IPCounter = 1;
			while (!foundAddress && IPCounter <= 255)
			{
				endpointAddress.D = IPCounter;
				if (localAddressesAsString.Contains(endpointAddress.ToString()))
				{
					foundAddress = true;
					UE_LOG(LogArri, Log, TEXT("Added local Address via Wildcard @ : %s"), *endpointAddress.ToString());
				}
				else
				{
					IPCounter++;
				}
			}
		}
		else
		{
			UE_LOG(LogArri, Error, TEXT("Could not find any connected Adapters. Make sure you are connected to a network"));
		}
		
	}
	else
	{
		FIPv4Address::Parse(connectionSettings.IPAddress, endpointAddress);		
	}

	endpoint.Address = endpointAddress;
	endpoint.Port = connectionSettings.UDPPortNumber;

	UE_LOG(LogArri, Log, TEXT("Device Enpoint configured to Address: %s"), *endpointAddress.ToString());
	UE_LOG(LogArri, Log, TEXT("Device Enpoint configured to Port: %s"), *FString::FromInt(connectionSettings.UDPPortNumber));


	if (connectionSettings.Mode == UDPMode::MULTI)
	{
		FIPv4Address multicastAddress;
		FIPv4Address::Parse(connectionSettings.MulticastAddress, multicastAddress);

		if (!multicastAddress.IsOrganizationLocalMulticast())
		{
			UE_LOG(LogArri, Error, TEXT("Multicast Address must be in Organization Local Scope. of 239.192.x.x, current Address is: %s. Failed to create Socket. Please delete Source and add again with correct Multicast Address."), *connectionSettings.MulticastAddress);
			createSocket = false;
		}
		else
		{
			UE_LOG(LogArri, Log, TEXT("Socket configured to Multicast address: %s"), *multicastAddress.ToString());
			SocketPtr = FUdpSocketBuilder(TEXT("UDPSOCKET"))
				.AsNonBlocking()
				.AsReusable()
				.BoundToPort(connectionSettings.UDPPortNumber)
				.BoundToEndpoint(endpoint)
				.WithReceiveBufferSize(RECV_BUFFER_SIZE)
				.JoinedToGroup(multicastAddress, endpoint.Address)
				.WithMulticastLoopback()
				.WithMulticastTtl(2);
		}
	}
	else if (connectionSettings.Mode == UDPMode::UNI)
	{
		UE_LOG(LogArri, Log, TEXT("Socket configured to Unicast"));
		SocketPtr = FUdpSocketBuilder(TEXT("UDPSOCKET"))
			.AsNonBlocking()
			.AsReusable()
			.BoundToPort(connectionSettings.UDPPortNumber)
			.BoundToEndpoint(endpoint)
			.WithReceiveBufferSize(RECV_BUFFER_SIZE);
	}
	if (SocketPtr != nullptr)
	{
		TSharedPtr<FInternetAddr> SocketAddress = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
		
		SocketPtr->SetReuseAddr(true);
		SocketPtr->GetAddress(*SocketAddress);
		SocketAddr = SocketAddress->ToString(false);
		UE_LOG(LogArri, Log, TEXT("Socket address is confirmed: %s"), *SocketAddr);
	}
	else
	{
		UE_LOG(LogArri, Error, TEXT("Could not create Socket, please check whether you are using the correct IP of your preferred Network card or whether the Port is blocked by a different process."), *SocketAddr);
	}

	if (SocketAddr != endpointAddress.ToString())
	{
		UE_LOG(LogArri, Warning, TEXT("Socket Address does not match Endpoint Address. Socket: %s , Enpoint: %s"), *SocketAddr, *endpointAddress.ToString());
	}
	else
	{
		UE_LOG(LogArri, Log, TEXT("Socket Address matches Endpoint. Socket: %s , Endpoint: %s"), *SocketAddr, *endpointAddress.ToString());
	}
	if ((SocketPtr != nullptr) && (SocketPtr->GetSocketType() == SOCKTYPE_Datagram))
	{
		UE_LOG(LogArri, Log, TEXT("Socket created sucessfully for Arri Live Link Source"));
	}
	else
	{
		UE_LOG(LogArri, Warning, TEXT("Could not create socket for Arri LiveLink Source"));
	}

	Socket = MakeShareable(SocketPtr);
	return Socket;
}

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

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

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

uint32 UArriDevice::Run()
{
	TSharedRef<FInternetAddr> Sender = mSocketSubsystem->CreateInternetAddr();
	while (!mStopping)
	{
		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)
					{
						TArray<char> ReceivedData;
						ReceivedData.Reserve(Read);
						memcpy(ReceivedData.GetData(), mRecvBuffer.GetData(), Read);
						FString ReceivedBuf(Read, ReceivedData.GetData());

						FArriCameraRawMatadata ArriRawMetadata;
						FJsonObjectConverter::JsonObjectStringToUStruct<FArriCameraRawMatadata>(ReceivedBuf, &ArriRawMetadata,0, 0);

						FString Address = Sender->ToString(false);
						FString sCam = FString("Arri-");
						sCam += "UMC-4@";
						sCam += Address;
						mSubjectNameCam = FName(sCam);

						UE_LOG(LogArri, Verbose, TEXT("Receiving data from: %s"), *sCam);

						mMetadata = rawToUnrealMetadata(ArriRawMetadata);
					}
				}
			}
		}
	}
	return 0;
}

FArriMetadata UArriDevice::rawToUnrealMetadata(FArriCameraRawMatadata &ArriRawMetadata)
{
	FArriMetadata unrealMetadata;

	if (ArriRawMetadata.metadata.camera.state == 0)
	{
		unrealMetadata.cameraState = CameraState::CS_READY;
	}
	else if (ArriRawMetadata.metadata.camera.state == 1)
	{
		unrealMetadata.cameraState = CameraState::CS_RECORDING;
	}
	else if (ArriRawMetadata.metadata.camera.state == 2)
	{
		unrealMetadata.cameraState = CameraState::CS_REC_STARTING;
	}
	else if (ArriRawMetadata.metadata.camera.state == 4)
	{
		unrealMetadata.cameraState = CameraState::CS_REC_STOPPING;
	}
	else if (ArriRawMetadata.metadata.camera.state == 8)
	{
		unrealMetadata.cameraState = CameraState::CS_WARNING_ACTIVE;
	}
	else if (ArriRawMetadata.metadata.camera.state == 16)
	{
		unrealMetadata.cameraState = CameraState::CS_ERROR_ACTIVE;
	}
	else if (ArriRawMetadata.metadata.camera.state == 32)
	{
		unrealMetadata.cameraState = CameraState::CS_NOT_READY;
	}
	else if (ArriRawMetadata.metadata.camera.state == 64)
	{
		unrealMetadata.cameraState = CameraState::CS_BOOTING;
	}
	else if (ArriRawMetadata.metadata.camera.state == 128)
	{
		unrealMetadata.cameraState = CameraState::CS_UPDATING;
	}
	else if (ArriRawMetadata.metadata.camera.state == 256)
	{
		unrealMetadata.cameraState = CameraState::CS_UPDATE_FINISHED;
	}
	else if (ArriRawMetadata.metadata.camera.state == 512)
	{
		unrealMetadata.cameraState = CameraState::CS_PRE_RECORDING;
	}
	else if (ArriRawMetadata.metadata.camera.state == 1024)
	{
		unrealMetadata.cameraState = CameraState::CS_PREREC_STARTING;
	}
	else if (ArriRawMetadata.metadata.camera.state == 2048)
	{
		unrealMetadata.cameraState = CameraState::CS_PLAYBACK;
	}
	else if (ArriRawMetadata.metadata.camera.state == 4096)
	{
		unrealMetadata.cameraState = CameraState::CS_MEDIUM_FORMAT;
	}
	else
	{
		unrealMetadata.cameraState = CameraState::CS_UNKNOWN;

	}
	UE_LOG(LogArri, Verbose, TEXT("CameraState: %s"), *StaticEnum<CameraState>()->GetNameByValue((int64)unrealMetadata.cameraState).ToString());
	unrealMetadata.cameraModel						= ArriRawMetadata.metadata.camera.device.model;
	UE_LOG(LogArri, Verbose, TEXT("CameraModel: %s"), *unrealMetadata.cameraModel);
	unrealMetadata.cameraSerial =					ArriRawMetadata.metadata.camera.device.serialNumber;
	UE_LOG(LogArri, Verbose, TEXT("camera Serial Number: %s"), *unrealMetadata.cameraSerial);
	unrealMetadata.cameraSoftwareVersion			= ArriRawMetadata.metadata.camera.device.softwareVersion;
	UE_LOG(LogArri, Verbose, TEXT("camera Software Version: %s"), *unrealMetadata.cameraSoftwareVersion);
	unrealMetadata.umcSerialNumber					= ArriRawMetadata.metadata.camera.device.umc4SerialNumber;
	UE_LOG(LogArri, Verbose, TEXT("UMC Serial Number: %s"), *unrealMetadata.umcSerialNumber);
	unrealMetadata.umcSoftwareVersion				= ArriRawMetadata.metadata.camera.device.umc4SoftwareVersion;
	UE_LOG(LogArri, Verbose, TEXT("UMC Software Version: %s"), *unrealMetadata.umcSoftwareVersion);
	unrealMetadata.ndFilterDensity					= ArriRawMetadata.metadata.camera.optic.filter.ndFilterDensity;
	UE_LOG(LogArri, Verbose, TEXT("ND Filter Density: %f"), unrealMetadata.ndFilterDensity);

	if (ArriRawMetadata.metadata.camera.optic.filter.ndFilterType == 0)
	{
		unrealMetadata.ndFilterType = NDFilterType::ND_OFF;
	}
	else if (ArriRawMetadata.metadata.camera.optic.filter.ndFilterType == 1)
	{
		unrealMetadata.ndFilterType = NDFilterType::ND_ON;
	}
	else
	{
		unrealMetadata.ndFilterType = NDFilterType::ND_UNKNOWN;
	}
	UE_LOG(LogArri, Verbose, TEXT("ND Filter Type: %s"), *StaticEnum<CameraState>()->GetNameByValue((int64)unrealMetadata.ndFilterType).ToString());
	unrealMetadata.lensName							= ArriRawMetadata.metadata.camera.optic.lens.device.model;
	UE_LOG(LogArri, Verbose, TEXT("Lens Name: %s"), *unrealMetadata.lensName);
	unrealMetadata.lensSerialNumber					= ArriRawMetadata.metadata.camera.optic.lens.device.serialNumber;
	UE_LOG(LogArri, Verbose, TEXT("Lens Serial: %s"), *unrealMetadata.lensSerialNumber);
	unrealMetadata.motorRawEncoderLimitsFocusMin	= ArriRawMetadata.metadata.camera.optic.lens.device.motorRawEncoderLimitsFocus.min;
	UE_LOG(LogArri, Verbose, TEXT("Motor Encoder Limit Focus Min: %d"), unrealMetadata.motorRawEncoderLimitsFocusMin);
	unrealMetadata.motorRawEncoderLimitsFocusMax	= ArriRawMetadata.metadata.camera.optic.lens.device.motorRawEncoderLimitsFocus.max;
	UE_LOG(LogArri, Verbose, TEXT("Motor Encoder Limit Focus Max: %d"), unrealMetadata.motorRawEncoderLimitsFocusMax);
	unrealMetadata.motorRawEncoderLimitsIrisMin		= ArriRawMetadata.metadata.camera.optic.lens.device.motorRawEncoderLimitsIris.min;
	UE_LOG(LogArri, Verbose, TEXT("Motor Encoder Limit Iris Min: %d"), unrealMetadata.motorRawEncoderLimitsIrisMin);
	unrealMetadata.motorRawEncoderLimitsIrisMax		= ArriRawMetadata.metadata.camera.optic.lens.device.motorRawEncoderLimitsIris.max;
	UE_LOG(LogArri, Verbose, TEXT("Motor Encoder Limit Iris Max: %d"), unrealMetadata.motorRawEncoderLimitsIrisMax);
	unrealMetadata.motorRawEncoderLimitsFLMin		= ArriRawMetadata.metadata.camera.optic.lens.device.motorRawEncoderLimitsFL.min;
	UE_LOG(LogArri, Verbose, TEXT("Motor Encoder Limit FL Min: %d"), unrealMetadata.motorRawEncoderLimitsFLMin);
	unrealMetadata.motorRawEncoderLimitsFLMax		= ArriRawMetadata.metadata.camera.optic.lens.device.motorRawEncoderLimitsFL.max;
	UE_LOG(LogArri, Verbose, TEXT("Motor Encoder Limit FL Max: %d"), unrealMetadata.motorRawEncoderLimitsFLMax);
	unrealMetadata.focusUnit						= ArriRawMetadata.metadata.camera.optic.lens.state.focusUnit;

	if (unrealMetadata.focusUnit == "metric")
	{
		if (ArriRawMetadata.metadata.camera.optic.lens.state.focusDistanceMetric < 0)
		{
			unrealMetadata.focusDistance = 1000000.0;
			UE_LOG(LogArri, Verbose, TEXT("Focus Distance in cm from metric Metadata: INF"));
		}
		else
		{
			unrealMetadata.focusDistance = ArriRawMetadata.metadata.camera.optic.lens.state.focusDistanceMetric * UNIT_FACTOR;
			UE_LOG(LogArri, Verbose, TEXT("Focus Distance in cm from metric Metadata: %f"), unrealMetadata.focusDistance);
		}
	}
	else
	{
		if (ArriRawMetadata.metadata.camera.optic.lens.state.focusDistanceImperial < 0)
		{
			unrealMetadata.focusDistance = 1000000.0;
			UE_LOG(LogArri, Verbose, TEXT("Focus Distance in cm from imperial Metadata: INF"));
		}
		else
		{
			unrealMetadata.focusDistance = (ArriRawMetadata.metadata.camera.optic.lens.state.focusDistanceImperial / 3.2808) * UNIT_FACTOR;
			UE_LOG(LogArri, Verbose, TEXT("Focus Distance in cm from imperial Metadata: %f"), unrealMetadata.focusDistance);
		}
	}

	unrealMetadata.aperture = pow(2.0, ((ArriRawMetadata.metadata.camera.optic.lens.state.iris / 1000) - 1) / 2);
	UE_LOG(LogArri, Verbose, TEXT("Aperture: %f"), unrealMetadata.aperture);
	unrealMetadata.focalLength = ArriRawMetadata.metadata.camera.optic.lens.state.focalLength;
	UE_LOG(LogArri, Verbose, TEXT("Focal Length: %f"), unrealMetadata.focalLength);
	unrealMetadata.lensRawEncoderFocus = ArriRawMetadata.metadata.camera.optic.lens.state.lensRawEncoderValueFocus;
	UE_LOG(LogArri, Verbose, TEXT("Raw Enoder focus from lens: %d"), unrealMetadata.lensRawEncoderFocus);
	unrealMetadata.lensRawEncoderIris = ArriRawMetadata.metadata.camera.optic.lens.state.lensRawEncoderValueIris;
	UE_LOG(LogArri, Verbose, TEXT("Raw Enoder iris from lens: %d"), unrealMetadata.lensRawEncoderIris);
	unrealMetadata.lensRawEncoderFocalLength = ArriRawMetadata.metadata.camera.optic.lens.state.lensRawEncoderValueFL;
	UE_LOG(LogArri, Verbose, TEXT("Raw Enoder focal length from lens: %d"), unrealMetadata.lensRawEncoderIris);
	unrealMetadata.motorRawEncoderFocus = ArriRawMetadata.metadata.camera.optic.lens.state.motorRawEncoderValueFocus;
	UE_LOG(LogArri, Verbose, TEXT("Raw Enoder focus from motor: %d"), unrealMetadata.motorRawEncoderFocus);
	unrealMetadata.motorRawEncoderIris = ArriRawMetadata.metadata.camera.optic.lens.state.motorRawEncoderValueIris;
	UE_LOG(LogArri, Verbose, TEXT("Raw Enoder iris from motor: %d"), unrealMetadata.motorRawEncoderIris);
	unrealMetadata.motorRawEncoderFocalLength = ArriRawMetadata.metadata.camera.optic.lens.state.motorRawEncoderValueFL;
	UE_LOG(LogArri, Verbose, TEXT("Raw Enoder focal length from motor: %d"), unrealMetadata.motorRawEncoderFocalLength);
	unrealMetadata.shutterAngle = ArriRawMetadata.metadata.camera.sensor.shutterAngle;
	UE_LOG(LogArri, Verbose, TEXT("Shutter angle: %f"), unrealMetadata.shutterAngle);
	float expTime = ArriRawMetadata.metadata.camera.sensor.exposureTime;
	unrealMetadata.exposureTime = 1000 / expTime;
	UE_LOG(LogArri, Verbose, TEXT("Exposure time in milliseconds: %f"), unrealMetadata.exposureTime);
	unrealMetadata.exposureIndex = ArriRawMetadata.metadata.camera.sensor.exposureIndex;
	UE_LOG(LogArri, Verbose, TEXT("Exposure index: %d"), unrealMetadata.exposureIndex);
	unrealMetadata.mediaCapacity = ArriRawMetadata.metadata.camera.recording.media.capacity;
	UE_LOG(LogArri, Verbose, TEXT("Resuming Media capacity in seconds: %d"), unrealMetadata.mediaCapacity);
	unrealMetadata.framerate.Denominator = 1;
	unrealMetadata.framerate.Numerator = ArriRawMetadata.metadata.camera.recording.projectRate.timebase;
	UE_LOG(LogArri, Verbose, TEXT("Framerate: %f"), ArriRawMetadata.metadata.camera.recording.projectRate.timebase);
	unrealMetadata.dropFrame = ArriRawMetadata.metadata.camera.recording.projectRate.dropframe;
	UE_LOG(LogArri, Verbose, TEXT("dropframe: %s"), *FString(unrealMetadata.dropFrame ? TEXT("true") : TEXT("false")));
	unrealMetadata.frameNumber.FrameNumber = ArriRawMetadata.metadata.camera.recording.acquisitionTimestamp;
	UE_LOG(LogArri, Verbose, TEXT("Framenumber: %d"), unrealMetadata.frameNumber.FrameNumber.Value);
	unrealMetadata.clipNumber = ArriRawMetadata.metadata.camera.recording.clip.clipInfo.clipNumber;
	UE_LOG(LogArri, Verbose, TEXT("Clip number: %d"), unrealMetadata.clipNumber);
	unrealMetadata.cameraIndex = ArriRawMetadata.metadata.camera.recording.clip.slateInfo.cameraIndex;
	UE_LOG(LogArri, Verbose, TEXT("Clip number: %s"), *unrealMetadata.cameraIndex);
	unrealMetadata.cct = ArriRawMetadata.metadata.camera.processing.whitebalance.cct;
	UE_LOG(LogArri, Verbose, TEXT("Color Temperature: %d"), unrealMetadata.cct);
	unrealMetadata.tint = ArriRawMetadata.metadata.camera.processing.whitebalance.tint;
	UE_LOG(LogArri, Verbose, TEXT("Tint: %f"), unrealMetadata.tint);
	unrealMetadata.tilt = ArriRawMetadata.metadata.camera.positional.orientation.tilt;
	UE_LOG(LogArri, Verbose, TEXT("Tilt: %f"), unrealMetadata.tilt);
	unrealMetadata.roll = ArriRawMetadata.metadata.camera.positional.orientation.roll;
	UE_LOG(LogArri, Verbose, TEXT("Roll: %f"), unrealMetadata.roll);
	return unrealMetadata;
}

void UArriDevice::shutdownDevice()
{
	Stop();
	if (mThread != nullptr)
	{
		mThread->WaitForCompletion();
		delete mThread;
		mThread = nullptr;
	}
	if (mSocket != nullptr)
	{
		mSocket->Close();
		mSocket = nullptr;
		UE_LOG(LogArri, Log, TEXT("Socket closed"));
	}
	mBufferMap.Empty();
	mStopping = false;
}

FArriMetadata UArriDevice::getMetadata()
{
	return mMetadata;
}