#ifndef CFILECLIENT_H
#define CFILECLIENT_H

#include <deque>
#include <string.h>
#include "packet_ids.h"
#include "../../urbackupcommon/fileclient/tcpstack.h"
#include "socket_header.h"
#include "../../Interface/Pipe.h"
#include "../../Interface/File.h"
#include "../../Interface/Mutex.h"
#include "../os_functions.h"

#define TCP_PORT 35621
#define UDP_PORT 35622
#define UDP_SOURCE_PORT 35623


#define BUFFERSIZE 32768
#define NBUFFERS   32
#define NUM_FILECLIENTS 5
#define FILECLIENT_VERSION 36
#define WINDOW_SIZE 512*1024
#define BUFFERSIZE_UDP 4096
//#define DISABLE_WINDOW_SIZE

typedef unsigned int _u32;
typedef int _i32;
typedef unsigned short _u16;
typedef __int64 _u64;

#define SERVER_TIMEOUT_BACKUPPED 120000
#define SERVER_TIMEOUT 120000
#define LOG

class CRData;

class FileClient
{
public:

		class ReconnectionCallback
		{
		public:
			virtual IPipe * new_fileclient_connection(void)=0;
		};

		class NoFreeSpaceCallback
		{
		public:
			virtual bool handle_not_enough_space(const std::string &path)=0;
		};

		enum MetadataQueue
		{
			MetadataQueue_Data,
			MetadataQueue_Metadata,
			MetadataQueue_MetadataAndHash
		};

		class QueueCallback
		{
		public:
			virtual std::string getQueuedFileFull(MetadataQueue& metadata, size_t& folder_items, bool& finish_script, int64& file_id) = 0;
			virtual void unqueueFileFull(const std::string& fn, bool finish_script) = 0;
			virtual void resetQueueFull() = 0;
		};

		class ProgressLogCallback
		{
		public:
			virtual void log_progress(const std::string& fn, int64 total, int64 downloaded, int64 speed_bps) = 0;
		};


		FileClient(bool enable_find_servers, std::string identity, int protocol_version=0, bool add_request_checksums=false,
			FileClient::ReconnectionCallback *reconnection_callback=NULL,
			FileClient::NoFreeSpaceCallback *nofreespace_callback=NULL);
        ~FileClient(void);

		struct SAddrHint
		{
			SAddrHint()
				: zone(0)
			{}

			SAddrHint(const SLookupResult& lookup_res)
				: is_ipv6(lookup_res.is_ipv6)
			{
				if (lookup_res.is_ipv6)
				{
					memcpy(addr_ipv6, lookup_res.addr_v6, sizeof(addr_ipv6));
				}
				else
				{
					addr_ipv4 = lookup_res.addr_v4;
				}
			}

			union
			{
				unsigned int addr_ipv4;
				char addr_ipv6[16];
			};
			bool is_ipv6;
			unsigned int zone;

			bool same(const SAddrHint& other) const
			{
				if (is_ipv6 != other.is_ipv6)
					return false;

				if (is_ipv6)
					return memcmp(addr_ipv6, other.addr_ipv6, sizeof(addr_ipv6)) == 0;
				else
					return addr_ipv4 == other.addr_ipv4;
			}

			std::string toString() const;
		};

		_u32 GetServers(bool start, const std::vector<SAddrHint> &addr_hints);
        std::vector<SAddrHint> getServers(void);
        std::vector<SAddrHint> getWrongVersionServers(void);
        std::vector<std::string> getServerNames(void);
        _u32 getLocalIP(void);
        void setServerName(std::string pName);
        std::string getServerName(void);
        int getMaxVersion(void);
        bool isConnected(void);
        bool ListDownloaded(void);

        _u32 Connect(const SAddrHint& addr);
		_u32 Connect(IPipe *cp);
		
		void Shutdown();

        //---needs Connection
        _u32 GetFile(std::string remotefn, IFsFile *file, bool hashed, bool metadata_only, size_t folder_items, bool is_script, size_t file_id);

		//_u32 GetFileHashAndMetadata(std::string remotefn, std::string& hash, std::string& permissions, int64& filesize, int64& created, int64& modified);

		_u32 InformMetadataStreamEnd(const std::string& server_token, int tries);

