//! \~\file pifiletransfer.h
//! \~\ingroup IO-Utils
//! \~\brief
//! \~english Class for sending and receiving files and directories via \a PIBaseTransfer
//! \~russian Класс для отправки и приема файлов и папок с помощью \a PIBaseTransfer
//! \~\details
//! \~english PIFileTransfer provides functionality for transferring files and directories over a network using PIBaseTransfer protocol
//! \~russian PIFileTransfer предоставляет функциональность для передачи файлов и папок по сети с использованием протокола PIBaseTransfer
/*
    PIP - Platform Independent Primitives
    Class for send and receive files and directories via PIBaseTransfer
    Andrey Bychkov work.a.b@yandex.ru

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef PIFILETRANSFER_H
#define PIFILETRANSFER_H

#include "pibasetransfer.h"
#include "pidir.h"

#define __PIFILETRANSFER_VERSION 2


//! \~\ingroup IO-Utils
//! \~\brief
//! \~english Class for sending and receiving files and directories via \a PIBaseTransfer
//! \~russian Класс для отправки и приема файлов и папок с помощью \a PIBaseTransfer
class PIP_EXPORT PIFileTransfer: public PIBaseTransfer {
	PIOBJECT_SUBCLASS(PIFileTransfer, PIBaseTransfer);

public:
	//! \~english Constructs empty file transfer
	//! \~russian Создает пустой transfer файлов
	PIFileTransfer();

	//! \~english Destructor
	//! \~russian Деструктор
	~PIFileTransfer();


	//! \~english Stage of the file-transfer protocol.
	//! \~russian Этап протокола передачи файлов.
	enum StepType {
		pft_None /** \~english No active stage \~russian Активный этап отсутствует */,
		pft_Description /** \~english Exchange file list and metadata \~russian Обмен списком файлов и метаданными */,
		pft_Data /** \~english Exchange file data blocks \~russian Обмен блоками данных файлов */
	};

	//! \~english File-system entry description extended with destination relative path.
	//! \~russian Описание элемента файловой системы, расширенное относительным путем назначения.
	struct PIP_EXPORT PFTFileInfo: public PIFile::FileInfo {
		//! \~english Constructs transferable file info from base \a PIFile::FileInfo.
		//! \~russian Создает передаваемое описание файла из базового \a PIFile::FileInfo.
		PFTFileInfo(const PIFile::FileInfo & fi = PIFile::FileInfo()): PIFile::FileInfo(fi) {}
		//! \~english Relative destination path on the receiver side.
		//! \~russian Относительный путь назначения на стороне приемника.
		PIString dest_path;
	};

#pragma pack(push, 1)

	//! \~english Custom packet header used by the file-transfer protocol.
	//! \~russian Пользовательский заголовок пакета, используемый протоколом передачи файлов.
	struct PIP_EXPORT PFTHeader {
		union {
			struct {
				//! \~english Three-byte protocol signature "PFT".
				//! \~russian Трехбайтовая сигнатура протокола "PFT".
				char sig[3]; // PFT
				//! \~english Protocol version stored in the packet.
				//! \~russian Версия протокола, сохраненная в пакете.
				uchar version;
			};
			//! \~english Raw 32-bit representation of signature and version.
			//! \~russian Сырое 32-битное представление сигнатуры и версии.
			uint raw_sig;
		};

		//! \~english Current transfer step from \a StepType.
		//! \~russian Текущий этап передачи из \a StepType.
		int step;

		//! \~english File-transfer session identifier.
		//! \~russian Идентификатор сессии передачи файлов.
		int session_id;

		//! \~english Check if signature is valid
		//! \~russian Проверка валидности сигнатуры
		bool check_sig() {
			if (sig[0] != sign[0] || sig[1] != sign[1] || sig[2] != sign[2] || version != __PIFILETRANSFER_VERSION) return false;
			return true;
		}
	};
