#include "server_data_generator.h"
#include "core.h"
#include <QMessageBox>
#include <piiodevice.h>
#include <piqt.h>


template <typename T>
QList<T> reverseList(const QList<T> & l) {QList<T> r; for (int i = l.size() - 1; i >= 0; --i) r << l[i]; return r;}


SHServerDataGenerator::SHServerDataGenerator(QObject * parent): QObject(parent) {
	view_ = 0;
}


SHServerDataGenerator::~SHServerDataGenerator() {
}


void SHServerDataGenerator::resetTraceState() {
	if (!view_) return;
	QList<BlockBusItem*> buses = view_->buses();
	for (BlockBusItem * b: buses)
		b->setData(SH_Block_Traced, false);
	QList<BlockItem*> blocks = view_->blocks();
	for (BlockItem * b: blocks) {
		b->setData(SH_Block_Traced, false);
		for (BlockItemPin * p: b->pins())
			p->setData(SH_Block_Traced, false);
	}
}


QList<BlockItemPin * > SHServerDataGenerator::connectedPins(BlockItemPin * pin) {
	QList<BlockItemPin * > ret;
	if (pin->data(SH_Block_Traced).toBool()) return ret;
	pin->setData(SH_Block_Traced, true);
	QList<BlockBusItem * > cbs = pin->connectedBuses();
	for (BlockBusItem * bus: cbs) {
		QList<BlockItemPin * > bcp = bus->connectedPins();
		for (BlockItemPin * lp: bcp) {
			if (pin == lp) continue;
			if (lp->data(SH_Block_Traced).toBool()) continue;
			lp->setData(SH_Block_Traced, true);
			bool ic = true;
			SHPluginBaseBus * bplug = SHCORE->busPlugin(bus);
			if (bplug)
				ic = bplug->isConnected(pin, lp);
			if (ic) {
				ret << lp;
			}
		}
	}
	return ret;
}


QList<SHServerDataGenerator::TracedBlock> SHServerDataGenerator::getNeighbourBlocks(BlockItem * block, bool input) {
	QList<SHServerDataGenerator::TracedBlock> ret;
	QVector<BlockItemPin*> pins = block->pins();
	QSet<void*> gathered;
	for (BlockItemPin * bp: pins) {
		QList<BlockItemPin * > cpins = connectedPins(bp);
		for (BlockItemPin * p: cpins) {
			if (gathered.contains(p) || gathered.contains(p->parent())) continue;
			gathered << p << p->parent();
			BlockBusItem * bus = bp->connectedBuses().front();
			SHPluginBaseBus * plug = SHCORE->busPlugin(bus);
			QList<SSPair> pathes;
			if (plug) {
				if (input) {
					pathes = plug->readPathes(bp, p);
				} else {
					pathes = plug->writePathes(bp, p);
				}
			} else {
				QString dp = bp->propertyValueByName("device").toString();
				if (isFullpathValid(dp))
					pathes << SSPair(bp->text(), dp);
			}
			ret << TracedBlock{.block = p->parent(), .pin = p, .pathes = pathes};
		}
	}
	return ret;
}


QList<BlockItemPin * > SHServerDataGenerator::getFarBlocks(BlockItem * block) {
	QList<BlockItemPin * > ret;
	if (!blockFlags(block).testFlag(SHS::Gateway)) return ret;
	//qDebug() << "getFarBlocks start" << block->propertyValueByName("name").toString();
	QVector<BlockItemPin*> pins = block->pins();
	QSet<void*> gathered;
	for (BlockItemPin * bp: pins) {
		QList<BlockItemPin * > cpins = connectedPins(bp);
		for (BlockItemPin * p: cpins) {
			BlockItem * pb = p->parent();
			if (pb->data(SH_Block_Traced).toBool()) continue;
			pb->setData(SH_Block_Traced, true);
			ret << p;
			if (blockFlags(pb).testFlag(SHS::Gateway))
				ret << getFarBlocks(pb);
		}
	}
	return ret;
}


