/*! \file piconnection.h
 * \brief Complex I/O point
*/
/*
    PIP - Platform Independent Primitives
    Complex I/O point
    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 PICONNECTION_H
#define PICONNECTION_H

#include "pipacketextractor.h"


class PIP_EXPORT PIConnection: public PIObject
{
	PIOBJECT(PIConnection)
public:
	
	//! Constructs an empty connection
	PIConnection();
	
	//! Constructs connection and configure it from config file "config" from section "name"
	PIConnection(const PIString & config, const PIString & name);

	~PIConnection();
	
	
	/*! \brief Configure connection from config file "config" from section "name". Returns if configuration was successful
	 * \details \b Warning: all devices, filters and channels removed before configure! */
	bool configureFromConfig(const PIString & config, const PIString & name);
	
	//! Returns config file section of current connection configuration
	PIString makeConfig() const;
	
	
	/*! \brief Add device with full path "full_path", open mode "mode" to Device pool and connection
	 * \details Returns pointer to device or null if device can not be created. If "start" is true,
	 * read thread is started immediately. Else, you can start read thread with functions \a startThreadedRead()
	 * or \a startAllThreadedReads(). By default, read thread doesn`t start */
	PIIODevice * addDevice(const PIString & full_path, PIIODevice::DeviceMode mode = PIIODevice::ReadWrite, bool start = false);
	
	/*! \brief Remove device with full path "full_path" from connection
	 * \details Returns if device was removed. If there is no connection bounded to this device,
	 * it will be removed from Device pool */
	bool removeDevice(const PIString & full_path);
	
	/*! \brief Remove all device from connection
	 * \details If there is no connection bounded to there devices, they removed from Device pool */
	void removeAllDevices();
	
	//! Returns device with full path "full_path" or null if there is no such device
	PIIODevice * device(const PIString & full_path) const;
	
	//! Returns all devices bounded to this connection
	PIVector<PIIODevice * > boundedDevices() const;
	
	
	/*! \brief Add filter with name "name" to device with full path "full_path"
	 * \details If there is no filter with name "name", connection create new with split mode "mode" and bound
	 * to it device "full_path". If filter with name "name" already exists, device "full_path" add to this filter.
	 * \b Attention! "mode" is altual olny if new filter was created!
	 * This function returns PIPacketExtractor * assosiated with this filter */
	PIPacketExtractor * addFilter(const PIString & name, const PIString & full_path, PIPacketExtractor::SplitMode mode = PIPacketExtractor::None);
	
	//! Add filter with name "name" to device "dev"
	PIPacketExtractor * addFilter(const PIString & name, const PIIODevice * dev, PIPacketExtractor::SplitMode mode = PIPacketExtractor::None);
	
	/*! \brief Remove from filter with name "name" device with full path "full_path"
	 * \details If there is no devices bounded to this filter, it will be removed. Returns
	 * if device was removed */
	bool removeFilter(const PIString & name, const PIString & full_path);
	
	//! Remove from filter with name "name" device "dev"
	bool removeFilter(const PIString & name, const PIIODevice * dev);
	
	//! Remove filter with name "name". Returns if filter was removed
	bool removeFilter(const PIString & name);
	
	//! Remove all filters from connection
	void removeAllFilters();
	
	
	//! Returns all filters of connection
	PIVector<PIPacketExtractor * > filters() const;
	
	//! Returns all filter names of connection
	PIStringList filterNames() const;
	
	//! Returns PIPacketExtractor * assosiated with filter "name" or null if there is no such filter
	PIPacketExtractor * filter(const PIString & name) const;
	
	//! Returns all devices bounded to filter "name"
	PIVector<PIIODevice * > filterBoundedDevices(const PIString & name) const;
	
	
	/*! \brief Add to connection channel from "name_from" to "name_to"
	 * \details "name_from" and "name_to" can be full pathes of devices or filter names.
	 * Returns \b false if there if no such device or filter, else create channel and returns \b true */
	bool addChannel(const PIString & name_from, const PIString & name_to);
	
	//! Add to connection channel from "name_from" to "dev_to"
	bool addChannel(const PIString & name_from, const PIIODevice * dev_to) {return addChannel(name_from, devFPath(dev_to));}
	
	//! Add to connection channel from "dev_from" to "name_to"
	bool addChannel(const PIIODevice * dev_from, const PIString & name_to) {return addChannel(devFPath(dev_from), name_to);}
	
	//! Add to connection channel from "dev_from" to "dev_to"
	bool addChannel(const PIIODevice * dev_from, const PIIODevice * dev_to) {return addChannel(devFPath(dev_from), devFPath(dev_to));}
	
	/*! \brief Remove from connection channel from "name_from" to "name_to"
	 * \details "name_from" and "name_to" can be full pathes of devices or filter names.
	 * Returns \b false if there if no such device or filter, else remove channel and returns \b true */
	bool removeChannel(const PIString & name_from, const PIString & name_to);
	
	//! Remove from connection channel from "name_from" to "dev_to"
	bool removeChannel(const PIString & name_from, const PIIODevice * dev_to) {return removeChannel(name_from, devFPath(dev_to));}
	
	//! Remove from connection channel from "dev_from" to "name_to"
	bool removeChannel(const PIIODevice * dev_from, const PIString & name_to) {return removeChannel(devFPath(dev_from), name_to);}
	
	//! Remove from connection channel from "dev_from" to "dev_to"
	bool removeChannel(const PIIODevice * dev_from, const PIIODevice * dev_to) {return removeChannel(devFPath(dev_from), devFPath(dev_to));}
	
	/*! \brief Remove from connection all channels from "name_from"
	 * \details "name_from" can be full path of device or filter name.
	 * Returns \b false if there if no such device or filter, else remove channels and returns \b true */
	bool removeChannel(const PIString & name_from);
	
	//! Remove from connection all channels from "dev_from"
	bool removeChannel(const PIIODevice * dev_from) {return removeChannel(devFPath(dev_from));}
	
	//! Remove from connection all channels
	void removeAllChannels();
	
	//! Returns all channels of this connection as full pathes or filter names pair array (from, to)
	PIVector<PIPair<PIString, PIString> > channels() const;
	
	
	//! Start read thread of device with full path "full_path"
	void startThreadedRead(const PIString & full_path);
	
	//! Start read thread of device "dev"
	void startThreadedRead(const PIIODevice * dev) {startThreadedRead(devFPath(dev));}
	
	//! Start read threads of all Device pool device
	void startAllThreadedReads();
	
	//! Stop read thread of device with full path "full_path"
	void stopThreadedRead(const PIString & full_path);
	
	//! Stop read thread of device "dev"
	void stopThreadedRead(const PIIODevice * dev) {stopThreadedRead(devFPath(dev));}
	
	//! Stop read threads of all Device pool device
	void stopAllThreadedReads();
	
	
	//! Returns if there is no devices in this connection
	bool isEmpty() const {return device_modes.isEmpty();}
	
	
	//! Write data "data" to device with full path "full_path" and returns result of \a write() function of device
	int write(const PIString & full_path, const PIByteArray & data);
	
	
	//! Returns all connections in application
	static PIVector<PIConnection * > allConnections();
	
	//! Returns all devices in Device pool
	static PIVector<PIIODevice * > allDevices();
	
	class DevicePool: public PIObject {
		PIOBJECT(DevicePool)
		friend class PIConnection;
	public:
		DevicePool(): PIObject("PIConnection::DevicePool") {}
		
		PIIODevice * addDevice(PIConnection * parent, const PIString & fp, PIIODevice::DeviceMode mode = PIIODevice::ReadWrite, bool start = true);
		bool removeDevice(PIConnection * parent, const PIString & fp);
		void unboundConnection(PIConnection * parent);
		PIIODevice * device(const PIString & fp) const;
		PIVector<PIConnection * > boundedConnections() const;
		PIVector<PIIODevice * > boundedDevices() const;
		PIVector<PIIODevice * > boundedDevices(const PIConnection * parent) const;
		
	protected:
		struct DeviceData {
			DeviceData(): dev(0), rthread(0), started(false) {}
			~DeviceData();
			PIIODevice * dev;
			PIThread * rthread;
			bool started;
			PIVector<PIConnection * > listeners;
		};
		
		static void threadReadDP(void * ddp);
		void deviceReaded(DeviceData * dd, const PIByteArray & data);
		
		typedef PIMap<PIString, DeviceData * >::value_type DDPair;
		PIMap<PIString, DeviceData * > devices;
	};
	
	EVENT2(dataReceivedEvent, const PIString &, from, const PIByteArray &, data)
	EVENT2(packetReceivedEvent, const PIString &, from, const PIByteArray &, data)
	
