// (CVN) - Candy Valley Network GmbH

#include "LovenseFunctionLibrary.h"

// EXTERNAL INCLUDES
#include <Math/UnrealMathUtility.h>
#include <Misc/ConfigCacheIni.h>

// INTERNAL INCLUDES
#include "LovenseIntegration.h"
#include "LovenseManager.h"
#include "LovenseToy.h"
#include <Containers/Array.h>
#include "LovenseTypes.h"
#include <Serialization/JsonReader.h>
#include <Serialization/JsonSerializer.h>


ULovenseFunctionLibrary::ULovenseFunctionLibrary(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer) {}

void ULovenseFunctionLibrary::Initialize() {
	FLovenseIntegrationModule::Get().Initialize();
}

void ULovenseFunctionLibrary::StartLovense() {
	FLovenseManager::Get()->StartLovense();
}

void ULovenseFunctionLibrary::StopLovense() {
	FLovenseManager::Get()->StopLovense();
}

void ULovenseFunctionLibrary::UpdateAdapters() {
	FLovenseManager::Get()->UpdateAdapters();
}

void ULovenseFunctionLibrary::UpdateToys() {
	FLovenseManager::Get()->UpdateToys();
}

bool ULovenseFunctionLibrary::IsLovenseRunning() {
	return FLovenseManager::Get()->IsLovenseRunning();
}

bool ULovenseFunctionLibrary::IsUpdatingAdapters() {
	return FLovenseManager::Get()->IsUpdatingAdapters();
}

bool ULovenseFunctionLibrary::IsUpdatingToys() {
	return FLovenseManager::Get()->IsUpdatingToys();
}

const TArray<class ULovenseToy*>& ULovenseFunctionLibrary::GetToys() {
	return FLovenseManager::Get()->GetToys();
}

void ULovenseFunctionLibrary::ClearAllCommands(ULovenseToy* toy) {
	FLovenseManager::Get()->ClearAllCommands(toy);
}

bool ULovenseFunctionLibrary::SendCommands(
	TArray<class UELovenseCommands*> commands,
	ULovenseToy* toy,
	float time,
	float loopRunningSec,
	float loopPauseSec,
	FOnLovenseResponseDynamic callback
) {
	return FLovenseManager::Get()->SendCommands(
		commands,
		toy,
		time,
		loopRunningSec,
		loopPauseSec,
		FOnLovenseResponse::CreateWeakLambda(toy, [toy, callback](int32 responseValue) ->void { callback.ExecuteIfBound(toy, responseValue); })
	);
}

bool ULovenseFunctionLibrary::SendPosition(
	int position,
	ULovenseToy* toy,
	FOnLovenseResponseDynamic callback
) {
	return FLovenseManager::Get()->SendPosition(
		position,
		toy,
		FOnLovenseResponse::CreateWeakLambda(toy, [toy, callback](int32 responseValue) ->void { callback.ExecuteIfBound(toy, responseValue); })
	);
}



bool ULovenseFunctionLibrary::SetUpPatternV2(
	TArray<class UEPositionPatternValue*> positionValues,
	ULovenseToy* toy,
	FOnLovenseResponseDynamic callback
) {
	return FLovenseManager::Get()->SetUpPatternV2(
		positionValues,
		toy,
		FOnLovenseResponse::CreateWeakLambda(toy, [toy, callback](int32 responseValue) ->void { callback.ExecuteIfBound(toy, responseValue); })
	);
}

bool ULovenseFunctionLibrary::StartPatternV2(
	ULovenseToy* toy,
	int startTime,
	int offstTime,
	FOnLovenseResponseDynamic callback
) {
	return FLovenseManager::Get()->StartPatternV2(
		toy,
		startTime,
		offstTime,
		FOnLovenseResponse::CreateWeakLambda(toy, [toy, callback](int32 responseValue) ->void { callback.ExecuteIfBound(toy, responseValue); })
	);
}

