/*
    PIP - Platform Independent Primitives
    Complex I/O point
    Copyright (C) 2015  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/>.
*/

#include "piconnection.h"
#include "piconfig.h"

/** \class PIConnection
 * \brief Complex Input/Output point
 * 
 * \section PIConnection_synopsis Synopsis
 * %PIConnection provides abstract layer over physical devices,
 * filtering and connecting data streams. Each %PIConnection
 * works through Device Pool, so several %PIConnections can
 * read from single physical device. General scheme:
 * \image html piconnection.png
 * 
 * \section PIConnection_pool Device pool concept
 * Device pool is static object, single for each application, which
 * contains unique devices. Each %PIConnection works with real devices
 * through Device pool. Each device has assosiated thread for read
 * and it can be started or stopped with %PIConnection functions
 * \a startThreadedRead() and \a stopThreadedRead().
 * 
 * \section PIConnection_filters Filters
 * %PIConnection filter is a PIPacketExtractor and assosiated
 * array of devices or other filters. When read thread is successfully read
 * from device this data can be passed to one or more filters. Each filter
 * has name and filter names should be unique. You can use this name for
 * access to PIPacketExtractor* with function \a filter(), or get array of
 * assosiated devices and filters with function \a filterBoundedDevices().
 * One filter can receive data from several sources, and can be bounded to
 * several filters.
 * \image html piconnection_filters.png
 * 
 * \section PIConnection_diag Diagnostics
 * %PIConnection create PIDiagnostics for each device or filter. You can
 * access to these objects with functions \a diagnostic().
 * 
 * \section PIConnection_sender Senders
 * %PIConnection can send data to devices with named timers ("senders").
 * You can create sender or add device to sender with function \a addSender().
 * Each sender has internal timer and every tick execute virtual function
 * \a senderData(). Returns value of this function sended to bounded devices.
 * You can assign fixed send data to sender with function \a setSenderFixedData().
 * In this case sender will NOT execute \a senderData(), but send assigned data.
 * \image html piconnection_senders.png
 * 
 * \section PIConnection_config Configuration
 * You can create %PIConnection from config file section or configure
 * it later with function \a configureFromConfig(). Devices describes
 * with its full pathes, for details see \ref PIIODevice_sec7. Example:
 * \image html piconnection_conf.png
 * Also %PIConnection can create PIString with its configuration with
 * function \a makeConfig(). This string can be directly inserted into the
 * config file.
 * 
*/


PIVector<PIConnection * > PIConnection::_connections;


PIConnection::PIConnection(): PIObject() {
	_connections << this;
}


PIConnection::PIConnection(const PIString & name): PIObject(name) {
	_connections << this;
}


PIConnection::PIConnection(const PIString & config, const PIString & name_): PIObject(name_) {
	_connections << this;
	configureFromConfig(config, name_);
}


PIConnection::PIConnection(PIString * string, const PIString & name_): PIObject(name_) {
	_connections << this;
	configureFromString(string, name_);
}


PIConnection::~PIConnection() {
	__device_pool__->unboundConnection(this);
	removeAllFilters();
	_connections.removeAll(this);
}


bool PIConnection::configureFromConfig(const PIString & conf_path, const PIString & name_) {
	PIConfig conf(conf_path, PIIODevice::ReadOnly);
	return configure(conf, name_);
}


bool PIConnection::configureFromString(PIString * string, const PIString & name_) {
	PIConfig conf(string, PIIODevice::ReadOnly);
	return configure(conf, name_);
}