//! \events
//! \{
	
	//! \fn void dataReceivedEvent(const PIString & from, const PIByteArray & data)
	//! \brief Raise on data received from device with full path "from"
	
	//! \fn void packetReceivedEvent(const PIString & from, const PIByteArray & data)
	//! \brief Raise on packet received from filter with name "from"
	
//! \}
	
protected:
	
	//! Executes on data received from device with full path "from"
	virtual void dataReceived(const PIString & from, const PIByteArray & data) {}
	
	//! Executes on packet received from filter with name "from"
	virtual void packetReceived(const PIString & from, const PIByteArray & data) {}
	
	//! Validate header "rec" with source header "src" and size "size", executes from filter "filter_name"
	virtual bool filterValidateHeader(const PIString & filter_name, uchar * src, uchar * rec, int size);
	
	//! Validate footer "rec" with source footer "src" and size "size", executes from filter "filter_name"
	virtual bool filterValidateFooter(const PIString & filter_name, uchar * src, uchar * rec, int size);
	
	//! Validate payload "rec" with size "size", executes from filter "filter_name"
	virtual bool filterValidatePayload(const PIString & filter_name, uchar * rec, int size);
	
private:
	static bool filterValidateHeaderS(void * c, uchar * src, uchar * rec, int size);
	static bool filterValidateFooterS(void * c, uchar * src, uchar * rec, int size);
	static bool filterValidatePayloadS(void * c, uchar * rec, int size);
	void rawReceived(PIIODevice * dev, const PIString & from, const PIByteArray & data);
	void unboundExtractor(PIPacketExtractor * pe);
	EVENT_HANDLER2(void, packetExtractorReceived, uchar * , data, int, size);
	
	PIString devPath(const PIIODevice * d) const;
	PIString devFPath(const PIIODevice * d) const;
	
	struct Extractor {
		Extractor(): extractor(0) {}
		~Extractor();
		PIPacketExtractor * extractor;
		PIVector<PIIODevice * > devices;
	};
	
	typedef PIMap<PIString, Extractor * >::value_type PEPair;
	typedef PIMap<PIIODevice * , PIVector<PIPacketExtractor * > >::value_type BEPair;
	typedef PIMap<PIIODevice * , PIVector<PIIODevice * > >::value_type CPair;
	PIMap<PIString, Extractor * > extractors;
	PIMap<PIIODevice * , PIIODevice::DeviceMode> device_modes;
	PIMap<PIIODevice * , PIVector<PIPacketExtractor * > > bounded_extractors;
	PIMap<PIIODevice * , PIVector<PIIODevice * > > channels_;
	
	static PIVector<PIConnection * > _connections;
	
};


extern PIConnection::DevicePool * __device_pool__;

class __DevicePoolContainer__ {
public:
	__DevicePoolContainer__();
	static bool inited_;
};

static __DevicePoolContainer__ __device_pool_container__;


#endif // PICONNECTION_H