QString SHServerDataGenerator::gatePin(BlockItem * gw, BlockItem * b) {
	if (!view_) return QString();
	BlockBusItem * bus = view_->connectionBus(gw, b);
	if (bus == 0) return QString();
	BlockItemPin * pin = gw->pinAtBus(bus);
	if (pin == 0) return QString();
	return pin->text();
}


QList<SHServerDataGenerator::TracedBlock> SHServerDataGenerator::gatherBlocks(BlockItem * block, bool input) {
	//qDebug() << "*** db for" << (input ? "in" : "out");
	QList<TracedBlock> ret, fbl;
	if (!view_) return ret;
	resetTraceState();

	ret = getNeighbourBlocks(block, input);
	for (const TracedBlock & nb: ret) {
		QList<BlockItemPin * > far_pins = getFarBlocks(nb.block);
		//qDebug() << "NB block" << nb.block->propertyByName("name").toString() << nb.pin->text() << far_pins.size();
		/// gateways
		while (!far_pins.isEmpty()) {
			QList<BlockItem*> gwl({nb.block});
			BlockItemPin * fp = far_pins.takeLast();
			for (BlockItemPin * p: far_pins) gwl << p->parent();
			if (!input) gwl = reverseList(gwl);
			fbl << (TracedBlock){fp->parent(), fp, nb.pathes, gwl};
		}
	}
	ret << fbl;

	for (const TracedBlock & nb: ret) {
		QString gw;
		for (BlockItem * b: nb.gateways)
			gw += b->propertyByName("name").toString() + " -> ";
		//qDebug() << (nb.block->propertyByName("name").toString() + ":" + nb.pin->text()) << gw;
	}
	return ret;
/*
	QList<BlockItem * > cblocks, nblocks;
	QMap<BlockItem * , QList<BlockItem * > > pathes;
	cblocks << block;
	int level = 0;
	while (!cblocks.isEmpty()) {
		nblocks.clear();
		foreach (BlockItem * b, cblocks) {
			SHS::BlockFlags bf = (SHS::BlockFlag)(b->propertyByName(SH_Block_Property_Flags).value.toInt());
			if (b == block) {
				SHS::PacketsList pm = SHCORE->block_packets[b].filter(f_this);
				SHS::PacketsList & tpl(ret[b]);
				foreach (const SHS::Packet & i1, pm) {
					BlockItemPin * pin = b->pinByText(i1.pin);
					if (!pin) continue;
					if (pin->connectedBuses().isEmpty()) continue;
					BlockBusItem * tbus = pin->connectedBuses().front();
					SHPluginBaseBus * plug = SHCORE->busPlugin(tbus);
					QList<SSPair> ppathes;
					if (plug) {
						if (input) {
							ppathes = plug->readPathes(pin, 0);
						} else {
							ppathes = plug->writePathes(pin, 0);
						}
					} else {
						QString dp = pin->propertyValueByName("device").toString();
						if (isFullpathValid(dp))
							ppathes << SSPair(pin->text(), dp);
					}
					SHS::Packet cp(i1);
					foreach (const SSPair & p, ppathes) {
						cp.pin_path = p.second;
						tpl << cp;
					}
				}
			}
			if (bf.testFlag(SHS::Gateway) || b == block) {
				foreach (BlockItemPin * pin, b->pins()) {
					QList<BlockItemPin*> pins = connectedPins(pin);
					foreach (BlockItemPin * p, pins) {
						BlockItem * nb = p->parent();
						if (!nblocks.contains(nb) && !pathes.contains(nb)) {
							pathes[nb] = pathes[b];
							pathes[nb] << b;
							nblocks << nb;
						}
					}
				}
			}
		}
		level++;
		cblocks = nblocks;
	}
	QMapIterator<BlockItem * , QList<BlockItem * > > it(pathes);
	//qDebug() << "CHAINS" << input;
	while (it.hasNext()) {
		it.next();
		BlockItem * b = it.key();
		if (b == block) continue;
		QList<BlockItem * > path = it.value();
		path << b;
		if (path.size() < 2) continue;
		QString tpin, ppin;
		BlockBusItem * tbus = view_->connectionBus(path[0], path[1]);
		QList<SSPair> ppathes;
		BlockItemPin * tp(0), * tp1(0);
		if (tbus) {
			SHPluginBaseBus * plug = SHCORE->busPlugin(tbus);
			if (plug) {
				tp = path[0]->pinAtBus(tbus);
				tp1 = path[1]->pinAtBus(tbus);
				if (tp && tp1) {
					if (input) {
						ppathes = plug->readPathes(tp, tp1);
					} else {
						ppathes = plug->writePathes(tp, tp1);
					}
				}
			} else {
				tp = path[0]->pinAtBus(tbus);
				if (!tp) continue;
				QString dp = tp->propertyValueByName("device").toString();
				if (isFullpathValid(dp))
					ppathes << SSPair(tp->text(), dp);
			}
		}
		if (input)
			path = reverseList(path);



		QString _path;
		foreach (BlockItem * _b, path) {
			_path += _b->propertyValueByName("name").toString();
			_path += ", ";
		}
		qDebug() << "block" << b->propertyValueByName("name").toString() << _path;



		SHS::PacketsList packets = SHCORE->block_packets[b].filter(f_other);
		for (int i = 0; i < path.size() - 2; ++i) {
			BlockItem * bf = path[i], * bc = path[i + 1], * bt = path[i + 2];
			QString gwpn_i = gatePin(bc, bf), gwpn_o = gatePin(bc, bt);
			SHDesignerCore::gatewayPackets(bc, packets, gwpn_i, gwpn_o, SHCORE->blockPlugin(bc));
		}
		SHS::PacketsList tpl;
		SHS::PacketsList & blpm(ret[b]);
		foreach (const SSPair & p, ppathes) {
			tpl = packets;
			tpl.setPin(p.first, p.second);
			blpm << tpl;
		}
	}
*/
		/*for (QMap < >::const_iterator i0 = ret.constBegin(); i0 != ret.constEnd(); ++i0) {
			qDebug() << i0.key()->propertyValueByName("name");
			for (SHS::PacketsList::const_iterator i1 = i0.value().constBegin(); i1 != i0.value().constEnd(); ++i1) {
				qDebug() << " * PACKET" << i1.key() << i1.value().name << i1.value().pin << i1.value().pin_path;
			}
		}*/

	return ret;
}


