// (CVN) - Candy Valley Network GmbH

#pragma once

#include <CoreMinimal.h>

// EXTERNAL INCLUDES

// INTERNAL INCLUDES
#include "LovenseEvents.h"
#include "LovenseTypes.h"
#include "LovenseToy.h"
#include <Map.h>
#include "LovenseToyEventsWebSocketHandler.h"


/**
 * @brief This class defines the plugin interface. Use FLovenseManager::Get() to get the global manager instance.
 *
 *
 * ~~~
 * UpdatedAdapters() sequence:
 * 
 * +-----------+            +-----------------+                                         +-----------------+                                       +-----------------+ +-------------+
 * |    Any    |            | FLovenseManager |                                         | ILovenseAdapter |                                       | Lovense Servers | | Lovense App |
 * +-----------+            +-----------------+                                         +-----------------+                                       +-----------------+ +-------------+
 *       |                           |                                                           |                                                         |                 |
 *       | Call UpdateAdapters()     |                                                           |                                                         |                 |
 *       |-------------------------->|                                                           |                                                         |                 |
 *       |                           |                                                           |                                                         |                 |
 *       |                           | Call TryGetAdapterData()                                  |                                                         |                 |
 *       |                           |---------------------------------------------------------->|                                                         |                 |
 *       |                           |                                                           |                                                         |                 |
 *       |                           |                                                           | Poll "https://api.lovense-api.com/api/lan/v2/app"       |                 |
 *       |                           |                                                           |-------------------------------------------------------->|                 |
 *       |                           |                                                           |                                                         |                 |
 *       |                           |                                                           |                                                Response |                 |
 *       |                           |                                                           |<--------------------------------------------------------|                 |
 *       |                           |                                                           | -----------------\                                      |                 |
 *       |                           |                                                           |-| Parse response |                                      |                 |
 *       |                           |                                                           | |----------------|                                      |                 |
 *       |                           |                                                           |                                                         |                 |
 *       |                           |                                                  callback |                                                         |                 |
 *       |                           |<----------------------------------------------------------|                                                         |                 |
 *       |                           | -------------------------\                                |                                                         |                 |
 *       |                           |-| Adapter initialization |                                |                                                         |                 |
 *       |                           | |------------------------|                                |                                                         |                 |
 *       |                           |                                                           |                                                         |                 |
 *       |                           | For each created adapter -> Call GetToys()                |                                                         |                 |
 *       |                           |---------------------------------------------------------->|                                                         |                 |
 *       |                           |                                                           |                                                         |                 |
 *       |                           |                                                           | Request GetToys                                         |                 |
 *       |                           |                                                           |-------------------------------------------------------------------------->|
 *       |                           |                                                           |                                                         |                 |
 *       |                           |                                                           |                                                         |        Response |
 *       |                           |                                                           |<--------------------------------------------------------------------------|
 *       |                           |                                                           | ---------------------\                                  |                 |
 *       |                           |                                                           |-| Toy initialization |                                  |                 |
 *       |                           |                                                           | |--------------------|                                  |                 |
 *       |                           |                                                           |                                                         |                 |
 *       |                           |                                                  callback |                                                         |                 |
 *       |                           |<----------------------------------------------------------|                                                         |                 |
 *       |                           | -------------------------------------------------\        |                                                         |                 |
 *       |                           |-| Once all adapters received GetToys response,   |        |                                                         |                 |
 *       |                           | | shutdown active adapters and copy new adapters |        |                                                         |                 |
 *       |                           | |------------------------------------------------|        |                                                         |                 |
 * ~~~
 *
 * ~~~
 * Update adapters timer loop (when a command fails, see FLovenseManager::OnCommandFailed()):
 * 
 *                                          OnCommandFailed()
 *                                 [Reset updateAdaptersTimer interval]
 *                                                 |
 *                                                 V
 *                                       [Call UpdateAdapters()]  
 *                                                 |
 *                                                 V
 *                         +------- yes --- <Update failed?> --- no -------+
 *                         |                                               |
 *                         V                                               |
 * +--------> [Start updateAdaptersTimer]                                  |
 * |                       |                                               |
 * |                       V                                               |
 * |        [OnUpdateAdaptersTimerFinished()]                              |
 * |                       |                                               |
 * |                       V                                               |
 * ^      [Double updateAdaptersTimer interval]                            |
 * |                       |                                               |
 * |                       V                                               |
 * |             [Call UpdateAdapters()]                                   |
 * |                       |                                               |
 * |                       V                                               |
 * +------- yes --- <Update failed?> --- no -------------------------------+
 *                                                                         |
 *                                                                         V
 *                                                         [Reset updateAdaptersTimer interval]
 *
 * The loop will run until FLovenseManager::ResetUpdateAdaptersTimer() is called.
 * ~~~
 */