bool PIConnection::configure(PIConfig & conf, const PIString & name_) {
	if (!conf.isOpened()) return false;
	__device_pool__->unboundConnection(this);
	removeAllSenders();
	removeAllChannels();
	removeAllFilters();
	removeAllDevices();
	setName(name_);
	PIConfig::Entry ce(conf.getValue(name_));
	PIConfig::Branch db(ce.getValue("device").children()), fb(ce.getValue("filter").children()),
					 cb(ce.getValue("channel").children()), sb(ce.getValue("sender").children());
	PIStringList dev_list(ce.getValue("device").value());
	PIStringList name_list(ce.getValue("device").name());
	piForeachC (PIConfig::Entry * e, db) {
		dev_list << e->value();
		name_list << e->name();
	}
	PIMap<PIString, PIString> dev_aliases;
	for (int i = 0; i < dev_list.size_s(); ++i) {
		PIString fn(dev_list[i]);
		if (fn.isEmpty()) continue;
		PIString & n(name_list[i]);
		PIIODevice::DeviceMode dm = PIIODevice::ReadWrite;
		splitFullPathWithMode(fn, &fn, &dm);
		//piCout << fn;
		PIIODevice * dev = addDevice(fn, dm);
		if (!dev) continue;
		dev_aliases[n] = fn;
		device_names[n] = dev;
		setDeviceName(dev, n);
		dev->setName(name_ + ".device." + dev_list[i]);
		PIDiagnostics * diag = diags_.value(dev, 0);
		if (diag != 0)
			diag->setDisconnectTimeout(ce.getValue("device." + n + ".disconnectTimeout", diag->disconnectTimeout()));
	}
	int added(0), padded(-1), tries(0);
	bool pdebug = debug();
	setDebug(false);
	PIStringList filter_fails;
	while (added != padded && tries < 100) {
		padded = added;
		added = 0;
		++tries;
		piForeachC (PIConfig::Entry * e, fb) {
			PIPacketExtractor::SplitMode sm = PIPacketExtractor::None;
			PIString sms(e->getValue("splitMode").value());
			int smi = sms.toInt();
			if (smi >= 1 && smi <= 5) sm = (PIPacketExtractor::SplitMode)smi;
			else {
				sms = sms.trim().toLowerCase();
				if (sms.find("header") >= 0 && sms.find("footer") >= 0)
					sm = PIPacketExtractor::HeaderAndFooter;
				else {
					if (sms.find("header") >= 0)
						sm = PIPacketExtractor::Header;
					else {
						if (sms.find("footer") >= 0)
							sm = PIPacketExtractor::Footer;
						else {
							if (sms.find("time") >= 0)
								sm = PIPacketExtractor::Timeout;
							else {
								if (sms.find("size") >= 0)
									sm = PIPacketExtractor::Size;
							}
						}
					}
				}
			}
			PIStringList devs(e->value());
			PIConfig::Branch db(e->getValue("device").children());
			devs << e->getValue("device", "").value();
			piForeachC (PIConfig::Entry * e2, db)
				devs << e2->value();
			devs.removeStrings("");
			if (devs.isEmpty()) continue;
			PIString dname = dev_aliases.value(devs.front(), devs.front());
			PIPacketExtractor * pe = addFilter(e->name(), dname, sm);
			if (pe == 0) {
				if (!filter_fails.contains(dname))
					filter_fails << dname;
				continue;
			} else {
				filter_fails.removeAll(dname);
			}
			++added;
			for (int i = 1; i < devs.size_s(); ++i) {
				dname = dev_aliases.value(devs[i], devs[i]);
				if (addFilter(e->name(), dname, sm) != 0) {
					filter_fails.removeAll(dname);
					++added;
				} else {
					if (!filter_fails.contains(dname))
						filter_fails << dname;
				}
			}
			PIDiagnostics * diag = diags_.value(pe, 0);
			if (diag != 0)
				diag->setDisconnectTimeout(e->getValue("disconnectTimeout", diag->disconnectTimeout()));
			pe->setPayloadSize(e->getValue("payloadSize", pe->payloadSize()));
			pe->setPacketSize(e->getValue("packetSize", pe->packetSize()));
			pe->setTimeout(e->getValue("timeout", pe->timeout()));
			pe->setHeader(PIByteArray::fromString(e->getValue("header", "").value()));
			pe->setFooter(PIByteArray::fromString(e->getValue("footer", "").value()));
		}
	}
	setDebug(pdebug);
	piForeachC (PIString & f, filter_fails)
		piCoutObj << "\"addFilter\" error: no such device \"" << f << "\"!";
	piForeachC (PIConfig::Entry * e, cb) {
		PIString f(e->getValue("from").value()), t(e->getValue("to").value());
		addChannel(dev_aliases.value(f, f), dev_aliases.value(t, t));
	}
	piForeachC (PIConfig::Entry * e, sb) {
		PIStringList devs(e->value());
		PIConfig::Branch db(e->getValue("device").children());
		devs << e->getValue("device", "").value();
		piForeachC (PIConfig::Entry * e2, db)
			devs << e2->value();
		devs.removeStrings("");
		if (devs.isEmpty()) continue;
		float freq = e->getValue("frequency");
		piForeachC (PIString & d, devs)
			addSender(e->name(), dev_aliases.value(d, d), freq);
		PIByteArray fd(PIByteArray::fromString(e->getValue("fixedData").value()));
		setSenderFixedData(e->name(), fd);
	}
	return true;
}