bool ULovenseFunctionLibrary::StopPatternV2(
	ULovenseToy* toy,
	FOnLovenseResponseDynamic callback
) {
	return FLovenseManager::Get()->StopPatternV2(
		toy,
		FOnLovenseResponse::CreateWeakLambda(toy, [toy, callback](int32 responseValue) ->void { callback.ExecuteIfBound(toy, responseValue); })
	);
}

bool ULovenseFunctionLibrary::GetPatternV2SyncTime(
	ULovenseToy* toy,
	FOnGetSyncTime callback
) {
	double startTime = FPlatformTime::Seconds();
	UE_LOG(LogLovenseIntegration, Log, TEXT("####################  start time %f #######################"),startTime);

	return FLovenseManager::Get()->GetPatternV2SyncTime(
		toy,
		FOnLovenseResponse::CreateWeakLambda(toy, [startTime,callback](int32 responseValue) ->void { if (responseValue == 200) {
		double endTime = FPlatformTime::Seconds(); 
		UE_LOG(LogLovenseIntegration, Log, TEXT("####################  end time %f #######################"), endTime);
		UE_LOG(LogLovenseIntegration, Log, TEXT("####################  end time %d #######################"), (int32)((endTime - startTime) * 1000));

		callback.ExecuteIfBound((int32)((endTime-startTime)*1000));
	}})
	);
}

bool ULovenseFunctionLibrary::SendCommand_Preset(
	class ULovenseToy* toy,
	const FString& name,
	float timeSec, FOnLovenseResponseDynamic callback
) {
	if (timeSec < 0) {
		timeSec = 0;
	}
	return FLovenseManager::Get()->SendCommand_Preset(toy, name,timeSec, 
		FOnLovenseResponse::CreateWeakLambda(toy, [toy, callback](int32 responseValue) ->void { callback.ExecuteIfBound(toy, responseValue); }));
}

bool ULovenseFunctionLibrary::SendCommand_Pattern(
	class ULovenseToy* toy,
	const TArray<int32>& pattern,
	FOnLovenseResponseDynamic callback,
	bool bVibrate,
	bool bRotate,
	bool bPump,
	bool bThrust,
	bool bSuck,
	bool bFinger,
	bool bDepth,
	bool bOscillate,
	int32 interval,
	float time

) {
	if (pattern.Num() <= 0) return false;

	FLovensePattern lovensePattern = FLovensePattern();
	lovensePattern.bVibrate = bVibrate;
	lovensePattern.bRotate = bRotate;
	lovensePattern.bPump = bPump;
	lovensePattern.bThrust = bThrust;
	lovensePattern.bSuck = bSuck;
	lovensePattern.bFinger = bFinger;
	lovensePattern.bDepth = bDepth;
	lovensePattern.bOscillate = bOscillate;
	// Clamp to the minimum supported interval.
	lovensePattern.interval = FMath::Max(100, interval);
	// Only carry over the first 50 values of the pattern in case the caller passes in more, which is not supported by the Lovense API.
	lovensePattern.pattern.Append(pattern.GetData(), FMath::Min(pattern.Num(), 50));
	// Clamp to the minimum supported time.
	lovensePattern.time = time;
	return FLovenseManager::Get()->SendCommand_Pattern(toy, lovensePattern, FOnLovenseResponse::CreateWeakLambda(toy, [toy, callback](int32 responseValue) ->void { callback.ExecuteIfBound(toy, responseValue); }));
}

bool ULovenseFunctionLibrary::SendCommand_Stop(
	ULovenseToy* toy, FOnLovenseResponseDynamic callback
	
) {
	return FLovenseManager::Get()->SendCommand_Stop(toy, FOnLovenseResponse::CreateWeakLambda(toy, [toy, callback](int32 responseValue) ->void { callback.ExecuteIfBound(toy, responseValue); }));
}

