// Discord Rich Presence - plugin made by Mathew Simon.

#include "DiscordRichPresenceSubsystem.h"

#include "DiscordIpcConnection.h"
#include "DiscordRichPresenceModule.h"
#include "DiscordRichPresenceSettings.h"

#include "Engine/Engine.h"
#include "Engine/GameInstance.h"

bool UDiscordRichPresenceSubsystem::ShouldCreateSubsystem(UObject* Outer) const
{
	if (!Super::ShouldCreateSubsystem(Outer))
	{
		return false;
	}

	// Rien a afficher pour un serveur dedie ou un commandlet.
	if (IsRunningDedicatedServer() || IsRunningCommandlet())
	{
		return false;
	}

#if PLATFORM_WINDOWS
	return true;
#else
	return false;
#endif
}

void UDiscordRichPresenceSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
	Super::Initialize(Collection);

	const UDiscordRichPresenceSettings* Settings = GetDefault<UDiscordRichPresenceSettings>();

	if (!Settings->bEnabled)
	{
		return;
	}

	if (Settings->ClientId.IsEmpty())
	{
		UE_LOG(LogDiscordRichPresence, Warning,
			TEXT("Aucun Client ID : renseigne-le dans Project Settings > Plugins > Discord Rich Presence."));
		return;
	}

#if WITH_EDITOR
	if (GIsEditor && !Settings->bEnableInEditor)
	{
		return;
	}
#endif

	CurrentActivity = Settings->DefaultActivity;
	StartUnixTime = FDateTime::UtcNow().ToUnixTimestamp();
	bActivityDirty = true;
	bActivityCleared = false;

	Connection = MakeShared<FDiscordIpcConnection>();
	TryConnect();

	// 1 s suffit largement : les envois reels sont throttles plus bas.
	TickerHandle = FTSTicker::GetCoreTicker().AddTicker(
		FTickerDelegate::CreateUObject(this, &UDiscordRichPresenceSubsystem::Tick), 1.0f);
}

void UDiscordRichPresenceSubsystem::Deinitialize()
{
	if (TickerHandle.IsValid())
	{
		FTSTicker::GetCoreTicker().RemoveTicker(TickerHandle);
		TickerHandle.Reset();
	}

	if (Connection.IsValid())
	{
		Connection->ClearActivity();
		Connection->Disconnect();
		Connection.Reset();
	}

	Super::Deinitialize();
}

UDiscordRichPresenceSubsystem* UDiscordRichPresenceSubsystem::Get(const UObject* WorldContextObject)
{
	if (const UWorld* World = GEngine ? GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull) : nullptr)
	{
		if (UGameInstance* GameInstance = World->GetGameInstance())
		{
			return GameInstance->GetSubsystem<UDiscordRichPresenceSubsystem>();
		}
	}

	return nullptr;
}

bool UDiscordRichPresenceSubsystem::IsConnectedToDiscord() const
{
	return Connection.IsValid() && Connection->IsConnected() && Connection->IsReady();
}

void UDiscordRichPresenceSubsystem::TryConnect()
{
	if (!Connection.IsValid())
	{
		return;
	}

	LastConnectAttemptTime = FPlatformTime::Seconds();

	if (Connection->Connect(GetDefault<UDiscordRichPresenceSettings>()->ClientId))
	{
		// La presence sera poussee des que Discord aura repondu READY.
		bActivityDirty = !bActivityCleared;
	}
}

void UDiscordRichPresenceSubsystem::SetActivity(const FDiscordRichPresenceActivity& NewActivity)
{
	if (CurrentActivity == NewActivity && !bActivityCleared)
	{
		return;
	}

	CurrentActivity = NewActivity;
	bActivityCleared = false;
	bActivityDirty = true;
}

void UDiscordRichPresenceSubsystem::SetDetailsAndState(const FString& Details, const FString& State)
{
	FDiscordRichPresenceActivity Updated = CurrentActivity;
	Updated.Details = Details;
	Updated.State = State;
	SetActivity(Updated);
}

void UDiscordRichPresenceSubsystem::SetParty(int32 PartySize, int32 PartyMax)
{
	FDiscordRichPresenceActivity Updated = CurrentActivity;
	Updated.PartySize = FMath::Max(0, PartySize);
	Updated.PartyMax = FMath::Max(0, PartyMax);
	SetActivity(Updated);
}

void UDiscordRichPresenceSubsystem::ResetElapsedTime()
{
	StartUnixTime = FDateTime::UtcNow().ToUnixTimestamp();
	bActivityDirty = true;
}

void UDiscordRichPresenceSubsystem::ClearActivity()
{
	bActivityCleared = true;
	bActivityDirty = false;

	if (Connection.IsValid())
	{
		Connection->ClearActivity();
	}
}

bool UDiscordRichPresenceSubsystem::Tick(float DeltaTime)
{
	if (!Connection.IsValid())
	{
		return true;
	}

	const UDiscordRichPresenceSettings* Settings = GetDefault<UDiscordRichPresenceSettings>();
	const double Now = FPlatformTime::Seconds();

	if (!Connection->IsConnected())
	{
		// Discord n'est pas lance, ou a ete ferme : on retente calmement.
		if (Now - LastConnectAttemptTime >= Settings->ReconnectIntervalSeconds)
		{
			TryConnect();
		}
		return true;
	}

	// Traite READY / PING / erreurs. Peut fermer la connexion.
	if (!Connection->Poll() || !Connection->IsReady())
	{
		return true;
	}

	if (bActivityDirty
		&& !bActivityCleared
		&& (LastSendTime == 0.0 || Now - LastSendTime >= Settings->MinUpdateIntervalSeconds))
	{
		if (Connection->SendActivity(CurrentActivity, StartUnixTime))
		{
			bActivityDirty = false;
			LastSendTime = Now;
		}
	}

	return true;
}
