//! \~\file pibasetransfer.h
//! \~\ingroup IO-Utils
//! \~\brief
//! \~english Base class for reliable packet sessions with acknowledgements, pause and resume
//! \~russian Базовый класс для надежных пакетных сессий с подтверждениями, паузой и продолжением
/*
    PIP - Platform Independent Primitives
    Base class for reliable send and receive data in fixed packets with error correction, pause and resume
    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 PIBASETRANSFER_H
#define PIBASETRANSFER_H

#include "picrc.h"
#include "pidiagnostics.h"

#include <atomic>


//! \~\ingroup IO-Utils
//! \~\brief
//! \~english Base transport for reliable fixed-size packet exchange over an external channel.
//! \~russian Базовый транспорт для надежного обмена пакетами фиксированного размера через внешний канал.
//! \~\details
//! \~english This class provides a foundation for reliable data transfer with features like error correction, pause/resume functionality,
//! and packet-based communication.
//! \~russian Этот класс предоставляет основу для надежной передачи данных с возможностью коррекции ошибок, паузы/возобновления и пакетной
//! коммуникации.
class PIP_EXPORT PIBaseTransfer: public PIObject {
	PIOBJECT_SUBCLASS(PIBaseTransfer, PIObject);

public:
	//! \~english Constructs transfer with default packet size, timeout and diagnostics.
	//! \~russian Создает передачу с размером пакета, таймаутом и диагностикой по умолчанию.
	PIBaseTransfer();

	//! \~english Stops active transfer state and owned diagnostics.
	//! \~russian Останавливает активное состояние передачи и встроенную диагностику.
	~PIBaseTransfer();

#pragma pack(push, 1)
	//! \~english Common header placed before every protocol packet.
	//! \~russian Общий заголовок, размещаемый перед каждым пакетом протокола.
	struct PIP_EXPORT PacketHeader {
		//! \~english Packet signature used to recognize transfer packets.
		//! \~russian Сигнатура пакета, используемая для распознавания пакетов передачи.
		uint sig;
		//! \~english Packet type from the transfer protocol.
		//! \~russian Тип пакета из протокола передачи.
		int type; // PacketType
		//! \~english Session identifier for current transfer exchange.
		//! \~russian Идентификатор сессии текущего обмена.
		int session_id;
		//! \~english Sequential packet identifier inside the session.
		//! \~russian Последовательный идентификатор пакета внутри сессии.
		uint id;
		//! \~english CRC calculated for the packet payload.
		//! \~russian CRC, вычисленная для полезной нагрузки пакета.
		uint crc;
		//! \~english Returns whether the packet signature matches \a packetSignature().
		//! \~russian Возвращает, совпадает ли сигнатура пакета с \a packetSignature().
		bool check_sig() { return (sig == signature); }
	};

	//! \~english Logical fragment description used to split data into packets.
	//! \~russian Описание логического фрагмента, используемое для разбиения данных на пакеты.
	struct PIP_EXPORT Part {
		//! \~english Constructs part metadata for item "id_", fragment size "size_" and offset "start_".
		//! \~russian Создает метаданные части для элемента "id_", размера фрагмента "size_" и смещения "start_".
		Part(uint id_ = 0, ullong size_ = 0, ullong start_ = 0): id(id_), size(size_), start(start_) {}
		//! \~english Identifier of the logical item being transferred.
		//! \~russian Идентификатор логического элемента, который передается.
		uint id;
		//! \~english Size of this fragment in bytes.
		//! \~russian Размер этого фрагмента в байтах.
		ullong size;
		//! \~english Byte offset of this fragment inside the logical item.
		//! \~russian Смещение этого фрагмента в байтах внутри логического элемента.
		ullong start;
	};
#pragma pack(pop)


	//! \~english Requests cancellation of the current send session.
	//! \~russian Запрашивает отмену текущей сессии отправки.
	void stopSend();

	//! \~english Requests cancellation of the current receive session.
	//! \~russian Запрашивает отмену текущей сессии приема.
	void stopReceive();


	//! \~english Returns whether a send session is active.
	//! \~russian Возвращает, активна ли сессия отправки.
	bool isSending() const { return is_sending; }

	//! \~english Returns whether a receive session is active.
	//! \~russian Возвращает, активна ли сессия приема.
	bool isReceiving() const { return is_receiving; }

	//! \~english Returns whether the transfer is currently paused.
	//! \~russian Возвращает, находится ли передача сейчас на паузе.
	bool isPause() const { return is_pause; }

	//! \~english Switches current session between paused and resumed states.
	//! \~russian Переключает текущую сессию между состояниями паузы и продолжения.
	void setPause(bool pause_);


	//! \~english Sets maximum encoded packet size in bytes.
	//! \~russian Устанавливает максимальный размер кодированного пакета в байтах.
	void setPacketSize(int size) { packet_size = size; }

	//! \~english Returns maximum encoded packet size in bytes.
	//! \~russian Возвращает максимальный размер кодированного пакета в байтах.
	int packetSize() const { return packet_size; }

	//! \~english Sets session timeout in seconds for start negotiation, acknowledgements and pause recovery.
	//! \~russian Устанавливает таймаут сессии в секундах для согласования старта, подтверждений и восстановления после паузы.
	void setTimeout(double sec);

	//! \~english Returns session timeout in seconds.
	//! \~russian Возвращает таймаут сессии в секундах.
	double timeout() const { return timeout_; }

	//! \~english Enables or disables CRC validation for data packets.
	//! \~russian Включает или выключает проверку CRC для пакетов данных.
	void setCRCEnabled(bool en = true) { crc_enabled = en; }

	//! \~english Returns whether CRC validation is enabled.
	//! \~russian Возвращает, включена ли проверка CRC.
	bool isCRCEnabled() const { return crc_enabled; }

	//! \~english Returns short textual state of the current session.
	//! \~russian Возвращает краткое текстовое состояние текущей сессии.
	PIString stateString() const { return state_string; }

	//! \~english Returns a map of successfully received or transmitted packets for the current session.
	//! \~russian Возвращает карту успешно принятых или переданных пакетов для текущей сессии.
	PIString packetMap() const { return pm_string; }

	//! \~english Returns total number of bytes planned for the current session.
	//! \~russian Возвращает общее число байтов, запланированных для текущей сессии.
	llong bytesAll() const { return bytes_all; }

	//! \~english Returns number of bytes already processed in the current session.
	//! \~russian Возвращает число байтов, уже обработанных в текущей сессии.
	llong bytesCur() const { return bytes_cur; }

	//! \~english Get diagnostics object
	//! \~russian Получить объект диагностики
	//! \~\return
	//! \~english Diagnostic object reference
	//! \~russian Ссылка на объект диагностики
	const PIDiagnostics & diagnostic() { return diag; }

	//! \~english Returns the packet signature constant used by the protocol.
	//! \~russian Возвращает константу сигнатуры пакета, используемую протоколом.
	static uint packetSignature() { return signature; }

	//! \~\handlers
	//! \~\{

	//! \~\fn void received(PIByteArray data)
	//! \~english Processes a single encoded packet received from an external transport.
	//! \~russian Обрабатывает один закодированный пакет, полученный от внешнего транспорта.
	//! \~\note
	//! \~english This handler must be called when data is received by an external transport.
	//! \~russian Этот обработчик необходимо вызывать при получении данных внешним транспортом.
	EVENT_HANDLER1(void, received, PIByteArray, data);

	//! \~\fn void stop()
	//! \~english Stops both sending and receiving sides of the current session.
	//! \~russian Останавливает и отправку, и прием текущей сессии.
	//! \~\note
	//! \~english Equivalent to calling both `stopSend()` and `stopReceive()`.
	//! \~russian Эквивалентно вызову `stopSend()` и `stopReceive()`.
	EVENT_HANDLER(void, stop) {
		stopSend();
		stopReceive();
	}

	//! \~\fn void pause()
	//! \~english Switches current session to paused state.
	//! \~russian Переводит текущую сессию в состояние паузы.
	//! \~\note
	//! \~english Triggers the `paused()` signal.
	//! \~russian Генерирует событие `paused()`.
	EVENT_HANDLER(void, pause) { setPause(true); }

	//! \~\fn void resume()
	//! \~english Resumes the current paused session.
	//! \~russian Продолжает текущую приостановленную сессию.
	//! \~\note
	//! \~english Triggers the `resumed()` signal.
	//! \~russian Генерирует событие `resumed()`.
	EVENT_HANDLER(void, resume) { setPause(false); }

	//! \~\}
	//! \~\events
	//! \~\{

	//! \~\fn void receiveStarted()
	//! \~english Emitted when a receive session is accepted and initialized.
	//! \~russian Генерируется, когда сессия приема принята и инициализирована.
	//! \~\note
	//! \~english Triggered by internal logic after receiving a valid `pt_Start` packet.
	//! \~russian Генерируется внутренней логикой после получения корректного пакета типа `pt_Start`.
	EVENT(receiveStarted);

	//! \~\fn void paused()
	//! \~english Emitted when the transfer enters paused state.
	//! \~russian Генерируется, когда передача переходит в состояние паузы.
	//! \~\note
	//! \~english Triggered when `pause` handler is invoked (e.g., via `setPause(true)` or incoming `pt_Pause`).
	//! \~russian Генерируется при вызове обработчика `pause` (например, через `setPause(true)` или получении `pt_Pause`).
	EVENT(paused);

	//! \~\fn void resumed()
	//! \~english Emitted when the transfer leaves paused state.
	//! \~russian Генерируется, когда передача выходит из состояния паузы.
	//! \~\note
	//! \~english Triggered when `resume` handler is invoked (e.g., via `setPause(false)` or incoming `pt_Start` during pause).
	//! \~russian Генерируется при вызове обработчика `resume` (например, через `setPause(false)` или получении `pt_Start` во время
	//! паузы).
	EVENT(resumed);

	//! \~\fn void receiveFinished(bool ok)
	//! \~english Emitted when the receive session finishes with result "ok".
	//! \~russian Генерируется, когда сессия приема завершается с результатом "ok".
	//! \~\param ok
	//! \~english `true` if all packets were received and verified, `false` on error or interruption.
	//! \~russian `true`, если все пакеты получены и проверены, `false` — при ошибке или прерывании.
	EVENT1(receiveFinished, bool, ok);

	//! \~\fn void sendStarted()
	//! \~english Emitted when a prepared send session starts.
	//! \~russian Генерируется при запуске подготовленной сессии отправки.
	//! \~\note
	//! \~english Triggered after `buildSession()` and before the first packet is sent.
	//! \~russian Генерируется после `buildSession()` и до отправки первого пакета.
	EVENT(sendStarted);

	//! \~\fn void sendFinished(bool ok)
	//! \~english Emitted when the send session finishes with result "ok".
	//! \~russian Генерируется, когда сессия отправки завершается с результатом "ok".
	//! \~\param ok
	//! \~english `true` if all packets were sent and acknowledged, `false` on error or timeout.
	//! \~russian `true`, если все пакеты отправлены и подтверждены, `false` — при ошибке или таймауте.
	EVENT1(sendFinished, bool, ok);

	//! \~\fn void sendRequest(PIByteArray &data)
	//! \~english Emitted for every encoded packet that must be written to the external transport.
	//! \~russian Генерируется для каждого закодированного пакета, который нужно записать во внешний транспорт.
	//! \~\param data
	//! \~english Encoded packet including protocol header and payload.
	//! \~russian Закодированный пакет, включая заголовок протокола и полезную нагрузку.
	//! \~\note
	//! \~english The external transport should send this data.
	//! \~russian Внешний транспорт должен отправить эти данные.
	EVENT1(sendRequest, PIByteArray &, data);

	//! \~\}


protected:
	//! \~english Builds session packet layout for logical parts in "parts".
	//! \~russian Формирует раскладку пакетов сессии для логических частей из "parts".
	void buildSession(PIVector<Part> parts);

	//! \~english Returns payload bytes for one requested logical fragment.
	//! \~russian Возвращает байты полезной нагрузки для одного запрошенного логического фрагмента.
	virtual PIByteArray buildPacket(Part fi)                               = 0;

	//! \~english Consumes one received logical fragment and optional custom packet header.
	//! \~russian Обрабатывает один принятый логический фрагмент и необязательный пользовательский заголовок пакета.
	virtual void receivePart(Part fi, PIByteArray ba, PIByteArray pheader) = 0;

	//! \~english Called after a new receive session is accepted and initialized.
	//! \~russian Вызывается после принятия и инициализации новой сессии приема.
	virtual void beginReceive() { ; }

	//! \~english Custom header
	//! \~russian Пользовательский заголовок
	virtual PIByteArray customHeader() { return PIByteArray(); }

	//! \~english Runs the prepared send session until success, failure or cancellation.
	//! \~russian Выполняет подготовленную сессию отправки до успеха, ошибки или отмены.
	bool send_process();

	uint packet_header_size, part_header_size;
	std::atomic<bool> break_{true}, is_sending{false}, is_receiving{false}, is_pause{false};
	PIString state_string;
	llong bytes_all, bytes_cur;

private:
	enum PacketType {
		pt_Unknown,
		pt_Data,
		pt_ReplySuccess,
		pt_ReplyInvalid,
		pt_Break,
		pt_Start,
		pt_Pause
	};

#pragma pack(push, 1)
	struct PIP_EXPORT StartRequest {
		uint packets;
		ullong size;
	};
#pragma pack(pop)

	BINARY_STREAM_FRIEND(PIBaseTransfer::StartRequest);

	void processData(int id, PIByteArray & data);
	PIByteArray build_packet(int id);
	int checkSession();
	void sendBreak(int session_id);
	void sendReply(PacketType reply);
	bool getStartRequest();
	bool finish_send(bool ok);
	void finish_receive(bool ok, bool quet = false);

	static const uint signature;
	int packet_size, packets_count;
	double timeout_;
	PIVector<PIVector<Part>> session;
	PIVector<PacketType> replies;
	PITimeMeasurer send_tm, pause_tm;
	PacketHeader header;
	CRC_16 crc;
	int send_queue;
	int send_up;
	PIDiagnostics diag;
	PIMutex mutex_session;
	PIMutex mutex_send;
	PIMutex mutex_header;
	bool crc_enabled;
	PIString pm_string;
};


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

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

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

//! \~\relatesalso PICout
//! \~english Writes Part to \a PICout.
//! \~russian Выводит Part в \a PICout.
inline PICout operator<<(PICout s, const PIBaseTransfer::Part & v) {
	s.saveAndSetControls(0);
	s << "Part(\"" << v.id << "\", " << PIString::readableSize(v.start) << " b | " << PIString::readableSize(v.size) << " b)";
	s.restoreControls();
	return s;
}


#endif // PIBASETRANSFER_H