PIString PIConnection::makeConfig() const {
	PIString ret;
	ret << "[" << name() << "]\n";
	PIVector<PIIODevice * > devs(boundedDevices());
	int dn(-1);
	piForeachC (PIIODevice * d, devs) {
		PIStringList dnl(deviceNames(d));
		if (dnl.isEmpty()) dnl << PIString::fromNumber(++dn);
		piForeachC (PIString & dname, dnl) {
			ret << "device." << dname << " = " << d->constructFullPath();
			if (d->mode() == PIIODevice::ReadOnly) ret << " (ro)";
			if (d->mode() == PIIODevice::WriteOnly) ret << " (wo)";
			ret << " #s\n";
			PIDiagnostics * diag = diags_.value(const_cast<PIIODevice * >(d), 0);
			if (diag != 0)
				ret << "device." << dname << ".disconnectTimeout = " << diag->disconnectTimeout() << " #f\n";
		}
	}
	piForeachC (PEPair & f, extractors) {
		if (f.second == 0) continue;
		if (f.second->extractor == 0) continue;
		PIString prefix = "filter." + f.first;
		for (int i = 0; i < f.second->devices.size_s(); ++i)
			ret << prefix << ".device." << i << " = " << devPath(f.second->devices[i]) << " #s\n";
		PIDiagnostics * diag = diags_.value(f.second->extractor, 0);
		if (diag != 0)
			ret << prefix << ".disconnectTimeout = " << diag->disconnectTimeout() << " #f\n";
		ret << prefix << ".splitMode = ";
		switch (f.second->extractor->splitMode()) {
		case PIPacketExtractor::None: ret << "none"; break;
		case PIPacketExtractor::Header: ret << "header"; break;
		case PIPacketExtractor::Footer: ret << "footer"; break;
		case PIPacketExtractor::HeaderAndFooter: ret << "header & footer"; break;
		case PIPacketExtractor::Size: ret << "size"; break;
		case PIPacketExtractor::Timeout: ret << "timeout"; break;
		}
		ret << " #s\n";
		ret << prefix << ".payloadSize = " << f.second->extractor->payloadSize() << " #n\n";
		ret << prefix << ".packetSize = " << f.second->extractor->packetSize() << " #n\n";
		ret << prefix << ".timeout = " << f.second->extractor->timeout() << " #f\n";
		ret << prefix << ".header = " << f.second->extractor->header().toString() << " #s\n";
		ret << prefix << ".footer = " << f.second->extractor->footer().toString() << " #s\n";
	}
	dn = 0;
	piForeachC (CPair & c, channels_) {
		piForeachC (PIIODevice * d, c.second) {
			PIString prefix = "channel." + PIString::fromNumber(dn); ++dn;
			ret << prefix << ".from = " << devPath(c.first) << " #s\n";
			ret << prefix << ".to = " << devPath(d) << " #s\n";
		}
	}
	piForeachC (SPair & s, senders) {
		if (s.second == 0) continue;
		PIString prefix = "sender." + s.second->name();
		for (int i = 0; i < s.second->devices.size_s(); ++i)
			ret << prefix << ".device." << i << " = " << devPath(s.second->devices[i]) << " #s\n";
		double int_ = s.second->int_;
		if (int_ > 0.)
			ret << prefix << ".frequency = " << (1000. / int_) << " #f\n";
		if (!s.second->sdata.isEmpty())
			ret << prefix << ".fixedData = " << s.second->sdata.toString() << " #s\n";
	}
	ret << "[]\n";
	return ret;
}


PIIODevice * PIConnection::addDevice(const PIString & full_path, PIIODevice::DeviceMode mode, bool start) {
	PIString fp(PIIODevice::normalizeFullPath(full_path));
	PIIODevice * dev = __device_pool__->addDevice(this, fp, mode, start);
	if (dev) {
		dev->setName(name() + ".device." + fp);
		device_modes[dev] = mode;
		__device_pool__->lock();
		if (diags_.value(dev, 0) == 0) {
			PIDiagnostics * d = new PIDiagnostics(false);
			diags_[dev] = d;
			CONNECTU(d, qualityChanged, this, diagQualityChanged);
		}
		__device_pool__->unlock();
	}
	return dev;
}


PIStringList PIConnection::deviceNames(const PIIODevice *dev) const {
	PIStringList ret;
	piForeachC (DNPair & s, device_names)
		if (s.second == dev)
			ret << s.first;
	return ret;
}


bool PIConnection::removeDevice(const PIString & full_path) {
	PIString fp(PIIODevice::normalizeFullPath(full_path));
	PIIODevice * dev = __device_pool__->device(fp);
	if (dev == 0) return false;
	PIStringList dntd(deviceNames(dev));
	piForeachC (PIString & n, dntd)
		device_names.removeOne(n);
	piForeachC (SPair & s, senders) {
		if (s.second == 0) continue;
		s.second->lock();
		s.second->devices.removeAll(dev);
		s.second->unlock();
	}
	device_modes.remove(dev);
	piForeachC (PEPair & i, extractors) {
		if (i.second == 0) continue;
		i.second->devices.removeAll(dev);
	}
	bounded_extractors.remove(dev);
	channels_.remove(dev);
	for (PIMap<PIIODevice * , PIVector<PIIODevice * > >::iterator it = channels_.begin(); it != channels_.end(); ++it)
		it.value().removeAll(dev);
	__device_pool__->lock();
	if (diags_.value(dev, 0) != 0)
		delete diags_.value(dev);
	diags_.remove(dev);
	__device_pool__->unlock();
	return __device_pool__->removeDevice(this, fp);
}


void PIConnection::removeAllDevices() {
	device_names.clear();
	PIVector<PIIODevice * > bdevs(__device_pool__->boundedDevices(this));
	__device_pool__->lock();
	piForeach (PIIODevice * d, bdevs) {
		piForeachC (SPair & s, senders) {
			if (s.second == 0) continue;
			s.second->lock();
			s.second->devices.removeAll(d);
			s.second->unlock();
		}
		channels_.remove(d);
		for (PIMap<PIIODevice * , PIVector<PIIODevice * > >::iterator it = channels_.begin(); it != channels_.end(); ++it)
			it.value().removeAll(d);
		if (diags_.value(d, 0) != 0)
			delete diags_.value(d);
		diags_.remove(d);
	}
	__device_pool__->unboundConnection(this);
	__device_pool__->unlock();
	device_modes.clear();
	bounded_extractors.clear();
	piForeachC (PEPair & i, extractors) {
		if (i.second == 0) continue;
		i.second->devices.clear();
	}
}


PIIODevice * PIConnection::deviceByFullPath(const PIString & full_path) const {
	PIString fp(PIIODevice::normalizeFullPath(full_path));
	DevicePool::DeviceData * dd = __device_pool__->devices.value(fp);
	if (dd == 0) return 0;
	if (dd->dev == 0) return 0;
	if (!dd->listeners.contains(const_cast<PIConnection * >(this))) return 0;
	return dd->dev;
}


