//Copyright (c) 2022 Emoshape Inc.
//All Rights Reserved
 
//This product is protected by copyright and distributed under
//licenses restricting copying, distribution, and decompilation.

#pragma once

#include "CoreMinimal.h"
#include "SocketSubsystem.h"
#include "Common/UdpSocketReceiver.h"
#include "Interfaces/IHttpRequest.h"
#include "HttpModule.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "ESSubSystem.generated.h"

USTRUCT(BlueprintType)
struct FViseme
{
	GENERATED_BODY()
public:
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = EmoShapePlugin)
	int32 audio_offset;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = EmoShapePlugin)
	int32 viseme_id;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = EmoShapePlugin)
	FString animation;
};

USTRUCT(BlueprintType)
struct FAPIViseme {

	GENERATED_BODY()
public: 
	int32 privAudioOffset;
	int32 privVisemeId;
	FString privAnimation;
};

DECLARE_LOG_CATEGORY_EXTERN(EmoShapeCategory, Display, All);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FMetaSoulEventSignature);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FMetaSoulEventSignatureAudio, const TArray<uint8>&, audio_buffer, int64, audio_length, const TArray<FViseme>&, visems, FString, reply);

class ESIOClient;
UENUM(BlueprintType) 
enum PossibleResponses
{
	ResponseNone     UMETA(DisplayName = "ResponseNone"),
	ResponseText      UMETA(DisplayName = "ResponseText"),
	ResponseBuffer   UMETA(DisplayName = "ResponseBuffer"),
	ResponseLogin   UMETA(DisplayName = "ResponseLogin"),
	ResponseSER   UMETA(DisplayName = "ResponseSER"),
	ResponseHeartBeat   UMETA(DisplayName = "ResponseHeartBeat"),    
};

UCLASS()
class EMOSHAPESUBSYSTEM_API UESSubSystem : public UGameInstanceSubsystem
{
	GENERATED_BODY()

public:
	
	virtual void Initialize(FSubsystemCollectionBase& Collection) override;

	ESIOClient* IOClient;

	///-------- SOCKET SECTION -------///
	///
	int32 Port = 2424;
	int32 BufferSize = 256;    
	FSocket* Socket = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateSocket(NAME_Stream, TEXT("default"), false);
	FSocket* ListenSocket; 
	FString CustomLineBreaker = " 5c72";    
	FUdpSocketReceiver* UDPReceiver = nullptr;