class LOVENSEINTEGRATION_API FLovenseManager {
	friend class FLovenseIntegrationModule;

public:

	void SortToysArray(TArray<ULovenseToy*>& toysArray)
	{
		int n = toysArray.Num();

		for (int i = 0; i < n - 1; i++)
		{
			int minIndex = i;

			for (int j = i + 1; j < n; j++)
			{
				if (toysArray[j]->GetToyDescription().domain < toysArray[minIndex]->GetToyDescription().domain ||
					(toysArray[j]->GetToyDescription().domain == toysArray[minIndex]->GetToyDescription().domain &&
						toysArray[j]->GetToyID() < toysArray[minIndex]->GetToyID()))
				{
					minIndex = j;
				}
			}

			if (minIndex != i)
			{
				// change site
				ULovenseToy* temp = toysArray[i];
				toysArray[i] = toysArray[minIndex];
				toysArray[minIndex] = temp;
			}
		}
	}
	FLovenseManager();

	/** @brief Get the instance of the lovense manager. Always valid. */
	static FLovenseManager* Get();

	/**
	 * @brief Will start the integration. This includes:
	 * \n	1. Fetching config values.
	 * \n	2. Starting the heartbeat to update timers.
	 * \n	3. Notifying listeners that the integration has started.
	 * \n	4. Updating adapters.
	 * @note Does not check if the integration is already running, as calling this while the integration is already running is safe.
	 */
	void StartLovense();
	/**
	 * @brief Will stop the integration. This includes:
	 * \n	1. Stopping and resetting timers.
	 * \n	2. Clearing any currently running commands.
	 * \n	3. Clearing active and temporary data.
	 * \n	4. Stopping the heartbeat.
	 * \n	5. Notifying listeners that the integration has stopped.
	 * @note Does not check if the integration is already stopped, as calling this while the integration is already stopped is safe.
	 */
	void StopLovense();

	/** @brief Will fetch all available lovense adapters and update toys. See top of header for a sequence diagram. */
	void UpdateAdapters();

	/** @brief Will fetch all available toys from the currently active lovense adapters. */
	void UpdateToys();

	/**
	 * @brief Will command a single or all toys to set the speed of all functions to 0.
	 * @param toy Specifies the toy commands should be cleared on. Can be nullptr, in that case commands are cleared on all available toys.
	 */
	void ClearAllCommands(class ULovenseToy* toy = nullptr);

	/** @brief Loads the Lovense config ini from disk. Does not fetch data. */
	void ReloadLovenseConfig();

	/** @return Object holding global Lovense Integration delegates. */
	FORCEINLINE class ULovenseEvents* GetLovenseEvents() { return this->lovenseEvents.Get(); }
	/**
	 * @return All currently available toys.
	 * \n <b>Warning -</b> If you cache these, make sure to subscribe to ULovenseEvents::onLovenseUpdatedToys and re-get them, as the toy objects will have been destroyed.
	 */
	FORCEINLINE const TArray<class ULovenseToy*>& GetToys() { SortToysArray(this->toys); return this->toys; }
	/** @return Whether the integration is currently running. */
	FORCEINLINE bool IsLovenseRunning() { return this->bIsLovenseRunning; }
	/** @return Whether the lovense adapters are currently being updated. */
	FORCEINLINE bool IsUpdatingAdapters() { return this->bIsUpdatingAdapters; }
	/** @return Whether the toys are currently being updated. */
	FORCEINLINE bool IsUpdatingToys() { return this->bIsUpdatingToys; }