PIIODevice * PIConnection::deviceByName(const PIString & name) const {
	return device_names.value(name, 0);
}


PIVector<PIIODevice * > PIConnection::boundedDevices() const {
	return __device_pool__->boundedDevices(this);
}


PIPacketExtractor * PIConnection::addFilter(const PIString & name_, const PIString & full_path, PIPacketExtractor::SplitMode mode) {
	PIString fp(PIIODevice::normalizeFullPath(full_path));
	PIString fname_ = name_.trimmed();
	Extractor * e = extractors.value(fname_);
	if (full_path.isEmpty()) return (e == 0 ? 0 : e->extractor);
	PIIODevice * dev = deviceByFullPath(fp);
	PIPacketExtractor * pe(0);
	if (extractors.value(full_path) != 0) pe = extractors.value(full_path)->extractor;
	if (pe != 0) dev = pe;
	if (dev == 0) {
		piCoutObj << "\"addFilter\" error: no such device \"" << full_path << "\"!";
		return 0;
	}
	if (e == 0) {
		e = new Extractor();
		extractors[fname_] = e;
	}
	if (e->extractor == 0) {
		e->extractor = new PIPacketExtractor(0, mode);
		e->extractor->setName(fname_);
		e->extractor->setThreadedReadData(new PIPair<PIConnection * , PIString>(this, fname_));
		e->extractor->setHeaderCheckSlot(filterValidateHeaderS);
		e->extractor->setFooterCheckSlot(filterValidateFooterS);
		e->extractor->setPayloadCheckSlot(filterValidatePayloadS);
		__device_pool__->lock();
		if (diags_.value(e->extractor, 0) == 0) {
			PIDiagnostics * d = new PIDiagnostics(false);
			diags_[e->extractor] = d;
			CONNECTU(d, qualityChanged, this, diagQualityChanged);
		}
		__device_pool__->unlock();
		CONNECT2(void, uchar * , int, e->extractor, packetReceived, this, packetExtractorReceived)
	}
	if (!e->devices.contains(dev)) {
		bounded_extractors[dev] << e->extractor;
		//if (PIString(dev->className()) == "PIPacketExtractor") dev->setThreadSafe(false);
		e->devices << dev;
	}
	return e->extractor;
}


bool PIConnection::removeFilter(const PIString & name_, const PIString & full_path) {
	PIString fp(PIIODevice::normalizeFullPath(full_path));
	Extractor * p = extractors.value(name_.trimmed());
	if (p == 0) return false;
	bool ret = false;
	for (int i = 0; i < p->devices.size_s(); ++i) {
		if (devFPath(p->devices[i]) == fp || devFPath(p->devices[i]) == full_path) {
			bounded_extractors[p->devices[i]].removeAll(p->extractor);
			p->devices.remove(i);
			--i;
			ret = true;
		}
	}
	if (p->devices.isEmpty()) {
		unboundExtractor(p->extractor);
		delete p;
	}
	return ret;
}


bool PIConnection::removeFilter(const PIString & name, const PIIODevice * dev) {
	if (dev == 0) return false;
	return removeFilter(name, devFPath(dev));
}


bool PIConnection::removeFilter(const PIString & name_) {
	Extractor * p = extractors.value(name_.trimmed());
	if (p == 0) return false;
	unboundExtractor(p->extractor);
	delete p;
	return true;
}


void PIConnection::removeAllFilters() {
	__device_pool__->lock();
	piForeachC (PEPair & i, extractors) {
		if (i.second == 0) continue;
		channels_.remove(i.second->extractor);
		for (PIMap<PIIODevice * , PIVector<PIIODevice * > >::iterator it = channels_.begin(); it != channels_.end(); ++it)
			it.value().removeAll(i.second->extractor);
		if (diags_.value(i.second->extractor, 0) != 0)
			delete diags_.value(i.second->extractor);
		diags_.remove(i.second->extractor);
		delete i.second;
	}
	extractors.clear();
	bounded_extractors.clear();
	__device_pool__->unlock();
}


PIVector<PIPacketExtractor * > PIConnection::filters() const {
	PIVector<PIPacketExtractor * > ret;
	piForeachC (PEPair & i, extractors)
		if (i.second != 0)
			if (i.second->extractor != 0) ret << i.second->extractor;
	return ret;
}


PIStringList PIConnection::filterNames() const {
	PIStringList ret;
	piForeachC (PEPair & i, extractors)
		if (i.second != 0)
			if (i.second->extractor != 0) ret << i.first;
	return ret;
}


PIPacketExtractor * PIConnection::filter(const PIString & name_) const {
	PIString fname_ = name_.trimmed();
	piForeachC (PEPair & i, extractors)
		if (i.second != 0)
			if (i.second->extractor != 0 && i.first == fname_)
				return i.second->extractor;
	return 0;
}


PIVector<PIIODevice * > PIConnection::filterBoundedDevices(const PIString & name_) const {
	PIVector<PIIODevice * > ret;
	Extractor * p = extractors.value(name_.trimmed());
	if (p == 0) return ret;
	return p->devices;
}


