#pragma once

#include "CoreMinimal.h"
#include "Subsystems/EngineSubsystem.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "Runtime/Online/HTTP/Public/Http.h"
#include "PaperFlipbook.h"
#include "MediaPlayer.h"
#include "IImageWrapper.h"
#include "Engine/ObjectLibrary.h"
#include "Engine/Texture2D.h"
#include "PortaUtils.h"
#include "RuntimeAudioImporterLibrary.h"
#include "Sound/SoundWave.h"
#include "PortaAssetSubsystem.generated.h"

UENUM()
enum class EPortaMediaStatus : uint8
{
	Downloading,
	Ready,
	Error
};

DECLARE_DYNAMIC_DELEGATE_OneParam(FPortaMediaStatusDelegate, EPortaMediaStatus, Status);
DECLARE_MULTICAST_DELEGATE_OneParam(FMediaStatusChangedDelegate, EPortaMediaStatus);
DECLARE_DELEGATE_OneParam(FPortaAudioImportDoneDelegate, bool);

USTRUCT(BlueprintType)
struct PORTAINTERFACE_API FPresetFieldInfo
{
	GENERATED_BODY()

	UPROPERTY()
	FString DisplayName;

	UPROPERTY()
	FString Level;

	UPROPERTY()
	bool IsRootLevel;
};

USTRUCT(BlueprintType)
struct PORTAINTERFACE_API FPresetGroupInfo
{
	GENERATED_BODY()

	UPROPERTY()
	FString Name;

	UPROPERTY()
	TArray<FPresetFieldInfo> Fields;
};

USTRUCT(BlueprintType)
struct PORTAINTERFACE_API FPresetControllerInfo
{
	GENERATED_BODY()

	UPROPERTY()
	FString DisplayName;

	UPROPERTY()
	bool IsRootLevel = true;

	UPROPERTY()
	bool IsController = true;
};

USTRUCT(BlueprintType)
struct PORTAINTERFACE_API FPresetInfo
{
	GENERATED_BODY()

	UPROPERTY()
	FString Name;

	UPROPERTY()
	TArray<FPresetGroupInfo> Groups;

	UPROPERTY()
	TArray<FPresetControllerInfo> Controllers;

};

UCLASS()
class PORTAINTERFACE_API UMediaInfo : public UObject
{
	GENERATED_BODY()
public:
	void SetStatus(EPortaMediaStatus NewStatus)
	{
		if (NewStatus != Status)
		{
			Status = NewStatus;
			OnStatusChanged.Broadcast(Status);
		}
	}

	FMediaStatusChangedDelegate OnStatusChanged;


	UPROPERTY()
	FString Url;

	UPROPERTY()
	EPortaMediaStatus Status = EPortaMediaStatus::Downloading;
};

UCLASS()
class PORTAINTERFACE_API UPageInfo : public UObject
{
	GENERATED_BODY()
public:
	EPortaMediaStatus GetStatus();

	void OnMediaReady(EPortaMediaStatus Status);
	void BroadcastStatus();

	FMediaStatusChangedDelegate OnStatusChanged;

	UPROPERTY()
	int Id;

	UPROPERTY()
	TArray<UMediaInfo*> Media;

	UPROPERTY()
	EPortaMediaStatus Status = EPortaMediaStatus::Downloading;
};

USTRUCT()
struct PORTAINTERFACE_API FAudioImportInfo
{
	GENERATED_BODY()
public:
	FAudioImportInfo() {}
	FAudioImportInfo(const FString& FilePath, FPortaAudioImportDoneDelegate OnDone) : FilePath(FilePath), OnDone(OnDone)
	{}
	
	FString FilePath;

	FPortaAudioImportDoneDelegate OnDone;
};

UCLASS()
class PORTAINTERFACE_API UPortaAssetSubsystem : public UGameInstanceSubsystem
{
	GENERATED_BODY()

public:
	DECLARE_DELEGATE(FAssetDownloadDoneDelegate);

	static UPortaAssetSubsystem* Get() { return PortaUtils::GetGameInstanceSubsystem<UPortaAssetSubsystem>(); }

	UPortaAssetSubsystem();

	virtual void Initialize(FSubsystemCollectionBase& Collection) override;
	
