#ifndef _DATA_DOWNLOADER_H_
#define _DATA_DOWNLOADER_H_

#include <unordered_map>
#include <vector>
#include <memory>
#include <future>
#include <atomic>
#include <thread>
#include <mutex>
#include <list>
#include <set>
#include <functional>
#include <chrono>

#include "./Strings/MyString.h"

class DownloadJob;
struct CURLMsg;

typedef size_t DownloadID;
typedef void CURL;
typedef void CURLM;

#ifdef _WIN32
typedef long long curl_off_t;
#elif __ANDROID_API__
#include "curl/curlbuild.h" // typedef is here
#else
typedef long curl_off_t;
#endif


//===================================================================================

class DataDownloader 
{
public:
	typedef enum DATA_TYPE 
	{
		BINARY = 0,
		TEXT = 1
	} DATA_TYPE;

	DataDownloader();
	~DataDownloader();

    void SetIsInternetConnectionAvailable(bool val);
    bool IsInternetConnectionAvailable() const;
    
    
	std::shared_ptr<DownloadJob> AddDownload(const MyStringAnsi & url);
	std::shared_ptr<DownloadJob> AddDownload(const MyStringAnsi & url, const MyStringAnsi & name);
	std::shared_ptr<DownloadJob> AddDownload(const MyStringAnsi & url, const MyStringAnsi & name,
		std::function<void(std::shared_ptr<DownloadJob>)> onFinish, DATA_TYPE dataType);
	std::shared_ptr<DownloadJob> AddDownload(const MyStringAnsi & url, const MyStringAnsi & name,
		std::function<void(std::shared_ptr<DownloadJob>)> onFinish, 
		std::function<void(std::shared_ptr<DownloadJob>)> onFinishMainThread, DATA_TYPE dataType);
	
    void SetDownloadBeginCallback(std::function<void()> dlBeginCallback);
    void SetDownloadEndedCallback(std::function<void()> dlEndedCallback);
    void SetNoInternetConnectionFailureCallback(std::function<void()> connectionFailureCallback,
                                                std::function<void()> connectionBackCallback);
    
	bool IsDownloading() const;
	bool IsDownloading(DownloadID downloadID) const;

	void TryStart();
	void WaitToFinish(DownloadID downloadID);

	bool Exist(DownloadID downloadID) const;
	bool ExistURL(const MyStringAnsi & url) const;
		
	std::shared_ptr<DownloadJob> GetDownloadJob(DownloadID downloadID) const;
	

	void UpdateMainThread();
	

	friend class DownloadJob;

protected:
	static const std::vector<char> EMPTY_DATA;
	
    bool internetConnectionAvailable;
    std::function<void()> dlBeginCallback;
    std::function<void()> dlEndedCallback;
    std::function<void()> connectionFailureCallback;
    std::function<void()> connectionBackCallback;
    
	CURLM * curlm;
	std::thread dlThread;
	std::mutex dlThreadLock;

	std::atomic<bool> running;	
	std::unordered_map<DownloadID, std::shared_ptr<DownloadJob>> jobs;
	std::set<MyStringAnsi> activeURLs;
	std::list<std::shared_ptr<DownloadJob>> jobQueue;
	DownloadID lastID;
    
    //nemelo by to byt atomic?
    std::atomic<std::chrono::time_point<std::chrono::high_resolution_clock>> lastFailureTime;

	void Start();
	int UpdateBackgroundThread();
    void DownloadFailedBackgroundThread(std::shared_ptr<DownloadJob> job, CURLMsg * msg);
    void DownloadOKBackgroundThread(std::shared_ptr<DownloadJob> job, CURLMsg * msg);
    
	void RemoveFinishedDownloads();
    bool CanDownloadAfterFailure() const;
};

//===================================================================================



class DownloadJob
{
public:
	const DownloadID id;
	
	~DownloadJob();
	
	void RemoveFromDataDownloader();

	int GetResultStatus() const { return this->resCode; }

	bool IsFinished() const { return (finished.load()); }
	void Cancel();
	const MyStringAnsi & GetName() const { return this->name; }
    const MyStringAnsi & GetURL() const { return this->url; }
	const std::vector<char> & GetData() const { return this->data; }

	friend class DataDownloader;
	//friend static int curlWriteData(char *data, size_t size, size_t nmemb, void * j);

protected:	
	DownloadJob(const MyStringAnsi & url, const MyStringAnsi & name, DownloadID id, DataDownloader * dd);
	

	MyStringAnsi url;
	MyStringAnsi name;
	std::vector<char> data;
	std::atomic<bool> finished;
	DataDownloader * dd;
	CURL * curl;
	int resCode;
	bool shouldCancel;
	std::function<void(std::shared_ptr<DownloadJob>)> onFinish;
	std::function<void(std::shared_ptr<DownloadJob>)> onFinishMainThread;
	DataDownloader::DATA_TYPE dataType;

	static size_t curlWriteData(char *data, size_t size, size_t nmemb, void * j);
	static size_t curlDownloadInfo(void *j, curl_off_t dltotal, curl_off_t dlnow,
		curl_off_t ultotal, curl_off_t ulnow);

	int Start();
	void Kill();

};

#endif

