#include <piiodevice.h>
#include "database_storage.h"
#include "plugin_hexlang.h"
#include "core.h"
#include "ccm_SHS_shared.h"


TableRow TableRow_fromRecord(const QSqlRecord & r) {
	TableRow ret;
	ret.id = r.value("id").toInt();
	ret.name = r.value("name").toString();
	ret.description = r.value("description").toString();
	return ret;
}




void BusType_save(const BusType & b, const QSqlDatabase & base) {
	if (!base.isOpen()) return;
	QSqlQuery q(base);
	q.exec(QString("DELETE FROM bus_types WHERE id=%1").arg(b.id));
	q.prepare(QString("INSERT INTO bus_types (id,name,data) VALUES (%1,:n,:d)").arg(b.id));
	q.bindValue(":n", b.name);
	q.bindValue(":d", piqSerialize(b));
	q.exec();
}


void BusType_load(BusType & b, const QSqlRecord & r) {
	b = piqDeserialize<BusType>(r.value("data").toByteArray());
	b.id = r.value("id").toInt();
	b.name = r.value("name").toString();
}




void BlockType_save(const BlockType & b, const QSqlDatabase & base) {
	if (!base.isOpen()) return;
	QSqlQuery q(base);
	q.exec(QString("DELETE FROM block_types WHERE id=%1").arg(b.id));
	q.prepare(QString("INSERT INTO block_types (id,name,data) VALUES (%1,:n,:d)").arg(b.id));
	q.bindValue(":n", b.name);
	q.bindValue(":d", piqSerialize(b));
	q.exec();
}


void BlockType_load(BlockType & b, const QSqlRecord & r) {
	b = piqDeserialize<BlockType>(r.value("data").toByteArray());
	b.id = r.value("id").toInt();
	b.name = r.value("name").toString();
}




SHDatabaseStorage::SHDatabaseStorage() {
}


SHDatabaseStorage::~SHDatabaseStorage() {
	unload();
}


bool SHDatabaseStorage::load(QSqlDatabase & base) {
	unload();
	if (!base.isOpen()) return false;
	if (!base.tables().contains("common")) {
		qDebug() << "Loading old SQL version ...";
		load_v0(base);
		qDebug() << "Resaving old SQL version ...";
		save(base);
		qDebug() << "Resaving old SQL version done";
	}
	load_v1(base);
	return true;
}


void SHDatabaseStorage::save(QSqlDatabase & base) {
	if (!base.isOpen()) return;
	initDatabase(base);
	base.transaction();
	QSqlQuery q(base);
	q.exec("DELETE FROM common *");
	q.prepare("INSERT INTO common (base_info) VALUES (:d)");
	DatabaseInfo info;
	info.version = currentDatabaseVersion;
	q.bindValue(":d", piqSerialize(info));
	q.exec();
	auto it2 = table_vendors.makeIterator();
	while (it2.next()) {
		q.exec(QString("DELETE FROM vendors WHERE id=%1").arg(it2.key()));
		q.prepare(QString("INSERT INTO vendors (id,name,description) VALUES (%1,:n,:d)").arg(it2.key()));
		q.bindValue(":n", it2.value().name);
		q.bindValue(":d", it2.value().description);
		q.exec();
	}
	auto it3 = table_groups.makeIterator();
	while (it3.next()) {
		q.exec(QString("DELETE FROM groups WHERE id=%1").arg(it3.key()));
		q.prepare(QString("INSERT INTO groups (id,name,description) VALUES (%1,:n,:d)").arg(it3.key()));
		q.bindValue(":n", it3.value().name);
		q.bindValue(":d", it3.value().description);
		q.exec();
	}
	auto it0 = bus_types.makeIterator();
	while (it0.next()) {
		BusType_save(it0.value(), base);
	}
	auto it1 = block_types.makeIterator();
	while (it1.next()) {
		BlockType_save(it1.value(), base);
	}
	base.commit();
}


void SHDatabaseStorage::initDatabase(QSqlDatabase & base) {
	if (!base.isOpen()) return;
	base.transaction();
	QStringList etables(base.tables());
	QSqlQuery q(base);
	QVector<SSPair> tables;
	QPIConfig conf(":/sql_SH_designer.conf", QIODevice::ReadOnly);
	QPIConfig::Branch tl = conf.getValue("v1").children();
	foreach (const QPIConfig::Entry * i, tl)
		tables << SSPair(i->name(), i->value());
	foreach (const SSPair & tp, tables) {
		if (!etables.contains(tp.first)) {
			if (!q.exec(tp.second))
				qDebug() << q.lastError();
		} else
			checkTable(base, tp.first, tp.second);
	}
	base.commit();
}