	/** @brief Checks if ipString is a valid IP. */
	bool IsIPStringValid(const FString& ipString, const TCHAR* delimiter = TEXT("."));
	/** @brief Checks if domainString is a valid .lovense.club domain. */
	bool IsDomainStringValid(const FString& domainString);
	/** @brief Checks if portString is a valid port. */
	bool IsPortStringValid(const FString& portString);

	bool IsPortIntValid(const int32& port);

	/** @brief Will convert an IP string to a .lovense.club domain string (e.g. "123.123.123.123" -> "123-123-123-123.lovense.club") */
	FString ConvertIPStringToDomainString(const FString& ipString);
	/** @brief Will convert a .lovense.club domain string to an IP string (e.g. "123-123-123-123.lovense.club" -> "123.123.123.123") */
	FString ConvertDomainStringToIPString(const FString& domainString);

	/** @brief Wrapper function to start a timer on the timer manager without a delegate. */
	void StartTimer(FTimerHandle& timerHandle, float timerRate, bool bIsLooped);
	/** @brief Wrapper function to start a timer on the timer manager with a non-dynamic delegate. */
	void StartTimer(FTimerHandle& timerHandle, const FTimerDelegate& delegate, float timerRate, bool bIsLooped);
	void StartTimerByEventsAPI(FTimerHandle& timerHandle, const FTimerDelegate& delegate, float timerRate, bool bIsLooped);
	/** @brief Wrapper function to start a timer on the timer manager with a dynamic delegate. */
	void StartTimer(FTimerHandle& timerHandle, const FTimerDynamicDelegate& delegate, float timerRate, bool bIsLooped);
	/** @brief Wrapper function to clear a timer on the timer manager. */
	void StopTimer(FTimerHandle& timerHandle);
	/** @brief Wrapper function to check if a timer is currently running. */
	bool IsTimerRunning(FTimerHandle timerHandle);
	/** @brief Wrapper function to check if a timer has just finished and is currently executing its delegate. */
	bool IsTimerExecuting(FTimerHandle timerHandle);

	/**
	 * @brief Will send the specified command to a single or all available toys.
	 * @note See the respective SendCommand_*() functions for more information on the respective commands.
	 * @param command The command to be sent.
	 * @param toy Specifies the toy the command is sent to. Can be nullptr, in that case the command is sent to all available toys.
	 * @param valueA Not relevant for the Test, RotateChange, AirIn, AirOut and Battery command. The speed of the function. Valid range is 0-20, 0 meaning off.
	 * @param time Only relevant for the A* commands. Time in seconds for how long the command should run. Only full seconds are supported.
	 * @param valueB Only relevant for the AVibRotate AVibAir commands.
	 * \n For AVibRotate: The speed of the rotation. Valid range is 0-20, 0 meaning off.
	 * \n For AVibAir: The speed of the air pulsation. Valid range is 0-3, 0 meaning off.
	 * @param valueC Only relevant for the AVibRotateAir command. The speed of the air pulsation. Valid range is 0-3, 0 meaning off.
	 * @param pattern Only relevant for the Pattern command. The data defining the pattern. See FLovensePattern for more information.
	 * @param callback Only relevant for the Battery command.
	 * @return Whether the command was successfully sent.
	 */
	
	bool SendCommands(
		TArray<class UELovenseCommands*> commands,
		class ULovenseToy* toy,
		float time = 0,
		float loopRunningSec = 0,
		float loopPauseSec = 0,
		FOnLovenseResponse callback = FOnLovenseResponse()
	);

	bool SendPosition(
		int position,
		class ULovenseToy* toy,
		FOnLovenseResponse callback = FOnLovenseResponse()

	);

	bool SetUpPatternV2(
		TArray<class UEPositionPatternValue*> positionValues,
		ULovenseToy* toy,
		FOnLovenseResponse callback
	);

	bool StartPatternV2(
		ULovenseToy* toy,
		int startTime,
		int offsetTime,
		FOnLovenseResponse callback
	);

	bool StopPatternV2(
		ULovenseToy* toy,
		FOnLovenseResponse callback
	);

	bool GetPatternV2SyncTime(
		ULovenseToy* toy,
		FOnLovenseResponse callback
	);

