//! \~\file pidiagnostics.h
//! \~\ingroup IO-Utils
//! \~\brief
//! \~english Connection quality diagnostics
//! \~russian Диагностика качества связи
/*
    PIP - Platform Independent Primitives
    Speed and quality in/out diagnostics
    Andrey Bychkov work.a.b@yandex.ru, Ivan Pelipenko peri4ko@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 PIDIAGNOSTICS_H
#define PIDIAGNOSTICS_H

#include "piqueue.h"
#include "pitimer.h"


//! \~\ingroup IO-Utils
//! \brief
//! \~english Connection diagnostics for packet frequency, throughput and receive quality
//! \~russian Диагностика соединения для частоты пакетов, пропускной способности и качества приема
//! \details
//! \~english This class provides connection quality diagnostics based on packet reception statistics
//! \~russian Класс обеспечивает диагностику качества связи на основе статистики приема пакетов
class PIP_EXPORT PIDiagnostics: public PITimer {
	PIOBJECT_SUBCLASS(PIDiagnostics, PITimer);
	friend class PIConnection;

public:
	NO_COPY_CLASS(PIDiagnostics);

	//! \~english Constructs diagnostics and optionally starts periodic evaluation immediately.
	//! \~russian Создает диагностику и при необходимости сразу запускает периодическую оценку.
	PIDiagnostics(bool start_ = true);

	//! \~english Stops diagnostics updates.
	//! \~russian Останавливает обновление диагностики.
	virtual ~PIDiagnostics();

	//! \~english Receive quality estimated from recent packet history.
	//! \~russian Качество приема, оцениваемое по недавней истории пакетов.
	enum Quality {
		Unknown = 1 /** \~english No receive history yet \~russian История приема еще отсутствует */,
		Failure = 2 /** \~english No correct packets in the recent window \~russian В недавнем окне нет корректных пакетов */,
		Bad     = 3 /** \~english Correct packets are at most 20 percent \~russian Корректных пакетов не более 20 процентов */,
		Average =
			4 /** \~english Correct packets are above 20 and up to 80 percent \~russian Корректных пакетов больше 20 и до 80 процентов */
			,
		Good = 5 /** \~english Correct packets are above 80 percent \~russian Корректных пакетов больше 80 процентов */
	};

	//! \~english Snapshot of current counters and derived statistics.
	//! \~russian Снимок текущих счетчиков и производных статистик.
	struct PIP_EXPORT State {
		//! \~english Constructs zeroed state with formatted speed strings.
		//! \~russian Создает обнуленное состояние с отформатированными строками скоростей.
		State();

		//! \~english Latest receive frequency for the current timer interval.
		//! \~russian Последняя частота приема для текущего интервала таймера.
		float immediate_freq            = 0.f;

		//! \~english Averaged receive frequency over the disconnect window.
		//! \~russian Усредненная частота приема по окну отключения.
		float integral_freq             = 0.f;

		//! \~english Number of correct received packets per second.
		//! \~russian Число корректно принятых пакетов в секунду.
		ullong received_packets_per_sec = 0ull;

		//! \~english Total number of correct received packets.
		//! \~russian Общее число корректно принятых пакетов.
		ullong received_packets         = 0ull;

		//! \~english Total number of incorrect received packets.
		//! \~russian Общее число некорректно принятых пакетов.
		ullong received_packets_wrong   = 0ull;

		//! \~english Number of received bytes per second.
		//! \~russian Число принятых байтов в секунду.
		ullong received_bytes_per_sec   = 0ull;

		//! \~english Total number of correctly received bytes.
		//! \~russian Общее число корректно принятых байтов.
		ullong received_bytes           = 0ull;

		//! \~english Total number of bytes from incorrect packets.
		//! \~russian Общее число байтов из некорректных пакетов.
		ullong received_bytes_wrong     = 0ull;

		//! \~english Number of sent packets per second.
		//! \~russian Число отправленных пакетов в секунду.
		ullong sended_packets_per_sec   = 0ull;

		//! \~english Total number of sent packets.
		//! \~russian Общее число отправленных пакетов.
		ullong sended_packets           = 0ull;

		//! \~english Number of sent bytes per second.
		//! \~russian Число отправленных байтов в секунду.
		ullong sended_bytes_per_sec     = 0ull;

		//! \~english Total number of sent bytes.
		//! \~russian Общее число отправленных байтов.
		ullong sended_bytes             = 0ull;

		//! \~english Human-readable receive speed string.
		//! \~russian Строка скорости приема в человекочитаемом виде.
		PIString receive_speed;

		//! \~english Human-readable send speed string.
		//! \~russian Строка скорости отправки в человекочитаемом виде.
		PIString send_speed;

		//! \~english Current receive quality category.
		//! \~russian Текущая категория качества приема.
		PIDiagnostics::Quality quality = PIDiagnostics::Unknown;
	};

	//! \~english Returns a thread-safe snapshot of current diagnostics state.
	//! \~russian Возвращает потокобезопасный снимок текущего состояния диагностики.
	PIDiagnostics::State state() const;

	//! \~english Returns the disconnect timeout and averaging window.
	//! \~russian Возвращает таймаут отключения и окно усреднения.
	PISystemTime disconnectTimeout() const { return disconn_; }

	//! \~english Sets the disconnect timeout in seconds.
	//! \~russian Устанавливает таймаут отключения в секундах.
	void setDisconnectTimeout(float s) DEPRECATEDM("use setDisconnectTimeout(PISystemTime)") {
		setDisconnectTimeout(PISystemTime::fromSeconds(s));
	}

	//! \~english Sets the disconnect timeout and averaging window.
	//! \~russian Устанавливает таймаут отключения и окно усреднения.
	void setDisconnectTimeout(PISystemTime tm) { setProperty("disconnectTimeout", tm); }

	//! \~english Returns current receive quality.
	//! \~russian Возвращает текущее качество приема.
	PIDiagnostics::Quality quality() const;

	//! \~english Returns formatted receive throughput string.
	//! \~russian Возвращает строку отформатированной скорости приема.
	PIString receiveSpeed() const;

	//! \~english Returns formatted send throughput string.
	//! \~russian Возвращает строку отформатированной скорости отправки.
	PIString sendSpeed() const;


	//! \handlers
	//! \{

	//! \fn void start()
	//! \~english Starts periodic diagnostics evaluation with the default interval.
	//! \~russian Запускает периодическую оценку диагностики с интервалом по умолчанию.
	EVENT_HANDLER0(void, start);

	//! \fn void start(PISystemTime interval)
	//! \~english Starts periodic diagnostics evaluation with interval "interval".
	//! \~russian Запускает периодическую оценку диагностики с интервалом "interval".
	EVENT_HANDLER1(void, start, PISystemTime, interval);

	//! \fn void reset()
	//! \~english Resets counters, rolling history and derived statistics.
	//! \~russian Сбрасывает счетчики, скользящую историю и производные статистики.
	EVENT_HANDLER0(void, reset);

	//! \fn void received(int size, bool correct = true)
	//! \~english Notifies diagnostics about one received packet of size "size".
	//! \~russian Уведомляет диагностику об одном принятом пакете размером "size".
	EVENT_HANDLER1(void, received, int, size) { received(size, true); }
	EVENT_HANDLER2(void, received, int, size, bool, correct);

	//! \fn void sended(int size)
	//! \~english Notifies diagnostics about one sent packet of size "size".
	//! \~russian Уведомляет диагностику об одном отправленном пакете размером "size".
	EVENT_HANDLER1(void, sended, int, size);

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

	//! \fn void qualityChanged(PIDiagnostics::Quality new_quality, PIDiagnostics::Quality old_quality)
	//! \~english Emitted when receive quality changes from "old_quality" to "new_quality".
	//! \~russian Генерируется при изменении качества приема с "old_quality" на "new_quality".
	EVENT2(qualityChanged, PIDiagnostics::Quality, new_quality, PIDiagnostics::Quality, old_quality);

	//! \}