void SHDatabaseStorage::checkTable(QSqlDatabase & base, const QString & tname, QString tinit) {
	tinit = tinit.mid(tinit.indexOf("(") + 1);
	tinit.resize(tinit.lastIndexOf(")"));
	QStringList cols = tinit.split(",");
	foreach (QString c, cols) {
		QSqlQuery(QString("ALTER TABLE %1 ADD COLUMN %2").arg(tname, c), base);
	}
}


void SHDatabaseStorage::loadBlockPinsProperties(BlockItem * block, const QMap<QString, PropertyStorage> & db) {
	if (db.isEmpty()) return;
	QMapIterator<QString, PropertyStorage> it(db);
	while (it.hasNext()) {
		it.next();
		BlockItemPin * pin = block->pinByText(it.key());
		if (!pin) continue;
		PropertyStorage sp = it.value().properties(), pp;
		SHPluginBaseBus * plugin = SHCORE->busPlugin(pin->busType());
		if (plugin)
			pp = plugin->addPropertiesToPin(pin, sp);
		pin->properties() = mergeProperties(sp, pp).properties();
	}
}


PropertyStorage SHDatabaseStorage::loadBlockProperties(BlockItem * block, const PropertyStorage & db, const QString & plugin_name) {
	PropertyStorage pp;
	SHPluginBaseBlock * plugin = SHCORE->blockPlugin(plugin_name);
	if (plugin)
		pp = plugin->addProperties(block, db);
	foreach (BlockItemPin * pin, block->pins()) {
		SHPluginBaseBus * bp = SHCORE->busPlugin(pin->busType());
		if (bp) {
			PropertyStorage pin_pp = bp->addPropertiesToBlock(block, db);
			foreach (const PropertyStorage::Property & p, pin_pp) {
				if (!pp.isPropertyExists(p.name))
					pp << p;
			}
		}
	}
	return mergeProperties(db, pp);
}


PropertyStorage SHDatabaseStorage::loadPinProperties(BlockItemPin * pin, PropertyStorage sp, const QString & plugin_name) {
	SHPluginBaseBus * plugin = SHCORE->busPlugin(plugin_name);
	PropertyStorage pp;
	if (plugin)
		pp = plugin->addPropertiesToPin(pin, sp);
	return mergeProperties(sp, pp);
}


PropertyStorage SHDatabaseStorage::loadBusProperties(BlockBusItem * bus, const PropertyStorage & db, const QString & plugin_name) {
	PropertyStorage pp;
	SHPluginBaseBus * plugin = SHCORE->busPlugin(plugin_name);
	if (plugin)
		pp = plugin->addProperties(bus, db);
	return mergeProperties(db, pp);
}


void SHDatabaseStorage::unload() {
	qDeleteAll(src_blocks.values());
	src_blocks.clear();
	bus_types.clear();
	block_types.clear();
	table_vendors.clear();
	table_groups.clear();
}


void SHDatabaseStorage::loadBus(BlockBusItem * bus) {
	BusType bt = bus_types.value(bus->busType());
	bus->setBusName(bt.name);
	bus->setEndpointsNumber(bt.endpoints);
	bus->setWidth(bt.width);
	bus->setColor(bt.color);
	bus->setImages(bt.image_bus, bt.image_end);
	bus->setBusImageScale(bt.image_bus_scale);
	bus->setEndpointImageScale(bt.image_endpoint_scale);
	bus->updateProperties(bt.properties.properties(), SHS::Fixed);
	bus->update();
}


void SHDatabaseStorage::loadBlockModel(BlockItem * block) {
	int id = block->data(SH_Block_ID).toInt();
	block->loadModel(block_types[id].model);
}


void updateProperties(PropertyStorage * ps, PropertyStorage * src) {
	QList<PropertyStorage::Property> int_pl;
	foreach (const auto & p, ps->properties())
		if ((p.flags & SHS::Internal) > 0)
			int_pl << p;
	ps->updateProperties(src->properties(), SHS::Fixed);
	foreach (const auto & p, int_pl)
		ps->addProperty(p);
}