	/**
	 * @brief Makes a single or all available toys vibrate for 1.0 seconds to test connection.
	 * @note This command has a cooldown of 1.0 seconds. If this command is sent again before the cooldown is over, that call will be ignored.
	 * @param toy Specifies the toy the command is sent to. Can be nullptr, in that case the command is sent to all available toys.
	 * @return Whether the command was successfully sent.
	 */
	bool SendCommand_Test(class ULovenseToy* toy);

	/**
	 * @brief Stop all commands a single or all available toys might be executing.
	 * @return Whether the command was successfully sent.
	 */
	bool SendCommand_Stop(class ULovenseToy* toy, FOnLovenseResponse callback);

	bool SendCommand_Pattern(class ULovenseToy* toy, FLovensePattern& pattern, FOnLovenseResponse callback);

	bool SendCommand_Preset(class ULovenseToy* toy, const FString& name,float timeSec, FOnLovenseResponse callback);

	bool StartEventsAPI(FString ip, int32 port, FOnEventCode& resultCodeCallback, FOnGetLovenseEventToys& toysCallback);

	void SetConnectEventCallback(const FString ip, FOnLovenseConnectStatusEvent& connectCallback);

	void SetBatteryEventCallback(const FString ip, FOnLovenseBatteryEvent& batteryCallback);

	void SetFunctionsValueEventCallback(const FString ip, FOnFunctionsEvent& functionsValueCallback);

	void SetEveryShakeEventCallback(const FString ip, FOnEveryShakeEvent& everyShakeCallback);

	void SetButtonEventCallback(const FString ip, FOnButtonEvent& buttonCallback);
	
	void SetMotionChangedEventCallback(const FString ip, FOnMotionChangedEvent& motionChangedCallback);

	bool StopEventsAPI(const FString ip);
	/** @brief Iterates over all active adapters. */
	void InitToySupport(TMap<FString, TArray<ELovenseCommandType>> typeSupportMap);

	bool ToyIsSupportThisCommand(FString toyType, ELovenseCommandType commandType);

	TArray<ELovenseCommandType> GetSupportCommandsByType(const FString toyType);

	template<typename TCallback>
	FORCEINLINE void ForEachAdapter(TCallback callback) {
		for (TSharedPtr<class ILovenseAdapter> adapter : this->lovenseAdapters) callback(adapter);
	}

	/**
	 * @brief Called when a SendCommand_*() HTTP request or UpdateToys() failed. Will reset the Update Adapters Timer and tries to update adapters.
	 * \n See top of header for an activity diagram showing the Update Adapters Timer loop.
	 * \n The timer loop will run until ResetUpdateAdaptersTimer() is called.
	 * \n Commands can fail for these reasons (not exhaustive):
	 * \n	1. User disconnects toy(s).
	 * \n	2. User closes Lovense App.
	 * \n	3. User exits game mode/disables remote control in the Lovense App.
	 * \n	4. The device the Lovense App is running on loses network connection.
	 */
	void OnCommandFailed();

	/**
	 * @brief Get the cached "ToyDelay" config value.
	 * \n Toy delay is in milliseconds. Value range is 0-2000ms and defaults to 500ms.
	 * \n The toy delay adjusts the start time for pattern generation based on animations. (See ULovenseToyAnimationControlComponent)
	 */
	FORCEINLINE int32 GetToyDelay() { return this->toyDelay; }
	/**
	 * @brief Set the cached "ToyDelay" config value.
	 * \n Toy delay is in milliseconds. Value range is 0-2000ms and defaults to 500ms.
	 * \n The toy delay adjusts the start time for pattern generation based on animations. (See ULovenseToyAnimationControlComponent)
	 * \n <b>Warning -</b> This only sets the cached value and will not update the config! Use ULovenseFunctionLibrary::SetToyDelay() instead, which will call this.
	 */
	FORCEINLINE void SetToyDelay(int32 value) { this->toyDelay = value; }
	/**
	 * @brief Get the cached "ToyStrengthMultiplier" config value.
	 * \n Value range is 0.0f-1.0f and defaults to 1.0f.
	 * \n The toy strength multiplier scales the speed values before commands are sent.
	 */
	FORCEINLINE float GetToyStrengthMultiplier() { return this->toyStrengthMultiplier; }
	/**
	 * @brief Set the cached "ToyStrengthMultiplier" config value.
	 * \n Value range is 0.0f-1.0f and defaults to 1.0f.
	 * \n The toy strength multiplier scales the speed values before commands are sent.
	 * \n <b>Warning -</b> This only sets the cached value and will not update the config! Use ULovenseFunctionLibrary::SetToyStrengthMultiplier() instead, which will call this.
	 */
	FORCEINLINE void SetToyStrengthMultiplier(float value) { this->toyStrengthMultiplier = value; }