		_u32 StopPhashLoad(const std::string& server_token, const std::string& phash_fn, int tries);

		_u32 FinishScript(std::string remotefn);

		void addThrottler(IPipeThrottler *throttler);

		_i64 getTransferredBytes(void);

		_i64 getRealTransferredBytes();

		_i64 getReceivedDataBytes(bool with_sparse);

		void resetReceivedDataBytes(bool with_sparse);

		static std::string getErrorString(_u32 ec);

		void setReconnectionTimeout(unsigned int t);

		void setQueueCallback(FileClient::QueueCallback* cb);

		void setProgressLogCallback(FileClient::ProgressLogCallback* cb);

		FileClient::ProgressLogCallback* getProgressLogCallback();

		void setNoFreeSpaceCallback(FileClient::NoFreeSpaceCallback* cb);

		_u32 Flush();

		bool Reconnect(void);

		bool isDownloading();

		IFile* releaseSparseExtendsFile();

		void resetSparseExtentsFile();
              
		static IFile* temporaryFileRetry();

		static bool writeFileRetry(IFile* f, const char* buf, _u32 bsize);

		void setReconnectTries(int tries);
		
		void setAddChecksum(bool add_request_checksums);

private:
		int getReconnectTriesDecr();

		void bindToNewInterfaces();

		_u32 fillQueue();

		void logProgress(const std::string& remotefn, _u64 filesize, _u64 received);

		bool alreadyHasAddrv4(sockaddr_in addr);

		bool alreadyHasAddrv6(sockaddr_in6 addr);

		struct SSocket
		{
			SOCKET udpsock;
			bool is_ipv6;
			union
			{
				sockaddr_in addr_ipv4;
				sockaddr_in6 addr_ipv6;
			};
			sockaddr_in broadcast_addr;
		};

		std::string ipToString(SSocket& s);

        std::vector<SSocket> udpsocks;
        IPipe *tcpsock;

        int64 starttime;
        int64 connect_starttime;

        bool socket_open;
        bool connected;

        char buffer[BUFFERSIZE_UDP];


        std::vector<SAddrHint> servers;
        std::vector<SAddrHint> wvservers;
        std::vector<std::string> servernames;
        std::vector<std::string> games;

        CTCPStack stack;

        _u32 local_ip;

        std::string mServerName;

        int max_version;

		SAddrHint server_addr;

	int connection_id;

		int protocol_version;

		_i64 real_transferred_bytes;
		_i64 transferred_bytes;
		std::vector<IPipeThrottler*> throttlers;

		FileClient::ReconnectionCallback *reconnection_callback;
		FileClient::NoFreeSpaceCallback *nofreespace_callback;

		FileClient::QueueCallback* queue_callback;
		unsigned int reconnection_timeout;

		bool retryBindToNewInterfaces;
		
		std::string identity;

		_i64 received_data_bytes;

		IMutex* mutex;

		struct SQueueItem
		{
			SQueueItem(std::string fn, bool finish_script)
				: fn(fn), finish_script(finish_script)
			{

			}

			std::string fn;
			bool finish_script;
		};

		std::deque<SQueueItem> queued;

		char dl_buf[BUFFERSIZE];
		size_t dl_off;

		int64 last_transferred_bytes;
		int64 last_progress_log;
		FileClient::ProgressLogCallback* progress_log_callback;

		bool needs_flush;

		bool is_downloading;

		IFile* sparse_extends_f;

		_i64 sparse_bytes;

		int reconnect_tries;
};

const _u32 ERR_CONTINUE=0;
const _u32 ERR_SUCCESS=1;
const _u32 ERR_TIMEOUT=2;
const _u32 ERR_CANNOT_OPEN_FILE=3;
const _u32 ERR_SOCKET_ERROR=4;
const _u32 ERR_CONNECTED=5;
const _u32 ERR_ERROR=6;
const _u32 ERR_BASE_DIR_LOST=7;
const _u32 ERR_HASH=8;
const _u32 ERR_INT_ERROR=9;
const _u32 ERR_CONN_LOST=10;
const _u32 ERR_ERRORCODES=11;
const _u32 ERR_READ_ERROR = 12;

const _u32 sleeptime=50;

#endif