bool PIConnection::addChannel(const PIString & name0, const PIString & name1) {
	//piCout << "addChannel" << name0 << name1;
	if (name0.isEmpty() || name1.isEmpty()) return false;
	PIIODevice * dev0 = deviceByFullPath(name0), * dev1 = deviceByFullPath(name1);
	PIPacketExtractor * pe0(0), * pe1(0);
	if (extractors.value(name0) != 0) pe0 = extractors.value(name0)->extractor;
	if (extractors.value(name1) != 0) pe1 = extractors.value(name1)->extractor;
	if (pe0 != 0) dev0 = pe0;
	if (pe1 != 0) dev1 = pe1;
	if (dev0 == 0 || dev1 == 0) {
		if (dev0 == 0) piCoutObj << "\"addChannel\" error: no such device \"" << name0 << "\"!";
		if (dev1 == 0) piCoutObj << "\"addChannel\" error: no such device \"" << name1 << "\"!";
		return false;
	}
	if (!channels_[dev0].contains(dev1))
		channels_[dev0] << dev1;
	return true;
}


bool PIConnection::removeChannel(const PIString & name0, const PIString & name1) {
	PIIODevice * dev0 = deviceByFullPath(name0), * dev1 = deviceByFullPath(name1);
	PIPacketExtractor * pe0(0), * pe1(0);
	if (extractors.value(name0) != 0) pe0 = extractors.value(name0)->extractor;
	if (extractors.value(name1) != 0) pe1 = extractors.value(name1)->extractor;
	if (pe0 != 0) dev0 = pe0;
	if (pe1 != 0) dev1 = pe1;
	if (dev0 == 0 || dev1 == 0) return false;
	channels_[dev0].removeAll(dev1);
	return true;
}


bool PIConnection::removeChannel(const PIString & name0) {
	PIIODevice * dev0 = deviceByFullPath(name0);
	PIPacketExtractor * pe0(0);
	if (extractors.value(name0) != 0) pe0 = extractors.value(name0)->extractor;
	if (pe0 != 0) dev0 = pe0;
	if (dev0 == 0) return false;
	channels_.remove(dev0);
	for (PIMap<PIIODevice * , PIVector<PIIODevice * > >::iterator it = channels_.begin(); it != channels_.end(); ++it)
		it.value().removeAll(dev0);
	return true;
}


void PIConnection::removeAllChannels() {
	channels_.clear();
}


PIString PIConnection::devPath(const PIIODevice * d) const {
	if (d == 0) return PIString();
	if (strcmp(d->className(), "PIPacketExtractor") == 0) return d->name();
	return d->constructFullPath();
}


PIString PIConnection::devFPath(const PIIODevice * d) const {
	if (d == 0) return PIString();
	if (d->isPropertyExists("__fullPath__")) return d->property("__fullPath__").toString();
	return d->name();
}


PIVector<PIPair<PIString, PIString > > PIConnection::channels() const {
	PIVector<PIPair<PIString, PIString > > ret;
	piForeachC (CPair & i, channels_) {
		PIString fp0(devFPath(i.first));
		piForeachC (PIIODevice * d, i.second)
			ret << PIPair<PIString, PIString>(fp0, devFPath(d));
	}
	return ret;
}


void PIConnection::addSender(const PIString & name_, const PIString & full_path, float frequency, bool start_) {
	PIString fp(PIIODevice::normalizeFullPath(full_path));
	PIString fname_ = name_.trimmed();
	if (fp.isEmpty() || frequency <= 0.) return;
	Sender * s = senders.value(fname_);
	PIIODevice * dev = deviceByFullPath(fp);
	if (s == 0) {
		s = new Sender(this);
		s->setName(fname_);
		s->int_ = 1000. / frequency;
		senders[fname_] = s;
	}
	if (dev == 0) {
		piCoutObj << "\"addSender\" error: no such device \"" << full_path << "\"!";
		return;
	}
	if (!s->isRunning() && start_) {
		//piCoutObj << name_ << "start" << 1000. / frequency;
		if (!__device_pool__->fake) s->start(s->int_);
	}
	s->lock();
	if (!s->devices.contains(dev))
		s->devices << dev;
	s->unlock();
}


bool PIConnection::removeSender(const PIString & name, const PIString & full_path) {
	PIString fp(PIIODevice::normalizeFullPath(full_path));
	Sender * s = senders.value(name, 0);
	PIIODevice * d = deviceByFullPath(fp);
	if (s == 0 || d == 0) return false;
	s->lock();
	bool ret = s->devices.contains(d);
	if (ret)
		s->devices.removeAll(d);
	s->unlock();
	return ret;
}


bool PIConnection::removeSender(const PIString & name) {
	Sender * s = senders.value(name, 0);
	if (s == 0) return false;
	delete s;
	senders.remove(name);
	return true;
}


bool PIConnection::setSenderFixedData(const PIString & name, const PIByteArray & data) {
	Sender * s = senders.value(name, 0);
	if (s == 0) return false;
	s->lock();
	s->sdata = data;
	s->unlock();
	return true;
}


bool PIConnection::clearSenderFixedData(const PIString & name) {
	Sender * s = senders.value(name, 0);
	if (s == 0) return false;
	s->lock();
	s->sdata.clear();
	s->unlock();
	return true;
}


PIByteArray PIConnection::senderFixedData(const PIString & name) const {
	Sender * s = senders.value(name, 0);
	if (s == 0) return PIByteArray();
	return s->sdata;
}


float PIConnection::senderFrequency(const PIString & name) const {
	Sender * s = senders.value(name, 0);
	if (s == 0) return -1.f;
	double i = s->interval();
	if (i == 0.) return 0.f;
	return 1000. / s->interval();
}