	/** @brief Get the global set of Command Delay Timers used when commands are broadcasted. See FLovenseCommandDelayTimers for more information. */
	FORCEINLINE const FLovenseCommandDelayTimers& GetCommandDelayTimers() { return this->commandDelayTimers; }

private:
	/** @brief Called by the lovense integration module in StartupModule() after the manager is created. Will create the lovense events object instance. */
	void Initialize();
	/** @brief Called by the lovense integration module in ShutdownModule() after the manager is created. Will destroy the lovense events object instance. */
	void Shutdown();

	/**
	 * @brief Initializes the passed in adapter with the passed in adapter description and calls GetToys() on the adapter.
	 * @note The GetToys() callback handling is part of this method.
	 */
	void InitializeAdapter(TSharedPtr<class ILovenseAdapter> adapter, FLovenseAdapterDescription& adapterDescription);
	/**
	 * @brief Filters adapters in adaptersResponseData if an IP override is specified and adds offline adapters.
	 * \n Offline adapters have a deviceId in this format: "[Api]_Offline[Platform]" e.g. "Remote_OfflineMobile" or "Connect_OfflineDesktop".
	 * \n They also have a straight IP string as platform instead of the .lovense.club link returned by the Lovense servers.
	 */
	void FilterAdaptersAndAddOfflineAdapters();
	/**
	 * @brief Shuts down any adapter that is not found in adaptersResponseData.
	 * \n This is part of an earlier implementation of the integration and is strictly speaking not necessary anymore as we recreate the active adapters on update.
	 * \n A possible benefit of keeping it is that any adapter that has become invalid will be shut down early in the update rather than when the update is finished.
	 */
	void PruneAdapters();
	/**
	 * @brief Recreates adapters and polls for toys to update toy information.
	 * \n We have this internal implementation as UpdateToys() checks for bIsUpdatingAdapters, but we use this method internally during adapter update.
	 */
	void UpdateToys_Internal();

	/**
	 * @brief Clears temporary adapter and toy data used during UpdateAdapters() and UpdateToys().
	 * @param bClearCurrentData If true, will clear active adapter and toy data as well.
	 */
	void ClearData(bool bClearCurrentData);

	/**
	 * @brief Event called when the Update Adapters timer has finished.
	 * \n Will double the timer interval and restart it and will also call UpdateAdapters().
	 * \n See top of header for an activity diagram showing the Update Adapters Timer loop.
	 * \n The timer loop will run until ResetUpdateAdaptersTimer() is called.
	 */
	void OnUpdateAdaptersTimerFinished();
	/**
	 * @brief Starts the Update Adapters timer with the current timer interval.
	 * \n If the Update Adapters timer is already running, it won't be restarted.
	 * \n See top of header for an activity diagram showing the Update Adapters timer loop.
	 * \n The timer loop will run until ResetUpdateAdaptersTimer() is called.
	 * \n This is called when there was a problem updating adapters or toys.
	 */
	void StartUpdateAdaptersTimer();
	/**
	 * @brief Stops the Update Adapters timer if it's running and reset the timer interval to it's default of 5 seconds.
	 * \n This is the exit point for the Update Adapters timer loop.
	 * \n See top of header for an activity diagram showing the Update Adapters Timer loop.
	 * \n This is called when adapters or toys where updated without a problem.
	 */
	void ResetUpdateAdaptersTimer();