	UFUNCTION(BlueprintCallable, Category = "EmoShapeSubSystem") 
	bool StartUDPReceiver(
		const FString& YourChosenSocketName ,
		const FString& TheIP ,
		const int32 ThePort
	);

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = EmoShapeSubSystem) 
	FString UdpText = "";

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = EmoShapeSubSystem) 
	FString RemoveIP = "";

	UFUNCTION(BlueprintCallable, Category = "EmoShapeSubSystem") 
	void CloseReceiver();

	void Recv(const FArrayReaderPtr& ArrayReaderPtr, const FIPv4Endpoint& EndPt);

	///-------- EPU SDK SECTION -------///
    ///       
           

	FString Message;

	/// ------- NEW API --------

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = EmoShapePlugin)  
	FString MetaSoulSecret; // fnXHexW9pCf5RacfllIgigKGyr8mYsh8
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = EmoShapePlugin)
	FString Epu_ID = "";
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = EmoShapePlugin)
	FString AzureKey = "";
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = EmoShapePlugin)
	FString AzureKeyRegion = "westeurope";
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = EmoShapePlugin)
	FString OpenAIKey = "";
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = EmoShapePlugin)
	FString Token;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = EmoShapePlugin)
	FString ChatHistory = "";

	/// ------- / NEW API --------


	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = EmoShapePlugin) 
	bool bTalkmode = false;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = EmoShapePlugin) 
	bool bConnected = false;    
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = EmoShapePlugin) 
	int32 BytesSent = 0;    
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = EmoShapePlugin) 
	TArray<uint8> ReceivedData;
   
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = EmoShapePlugin) 
	TMap<FString, FString> MEmotions;
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = EmoShapePlugin) 
	FString GptData;
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = EmoShapePlugin)
	TArray<int> VisemeOffset;
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = EmoShapePlugin) 
	TArray<int> VisemeID;

	bool SharedMem = false; 
	void* SharedMemoryHandle;           ///<  Mapped memory handle.
	unsigned char* SharedMemoryData;	///<  Pointer to memory data.
	size_t SharedMemorySize;            ///<  Length of the memory (memory size).
	void* SharedMemoryMutex;      
   
	/// ------- NEW API --------

	
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin")
	void Login();

	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin")
	void Logout();

	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin")
	void CustomWave(FString type, int32 apex, int32 curve, int32 level, int32 duration, int32 origin);

	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin")
	void AudioData(FString text, FString voice_name = "en-US-JennyNeural", bool generateEmo = false, FString chat_history = "", int32 coef = 30);

	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin")
	static TArray<FViseme> parseVisems(const TArray<FViseme>& visems);

	UPROPERTY(BlueprintAssignable, meta=(DisplayName = "on login"))
	FMetaSoulEventSignature onLogin;

	UPROPERTY(BlueprintAssignable, meta = (DisplayName = "on logout"))
	FMetaSoulEventSignature onLogout;

	UPROPERTY(BlueprintAssignable)
	FMetaSoulEventSignature onConnectionFail;

	UPROPERTY(BlueprintAssignable, meta = (DisplayName = "on audio data"))
	FMetaSoulEventSignatureAudio onAudioData;
    
	/// ------- / NEW API --------


	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin")  
	void StartUDP();
    
	//Connects to the EPU SDK
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
	void Connect(const FString & ipAddress);
        
	//Alternative Method to Connect to the EPU SDK
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
	void ConnectAlternative(const FString & ipAddress);
	    
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
	void CheckConnection();
	  
	//Using this while active cause a overhead at the processor, try to call something else first to get a EPU_OFF message
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin")  
	void StartEPU(FString method, FString key);
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
	void StartEPUProcess(FString path, bool silent);
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin")
	void StopEPUProcess();

	//Beware using this make sure its running first    
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
	void PauseEPU();
	     
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin")  
	void CloseEPU();

	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin")  
	void SendCustomMessage(FString Msg, bool end);

	void LongLineSend(FString &Msg);
	//Same goes here make sure its paused first    
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
	void ResumeEPU();

	//Reset EPU 
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
	void ResetEPU();   

	//Send a message for the EPU to process and generate a emotional response
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
	void SendMessage(FString message);

    UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
    void SendBroadcast(FString message);
    
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
	void SendTextToSpeech(FString message);

	//Set the Sensibility (user and robot messages) of the EPU
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
	void SetSenses(bool robot, FString value);

	//Set the Persistence (user and robot messages) of the EPU
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
	void SetPersistence(bool robot, FString value);

	//Set the Objective Appraisal ON (user and robot messages) of the EPU
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
	void SetObjectiveAppraisal(bool robot);

    //This sets him to read a text and generate a emotion for further use, Example: Copy a text from Barrack Obama in the Wikipedia and send it,
    //he will generate a emotional response and you can save it. (Max of 100 words)
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
    void SetSimbolicLearning(FString message);

    //Save the SimbolicLearning to a word
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin")  
    void SaveSimbolicLearning(FString word);

    //Erase all learned words and personality(not sure about this one)
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
    void EraseLexic();

    //Inject manually a emotion this can be usefull to be sure something will trigger within the EPU
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
    void InjectEmotion(FString emotion, FString level, FString duration, FString origin_id, FString apex, FString curve);

    //This one is used to ask for a emotional buffer for later retrieval with RecieveMessage.
    UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin")  
    void AskForEmotionalBuffer();

	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
    void BroadcastMessageToAllClients(FString message);

    UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin")  
    void EnableDisableToneOfVoiceDetection(bool on);

    //This just close the socket connection, not the EPU
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin")
    void EnableDisableFaceTracking(bool on);

	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
    void AddRemoveUserForFaceTracking(FString username, bool add);

	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin")
    void SetSpeechRecognition(bool on);

	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
    void GetCurrentFaceTrackingUser();
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
    void StartSharedMem();
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin")
    void StopSharedMem();
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
    void GetSupportedLanguages();
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin")  
    void SetLanguages(FString index);    
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin")  
    void StopConnection();

    //Handy function to convert the binary response to a FString
    UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
    FString UTF8toString(const TArray<uint8>& BinaryArray);

	//Usually you will always use the ReceivedData array for this but, I let open if case someone wants to make something different
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
	void UpdateEmotionTMap(TArray<FString> mArray);
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
	void ReadMDADSharedMemory();
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
	void ReadGPTSharedMemory();
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
	void ReadVisemeSharedMemory();
   
    ///This is the recieveMessage you call this whetever you want to receive something from the EPU SDK Buffer,
    ///just take in note this use a nonblocking setting, so if you put a .Send() and a .Recv() one after the other it will not compute the correct values.    
    ///Nowadays this is mostly used to receive IO messages and special systems if needed, but you can also get the emotional states but the Shared Memory method is recommended nowadays.
    
    UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
    void ReceiveMessage();

	//-------- HTTP SECTION -------//
	      
	FString HeartBeatParam;
	      
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
	void SendPut(FString PathSuffix, UESJsonObject* Json, PossibleResponses functionName);

	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
	void SendPost(FString PathSuffix, UESJsonObject* Json, PossibleResponses functionName);

	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
	void SendGet(FString PathSuffix, PossibleResponses functionName);

	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin")  
	void PostSER(FString Pathsuffix, FSecretParam Secret_Param, TArray<uint8> audio);
	      
	auto GetResponse(FString name) -> void(UESSubSystem::*)(TSharedPtr<IHttpRequest, ESPMode::ThreadSafe>, TSharedPtr<IHttpResponse, ESPMode::ThreadSafe>, bool);
	      
	void PostResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);
	void PostResponseAudioData(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);
	void PostResponseBuffer(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);
	void PostResponseText(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);
	void PostResponseLogin(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);
	void PostResponseLoginWeb(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);
	void PostResponseLogout(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);
	void PostResponseSER(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);
	void PostResponseHeartBeat(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);    
	       
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
	void ConvertSERToEmotions(TArray<int> SEREmotionalValues);   
	      
	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
	void HandleSERResponse(FString text);

	//-------- AUDIO SECTION -------//

	UFUNCTION(BlueprintCallable, Category = "EmoShapePlugin") 
	void StereoToMono(TArray<uint8> stereoWavBytes, TArray<uint8>& monoWavBytes);

	//-------- UTILITIES SECTION -------//
    
	TMap<FString, void(UESSubSystem::*)(TSharedPtr<IHttpRequest, ESPMode::ThreadSafe>, TSharedPtr<IHttpResponse, ESPMode::ThreadSafe>, bool)> PseudoReflectionMap;

