/*! \file pidiagnostics.h
 * \brief Connection quality diagnostics
*/
/*
	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 "pitimer.h"
#include "piqueue.h"


class PIP_EXPORT PIDiagnostics: public PITimer
{
	PIOBJECT_SUBCLASS(PIDiagnostics, PITimer)
	friend class PIConnection;
public:
	NO_COPY_CLASS(PIDiagnostics)
	
	//! Constructs an empty diagnostics and if "start_" start it
	PIDiagnostics(bool start_ = true);
	
	virtual ~PIDiagnostics();

	//! Connection quality
	enum Quality {
		Unknown /** Unknown, no one packet received yet */ = 1,
		Failure /** No connection, no one correct packet received for last period */ = 2,
		Bad /** Bad connection, correct packets received <= 20% */ = 3,
		Average /** Average connection, correct packets received > 20% and <= 80% */ = 4,
		Good /** Good connection, correct packets received > 80% */ = 5
	};

	//! Information about current diagnostics state
	struct PIP_EXPORT State {
		State();
		float immediate_freq;
		float integral_freq;
		ullong received_packets_per_sec;
		ullong received_packets;
		ullong received_packets_wrong;
		ullong received_bytes_per_sec;
		ullong received_bytes;
		ullong received_bytes_wrong;
		ullong sended_packets_per_sec;
		ullong sended_packets;
		ullong sended_bytes_per_sec;
		ullong sended_bytes;
		PIString receive_speed;
		PIString send_speed;
		PIDiagnostics::Quality quality;
	};
	

	//! Returns current state
	PIDiagnostics::State state() const;
	
	//! Returns period of full disconnect in seconds and period of averaging frequency
	float disconnectTimeout() const {return disconn_;}
	
	//! Returns period of full disconnect in seconds and period of averaging frequency
	void setDisconnectTimeout(float s) {setProperty("disconnectTimeout", s);}
	
	//! Returns connection quality
	PIDiagnostics::Quality quality() const;
	
	//! Returns receive speed in format "n {B|kB|MB|GB|TB}/s"
	PIString receiveSpeed() const;
	
	//! Returns send speed in format "n {B|kB|MB|GB|TB}/s"
	PIString sendSpeed() const;
	
	
	EVENT_HANDLER0(void, start) {PITimer::start(100.); changeDisconnectTimeout(disconn_);}
	EVENT_HANDLER1(void, start, double, msecs) {if (msecs > 0.) {PITimer::start(msecs); changeDisconnectTimeout(disconn_);}}
	EVENT_HANDLER0(void, reset);
	
	EVENT_HANDLER1(void, received, int, size) {received(size, true);}
	EVENT_HANDLER2(void, received, int, size, bool, correct);
	EVENT_HANDLER1(void, sended, int, size);
	
	EVENT2(qualityChanged, PIDiagnostics::Quality, new_quality, PIDiagnostics::Quality, old_quality)
	
//! \handlers
//! \{
	
	//! \fn void start(double msecs = 1000.)
	//! \brief Start diagnostics evaluations with period "msecs" milliseconds
	
	//! \fn void reset()
	//! \brief Reset diagnostics counters
	
	//! \fn void received(int size, bool correct = true)
	//! \brief Notify diagnostics about "correct" corected received packet
	
	//! \fn void sended(int size)
	//! \brief Notify diagnostics about sended packet
	
//! \}
//! \events
//! \{

	//! \fn void qualityChanged(PIDiagnostics::Quality new_quality, PIDiagnostics::Quality old_quality)
	//! \brief Raise on change receive quality from "old_quality" to "new_quality"

//! \}
	
private:
	struct PIP_EXPORT Entry {
		Entry() {bytes_ok = bytes_fail = 0; cnt_ok = cnt_fail = 0; empty = true;}
		ullong bytes_ok;
		ullong bytes_fail;
		uint cnt_ok;
		uint cnt_fail;
		bool empty;
	};
	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(void *, int);
	Entry calcHistory(PIQueue<Entry> & hist, int & cnt);
	void propertyChanged(const PIString &);
	void changeDisconnectTimeout(float disct);

	PIQueue<Entry> history_rec, history_send;
	float 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