private:
	struct PIP_EXPORT Entry {
		ullong bytes_ok   = 0;
		ullong bytes_fail = 0;
		uint cnt_ok       = 0;
		uint cnt_fail     = 0;
		bool empty        = true;
	};
	friend bool operator==(const PIDiagnostics::Entry & f, const PIDiagnostics::Entry & s);
	friend bool operator!=(const PIDiagnostics::Entry & f, const PIDiagnostics::Entry & s);
	friend bool operator<(const PIDiagnostics::Entry & f, const PIDiagnostics::Entry & s);

	void tick(int) override;
	Entry calcHistory(PIQueue<Entry> & hist, int & cnt);
	void propertyChanged(const char *) override;
	void changeDisconnectTimeout(PISystemTime disct);

	PIQueue<Entry> history_rec, history_send;
	PISystemTime disconn_;
	State cur_state;
	mutable PIMutex mutex_state;
};

inline bool operator==(const PIDiagnostics::Entry & f, const PIDiagnostics::Entry & s) {
	return f.bytes_ok == s.bytes_ok && f.bytes_fail == s.bytes_fail && f.cnt_ok == s.cnt_ok && f.cnt_fail == s.cnt_fail &&
	       f.empty == s.empty;
}
inline bool operator!=(const PIDiagnostics::Entry & f, const PIDiagnostics::Entry & s) {
	return !(f == s);
}
inline bool operator<(const PIDiagnostics::Entry & f, const PIDiagnostics::Entry & s) {
	return f.bytes_ok < s.bytes_ok;
}

#endif // PIDIAGNOSTICS_H