ULovenseEvents* ULovenseFunctionLibrary::GetLovenseEvents() {
	return FLovenseManager::Get()->GetLovenseEvents();
}

bool ULovenseFunctionLibrary::GetStartWithLovenseActive() {
#if WITH_LOVENSE
	bool bStartWithLovenseActive = false;
	bool bResult = GConfig->GetBool(
		*LovenseIntegration::configSectionName,
		*LovenseIntegration::configStartWithLovenseActiveKeyName,
		bStartWithLovenseActive,
		FString::Printf(TEXT("%s%s.ini"), *FPaths::GeneratedConfigDir(), *LovenseIntegration::configFileName)
	);
	return bResult ? bStartWithLovenseActive : false;
#else
	return false;
#endif
}

FString ULovenseFunctionLibrary::GetDeviceIpOverride() {
#if WITH_LOVENSE
	FString deviceIpOverride = FString();
	bool bResult = GConfig->GetString(
		*LovenseIntegration::configSectionName,
		*LovenseIntegration::configDeviceIpOverrideKeyName,
		deviceIpOverride,
		FString::Printf(TEXT("%s%s.ini"), *FPaths::GeneratedConfigDir(), *LovenseIntegration::configFileName)
	);
	return bResult && FLovenseManager::Get()->IsIPStringValid(deviceIpOverride) ? deviceIpOverride : TEXT("");
#else
	return TEXT("");
#endif
}

bool ULovenseFunctionLibrary::GetUseInputDevice() {
#if WITH_LOVENSE
	return FLovenseManager::Get()->GetUseInputDevice();
#else
	return false;
#endif
}



bool ULovenseFunctionLibrary::GetUseHttps() {
#if WITH_LOVENSE
	return FLovenseManager::Get()->GetUseHttps();
#else
	return false;
#endif
}


FString ULovenseFunctionLibrary::GetDevicePortOverride() {
#if WITH_LOVENSE
	FString devicePortOverride = FString();
	bool bResult = GConfig->GetString(
		*LovenseIntegration::configSectionName,
		*LovenseIntegration::configDevicePortOverrideKeyName,
		devicePortOverride,
		FString::Printf(TEXT("%s%s.ini"), *FPaths::GeneratedConfigDir(), *LovenseIntegration::configFileName)
	);
	return bResult && FLovenseManager::Get()->IsPortStringValid(devicePortOverride) ? devicePortOverride : TEXT("");
#else
	return TEXT("");
#endif
}

int32 ULovenseFunctionLibrary::GetToyDelay() {
	return FLovenseManager::Get()->GetToyDelay();
}

float ULovenseFunctionLibrary::GetToyStrengthMultiplier() {
	return FLovenseManager::Get()->GetToyStrengthMultiplier();
}


bool ULovenseFunctionLibrary::IsIPStringValid(const FString& ipString) {
	return FLovenseManager::Get()->IsIPStringValid(ipString);
}

bool ULovenseFunctionLibrary::IsPortStringValid(const FString& portString) {
	return FLovenseManager::Get()->IsPortStringValid(portString);
}

void ULovenseFunctionLibrary::SetStartWithLovenseActive(bool bStartActive) {
#if WITH_LOVENSE
	FString configFilePath = FString::Printf(TEXT("%s%s.ini"), *FPaths::GeneratedConfigDir(), *LovenseIntegration::configFileName);
	GConfig->SetBool(*LovenseIntegration::configSectionName, *LovenseIntegration::configStartWithLovenseActiveKeyName, bStartActive, configFilePath);
	GConfig->Flush(false, configFilePath);
#endif
}

void ULovenseFunctionLibrary::SetDeviceIpOverride(const FString& ipOverride) {
#if WITH_LOVENSE
	FString configFilePath = FString::Printf(TEXT("%s%s.ini"), *FPaths::GeneratedConfigDir(), *LovenseIntegration::configFileName);
	GConfig->SetString(*LovenseIntegration::configSectionName, *LovenseIntegration::configDeviceIpOverrideKeyName, *ipOverride, configFilePath);
	GConfig->Flush(false, configFilePath);
	FLovenseManager::Get()->SetIpOverride(ipOverride);
	FLovenseManager::Get()->ResetSearch();
#endif
}