void PIConnection::removeAllSenders() {
	piForeachC (SPair & s, senders)
		if (s.second != 0)
			delete s.second;
	senders.clear();
}


void PIConnection::startThreadedRead(const PIString & full_path) {
	PIString fp(PIIODevice::normalizeFullPath(full_path));
	DevicePool::DeviceData * dd = __device_pool__->devices.value(fp, 0);
	if (dd == 0) return;
	if (dd->dev == 0) return;
	if (dd->started || dd->dev->mode() == PIIODevice::WriteOnly) return;
	if (!__device_pool__->fake) dd->rthread->start();
	dd->started = true;
}


void PIConnection::startAllThreadedReads() {
	piForeachC (DevicePool::DDPair & d, __device_pool__->devices)
		startThreadedRead(d.first);
}


void PIConnection::startSender(const PIString & name) {
	Sender * s = senders.value(name, 0);
	if (s == 0) return;
	if (!s->isRunning() && !__device_pool__->fake)
		s->start(s->int_);
}


void PIConnection::startAllSenders() {
	piForeachC (SPair & s, senders) {
		if (s.second == 0) continue;
		if (!s.second->isRunning() && !__device_pool__->fake)
			s.second->start(s.second->int_);
	}
}


void PIConnection::stopThreadedRead(const PIString & full_path) {
	PIString fp(PIIODevice::normalizeFullPath(full_path));
	DevicePool::DeviceData * dd = __device_pool__->devices.value(fp, 0);
	if (dd == 0) return;
	if (dd->dev == 0) return;
	if (!dd->started || dd->dev->mode() == PIIODevice::WriteOnly) return;
	dd->rthread->stop();
	dd->started = false;
}


void PIConnection::stopAllThreadedReads() {
	piForeachC (DevicePool::DDPair & d, __device_pool__->devices)
		stopThreadedRead(d.first);
}


void PIConnection::stopSender(const PIString & name) {
	Sender * s = senders.value(name, 0);
	if (s == 0) return;
	if (s->isRunning()) s->stop();
}


void PIConnection::stopAllSenders() {
	piForeachC (SPair & s, senders) {
		if (s.second == 0) continue;
		if (s.second->isRunning())
			s.second->stop();
	}
}


PIDiagnostics * PIConnection::diagnostic(const PIString & full_path_name) const {
	PIIODevice * dev = deviceByFullPath(full_path_name);
	if (dev == 0) dev = device_names.value(full_path_name, 0);
	PIPacketExtractor * pe(0);
	if (extractors.value(full_path_name) != 0) pe = extractors.value(full_path_name)->extractor;
	if (pe != 0) dev = pe;
	if (dev == 0) return 0;
	return diags_.value(dev, 0);
}


int PIConnection::writeByFullPath(const PIString & full_path, const PIByteArray & data) {
	PIString fp(full_path);
	if (fp.endsWith(")"))
		splitFullPathWithMode(fp, &fp, 0);
	fp = PIIODevice::normalizeFullPath(fp);
	PIIODevice * dev = __device_pool__->device(fp);
	//piCout << "SEND" << full_path << fp;
	return write(dev, data);
}


int PIConnection::writeByName(const PIString & name, const PIByteArray & data) {
	PIIODevice * dev = deviceByName(name);
	return write(dev, data);
}


int PIConnection::write(PIIODevice * dev, const PIByteArray & data) {
	if (dev == 0) {
		piCoutObj << "Null Device!";
		return -1;
	}
	if (!dev->canWrite()) {
		piCoutObj << "Device \"" << dev->constructFullPath() << "\" can`t write!";
		return -1;
	}
	int ret = dev->write(data);
	PIDiagnostics * diag = diags_.value(dev);
	if (diag != 0 && ret > 0) diag->sended(ret);
	return ret;
}


PIVector< PIConnection * > PIConnection::allConnections() {
	return _connections;
}


PIVector< PIIODevice * > PIConnection::allDevices() {
	return __device_pool__->boundedDevices();
}


bool PIConnection::setFakeMode(bool yes) {
	bool ret = isFakeMode();
	__device_pool__->fake = yes;
	return ret;
}


bool PIConnection::isFakeMode() {
	return __device_pool__->fake;
}




PIIODevice * PIConnection::DevicePool::addDevice(PIConnection * parent, const PIString & fp, PIIODevice::DeviceMode mode, bool start) {
	DeviceData * dd = devices[fp];
	int pmode(0);
	bool need_start = false;
	if (dd == 0) {
		dd = new DeviceData();
		devices[fp] = dd;
	}
	if (dd->dev == 0) {
		//piCout << "new device" << fp;
		dd->dev = PIIODevice::createFromFullPath(fp);
		if (dd->dev == 0) {
			piCoutObj << "Error: can`t create device \"" << fp << "\"!"; //:" << errorString();
			return 0;
		}
		dd->dev->setProperty("__fullPath__", fp);
	} else
		pmode = dd->dev->mode();
	if (!dd->listeners.contains(parent))
		dd->listeners << parent;
	if (pmode == mode || pmode == PIIODevice::ReadWrite)
		return dd->dev;
	if ((mode & PIIODevice::ReadOnly) > 0) {
		if (dd->rthread != 0) {
			delete dd->rthread;
			dd->rthread = 0;
			dd->started = false;
		}
		dd->rthread = new PIThread(dd, threadReadDP);
		dd->rthread->setName("__S__connection_" + fp + "_read_thread");
		need_start = true;
		pmode |= PIIODevice::ReadOnly;
	}
	if ((mode & PIIODevice::WriteOnly) > 0)
		pmode |= PIIODevice::WriteOnly;
	if (!fake) {
		dd->dev->close();
		dd->dev->open((PIIODevice::DeviceMode)pmode);
	}
	if (need_start && start) {
		if (!fake) dd->rthread->start();
		dd->started = true;
	}
	return dd->dev;
}