#pragma pack(pop)


	//! \~english Sends one file-system entry identified by "file".
	//! \~russian Отправляет один элемент файловой системы, заданный "file".
	bool send(const PIFile & file);

	//! \~english Sends one file or directory by path.
	//! \~russian Отправляет один файл или каталог по пути.
	bool send(const PIString & file);

	//! \~english Sends all files or directories listed in "files".
	//! \~russian Отправляет все файлы или каталоги из списка "files".
	bool send(const PIStringList & files);

	//! \~english Sends one file-system entry described by "entry".
	//! \~russian Отправляет один элемент файловой системы, описанный "entry".
	bool send(PIFile::FileInfo entry) { return send(PIVector<PIFile::FileInfo>() << entry); }

	//! \~english Sends file entries from "entries", recursively expanding directories.
	//! \~russian Отправляет элементы файловой системы из "entries", рекурсивно раскрывая каталоги.
	bool send(PIVector<PIFile::FileInfo> entries);


	//! \~english Sets destination directory used for received files.
	//! \~russian Устанавливает каталог назначения для принимаемых файлов.
	void setDirectory(const PIDir & d) { dir = d; }

	//! \~english Sets destination directory used for received files by path.
	//! \~russian Устанавливает каталог назначения для принимаемых файлов по пути.
	//! \~russian Установить директорию по пути
	void setDirectory(const PIString & path) { dir.setDir(path); }

	//! \~english Returns destination directory used for received files.
	//! \~russian Возвращает каталог назначения для принимаемых файлов.
	PIDir directory() const { return dir; }


	//! \~english Returns whether the overall file transfer workflow is active.
	//! \~russian Возвращает, активен ли общий процесс передачи файлов.
	bool isStarted() const { return started_; }

	//! \~english Returns current file path or scanning status string.
	//! \~russian Возвращает путь текущего файла или строку состояния сканирования.
	PIString curFile() const;

	//! \~english Returns total size of the current file being processed.
	//! \~russian Возвращает общий размер текущего обрабатываемого файла.
	llong bytesFileAll() const { return bytes_file_all; }

	//! \~english Returns processed size of the current file being processed.
	//! \~russian Возвращает уже обработанный объем текущего файла.
	llong bytesFileCur() const { return bytes_file_cur; }

	//! \~english Get pointer to current file name
	//! \~russian Получить указатель на имя текущего файла
	const PIString * curFile_ptr() const { return &cur_file_string; }

	//! \~english Get pointer to total bytes of current file
	//! \~russian Получить указатель на общее количество байт текущего файла
	const llong * bytesFileAll_ptr() const { return &bytes_file_all; }

	//! \~english Get pointer to current bytes of current file
	//! \~russian Получить указатель на текущие байты текущего файла
	const llong * bytesFileCur_ptr() const { return &bytes_file_cur; }

	//! \events
	//! \{

	//! \~\fn  void receiveFilesStarted()
	//! \~english Emitted when a new incoming file-transfer workflow starts.
	//! \~russian Генерируется при запуске нового входящего процесса передачи файлов.
	EVENT(receiveFilesStarted);

	//! \~\fn  void receiveFilesFinished(bool ok)
	//! \~english Emitted when receiving files finishes with result "ok".
	//! \~russian Генерируется, когда прием файлов завершается с результатом "ok".
	EVENT1(receiveFilesFinished, bool, ok);

	//! \~\fn  void sendFilesStarted()
	//! \~english Emitted when preparing and sending files starts.
	//! \~russian Генерируется при начале подготовки и отправки файлов.
	EVENT(sendFilesStarted);

	//! \~\fn  void sendFilesFinished(bool ok)
	//! \~english Emitted when sending files finishes with result "ok".
	//! \~russian Генерируется, когда отправка файлов завершается с результатом "ok".
	EVENT1(sendFilesFinished, bool, ok);

	//! \~\fn  void receiveFilesRequest(PIStringList files, llong total_bytes, bool * ok)
	//! \~english Emitted after the description phase so user code can accept or reject the incoming file set.
	//! \~russian Генерируется после фазы описания, чтобы пользовательский код мог принять или отклонить входящий набор файлов.
	EVENT3(receiveFilesRequest, PIStringList, files, llong, total_bytes, bool *, ok);

	//! \}

private:
	static const char sign[];
	PIVector<PFTFileInfo> files_;
	PIString cur_file_string;
	llong bytes_file_all, bytes_file_cur;
	PFTHeader pftheader;
	PIDir dir;
	PIFile work_file;
	PIByteArray desc;
	PIDir scan_dir;
	bool started_, scanning;
	PIMutex step_mutex;

	bool sendFiles(const PIVector<PFTFileInfo> & files);
	void processFile(int id, ullong start, PIByteArray & data);
	void receivePart(Part fi, PIByteArray ba, PIByteArray pheader) override;
	PIByteArray buildPacket(Part fi) override;
	PIByteArray customHeader() override;
	void beginReceive() override;
	EVENT_HANDLER1(void, send_finished, bool, ok);
	EVENT_HANDLER1(void, receive_finished, bool, ok);
};

//! \~english Binary stream write operator for PFTHeader
//! \~russian Оператор записи в бинарный поток для PFTHeader
BINARY_STREAM_WRITE(PIFileTransfer::PFTHeader) {
	s << v.raw_sig << v.step << v.session_id;
	return s;
}
//! \~english Binary stream read operator for PFTHeader
//! \~russian Оператор чтения из бинарного потока для PFTHeader
BINARY_STREAM_READ(PIFileTransfer::PFTHeader) {
	s >> v.raw_sig >> v.step >> v.session_id;
	return s;
}

//! \~english Binary stream write operator for PFTFileInfo
//! \~russian Оператор записи в бинарный поток для PFTFileInfo
BINARY_STREAM_WRITE(PIFileTransfer::PFTFileInfo) {
	s << v.dest_path << v.size << v.time_access << v.time_modification << v.flags << v.id_user << v.id_group << v.perm_user.raw
	  << v.perm_group.raw << v.perm_other.raw;
	return s;
}
//! \~english Binary stream read operator for PFTFileInfo
//! \~russian Оператор чтения из бинарного потока для PFTFileInfo
BINARY_STREAM_READ(PIFileTransfer::PFTFileInfo) {
	s >> v.dest_path >> v.size >> v.time_access >> v.time_modification >> v.flags >> v.id_user >> v.id_group >> v.perm_user.raw >>
		v.perm_group.raw >> v.perm_other.raw;
	return s;
}

//! \~\relatesalso PICout
//! \~english Output \a PICout operator for PFTFileInfo
//! \~russian Оператор вывода в \a PICout для PFTFileInfo
inline PICout operator<<(PICout s, const PIFileTransfer::PFTFileInfo & v) {
	s.saveAndSetControls(0);
	s << "FileInfo(\"" << v.dest_path << "\", " << PIString::readableSize(v.size) << ", " << v.perm_user.toString() << " "
	  << v.perm_group.toString() << " " << v.perm_other.toString() << ", " << v.time_access.toString() << ", "
	  << v.time_modification.toString() << ", 0x" << PICoutManipulators::Hex << v.flags << ")";
	s.restoreControls();
	return s;
}
#endif // PIFILETRANSFER_H
