// (CVN) - Candy Valley Network GmbH

#pragma once

#include <CoreMinimal.h>

// EXTERNAL INCLUDES
#include <IHttpRequest.h>

// INTERNAL INCLUDES
#include "LovenseTypes.h"


/**
 * @brief Abstract base class for Lovense adapters.
 * \n These adapters handle the communication with the Lovense API's and hold the toy objects for the toys which are connected to the respective Lovense App.
 * \n Each Lovense API+Platform combination has different requirements, so they get their own adapter implementation.
 * \n Multiple adapters can be active at the same time, one for each Lovense App instance found in the local network.
 */
class ILovenseAdapter {
	friend class FLovenseManager;

public:
	/**
	 * @brief Polls "https://api.lovense-api.com/api/lan/v2/app" for toys in the local network.
	 * \n This is the same for all API+Platform combinations, so we can make it static.
	 * @param callback Called when HTTP request has been completed. If request was unsuccessful, will pass a default initialized FLovenseGetAdaptersResponseData.
	 * \n This will not be called if the integration is not running.
	 * @return Whether the http request was dispatched successfully.
	 */
	static bool TryGetAdapterData(FOnLovenseGetAdaptersResponse callback);

	/** @brief The adapter description that was used to initialize this adapter. Holds (mostly) raw TryGetAdapterData() HTTP request json data for this adapter. */
	FORCEINLINE const FLovenseAdapterDescription& GetAdapterDescription() { return this->adapterDescription; }
	/**
	 * @brief Array of toy deviceId string and test timer handle.
	 * \n This holds the toy deviceId so we can check if a toy is running a test command and to send a command to that toy to stop vibrating when the timer finishes.
	 */
	const TArray<TPair<FString, FTimerHandle>> GetToyTestTimerHandles() { return this->toyTestTimerHandles; }

	/**
	 * @brief Initializes the adapter with the passed in adapter description.
	 * @return Whether the adapter has a valid platform and device IP.
	 */
	virtual bool Initialize(FLovenseAdapterDescription& description);
	/** @brief Deinitialize the adapter. This will stop any running test timers and clear toy objects. */
	virtual void Shutdown();

	/**
	 * @brief Polls the Lovense App for toys and creates toy objects for all received toys.
	 * \n We always directly poll the respective Lovense Apps to get our toy info so we don't put unnecessary load on the Lovense servers.
	 * @param callback Called when HTTP request has been completed.
	 * \n If request was unsuccessful, will pass an empty toy object array. FLovenseGetToysResponseData might be partially filled in.
	 * \n This will not be called if the integration is not running.
	 * @return Whether the http request was dispatched successfully.
	 */
	virtual void GetToys(FOnLovenseGetToysResponse callback);

	virtual bool RemoveToy(const FString toyId);

	/**
	 * @brief See FLovenseManager::SendCommand_Test() for information on this command.
	 * @param toyId Must have a valid id, or command will fail.
	 */
	virtual bool SendCommands(TArray<class UELovenseCommands*> commands, const FString& toyId, float timeSec, float loopRunningSec, float loopPauseSec, FOnLovenseResponse callback) { return false; }
	
	virtual bool SendPosition(int position, const FString& toyId, FOnLovenseResponse callback) { return false; }
	
	virtual bool SetUpPatternV2(TArray<class UEPositionPatternValue*> positionValues, const FString& toyId, FOnLovenseResponse callback) { return false; }
	virtual bool StartPatternV2(const FString& toyId, double startTime, double offsetTime, FOnLovenseResponse callback) { return false; }
	virtual bool StopPatternV2(const FString& toyId, FOnLovenseResponse callback) { return false; }
	virtual bool GetPatternV2SyncTime(FOnLovenseResponse callback) { return false; }
	/**
	 * @brief See FLovenseManager::SendCommand_Stop() for information on this command.
	 * @param parameters URI query string (including '?') used by Lovense Connect implementations. Contains toyId parameter if specified.
	 * @param toyId Used by Lovense Remote implementations.
	 */
	virtual bool SendCommand_Stop(const FString& toyId, FOnLovenseResponse callback){ return false; }
	/**
	 * @brief See FLovenseManager::SendCommand_Vibrate() for information on this command.
	 * @param parameters URI query string (including '?') used by Lovense Connect implementations. Contains toyId parameter if specified and vibrationSpeed.
	 * @param toyId Used by Lovense Remote implementations.
	 * @param vibrationSpeed Used by Lovense Remote implementations.
	 */

	virtual bool SendCommand_Preset(const FString& toyId, const FString& name,float timeSec, FOnLovenseResponse callback) { return false; }
	/**
	 * @brief See FLovenseManager::SendCommand_Battery() for information on this command.
	 * @param parameters URI query string (including '?') used by Lovense Connect implementations. Contains toyId parameter if specified.
	 * @param toyId Used by Lovense Remote implementations.
	 */
	/*virtual bool SendCommand_Battery(const FString& toyId, FOnLovenseResponse callback) { return false; }*/
	/**
	 * @brief See FLovenseManager::SendCommand_AVibrate() for information on this command.
	 * @param parameters URI query string (including '?') used by Lovense Connect implementations. Contains toyId parameter if specified, vibrationSpeed and time.
	 * @param toyId Used by Lovense Remote implementations.
	 * @param vibrationSpeed Used by Lovense Remote implementations.
	 * @param time Used by Lovense Remote implementations.
	 */
	