bool PIConnection::DevicePool::removeDevice(PIConnection * parent, const PIString & fp) {
	DeviceData * dd = devices.value(fp);
	if (dd == 0)
		return false;
	if (dd->dev == 0)
		return false;
	bool ok = dd->listeners.contains(parent);
	dd->listeners.removeAll(parent);
	if (dd->listeners.isEmpty()) {
		delete dd;
		devices.remove(fp);
	}
	return ok;
}


void PIConnection::DevicePool::unboundConnection(PIConnection * parent) {
	PIStringList rem;
	piForeachC (DDPair & i, devices) {
		if (i.second == 0) {
			rem << i.first;
			continue;
		}
		i.second->listeners.removeAll(parent);
		if (i.second->listeners.isEmpty())
			rem << i.first;
	}
	piForeachC (PIString & i, rem) {
		DeviceData * dd = devices.value(i);
		if (dd == 0)
			continue;
		delete dd;
		devices.remove(i);
	}
}


PIIODevice * PIConnection::DevicePool::device(const PIString & fp) const {
	DeviceData * dd = devices.value(fp);
	if (dd == 0) return 0;
	return dd->dev;
}


PIVector<PIConnection * > PIConnection::DevicePool::boundedConnections() const {
	PIVector<PIConnection * > ret;
	piForeachC (DDPair & i, devices) {
		if (i.second == 0)
			continue;
		ret << i.second->listeners;
	}
	for (int i = 0; i < ret.size_s(); ++i)
		for (int j = i + 1; j < ret.size_s(); ++j)
			if (ret[i] == ret[j]) {
				ret.remove(j);
				--j;
			}
	return ret;
}


PIVector< PIIODevice * > PIConnection::DevicePool::boundedDevices() const {
	PIVector<PIIODevice * > ret;
	piForeachC (DDPair & i, devices) {
		if (i.second == 0) continue;
		if (i.second->dev == 0) continue;
		ret << i.second->dev;
	}
	return ret;
}


PIVector<PIIODevice * > PIConnection::DevicePool::boundedDevices(const PIConnection * parent) const {
	PIVector<PIIODevice * > ret;
	piForeachC (DDPair & i, devices) {
		if (i.second == 0) continue;
		if (i.second->dev == 0) continue;
		if (i.second->listeners.contains(const_cast<PIConnection*>(parent)))
			ret << i.second->dev;
	}
	return ret;
}


PIConnection::DevicePool::DeviceData::~DeviceData() {
	if (rthread != 0) {
		rthread->stop();
		delete rthread;
		rthread = 0;
	}
	if (dev != 0) {
		delete dev;
		dev = 0;
	}
}


void PIConnection::DevicePool::run() {
	PIVector<PIConnection * > conns(PIConnection::allConnections());
	piForeach (PIConnection * c, conns) {
		piForeachC (PIConnection::DPair & d, c->diags_) {
			if (d.second == 0) continue;
			d.second->tick(0, 1);
		}
	}
}


void PIConnection::DevicePool::threadReadDP(void * ddp) {
	DeviceData * dd((DeviceData * )ddp);
	if (dd->dev == 0) {piMSleep(100); return;}
	PIByteArray ba;
	ba = dd->dev->read(dd->dev->threadedReadBufferSize());
	if (ba.isEmpty()) {piMSleep(10); return;}
	//piCout << "Readed from" << dd->dev->path() << Hex << ba;
	__device_pool__->deviceReaded(dd, ba);
}


void PIConnection::DevicePool::deviceReaded(PIConnection::DevicePool::DeviceData * dd, const PIByteArray & data) {
	PIString from = dd->dev->property("__fullPath__").toString();
	piForeach (PIConnection * ld, dd->listeners)
		ld->rawReceived(dd->dev, from, data);
}


void PIConnection::splitFullPathWithMode(PIString fpwm, PIString * full_path, PIIODevice::DeviceMode * mode) {
	PIIODevice::DeviceMode dm = PIIODevice::ReadWrite;
	if (fpwm.find("(") > 0 && fpwm.find(")") > 0) {
		PIString dms(fpwm.right(fpwm.length() - fpwm.find("(")).takeRange("(", ")").trim().toLowerCase().removeAll(" "));
		//piCout << dms;
		if (dms == "r" || dms == "ro" || dms == "read" || dms == "readonly")
			dm = PIIODevice::ReadOnly;
		if (dms == "w" || dms == "wo" || dms == "write" || dms == "writeonly")
			dm = PIIODevice::WriteOnly;
		fpwm.cutRight(fpwm.length() - fpwm.find("(") + 1).trim();
	}
	if (full_path) *full_path = fpwm;
	if (mode) *mode = dm;
}