void SHServerDataGenerator::applyTracedToObject(SHS::Object & o, const SHServerDataGenerator::TracedBlock & t, SHS::PacketFlag flag) {
	QVector<SHS::PluginCommand> commands;
	SHS::PluginCommand c;
	c.arguments = o.properties;
	c.plugin_id = SHCORE->packetPluginID(t.pin);
	commands << c;
	for (BlockItem * gw: t.gateways) {
		c.arguments = visibleProperties(gw);
		c.plugin_id = SHCORE->packetGatewayID(gw);
		commands << c;
	}
	o.applyPathes(t.devices(), flag);
	o.applyPluginCommands(commands, flag);
}


QVariantMap SHServerDataGenerator::visibleProperties(const PropertyStorage * ps) {
	QVariantMap ret;
	for (int i = 0; i < ps->propertiesCount(); ++i) {
		const PropertyStorage::Property & p(ps->properties()[i]);
		if (!SHS::PropertyFlags(p.flags).testFlag(SHS::Hidden))
			ret[p.name] = p.value;
	}
	return ret;
}


SHS::BlockFlags SHServerDataGenerator::blockFlags(BlockItem * block) {
	return (SHS::BlockFlag)(block->propertyByName(SH_Block_Property_Flags).value.toInt());
}


SHS::Object SHServerDataGenerator::generateObject(BlockItem * b, int oid) {
	SHS::Object bo, src_bo;
	SHPluginBaseBlock * block_plugin = SHCORE->blockPlugin(b);
	if (block_plugin) {
		bo = block_plugin->createObject(b);
		bo.block_plugin_id = block_plugin->id();
	}
	//oids[b] = oid;
	//String name = b->propertyValueByName("name").toString();
	bo.id = oid;
	bo.desc = b->propertyValueByName("comment").toString();
	addToMap(bo.properties, visibleProperties(b));
	if (block_plugin) {
		QList<SSPair> add_props = block_plugin->addDatabaseProperties(b);
		for (const SSPair & p: add_props) {
			if (!bo.properties.contains(p.first))
				bo.properties[p.first] = p.second;
		}
	}
	//data.blocks.addObject(name, bo);
	src_bo = SHCORE->getStorage(b).block_types[b->data(SH_Block_ID).toInt()].object;
	SHDesignerCore::prepareObject(src_bo, b);
	SHDesignerCore::mergeObject(bo, src_bo);
	return bo;
}