void ULovenseFunctionLibrary::SetDevicePortOverride(const FString& portOverride) {
#if WITH_LOVENSE
	FString configFilePath = FString::Printf(TEXT("%s%s.ini"), *FPaths::GeneratedConfigDir(), *LovenseIntegration::configFileName);
	GConfig->SetString(*LovenseIntegration::configSectionName, *LovenseIntegration::configDevicePortOverrideKeyName, *portOverride, configFilePath);
	GConfig->Flush(false, configFilePath);
	FLovenseManager::Get()->SetPortOverride(portOverride);

	FLovenseManager::Get()->ResetSearch();
#endif
}

void ULovenseFunctionLibrary::SetToyDelay(int32 value) {
#if WITH_LOVENSE
	// We don't clamp the value here as the config value gets clamped when it is read in FLovenseManager::StartLovense().
	// But as the value gets cached in the Lovense Manager below, the caller should make sure the value is valid.
	FString configFilePath = FString::Printf(TEXT("%s%s.ini"), *FPaths::GeneratedConfigDir(), *LovenseIntegration::configFileName);
	GConfig->SetInt(*LovenseIntegration::configSectionName, *LovenseIntegration::configToyDelayKeyName, value, configFilePath);
	GConfig->Flush(false, configFilePath);
	FLovenseManager::Get()->SetToyDelay(value);
#endif
}

void ULovenseFunctionLibrary::SetToyStrengthMultiplier(float value) {
#if WITH_LOVENSE
	// We don't clamp the value here as the config value gets clamped when it is read in FLovenseManager::StartLovense().
	// But as the value gets cached in the Lovense Manager below, the caller should make sure the value is valid.
	FString configFilePath = FString::Printf(TEXT("%s%s.ini"), *FPaths::GeneratedConfigDir(), *LovenseIntegration::configFileName);
	GConfig->SetFloat(*LovenseIntegration::configSectionName, *LovenseIntegration::configToyStrengthMultiplierKeyName, value, configFilePath);
	GConfig->Flush(false, configFilePath);
	FLovenseManager::Get()->SetToyStrengthMultiplier(value);
#endif
}

void ULovenseFunctionLibrary::SetUseInputDevice(bool value) {
#if WITH_LOVENSE
	bool oldValue = GetUseInputDevice();
	if (value == oldValue) {
		return;
	}
	// We don't clamp the value here as the config value gets clamped when it is read in FLovenseManager::StartLovense().
	// But as the value gets cached in the Lovense Manager below, the caller should make sure the value is valid.
	FString configFilePath = FString::Printf(TEXT("%s%s.ini"), *FPaths::GeneratedConfigDir(), *LovenseIntegration::configFileName);
	GConfig->SetBool(*LovenseIntegration::configSectionName, *LovenseIntegration::configUseInputDevice, value, configFilePath);
	GConfig->Flush(false, configFilePath);
	FLovenseManager::Get()->SetUseInputDevice(value);
	FLovenseManager::Get()->ResetSearch();

#endif
}

void ULovenseFunctionLibrary::RestartSearch() {
#if WITH_LOVENSE
	FLovenseManager::Get()->ResetSearch();
#endif
}