bool PIConnection::filterValidateHeaderS(void * c, uchar * src, uchar * rec, int size) {
	PIPair<PIConnection * , PIString> * p((PIPair<PIConnection * , PIString> * )c);
	return p->first->filterValidateHeader(p->second, src, rec, size);
}


bool PIConnection::filterValidateFooterS(void * c, uchar * src, uchar * rec, int size) {
	PIPair<PIConnection * , PIString> * p((PIPair<PIConnection * , PIString> * )c);
	return p->first->filterValidateFooter(p->second, src, rec, size);
}


bool PIConnection::filterValidatePayloadS(void * c, uchar * rec, int size) {
	PIPair<PIConnection * , PIString> * p((PIPair<PIConnection * , PIString> * )c);
	return p->first->filterValidatePayload(p->second, rec, size);
}


void PIConnection::rawReceived(PIIODevice * dev, const PIString & from, const PIByteArray & data) {
	dataReceived(from, data);
	dataReceivedEvent(from, data);
	PIVector<PIPacketExtractor * > be(bounded_extractors.value(dev));
	//piCout << be;
	piForeach (PIPacketExtractor * i, be)
		i->threadedRead(const_cast<uchar * >(data.data()), data.size_s());
	PIVector<PIIODevice * > chd(channels_.value(dev));
	piForeach (PIIODevice * d, chd) {
		int ret = d->write(data);
		PIDiagnostics * diag = diags_.value(d);
		if (diag != 0 && ret > 0) diag->sended(ret);
	}
	PIDiagnostics * diag = diags_.value(dev);
	if (diag != 0) diag->received(data.size_s());
}


bool PIConnection::filterValidateHeader(const PIString & filter_name, uchar * src, uchar * rec, int size) {
	for (int i = 0; i < size; ++i)
		if (src[i] != rec[i])
			return false;
	return true;
}


bool PIConnection::filterValidateFooter(const PIString & filter_name, uchar * src, uchar * rec, int size) {
	for (int i = 0; i < size; ++i)
		if (src[i] != rec[i])
			return false;
	return true;
}


bool PIConnection::filterValidatePayload(const PIString & filter_name, uchar * rec, int size) {
	return true;
}


PIByteArray PIConnection::senderData(const PIString & sender_name) {
	return PIByteArray();
}


PIConnection::Extractor::~Extractor() {
	if (extractor != 0) {
		if (extractor->threadedReadData() != 0)
			delete (PIPair<PIConnection * , PIString> * )(extractor->threadedReadData());
		delete extractor;
		extractor = 0;
	}
}


void PIConnection::Sender::tick(void * , int) {
	if (parent == 0) return;
	PIByteArray data;
	if (!sdata.isEmpty()) data = sdata;
	else data = parent->senderData(name());
	if (data.isEmpty()) return;
	//piCoutObj << "write"<<data.size()<<"bytes to"<<devices.size()<<"devices";
	piForeach (PIIODevice * d, devices) {
		int ret = d->write(data);
		PIDiagnostics * diag = parent->diags_.value(d);
		if (diag != 0 && ret > 0) diag->sended(ret);
	}
}


void PIConnection::unboundExtractor(PIPacketExtractor * pe) {
	if (pe == 0) return;
	channels_.remove(pe);
	for (PIMap<PIIODevice * , PIVector<PIIODevice * > >::iterator it = channels_.begin(); it != channels_.end(); ++it)
		it.value().removeAll(pe);
	bounded_extractors.remove(pe);
	PIVector<PIIODevice * > k = bounded_extractors.keys();
	piForeach (PIIODevice * i, k) {
		PIVector<PIPacketExtractor * > & be(bounded_extractors[i]);
		be.removeAll(pe);
		if (be.isEmpty())
			bounded_extractors.remove(i);
	}
	__device_pool__->lock();
	if (diags_.value(pe, 0) != 0)
		delete diags_.value(pe);
	diags_.remove(pe);
	extractors.remove(pe->name());
	__device_pool__->unlock();
}


void PIConnection::packetExtractorReceived(uchar * data, int size) {
	PIString from(emitter() == 0 ? "" : emitter()->name());
	packetReceived(from, PIByteArray(data, size));
	packetReceivedEvent(from, PIByteArray(data, size));
	PIIODevice * cd = (PIIODevice * )emitter();
	if (cd == 0) return;
	PIVector<PIPacketExtractor * > be(bounded_extractors.value(cd));
	//piCout << be << (void*)data << size;
	piForeach (PIPacketExtractor * i, be)
		i->threadedRead(data, size);
	PIVector<PIIODevice * > chd(channels_.value(cd));
	piForeach (PIIODevice * d, chd) {
		int ret = d->write(data, size);
		PIDiagnostics * diag = diags_.value(d);
		if (diag != 0) diag->sended(ret);
	}
	PIDiagnostics * diag = diags_.value(cd);
	if (diag != 0) diag->received(size);
}


void PIConnection::diagQualityChanged(PIDiagnostics::Quality new_quality, PIDiagnostics::Quality old_quality) {
	qualityChanged(diags_.key((PIDiagnostics*)emitter()), new_quality, old_quality);
}



PIConnection::DevicePool * __device_pool__;

bool __DevicePoolContainer__::inited_(false);

__DevicePoolContainer__::__DevicePoolContainer__() {
	if (inited_) return;
	inited_ = true;
	__device_pool__ = new PIConnection::DevicePool();
}