void collectPlugins(QSet<QString> & out, const SHS::Object & o) {
	auto bpi = RUNTIME->plugins_manager->localInterface().getPlugin(o.block_plugin_id);
	if (bpi.plugin)
		out << bpi.manifest.name;
	QMapIterator<QString, SHS::Command> cit(o.commands);
	while (cit.hasNext()) {
		cit.next();
		auto ppi = RUNTIME->plugins_manager->localInterface().getPlugin(cit.value().packet_cmd.plugin_id);
		if (bpi.plugin)
			out << ppi.manifest.name;
	}
	QMapIterator<QString, SHS::Object> oit(o.objects);
	while (oit.hasNext()) {
		oit.next();
		collectPlugins(out, oit.value());
	}
}

void SHServerDataGenerator::generate(BlockItem * block, const QString & filename, const PropertyStorage & proj_props) {
	if (!view_) return;
	if (QFile::exists(filename))
		if (!QFile::remove(filename)) {
			QMessageBox::critical(0, "Generate database", QString("Can`t remove file \"%1\"!").arg(filename));
			return;
		}
	SHS::ServerData data;

	data.project = proj_props;
	data.server = block->properties();
	data.plugins = RUNTIME->plugins_props_project;

	/// Get all blockitems as SHS::Object
	QSet<BlockItem*> used_blocks;
	QList<BlockItem * > all_blocks = view_->blocks();
	QMap<BlockItem * , SHS::Object> objects;
	int oid = 1;//, pid = 1;
	for (BlockItem * b: all_blocks) {
		SHS::BlockFlags bf = blockFlags(b);
		if (bf.testFlag(SHS::AlwaysConnected))
			used_blocks << b;
		SHS::Object bo = generateObject(b, oid);
		if (bf.testFlag(SHS::AlwaysConnected)) {
			QVector<SHS::PluginCommand> commands;
			SHS::PluginCommand c;
			if (!b->pins().isEmpty())
				c.plugin_id = SHCORE->packetPluginID(b->pins()[0]);
			commands << c;
			bo.applyPluginCommands(commands, SHS::Input );
			bo.applyPluginCommands(commands, SHS::Output);
		}
		objects[b] = bo;
		oid++;
	}


	QList<TracedBlock> inputs  = gatherBlocks(block, true),
					   outputs = gatherBlocks(block, false);
	for (const TracedBlock & tb: inputs)
		used_blocks << tb.block;
	for (const TracedBlock & tb: inputs) {
		applyTracedToObject(objects[tb.block], tb, SHS::Output);
	}
	for (const TracedBlock & tb: outputs) {
		applyTracedToObject(objects[tb.block], tb, SHS::Input);
	}

	for (BlockItem * b: used_blocks) {
		objects[b].swapIOFlags();
	}
	QMapIterator<BlockItem * , SHS::Object> oit(objects);
	while (oit.hasNext()) {
		oit.next();
		if (!used_blocks.contains(oit.key()))
			continue;
		data.blocks.addObject(oit.key()->propertyValueByName("name").toString(), objects[oit.key()]);
	}

	QSet<QString> plugins;
	collectPlugins(plugins, data.blocks);
	plugins.remove("");
	data.used_plugins = plugins;
	qDebug() << "found used plugins:" << plugins;
	qDebug() << "***write to" << filename << QDir::current().absolutePath();

	saveToFile(filename, data);

}


PIStringList SHServerDataGenerator::TracedBlock::devices() const {
	PIStringList ret;
	for (const SSPair & i: pathes)
		ret << Q2PIString(i.second);
	return ret;
}
