#pragma once

#include "CoreMinimal.h"
#include "Subsystems/EngineSubsystem.h"
#include "hidapi.h"
#include "HAL/Runnable.h"
#include "SkyPortalFigure.h"


#include "SkyPortalSubsystem.generated.h"

#pragma region Definitions

UENUM(BlueprintType)
enum EPortalSide {
	LEFT UMETA(DisplayName = "Left side"),
	RIGHT UMETA(DisplayName = "Right side"),
	BOTH UMETA(DisplayName = "Both left and right"),
	TRAP UMETA(DisplayName = "Trap")
};

UENUM(BlueprintType)
enum EPortalCommand {
	A UMETA(DisplayName = "Activate"),
	C UMETA(DisplayName = "Color"),
	J UMETA(DisplayName = "Advanced color"),
	L UMETA(DisplayName = "Trap color"),
	M UMETA(DisplayName = "Music"),
	Q UMETA(DisplayName = "Query"),
	R UMETA(DisplayName = "Ready"),
	S UMETA(DisplayName = "Status")
};

UENUM(BlueprintType)
enum class EFigureStatus : uint8
{
	NOT_PRESENT = 0b00 UMETA(DisplayName = "Not Present"),
	PRESENT = 0b01 UMETA(DisplayName = "Present"),
	ADDED = 0b11 UMETA(DisplayName = "Added"),
	REMOVED = 0b10 UMETA(DisplayName = "Removed")
};

USTRUCT(BlueprintType)
struct FPortalStatusData
{
	GENERATED_USTRUCT_BODY()

	// Array of statuses
	UPROPERTY(BlueprintReadOnly, Category = "SkyPortal|Figure")
	TArray<EFigureStatus> StatusArray;

	// timestamp.
	// only one byte long. This means that after the value 0xFF, it overflows back to 0x00.
	// Since these are so far apart, it can be assumed that 0x00 is newer than anything in the range 0xF0 - 0xFF.
	UPROPERTY(BlueprintReadOnly, Category = "SkyPortal|Figure")
	uint8 Counter;

	// Should always be true
	UPROPERTY(BlueprintReadOnly, Category = "SkyPortal|Figure")
	bool bIsReady;

	// Overload the == operator
	bool operator==(const FPortalStatusData& Other) const
	{
		if (bIsReady == Other.bIsReady && Counter == Other.Counter) {
			if (StatusArray == Other.StatusArray) {
				return true;
			}
		}
		return false;
	}

	// Overload the != operator
	bool operator!=(const FPortalStatusData& Other) const
	{
		return !(*this == Other);
	}
};

/* Macro constants Definitions */
#define rw_buf_size  0x21
#define TIMEOUT 30000
#define DEBUG true
#define FIGURE_TOTAL_BLOCKS 64
#define FIGURE_BLOCK_SIZE 16

typedef struct {
	unsigned char buf[rw_buf_size]; int BytesTransferred;
} RWBlock;

typedef struct {
	unsigned char blockdata[FIGURE_TOTAL_BLOCKS][FIGURE_BLOCK_SIZE]; bool error;
} FigureDataBlock;

DECLARE_LOG_CATEGORY_EXTERN(LogHIDApi, Log, All);
DECLARE_LOG_CATEGORY_EXTERN(LogSkyportalIO, Log, All);

//// Delegates
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSkylanderAddedDelegate, int32, SkylanderID, int32, Index);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSkylanderRemovedDelegate, int32, SkylanderID, int32, Index);

#pragma endregion


class FPortalStatusChecker : public FRunnable {
public:
	// Constructor: pass the subsystem and desired check interval (in seconds)
	FPortalStatusChecker(USkyPortalSubsystem* InSubsystem, float InCheckInterval);

	// FRunnable interface
	virtual bool Init() override;
	virtual uint32 Run() override;
	virtual void Stop() override;
	virtual void Exit() override;

private:
	// Pointer to the subsystem that contains PortalStatus()
	USkyPortalSubsystem* SkyPortalSubsystem;

	// Time interval (in seconds) for status checking
	float CheckInterval;

	// Thread control variables
	FThreadSafeBool bShouldRun;

	// Helper function to check portal status
	void CheckPortalStatus();
};