void SHDatabaseStorage::loadBlock(BlockItem * block, bool with_model) {
	if (with_model) {
		QMap<QString, PropertyStorage> pprops;
		foreach (BlockItemPin * bp, block->pins())
			pprops[bp->text()] = bp->propertyStorage();
		loadBlockModel(block);
		foreach (BlockItemPin * bp, block->pins())
			if (pprops.contains(bp->text()))
				bp->properties() = pprops[bp->text()].properties();
	}
	int id = block->data(SH_Block_ID).toInt();
	BlockItem * bi = src_blocks.value(id, 0);
	if (bi) {
		updateProperties(block, bi);
		foreach (BlockItemPin * bp, block->pins()) {
			BlockItemPin * sp = bi->pinByText(bp->text());
			if (!sp) continue;
			updateProperties(bp, sp);
			bp->setPropertyValue(SH_Pin_Plugin_name, sp->propertyValueByName(SH_Pin_Plugin_name));
		}
	}
}


void SHDatabaseStorage::prepareBlock(BlockItem * block) {
	int id = block->data(SH_Block_ID).toInt();
	block->addProperty(PropertyStorage::Property(SH_Block_Property_Flags, "", block_types[id].flags, SHS::Internal | SHS::Hidden));
	block->addProperty(PropertyStorage::Property(SH_Block_Property_ID, "", id, SHS::Internal | SHS::Hidden));
	foreach (QGraphicsItem * i, block->decors()) {
		i->setData(1002, true);
		i->setFlags(QGraphicsItem::GraphicsItemFlags());
		QGraphicsSimpleTextItem * itext = qgraphicsitem_cast<QGraphicsSimpleTextItem * >(i);
		AlignedTextItem * iatext = qgraphicsitem_cast<AlignedTextItem * >(i);
		if (itext || iatext) {
			QString t((itext ? itext->data(bvidDecorText).toString() : iatext->data(bvidDecorText).toString()));
			QVariantMap & vm(block_types[id].display_data);
			for (QVariantMap::const_iterator j = vm.constBegin(); j != vm.constEnd(); ++j)
				t.replace("%" + j.key(), j.value().toString());
			foreach (const PropertyStorage::Property & p, block->properties())
				t.replace("%" + p.name, p.value.toString());
			QRectF obr = i->boundingRect();
			if (itext) itext->setText(t);
			else iatext->setText(t);
			if (itext) {
				QRectF nbr = i->boundingRect();
				QSizeF ds = (obr.size() - nbr.size()) / 2.;
				i->setPos(i->pos() + QPointF(ds.width(), ds.height()));
			}
		}
	}
}


BlockItem * SHDatabaseStorage::srcBlock(const BlockItem * block) const {
	if (!block) return 0;
	return src_blocks.value(block->propertyByName(SH_Block_Property_ID).value.toInt(), 0);
}


BusType SHDatabaseStorage::srcBus(const BlockBusItem * bus) const {
	return bus_types.value(bus->busType());
}

namespace V0 {
	enum PacketFlag {
		Input = 0x1,
		Output = 0x2,
	};
	Q_DECLARE_FLAGS(PacketFlags, PacketFlag)
	struct PacketVariable {
		PacketVariable() {index = -1; func_var_name_ptr = 0;}
		QString var_name;
		QString func_var_name;
		void * func_var_name_ptr;
		QVariant value;
		int index;
	private:
		PacketVariable(const QString & n, const QVariant & v = QVariant(), int i = -1, const QString & f = QString()) {
			func_var_name_ptr = 0;
			var_name = n;
			value = v;
			index = i;
			func_var_name = f;
		}
	};
	typedef QList<PacketVariable> PacketVariablesList;
	struct Packet {
		Packet(const QString & n = QString(), const QString & c = QString(),
			   const QString & d = QString(), PacketFlags f = PacketFlag(0),
			   const PacketVariablesList & vars = PacketVariablesList(),
			   const QString & func_tr_vars = QString(),
			   const QString & reply_by_packet_ = QString()
			   ):
			name(n), command(c), description(d), func_transform_vars(func_tr_vars),
			reply_by_packet(reply_by_packet_), flags(f), variables(vars) {
		}
		QString name;
		QString command;
		QString description;
		QString pin;
		QString pin_path;
		QString func_transform_vars;
		QString reply_by_packet;
		PacketFlags flags;
		PacketVariablesList variables;
	};
	typedef QList<Packet> PacketsList;
};
inline QDataStream & operator <<(QDataStream & s, const V0::Packet & p) {
	ChunkStream cs;
	cs.add(1, p.flags).add(2, p.name).add(3, p.command).add(4, p.description).add(5, p.pin)
		.add(6, p.func_transform_vars).add(7, p.reply_by_packet);
	s << cs.data(); return s;}
