// Oleg Petruny staff

#pragma once

#include "CoreMinimal.h"
#include "Engine/Engine.h"
#include "Async/AsyncWork.h"
#include "UObject/Object.h"
#include "AsyncLibrary.generated.h"

/*UENUM(BlueprintType)
enum class EAsyncExecs : uint8 {
	First,
	Loop,
	Last,
	Done
};*/

DECLARE_DYNAMIC_DELEGATE(FWorkDelegate);
DECLARE_DYNAMIC_DELEGATE(FCallbackDelegate);

UCLASS(Blueprintable, BlueprintType)
class LOST_EDGE_API UAsyncLibrary : public UObject {
	GENERATED_BODY()

private:
	FWorkDelegate work;
	FCallbackDelegate callback;

	class FAsyncWorker : public FNonAbandonableTask {
		friend class FAsyncTask<FAsyncWorker>;

	public:
		FWorkDelegate work;
		FAsyncWorker(FWorkDelegate workIn) {
			work = workIn;
		}

	private:
		void DoWork() {
			work.ExecuteIfBound();
		}
		FORCEINLINE TStatId GetStatId() const {
			RETURN_QUICK_DECLARE_CYCLE_STAT(FAsyncWorker, STATGROUP_ThreadPoolAsyncTasks);
		}
	};

	class FAsyncManager : public FNonAbandonableTask {
		friend class FAsyncTask<FAsyncManager>;

	public:
		FAsyncTask<FAsyncWorker> *worker;
		int count = 0;
		float delay = 0;
		FCallbackDelegate callback;
		FAsyncManager(FAsyncTask<FAsyncWorker> *workerIn, int countIn, float delayIn, FCallbackDelegate callbackIn) {
			worker = workerIn;
			count = countIn;
			delay = delayIn;
			callback = callbackIn;
		}

	private:
		void DoWork() {
			for (int i = 0; i < count || count == 0; i++) {
				FDateTime stamp = FDateTime::Now();
				while (true) {
					if (delay < (FDateTime::Now() - stamp).GetTotalMilliseconds()) {
						worker->StartSynchronousTask();
						worker->EnsureCompletion();
						break;
					}
				}
			}
			callback.ExecuteIfBound();
		}
		FORCEINLINE TStatId GetStatId() const {
			RETURN_QUICK_DECLARE_CYCLE_STAT(FAsyncManager, STATGROUP_ThreadPoolAsyncTasks);
		}
	};

	FAsyncTask<FAsyncWorker> *worker;
	FAsyncTask<FAsyncManager> *manager;

public:
	UAsyncLibrary() {}
	UAsyncLibrary(FWorkDelegate work, FCallbackDelegate callback, int execsTimes, float delayBetween, bool autoStart) {
		this->worker = new FAsyncTask<FAsyncWorker>(work);
		this->manager = new FAsyncTask<FAsyncManager>(this->worker, execsTimes, delayBetween, callback);
		if (autoStart) {
			this->manager->StartBackgroundTask();
		}
	}

/*
* Run a inserted delegate.
* Execs Times - 0 = infinity
* Delay Between - miliseconds between any task run
* Auto Start - if true, the task starts after creating
* Returns Task Asyncer reference
*/
	UFUNCTION(BlueprintCallable, Category = "Async Tasks")
		static void CreateAsyncTaskWithEventDelegate(FWorkDelegate workDelegate, FCallbackDelegate callback, UAsyncLibrary *&Asyncer, int execsTimes, float delayBetween, bool autoStart = true);
/*
* Run a binded function from any Object.
* Execs Times - 0 = infinity
* Delay Between - miliseconds between any task run
* Auto Start - if true, the task starts after creating
* Returns Task Asyncer reference
*/
	UFUNCTION(BlueprintCallable, Category = "Async Tasks", Meta = (DefaultToSelf = "objectWithFunctuion"))
		static void CreateAsyncTaskWithFunctionName(UObject *objectWithFunction, FName functionName, FCallbackDelegate callback, UAsyncLibrary *&Asyncer, int execsTimes, float delayBetween, bool autoStart = true);
/*
* Run the Task if it not running.
*/
	UFUNCTION(BlueprintCallable, Category = "Async Tasks")
		void StartAsyncTask();
	//UFUNCTION(BlueprintCallable, Category = "Async Tasks")//, Meta = (ExpandEnumAsExecs = "Execs")
		//void AsyncTaskWork(int execsTimes, float delayBetween); //, bool execFirst, bool execLast, EAsyncExecs &Execs
	//UFUNCTION(BlueprintCallable, Category = "Async Tasks")
	//	void AsyncTaskDoOnce();
};