	UFUNCTION()
	static void DoFullAssetSyncStatic();
	void DoFullAssetSync();

	UFUNCTION(BlueprintCallable, Category = "Porta")
	void SyncPlaylistStatic(int PlaylistID);

	UFUNCTION(BlueprintCallable, Category = "Porta")
	static void ListenToStatusChangeStatic(int PageId, FPortaMediaStatusDelegate Callback);
	void ListenToStatusChange(int PageId, FPortaMediaStatusDelegate Callback);

	UFUNCTION(BlueprintCallable, Category = "Porta")
	static EPortaMediaStatus GetPageStatusStatic(int PageId);
	EPortaMediaStatus GetPageStatus(int PageId);

	UFUNCTION(BlueprintCallable, Category = "Porta")
	static void DownloadAndSetTextureFromPresetStatic(const FString& PresetName, const FString& PropertyName, const FString& Url);

	UFUNCTION(BlueprintCallable, Category = "Porta")
	static void SetTextureFileFromPresetStatic(const FString& PresetName, const FString& PropertyName, const FString& FilePath);

	UFUNCTION(BlueprintCallable, Category = "Porta")
	static void SetTextureFromPresetStatic(const FString& PresetName, const FString& PropertyName, const FString& TextureName);

	UFUNCTION(BlueprintCallable, Category = "Porta")
	static void SetTextureAssetFromPresetStatic(const FString& PresetName, const FString& PropertyName, UTexture2D* Texture);

	UFUNCTION(BlueprintCallable, Category = "Porta")
	static void DownloadAndCallPresetFuncWithMediaStatic(const FString& PresetName, const FString& FuncName, const TArray<FString>& Args, const TArray<FString>& Urls, const FString& OtherArgs);

	UFUNCTION(BlueprintCallable, Category = "Porta")
	static void DownloadAndPlayVideoOnPresetStatic(const FString& PresetName, const FString& PropertyName, const FString& Url);

	UFUNCTION(BlueprintCallable, Category = "Porta")
	static void PlayVideoOnPresetStatic(const FString& PresetName, const FString& PropertyName, const FString& VideoName, bool Uncached = false);

	UFUNCTION(BlueprintCallable, Category = "Porta")
	static void DownloadAndSetAudioOnPresetStatic(const FString& PresetName, const FString& PropertyName, const FString& Url);

	UFUNCTION(BlueprintCallable, Category = "Porta")
	static void SetAudioFileOnPresetStatic(const FString& PresetName, const FString& PropertyName, const FString& FilePath);

	UFUNCTION(BlueprintCallable, Category = "Porta")
	static void SetTextureStatic(UMaterialInstanceDynamic* Material, const FString& TextureName);

	UFUNCTION(BlueprintCallable, Category = "Porta")
	static void PlayVideoStatic(UMediaPlayer* Player, const FString& VideoName, bool Uncached);

	UFUNCTION(BlueprintCallable, Category = "Porta")
	static TArray<FString> GetLoadedTextureNamesStatic(UObject* WorldContext);

	UFUNCTION(BlueprintCallable, Category = "Porta")
	void SetTexture(UMaterialInstanceDynamic* Material, const FString& TextureName);

	UFUNCTION(BlueprintCallable, Category = "Porta")
	void PlayVideo(UMediaPlayer* Player, const FString& VideoName, bool Uncached);

	UFUNCTION(BlueprintCallable, Category = "Porta")
	static void SetFlipbookStatic(const FString& PresetName, const FString& PropertyName, UPaperFlipbook* Flipbook);

	UFUNCTION(BlueprintCallable, Category = "Porta")
	static void DownloadAssetsStatic(const TArray<FString>& Urls);

	UFUNCTION(BlueprintCallable, Category = "Porta")
	static void DownloadAssetStatic(const FString& Url);

	UFUNCTION(BlueprintCallable, Category = "Porta")
	void DownloadAsset(const FString& Url);
	void DownloadAsset(const FString& Url, FAssetDownloadDoneDelegate OnDone);

	UFUNCTION(BlueprintCallable, Category = "Porta")
	void DownloadTexture(const FString& Url);
	void DownloadTexture(const FString& Url, FAssetDownloadDoneDelegate OnDone);