inline QDataStream & operator >>(QDataStream & s, V0::Packet & p) {
	ChunkStream cs(s);
	while (!cs.atEnd()) {
		switch (cs.read()) {
		case 1: p.flags = (V0::PacketFlags)cs.getData<int>(); break;
		case 2: cs.get(p.name); break;
		case 3: cs.get(p.command); break;
		case 4: cs.get(p.description); break;
		case 5: cs.get(p.pin); break;
		case 6: cs.get(p.func_transform_vars); break;
		case 7: cs.get(p.reply_by_packet); break;
		}
	}
	return s;
}

void SHDatabaseStorage::load_v0(QSqlDatabase & base) {
	if (!base.isOpen()) return;

	QSqlRecord r;
	QSqlQuery q(base);

	q.exec("SELECT * FROM vendors");
	while (q.next()) {
		TableRow t = TableRow_fromRecord(q.record());
		table_vendors[t.id] = t;
	}

	q.exec("SELECT * FROM groups");
	while (q.next()) {
		TableRow t = TableRow_fromRecord(q.record());
		table_groups[t.id] = t;
	}

	q.exec("SELECT * FROM bus_types");
	BlockBusItem * bi = new BlockBusItem();
	BusType bus;
	while (q.next()) {
		r = q.record();
		bus.id = r.value("id").toInt();
		bus.name = r.value("name").toString();
		bus.description = r.value("description").toString();
		bus.plugin = r.value("plugin").toString();
		bus.io_classes = r.value("io_classes").toString().split(";");
		bus.endpoints = r.value("endpoints").toInt();
		bus.width = r.value("width").toDouble();
		bus.color = QColor::fromRgba(r.value("color").toUInt());
		bus.image_bus = QImage::fromData(r.value("image_bus").toByteArray());
		bus.image_end = QImage::fromData(r.value("image_endpoint").toByteArray());
		bus.image_bus_scale = r.value("image_bus_scale").toDouble();
		bus.image_endpoint_scale = r.value("image_endpoint_scale").toDouble();
		bus_types[bus.id] = bus;
		bi->setBusType(bus.id);
		loadBus(bi);
		bus.properties = loadBusProperties(bi, qDeserialize<PropertyStorage>(r.value("properties").toByteArray(), QDataStream::Qt_5_13), bus.plugin);
		bus_types[bus.id] = bus;
	}
	delete bi;

	bool old_dev = false;
	q.exec("SELECT d.id,d.vendor AS vid,v.name AS vname,v.description AS vdesc, \
d.group_ AS gid,g.name AS gname,g.description AS gdesc, \
d.name,d.description,d.flags,d.properties,d.pins_properties,d.model,d.object,d.db_plugin,d.script_plugin,d.gateway_plugin,d.packet_plugin, \
d.width,d.height,d.color \
FROM devices AS d \
LEFT JOIN vendors AS v ON d.vendor = v.id \
LEFT JOIN groups AS g ON d.group_ = g.id");
	if (q.lastError().isValid()) {
			q.exec("SELECT d.id,d.vendor AS vid,v.name AS vname,v.description AS vdesc, \
d.group_ AS gid,g.name AS gname,g.description AS gdesc, \
d.name,d.description,d.flags,d.properties,d.pins_properties,d.model,d.packets,d.db_plugin,d.script_plugin,d.packet_plugin, \
d.width,d.height,d.color \
FROM devices AS d \
LEFT JOIN vendors AS v ON d.vendor = v.id \
LEFT JOIN groups AS g ON d.group_ = g.id");
			if (q.lastError().isValid()) {
				qDebug() << q.lastQuery() << "Error:" << q.lastError();
				return;
			}
			old_dev = true;
	}
	while (q.next()) {
		r = q.record();
		int id = r.value("id").toInt();
		BlockType block;
		block.id = id;
		block.properties = qDeserialize<PropertyStorage>(r.value("properties").toByteArray(), QDataStream::Qt_5_13);
		block.pins_properties = qDeserialize<QMap<QString, PropertyStorage> >(r.value("pins_properties").toByteArray(), QDataStream::Qt_5_13);
		block.model = r.value("model").toByteArray();
		if (old_dev) {
			V0::PacketsList packets;
			QByteArray pba = r.value("packets").toByteArray();
			if (!pba.isEmpty()) {
				QDataStream _s(pba);
				_s >> packets;
				for (const auto & p: packets) {
					SHS::Command cmd;
					cmd.pin = p.pin;
					cmd.arguments["__text__"] = p.command;
					cmd.user_arguments = HexLang::getUserArguments(p.command);
					cmd.flags = (SHS::PacketFlags)(int)p.flags;
					block.object.commands[p.name] = cmd;
				}
			}
		} else
			block.object = qDeserialize<SHS::Object>(r.value("object").toByteArray(), QDataStream::Qt_5_13);
		BlockItem * blockitem = new BlockItem();
		blockitem->setData(SH_Block_Source, 0);
		src_blocks[id] = blockitem;
		blockitem->loadModel(block.model);
		if (old_dev) {
			QVector<BlockItemPin * > pins = blockitem->pins();
			foreach (BlockItemPin * p, pins) {
				if (p->propertyValueByName(SH_Pin_Plugin_name).toString().isEmpty()) {
					p->addProperty(SH_Pin_Plugin_name, "HexLang", "", SHS::Internal | SHS::Hidden);
				}
			}
			block.model = blockitem->saveModel();
		}
		block.name = r.value("name").toString();
		block.description = r.value("description").toString();
		block.vendor_id = r.value("vid").toInt();
		block.vendor_name = r.value("vname").toString();
		block.vendor_description = r.value("vdesc").toString();
		block.group_id = r.value("gid").toInt();
		block.group_name = r.value("gname").toString();
		block.group_description = r.value("gdesc").toString();
		block.block_plugin = r.value("db_plugin").toString();
		block.script_plugin = r.value("script_plugin").toString();
		block.gateway_plugin = r.value("gateway_plugin").toString();
		block.packet_plugin = r.value("packet_plugin").toString();
		block.flags = r.value("flags").toInt();
		block.width = r.value("width").toDouble();
		block.height = r.value("height").toDouble();
		if (old_dev)
			block.pins_spacing = blockitem->pinsMargin();
		else
			block.pins_spacing = r.value("pins_margin").toDouble();
		block.color = r.value("color").toUInt();
		blockitem->properties() = loadBlockProperties(blockitem, block.properties, block.block_plugin).properties();
		loadBlockPinsProperties(blockitem, block.pins_properties);
		block_types[block.id] = block;
	}
}