/* Handle all the portal I/O
*
*
*
*/
UCLASS(MinimalAPI)
class USkyPortalSubsystem : public UEngineSubsystem
{
	GENERATED_BODY()

	//Portal ref used in the subsystem
	hid_device* PortalDevice;

public:
	// Override initialization and deinitialization methods
	virtual void Initialize(FSubsystemCollectionBase& Collection) override;
	virtual void Deinitialize() override;

	/********* Portal Actions *************/

	/* The first function to run, before anything else.
	*  It will re-init and prepare the portal for receiving/sending inputs
	*
	*  return false if portal is not found
	*/
	UFUNCTION(BlueprintCallable, CallInEditor, meta = (Category = "SkyPortal"))
	SKYPORTAL_API bool ConnectPortal();

	/*Send a **Status** command, to see if the portal is ready to receive new commands*/
	UFUNCTION(BlueprintCallable, BlueprintPure, meta = (Category = "SkyPortal|NOT FUNCTIONING"))
	SKYPORTAL_API bool bIsPortalReady();

	UFUNCTION(BlueprintCallable, meta = (Category = "SkyPortal|NOT FUNCTIONING"))
	SKYPORTAL_API void SendPortalCommand(EPortalCommand Command);

	UFUNCTION(BlueprintCallable, meta = (Category = "SkyPortal|NOT FUNCTIONING"))
	SKYPORTAL_API void SendPortalSound(USoundWave* Sound);

	

	/* Change portal color, ideally should be called just at the start.For gameplay usage, use ChangePortalColorside()*/
	UFUNCTION(BlueprintCallable, CallInEditor, meta = (AutoCreateRefTerm = "Color", Category = "SkyPortal|Cosmetic"))
	SKYPORTAL_API void ChangePortalColor(const FLinearColor& Color = FLinearColor::Green);

	/**
	 * Change the color of the portal, can separate side and even trap ligth
	 * @param	NextColor	New color
	 * @param	PortalSide	The actors to record
	 * @param	BlendTime	Blend between current color and NextColor, in milliseconds
	 */
	UFUNCTION(BlueprintCallable, CallInEditor, meta = (AutoCreateRefTerm = "NextColor", Category = "SkyPortal|Cosmetic"))
	SKYPORTAL_API void ChangePortalColorside(const FLinearColor& NextColor = FLinearColor::Green, const EPortalSide PortalSide = EPortalSide::BOTH, const float BlendTime = 500.0f);


	// Blueprint-assignable event property
	UPROPERTY(BlueprintAssignable, Category = "SkyPortal|Skylander")
	FOnSkylanderAddedDelegate OnSkylanderAdded;

	// Blueprint-assignable event property
	UPROPERTY(BlueprintAssignable, Category = "SkyPortal|Skylander")
	FOnSkylanderRemovedDelegate OnSkylanderRemoved;

	UPROPERTY(BlueprintReadOnly)
	FPortalStatusData CurrentStatusData;

	UPROPERTY(BlueprintReadOnly)
	FPortalStatusData OldStatusData;

	EPortalCommand GetPortalCommandFromChar(unsigned char Char);


	UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
	bool bPortalConnected = false;

	FString HidError;

	bool ReadBlock(unsigned int block, unsigned char data[0x10], int skylander);
	bool WriteBlock(unsigned int, unsigned char[0x10], int);
	bool CheckResponse(RWBlock*, char);
	void CheckComplexResponse();
	



private:
	FPortalStatusData ParsePortalStatus(const RWBlock& ResponseBlock);




	static void Sleep(int sleepMs);



	void ActivatePortal(int active);

	void RestartPortal();


	// Block/byte related data write/read functions
	bool OpenPortalHandle();
	void Write(RWBlock*);
	FigureDataBlock ReadFigureBlocks(uint8 FigureIndex);
	bool FalsePositive();

	// Pointer to the status checker thread
	FPortalStatusChecker* StatusChecker;
	FRunnableThread* StatusCheckerThread;


protected:

	//Constants
	const int VendorIds[4] = { 0x12ba, 0x54c, 0x1430, 0x1430 };
	const int ProductIds[4] = { 0x150, 0x967, 0x1f17 };



	/////Defaults values, should not be used
	//const int VendorId = 5168;
	//const int ProductId = 336;
};