	UFUNCTION(BlueprintCallable, Category = "Porta")
	void DownloadVideo(const FString& Url);
	void DownloadVideo(const FString& Url, FAssetDownloadDoneDelegate OnDone);

	UFUNCTION(BlueprintCallable, Category = "Porta")
	void DownloadAudio(const FString& Url);
	void DownloadAudio(const FString& Url, FAssetDownloadDoneDelegate OnDone);

	UFUNCTION(BlueprintCallable, Category = "Porta")
	static FPresetInfo DescribePreset(const FString& PresetName);

	UFUNCTION(BlueprintCallable, Category = "Porta")
	static void GetPresetLevels(const FString& PresetName, TMap<FString, FString>& Levels);

	UFUNCTION(BlueprintCallable, Category = "Porta")
	static FString GetThumbnail(const FString& AssetPath);

	UFUNCTION(BlueprintCallable, Category = "Porta")
	static TMap<FString, FString> GetThumbnails(const TArray<FString>& AssetPaths);

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Porta")
	TMap<FString, UTexture2D*> Textures;

	UPROPERTY(BlueprintReadOnly, Category = "Porta")
	FString AssetCacheFolder;

	UPROPERTY(BlueprintReadWrite, Category = "Porta")
	TArray<UMediaPlayer*> MediaPlayers;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Porta")
	TMap<FString, USoundWave*> SoundWaves;

	static bool IsImageFormat(const FString& Ext);
	static EImageFormat ImageFormatFromExt(const FString& Ext);

	static bool IsVideoFormat(const FString& Ext);
	static bool IsAudioFormat(const FString& Ext);

private:
	DECLARE_DELEGATE_RetVal_TwoParams(bool, FProcessDataDelegate, const FString&, const TArray<uint8>&);
	DECLARE_DELEGATE_OneParam(FProcessFileDoneDelegate, bool);
	DECLARE_DELEGATE_TwoParams(FProcessFileDelegate, const FString&, FProcessFileDoneDelegate);
	void AcquireAsset(const FString& Url, FProcessDataDelegate ProcessData, FProcessFileDelegate ProcessFile, FAssetDownloadDoneDelegate OnDone);

	void HandleFullAssetSyncResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);

	void HandleDownloadResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bConnectedSuccessfully, FProcessDataDelegate ProcessData, FProcessFileDelegate ProcessFile, FAssetDownloadDoneDelegate OnDone);

	bool SaveFile(const FString& FileName, const TArray<uint8>& Content);
	bool LoadImageFromBuffer(const FString& Name, const TArray<uint8>& RawFileData);

	TSharedRef<IHttpRequest, ESPMode::ThreadSafe> CreateDownloadRequest(const FString& Url);

	void QueueRequest(TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request);
	void DequeueRequest();

	static FString LocalPathFromUrl(const FString& Url);

	void QueryPlaylistPages(int Id);

	void DoPlaylistDownloads();

	UMediaInfo* AskToDownload(const FString& Url);

	void OnAudioImportResult(URuntimeAudioImporterLibrary* AudioLib, UImportedSoundWave* Wave, ERuntimeImportStatus Status);
	void QueueAudioImport(const FString& FilePath, FProcessFileDoneDelegate OnDone);
	void ProcessNextAudioImport();

	int AvailableRequests;
	TArray< TSharedRef<IHttpRequest, ESPMode::ThreadSafe> > PendingRequests;

	FString PortaApiUrl;
	FString PortaApiKey;
	FString PortaChannel;

	TSet<FString> RequiredUrls;

	float AssetSyncRetryTimerSec = 10.f;

	TArray<FAudioImportInfo> PendingAudioImports;

	UPROPERTY(Transient)
	TMap<int, UPageInfo*> Pages;

	UPROPERTY(Transient)
	TMap<FString, UMediaInfo*> MediaDownloads;

	UPROPERTY()
	UObjectLibrary* PresetLibrary;

	UPROPERTY(Transient)
	UObjectLibrary* SequenceLibrary;

	UPROPERTY()
	URuntimeAudioImporterLibrary* AudioLibrary;
};
