// (CVN) - Candy Valley Network GmbH

#pragma once

// EXTERNAL INCLUDES
#include <Containers/Ticker.h>
#include <Modules/ModuleInterface.h>
#include <Modules/ModuleManager.h>
#include <Runtime/Launch/Resources/Version.h>

#include "LovenseTypes.h"

// INTERNAL INCLUDES


DECLARE_LOG_CATEGORY_EXTERN(LogLovenseIntegration, All, All)


/**
 * @brief Lovense Integration Module class. Use FLovenseIntegrationModule::Get() to easily get the module instance.
 */
class FLovenseIntegrationModule : public IModuleInterface {
public:

	/** @brief Static pointer to the module instance used to make sure the lovense manager is only created once. */
	static FLovenseIntegrationModule* moduleInstance;

	// IModuleInterface
	virtual void StartupModule() override;
	virtual void ShutdownModule() override;
	// End IModuleInterface

	/**
	 * @brief Will generate the lovense config file and automatically start the integration if "StartWithLovenseActive" config is true.
	 * \n Usually gets called automatically in StartupModule(), but you can set the cvar "li.Initialization.Automatic" to false if you want to manually initialize.
	 * \n It's safe to call this even if the integration is already running.
	 */
	LOVENSEINTEGRATION_API void Initialize();

	/** @brief Registers the heartbeat ticker used to tick the timer manager which manages our various timers. */
	void StartHeartbeat();
	/** @brief Unregisters the heartbeat ticker used to tick the timer manager which manages our various timers. */
	void StopHeartbeat();

	void InitToySupport(TMap<FString, TArray<ELovenseCommandType>> typeSupportMap);

	/** @brief Get the instance of the module. Always valid. */
	FORCEINLINE static FLovenseIntegrationModule& Get() {
		// If module is already loaded, LoadModuleChecked() is basically just a map find, so not a big deal performance-wise.
		// We could consider returning our static moduleInstance instead, but this seems more reliable.
		static const FName NAME_LovenseIntegration(TEXT("LovenseIntegration"));
		return FModuleManager::LoadModuleChecked<FLovenseIntegrationModule>(NAME_LovenseIntegration);
	}

	/** @brief Get the instance of the timer manager. Only valid while the integration is running. */
	FORCEINLINE class FTimerManager* GetTimerManager() { return this->timerManager.Get(); }

	/** @brief Get the instance of the timer manager.  */
	FORCEINLINE class FTimerManager* GetTimerManagerEventsAPI() { if (this->timerManagerEventsAPI == nullptr) timerManagerEventsAPI = MakeShareable(new FTimerManager());  return this->timerManagerEventsAPI.Get(); }

	/**
	 * @brief Get the instance of the lovense manager.
	 * \n Usually you would use FLovenseManager:Get() for this, which calls this function via FLovenseIntegrationModule::Get() to ensure the module is loaded.
	 */
	FORCEINLINE class FLovenseManager* GetLovenseManager() { 
		return this->lovenseManager.Get(); }

private:
	/**
	 * @brief The heartbeat function which gets called by the ticker we register when the lovense integration is started.
	 * \n Ticks the timer manager which will update our various timers.
	 * @return If false, the ticker will be removed.
	 */
	bool HeartBeat(float deltaTime);

private:
	
	/** @brief Pointer to the timer manager used to manage our various timers. */
	TSharedPtr<class FTimerManager> timerManager = nullptr;

	/** @brief Pointer to the timer manager used to manage our various timers. */
	TSharedPtr<class FTimerManager> timerManagerEventsAPI = nullptr;

	/** @brief Delegate for the heartbeat ticker. Doesn't really need to be cached as the core ticker copies it on registration. */
	FTickerDelegate heartbeatTickerDelegate;
#if ENGINE_MAJOR_VERSION >= 5
	/** @brief Handle for the heartbeat ticker. Used to check if heartbeat is running and to unregister the ticker. */
	FTSTicker::FDelegateHandle heartbeatTickerDelegateHandle;
#else
	/** @brief Handle for the heartbeat ticker. Used to check if heartbeat is running and to unregister the ticker. */
	FDelegateHandle heartbeatTickerDelegateHandle;
#endif

	/** @brief Pointer to the lovense manager instance. */
	TSharedPtr<class FLovenseManager> lovenseManager = nullptr;
};
