/*! \file pipeer.h
 * \brief Peering net node
*/
/*
    PIP - Platform Independent Primitives
    Peer - named I/O ethernet node, forming self-organized peering network
    Copyright (C) 2014  Ivan Pelipenko peri4ko@gmail.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU 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 General Public License for more details.

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

#ifndef PIPEER_H
#define PIPEER_H

#include "piethernet.h"
#include "pidiagnostics.h"

class PIP_EXPORT PIPeer: public PIObject
{
	PIOBJECT(PIPeer)
private:
	struct PeerData {
		PeerData() {msg_count = msg_rec = 0;}
		void clear() {msg_count = msg_rec = 0; data.clear();}
		bool isEmpty() const {return msg_count == 0;}
		bool isFullReceived() const {return msg_count == msg_rec;}
		void addData(const PIByteArray & ba) {data.append(ba); msg_rec++;}
		void setData(const PIByteArray & ba) {data = ba; msg_rec = 0; msg_count = (data.size_s() - 1) / 4096 + 1;}
		PIByteArray data;
		int msg_count;
		int msg_rec;
	};
	
public:
	PIPeer(const PIString & name);
	virtual ~PIPeer();

	class PeerInfo {
		friend class PIPeer;
		friend PIByteArray & operator <<(PIByteArray & s, const PIPeer::PeerInfo & v);
		friend PIByteArray & operator >>(PIByteArray & s, PIPeer::PeerInfo & v);
	public:
		PeerInfo() {dist = sync = ping = 0; _neth = 0; _first = 0;}
		
		PIString name;
		PIStringList addresses;
		int dist;
		int ping;
		
		bool isNeighbour() const {return dist == 0;}
		
	protected:
		void addNeighbour(const PIString & n) {if (!neighbours.contains(n)) neighbours << n;}
		void addNeighbours(const PIStringList & l) {piForeachC (PIString & n, l) if (!neighbours.contains(n)) neighbours << n;}
		void removeNeighbour(const PIString & n) {neighbours.removeAll(n);}
		
		PIString nearest_address;
		PIStringList netmasks;
		PIStringList neighbours;
		int sync;
		PIString _naddress;
		PIEthernet * _neth;
		PIVector<uchar> _nuses;
		PeerInfo * _first;
		PeerData _data;
		
	};
	
	friend PIByteArray & operator <<(PIByteArray & s, const PIPeer::PeerInfo & v);
	friend PIByteArray & operator >>(PIByteArray & s, PIPeer::PeerInfo & v);

	bool send(const PIString & to, const PIByteArray & data) {return send(to, data.data(), data.size_s());}
	bool send(const PIString & to, const PIString & data) {return send(to, data.data(), data.size_s());}
	bool send(const PIString & to, const void * data, int size);
	bool send(const PeerInfo & to, const PIByteArray & data) {return send(to.name, data.data(), data.size_s());}
	bool send(const PeerInfo & to, const PIString & data) {return send(to.name, data.data(), data.size_s());}
	bool send(const PeerInfo & to, const void * data, int size) {return send(to.name, data, size);}
	bool send(const PeerInfo * to, const PIByteArray & data) {if (to == 0) return false; return send(to->name, data.data(), data.size_s());}
	bool send(const PeerInfo * to, const PIString & data) {if (to == 0) return false; return send(to->name, data.data(), data.size_s());}
	bool send(const PeerInfo * to, const void * data, int size) {if (to == 0) return false; return send(to->name, data, size);}
	void sendToAll(const PIByteArray & data) {piForeachC (PeerInfo & i, peers) send(i.name, data.data(), data.size_s());}
	void sendToAll(const PIString & data) {piForeachC (PeerInfo & i, peers) send(i.name, data.data(), data.size_s());}
	void sendToAll(const void * data, int size) {piForeachC (PeerInfo & i, peers) send(i.name, data, size);}
	
	bool isMulticastReceive() const {return rec_mc;}
	bool isBroadcastReceive() const {return rec_bc;}
	
	PIDiagnostics & diagnosticService() {return diag_s;}
	PIDiagnostics & diagnosticData() {return diag_d;}
	
	const PIVector<PIPeer::PeerInfo> & allPeers() const {return peers;}
	bool isPeerExists(const PIString & name) const {return getPeerByName(name) != 0;}
	
	const PeerInfo * getPeerByName(const PIString & name) const {piForeachC (PeerInfo & i, peers) if (i.name == name) return &i; return 0;}
	
	void lock() {mc_mutex.lock();}
	void unlock() {mc_mutex.unlock();}
	
	EVENT2(dataReceivedEvent, const PIString &, from, const PIByteArray &, data);
	EVENT1(peerConnectedEvent, const PIString &, name);
	EVENT1(peerDisconnectedEvent, const PIString &, name);
	
protected:
	virtual void dataReceived(const PIString & from, const PIByteArray & data) {;}
	virtual void peerConnected(const PIString & name) {;}
	virtual void peerDisconnected(const PIString & name) {;}
	
	EVENT_HANDLER2(bool, dataRead, uchar *, readed, int, size);
	EVENT_HANDLER2(bool, multicastRead, uchar *, readed, int, size);

private:
	EVENT_HANDLER2(void, timerEvent, void * , data, int, delim);

	bool hasPeer(const PIString & name) {piForeachC (PeerInfo & i, peers) if (i.name == name) return true; return false;}
	bool removePeer(const PIString & name) {for (uint i = 0; i < peers.size(); ++i) if (peers[i].name == name) {peers.remove(i); return true;} return false;}

	void sendPeerInfo(const PeerInfo & info);
	void sendPeerRemove(const PIString & peer);
	void sendSelfInfo() {sendPeerInfo(self_info);}
	void sendSelfRemove() {sendPeerRemove(name());}
	void syncPeers();
	void findNearestAddresses();
	void initEths(const PIStringList & al);
	void initMulticasts(const PIStringList & al);
	void destroyMulticasts();
	void sendMulticast(const PIByteArray & ba);
	
	PeerInfo * quickestPeer(const PIString & to);
	bool sendToNeighbour(PeerInfo * peer, const PIByteArray & ba);

	struct PeerPacket {
		int header; // 1 - new peer, 2 - remove peer, 3 - sync peers, 4 - data
		// Data packet: 4, from, to, ticks, data_size, data
	};

	typedef std::pair<PIString, PIVector<PeerInfo * > > napair;
	
	PIVector<PIEthernet * > eths;
	PIVector<PIEthernet * > mc_eths;
	PIEthernet * eth_send;
	PITimer timer;
	PIMutex mc_mutex, eth_mutex;
	bool rec_mc, rec_bc;

	PeerInfo self_info;
	PIVector<PeerInfo> peers;
	PIMap<PIString, PIVector<PeerInfo * > > addresses_map; // map {"to" = list of nearest peers}
	PIDiagnostics diag_s, diag_d;
	//PIString id_;

};

inline PIByteArray & operator <<(PIByteArray & s, const PIPeer::PeerInfo & v) {s << v.name << v.addresses << v.netmasks << v.dist << v.neighbours; return s;}
inline PIByteArray & operator >>(PIByteArray & s, PIPeer::PeerInfo & v) {s >> v.name >> v.addresses >> v.netmasks >> v.dist >> v.neighbours; return s;}

#endif // PIPEER_H