void ULovenseFunctionLibrary::SetUseHttps(bool bUseHttps) {
#if WITH_LOVENSE
	bool oldValue = GetUseHttps();
	if (bUseHttps == oldValue) {
		return;
	}
	// We don't clamp the value here as the config value gets clamped when it is read in FLovenseManager::StartLovense().
	// But as the value gets cached in the Lovense Manager below, the caller should make sure the value is valid.
	FString configFilePath = FString::Printf(TEXT("%s%s.ini"), *FPaths::GeneratedConfigDir(), *LovenseIntegration::configFileName);
	GConfig->SetBool(*LovenseIntegration::configSectionName, *LovenseIntegration::configUseHttps, bUseHttps, configFilePath);
	GConfig->Flush(false, configFilePath);
	FLovenseManager::Get()->SetUseHttps(bUseHttps);
	FLovenseManager::Get()->ResetSearch();

#endif
}


void ULovenseFunctionLibrary::StartEventsAPI(const FString ip,int32 port, FOnEventCode resultCodeCallback, FOnGetLovenseEventToys toysCallback) {
#if WITH_LOVENSE
	FLovenseManager::Get()->StartEventsAPI(ip, port, static_cast<FOnEventCode&>(resultCodeCallback), static_cast<FOnGetLovenseEventToys&>(toysCallback));
#endif
}

void ULovenseFunctionLibrary::SetConnectEventCallback(const FString ip, FOnLovenseConnectStatusEvent connectCallback) {
#if WITH_LOVENSE
	FLovenseManager::Get()->SetConnectEventCallback(ip, static_cast<FOnLovenseConnectStatusEvent&>(connectCallback));
#endif
}

void ULovenseFunctionLibrary::SetBatteryEventCallback(const FString ip, FOnLovenseBatteryEvent batteryCallback) {
#if WITH_LOVENSE
	FLovenseManager::Get()->SetBatteryEventCallback(ip, static_cast<FOnLovenseBatteryEvent&>(batteryCallback));
#endif
}

void ULovenseFunctionLibrary::SetFunctionsValueEventCallback(const FString ip, FOnFunctionsEvent functionsValueCallback) {
#if WITH_LOVENSE
	FLovenseManager::Get()->SetFunctionsValueEventCallback(ip, static_cast<FOnFunctionsEvent&>(functionsValueCallback));
#endif
}

void ULovenseFunctionLibrary::SetEveryShakeEventCallback(const FString ip, FOnEveryShakeEvent everyShakeCallback) {
#if WITH_LOVENSE
	FLovenseManager::Get()->SetEveryShakeEventCallback(ip, static_cast<FOnEveryShakeEvent&>(everyShakeCallback));
#endif
}

void ULovenseFunctionLibrary::SetButtonEventCallback(const FString ip, FOnButtonEvent buttonCallback) {
#if WITH_LOVENSE
	FLovenseManager::Get()->SetButtonEventCallback(ip, static_cast<FOnButtonEvent&>(buttonCallback));
#endif
}

void ULovenseFunctionLibrary::SetMotionChangedEventCallback(const FString ip, FOnMotionChangedEvent motionCallback) {
#if WITH_LOVENSE
	FLovenseManager::Get()->SetMotionChangedEventCallback(ip, static_cast<FOnMotionChangedEvent&>(motionCallback));
#endif
}


void ULovenseFunctionLibrary::StopEventsAPI(const FString ip) {
#if WITH_LOVENSE
	FLovenseManager::Get()->StopEventsAPI(ip);
#endif
}

bool ULovenseFunctionLibrary::ToyIsSupportThisCommand(const FString toyType, const ELovenseCommandType commandType) {
#if WITH_LOVENSE
	return FLovenseManager::Get()->ToyIsSupportThisCommand(toyType, commandType);
#endif
}

TArray<ELovenseCommandType> ULovenseFunctionLibrary::GetSupportCommandsByType(const FString toyType) {
#if WITH_LOVENSE
	return FLovenseManager::Get()->GetSupportCommandsByType(toyType);
#endif
}