void SHDatabaseStorage::load_v1(QSqlDatabase & base) {
	if (!base.isOpen()) return;

	QSqlRecord r;
	QSqlQuery q(base);

	q.exec("SELECT * FROM vendors");
	while (q.next()) {
		TableRow t = TableRow_fromRecord(q.record());
		table_vendors[t.id] = t;
	}

	q.exec("SELECT * FROM groups");
	while (q.next()) {
		TableRow t = TableRow_fromRecord(q.record());
		table_groups[t.id] = t;
	}

	q.exec("SELECT * FROM bus_types");
	BlockBusItem * bi = new BlockBusItem();
	BusType bus;
	while (q.next()) {
		r = q.record();
		BusType_load(bus, r);
		bi->setBusType(bus.id);
		loadBus(bi);
		bus.properties = loadBusProperties(bi, bus.properties, bus.plugin);
		bus_types[bus.id] = bus;
	}
	delete bi;

	q.exec("SELECT * FROM block_types");
	BlockType block;
	while (q.next()) {
		r = q.record();
		BlockType_load(block, r);
		block.vendor_name = table_vendors[block.vendor_id].name;
		block.vendor_description = table_vendors[block.vendor_id].description;
		block.group_name = table_groups[block.group_id].name;
		block.group_description = table_groups[block.group_id].description;
		BlockItem * blockitem = new BlockItem();
		src_blocks[block.id] = blockitem;
		blockitem->loadModel(block.model);
		blockitem->properties() = loadBlockProperties(blockitem, block.properties, block.block_plugin).properties();
		loadBlockPinsProperties(blockitem, block.pins_properties);
		block.prepare();
		block_types[block.id] = block;
	}
}