	/** @brief Event called on Update Toys timer loop. Will call UpdateToys(). */
	void OnUpdateToysTimerLoop();
	/** @brief Starts the Update Toys timer if it's not already running. Called when UpdateAdapters() or UpdateToys() finishes. */
	void StartUpdateToysTimer();

private:
	/** @brief Object holding global Lovense Integration delegates. */
	TStrongObjectPtr<class ULovenseEvents> lovenseEvents;
	/** @brief The (mostly) raw ILovenseAdapter::TryGetAdapterData() HTTP request json data. */
	FLovenseGetAdaptersResponseData adaptersResponseData;
	/** @brief All currently available adapters. */
	TArray<TSharedPtr<class ILovenseAdapter>> lovenseAdapters;
	/** @brief All currently available toys. This is just a cache for easy iteration. The respective adapters the toys belong to own the memory. */
	TArray<class ULovenseToy*> toys;

	// Temporary adapter and toy information used while UpdateAdapters() or UpdateToys() is still in progress.
	/** @brief Number of adapters who are currently waiting for a response to their GetToys HTTP request. */
	int32 numAdaptersWaitingForGetToysResponse;
	/** @brief Number of adapters who received an invalid response to their GetToys HTTP request. */
	int32 numAdaptersFailedGetToys;
	/**
	 * @brief New adapters which were polled by UpdateAdapters() or reinitialized by UpdateToys() and might currently be waiting for a response to their GetToys HTTP request.
	 * \n Once all temporary adapters have received their response, all currently active adapters will be replaced with these and this array will be cleared.
	 */
	TArray<TSharedPtr<class ILovenseAdapter>> temporaryLovenseAdapters;
	/**
	 * @brief New toys which were polled by temporaryLovenseAdapters.
	 * \n Once all temporary adapters have received their response, all currently active toys will be replaced with these and this array will be cleared.
	 */
	TArray<class ULovenseToy*> temporaryToys;

	/** @brief Whether lovense is currently running. */
	bool bIsLovenseRunning;
	/** @brief Whether the lovense adapters are currently being updated. */
	bool bIsUpdatingAdapters;
	/** @brief Whether the toys are currently being updated. */
	bool bIsUpdatingToys;

	/** @brief Handle for the Update Adapters Timer. See top of header for an activity diagram showing the Update Adapters Timer loop. */
	FTimerHandle updateAdaptersTimerHandle;
	/**
	 * @brief Current interval for the Update Adapters Timer. Starts with 5 seconds and doubles with each failed attempt.
	 * \n See top of header for an activity diagram showing the Update Adapters Timer loop.
	 */
	float currentUpdateAdaptersTimerInterval;
	/**
	 * @brief Handle for the Update Toys Timer.
	 * This is a looping timer that fires every 5 seconds to update toy information and is only stopped while updating adapters.
	 */
	FTimerHandle updateToysTimerHandle;
	/** @brief The global set of Command Delay Timers used when commands are broadcasted. See FLovenseCommandDelayTimers for more information. */
	FLovenseCommandDelayTimers commandDelayTimers;

	/**
	 * @brief The cached "ToyDelay" config value.
	 * \n Toy delay is in milliseconds. Value range is 0-2000ms and defaults to 500ms.
	 * \n The toy delay adjusts the start time for pattern generation based on animations. (See ULovenseToyAnimationControlComponent)
	 * \n Gets fetched from config in StartLovense().
	 */
	int32 toyDelay;
	/**
	 * @brief The cached "ToyStrengthMultiplier" config value.
	 * \n Value range is 0.0f-1.0f and defaults to 1.0f.
	 * \n The toy strength multiplier scales the speed values before commands are sent.
	 * \n Gets fetched from config in StartLovense().
	 */
	float toyStrengthMultiplier;
	/**
	 * @brief The cached "ToyVibrationEnabled" config value.
	 * \n If false, vibration speed values will be set to 0 before commands are sent, so toys will not vibrate. Defaults to true;
	 * \n Gets fetched from config in StartLovense().
	 */

	TMap<FString, TArray<ELovenseCommandType>> typeSupportMap;

	TMap<FString, TSharedPtr<LovenseToyEventsWebSocketHandler>> mapEventDictionary;
	bool GetDevicePlatformIsMobile();
};