void ULovenseFunctionLibrary::InitSupportByDataTable(UDataTable * LovenseSupportDataTable) {
#if WITH_LOVENSE
	if (LovenseSupportDataTable == nullptr) {
		return;
	}
	TMap<FString, TArray<ELovenseCommandType>> typeSupportMap;
	FString contextString = TEXT("GET");
	TArray<FName> _rowNames = LovenseSupportDataTable->GetRowNames();
	for (auto _data : _rowNames) {
		FSupportStruct* support = LovenseSupportDataTable->FindRow<FSupportStruct>(_data, contextString, false);
		TArray<ELovenseCommandType> supportCommands;
		TSharedPtr<FJsonObject> jsonObject = MakeShareable(new FJsonObject());
		TSharedRef<TJsonReader<TCHAR>> jsonReader = TJsonReaderFactory<TCHAR>::Create(support->func);
		FJsonSerializer::Deserialize(jsonReader, jsonObject);

		if (jsonObject->HasField(TEXT("v"))) {
			bool enable = jsonObject->GetBoolField(TEXT("v"));
			if (enable) {
				supportCommands.Add(ELovenseCommandType::VIBRATE);
			}
		}
		if (jsonObject->HasField(TEXT("v1"))) {
			bool enable = jsonObject->GetBoolField(TEXT("v1"));
			if (enable) {
				supportCommands.Add(ELovenseCommandType::VIBRATE1);
			}
		}
		if (jsonObject->HasField(TEXT("v2"))) {
			bool enable = jsonObject->GetBoolField(TEXT("v2"));
			if (enable) {
				supportCommands.Add(ELovenseCommandType::VIBRATE2);
			}
		}
		if (jsonObject->HasField(TEXT("v3"))) {
			bool enable = jsonObject->GetBoolField(TEXT("v3"));
			if (enable) {
				supportCommands.Add(ELovenseCommandType::VIBRATE3);
			}
		}
		if (jsonObject->HasField(TEXT("r"))) {
			bool enable = jsonObject->GetBoolField(TEXT("r"));
			if (enable) {
				supportCommands.Add(ELovenseCommandType::ROTATE);
			}
		}
		if (jsonObject->HasField(TEXT("p"))) {
			bool enable = jsonObject->GetBoolField(TEXT("p"));
			if (enable) {
				supportCommands.Add(ELovenseCommandType::PUMP);
			}
		}
		if (jsonObject->HasField(TEXT("s"))) {
			bool enable = jsonObject->GetBoolField(TEXT("s"));
			if (enable) {
				supportCommands.Add(ELovenseCommandType::SUCTION);
			}
		}
		if (jsonObject->HasField(TEXT("t"))) {
			bool enable = jsonObject->GetBoolField(TEXT("t"));
			if (enable) {
				supportCommands.Add(ELovenseCommandType::THRUST);
			}
		}
		if (jsonObject->HasField(TEXT("d"))) {
			bool enable = jsonObject->GetBoolField(TEXT("d"));
			if (enable) {
				supportCommands.Add(ELovenseCommandType::DEPTH);
			}
		}
		if (jsonObject->HasField(TEXT("pos"))) {
			bool enable = jsonObject->GetBoolField(TEXT("pos"));
			if (enable) {
				supportCommands.Add(ELovenseCommandType::POSITION);
			}
		}
		if (jsonObject->HasField(TEXT("o"))) {
			bool enable = jsonObject->GetBoolField(TEXT("o"));
			if (enable) {
				supportCommands.Add(ELovenseCommandType::OSCILLATE);
			}
		}
		FString supportType = support->type.ToLower().Replace(TEXT(" "), TEXT(""));
		if (supportType != TEXT("version")) {
			if (!typeSupportMap.Contains(supportType)) {
				typeSupportMap.Add(supportType, supportCommands);
			}
		}
	}
	FLovenseIntegrationModule::Get().InitToySupport(typeSupportMap);
#endif
}

void ULovenseFunctionLibrary::SetErrorEventCallback(FOnLovenseErrorResponse errorCallback) {
	FLovenseManager::Get()->SetErrorEventCallback(static_cast<FOnLovenseErrorResponse&>(errorCallback));
}