protected:
	/*// Called when the game starts
	virtual void BeginPlay() override;*/


private:

	//-------- HTTP SECTION -------//
    
	FHttpModule* Http;
	FString ApiBaseUrl = "https://www.emohuman.com/";

	FString AuthorizationHeader = TEXT("Authorization");
	void SetAuthorizationHash(FString Hash, TSharedRef<IHttpRequest>& Request);
    
	TSharedRef<IHttpRequest, ESPMode::ThreadSafe> RequestWithRoute(FString Pathsuffix);
	static void SetRequestHeaders(TSharedRef<IHttpRequest, ESPMode::ThreadSafe>& Request);

	TSharedRef<IHttpRequest,ESPMode::ThreadSafe> GetRequest(FString Pathsuffix);
	TSharedRef<IHttpRequest,ESPMode::ThreadSafe> PostRequest(FString Pathsuffix, FString ContentJsonString);
	TSharedRef<IHttpRequest,ESPMode::ThreadSafe> PutRequest(FString Pathsuffix, FString ContentJsonString);
	static void Send(TSharedRef<IHttpRequest,ESPMode::ThreadSafe>& Request);
	FProcHandle ProcessHandle;

	static bool ResponseIsValid(FHttpResponsePtr Response, bool bWasSuccessful);

	//-------- UTILITIES SECTION -------//
    
	template <typename StructType>
	void GetJsonStringFromStruct(StructType FilledStruct, FString& StringOutput);
	template <typename StructType>
	void GetStructFromJsonString(FHttpResponsePtr Response, StructType& StructOutput); 
	
};