	virtual bool SendCommand_Pattern(const FString& toyId, FLovensePattern& pattern,FOnLovenseResponse callback) { return false; }

	static void GetRemote(FString url, bool bIgnoreSSL, FString method, FString body, TMap<FString, FString> headers, FOnLovenseGetAdaptersResponse& callback);
	void RequestToys(FString url, bool bIgnoreSSL, FString method, FString body, TMap<FString, FString> headers, FOnLovenseGetToysResponse callback);


protected:
	ILovenseAdapter();
	ILovenseAdapter(const ILovenseAdapter&) {}
	virtual ~ILovenseAdapter() {}

	/**
	 * @brief Factory function for lovense adapters.
	 * \n Checks the adapterDescription.platform and adapterDescription.appType strings to determine which adapter should be created.
	 */
	static TSharedPtr<class ILovenseAdapter> CreateLovenseAdapter(const FLovenseAdapterDescription& adapterDescription);


	/**
	 * @brief Create HTTP request to send a command.
	 * \n Each API+Platform combination requires different HTTP request settings, so allow child classes to create the HTTP request with the correct settings.
	 * @param parameters String defining the URI path and query (including '?') for the request.
	 */
	virtual void WebThread_SendCommand(const FString& parameters, FOnLovenseResponse callback) = 0;

	/** @brief Creates a toy object and adds it to the toyStrongPointers array. Also adds the toyDescription to the responseData.toys array. */
	void CreateLovenseToy(FLovenseToyDescription& toyDescription);
	/**
	 * @brief Retrieve a json object holding an array of toys. The json structure is different between the API's, so allow child classes to override this.
	 * @param jsonObject Json object holding the GetToys request response data.
	 * @param outToysJsonObject Result json object holding the array of toys.
	 */
	virtual void GetToysDataJsonObject(TSharedPtr<class FJsonObject> jsonObject, TSharedPtr<class FJsonObject>& outToysJsonObject);
	/**
	 * @brief Parses a json object holding toy data and fills a toy description. The json structure is different between the API's, so allow child classes to override this.
	 * @param outToyDescription Result toy description data. Will not be default initialized.
	 * @param toyObject Json object holding data for a single toy.
	 */
	virtual void ParseToyDescription(FLovenseToyDescription& outToyDescription, TSharedPtr<class FJsonObject> toyObject) = 0;

	/**
	 * @brief Send a HTTP request to the respective Lovense App.
	 * @param parameters String defining the URI path and query for the request.
	 * @param callback Called when HTTP request has been completed. If request was unsuccessful, will pass -1, otherwise will pass value of "data" json field.
	 * \n This is used to poll battery status.
	 * \n This will not be called if the integration was stopped while the HTTP request was being processed.
	 */
	virtual bool SendCommand(const FString& parameters, FOnLovenseResponse callback = FOnLovenseResponse());

	/** @brief Test timer finished event. Will set vibration speed of the relevant toy to 0 and remove the relevant timer handle from the toyTestTimerHandles array. */
	virtual void OnToyTestTimerFinished();



protected:
	TSharedPtr<class LovenseToyEventsWebSocketHandler> lovenseToyEventsWebSocketHandler;
	/** @brief The adapter description that was used to initialize this adapter. Holds (mostly) raw TryGetAdapterData() HTTP request json data for this adapter. */
	FLovenseAdapterDescription adapterDescription;
	/** @brief Device IP string (excluding port) the Lovense App instance is running on. Parsed in Initialize(). */
	FString deviceIp;
	/**
	 * @brief The web protocol to use. Default is HTTPS, but will be set to HTTP if li.Request.UseDirectIP is true
	 * \n as the SSL certificate does not work with direct IPs and we can't ignore SSL host verification without modifying the engine.
	 */
	FString webProtocol;
	/** @brief Holds (mostly) raw GetToys() HTTP request json data. */
	FLovenseGetToysResponseData responseData;
	/** @brief Array of the Lovense toy object instances. Is strong object pointer so these UObjects aren't gc'd. */
	TArray<TStrongObjectPtr<class ULovenseToy>> toyStrongPointers;
	/**
	 * @brief Array of toy deviceId string and test timer handle.
	 * \n This holds the toy deviceId so we can check if a toy is running a test command and to send a command to that toy to stop vibrating when the timer finishes.
	 */
	TArray<TPair<FString, FTimerHandle>> toyTestTimerHandles;

	private:
		FString requestURL;

};

/**
 * @brief Dummy adapter used to check if a FLovenseAdapterDescription has valid data.
 */
class LovenseTestAdapter : public ILovenseAdapter {
protected:
	virtual void WebThread_SendCommand(const FString& parameters, FOnLovenseResponse callback) override { }
	virtual void ParseToyDescription(FLovenseToyDescription& toyDescription, TSharedPtr<FJsonObject> toyObject) override {}
};
