#include "core.h"
#include "blockview.h"
#include <QFileInfo>
#include <QMenuBar>
#include <QDir>
#include <QTimer>
#include <QStyleFactory>
#include <piiodevice.h>
#include <piqt.h>
#include <qvariantedit.h>
#include <piqt_connection_edit.h>
#include <piconnection.h>
#include "SH_network_types.h"
#include "SH_plugin_widgets.h"


SHDesignerCore::SHDesignerCore() {
	PIConnection::setFakeMode(true);
	connect(&file_watcher, SIGNAL(fileChanged(QString)), this, SLOT(fileChanged(QString)));
	connect(&RUNTIME->plugins_manager->localInterface(), &SHPluginsLoader::aboutUnloadPlugin, this, [this](SHPluginBase * p){
		deleteEditor(p->id());
	});
	auto dcnl = PIIODevice::availableClasses();
	auto dpl  = PIIODevice::availablePrefixes();
	for (int i = 0; i < dcnl.size_s(); ++i)
		io_prefixes[PI2QString(dcnl[i])] = PI2QString(dpl[i]) + "://";
}


SHDesignerCore::~SHDesignerCore() {
}


SHDesignerCore * SHDesignerCore::instance() {
	static SHDesignerCore core;
	return &core;
}


bool SHDesignerCore::isDefaultDBOpened() {
	return QSqlDatabase::database(QLatin1String(QSqlDatabase::defaultConnection), false).isOpen();
}


void SHDesignerCore::setProjectsDir(QString d) {
	d = parsePath(d);
	QDir::current().mkpath(d);
	if (!projects_dir.isEmpty()) {
	}
	projects_dir = d;
}


void SHDesignerCore::setDatabasesDir(QString d) {
	d = parsePath(QApplication::applicationDirPath() + "/" + d);
	QDir::current().mkpath(d);
	databases_dir = d;
}


void SHDesignerCore::setDatabaseFile(QString d) {
	if (base.isOpen()) {
		base.close();
		QSqlDatabase::removeDatabase(base.connectionName());
	}
	base = QSqlDatabase::addDatabase("QSQLITE");
	//base.setHostName(conf.getValue("host", "").stringValue());
	//base.setPort(conf.getValue("port", -1));
	if (d.isEmpty()) d = ".";
	base.setDatabaseName(d);
	if (!base.open()) {
		qDebug() << "[SHDesignerCore::addDatabase] Open" << d << "error:" << base.lastError();
		return;
	}
	reloadDB();
}


PropertyStorage SHDesignerCore::parseDefaultProperties(const QStringList & props) {
	PropertyStorage ret;
	foreach (const QString & s, props) {
		PropertyStorage::Property p = PropertyStorage::parsePropertyLine(s);
		if (p.name.isEmpty()) continue;
		ret << p;
	}
	return ret;
}


void SHDesignerCore::setCurrentProject(const QString & p) {
	current_project_dir = p;
	current_project_name = QFileInfo(p).fileName();
	//qDebug() << "setCurrentProject" << p << current_project_name << current_project_dir;
	QString old_db_path = currentProjectDir().filePath("db_" + current_project_name + ".dat");
	QString db_path     = currentProjectDir().filePath("db_" + current_project_name + SHFileSuffix + ".dat");
	if (QFile::exists(old_db_path) && !QFile::exists(db_path)) {
		QFile::copy(old_db_path, db_path);
		QMetaObject::invokeMethod(this, "databaseResaveRequest", Qt::QueuedConnection);
	}
	setDatabaseFile(db_path);
	loadPropertiesFromFile(currentProjectDir().filePath("plugins.user"), RUNTIME->plugins_props_project);
	QDir::setCurrent(currentProjectDir().absolutePath());
	emit changedPluginsProperties();
}


void SHDesignerCore::loadPropertiesFromFile(const QString & filename, QMap<uint, PropertyStorage> & props) {
	QMap<uint, PropertyStorage> income = loadFromFile<decltype(income)>(filename);
	auto pl = RUNTIME->plugins_manager->localInterface().loadedPlugins();
	for (const auto & p: pl) {
		uint id = p.plugin->id();
		income[id].updateProperties(props[id].properties());
		props[id] = income[id];
		//props[id].updateProperties(income[id].properties());
	}
}


void SHDesignerCore::savePropertiesToFile(const QString & filename, QMap<uint, PropertyStorage> & props) {
	saveToFile(filename, props);
}


void SHDesignerCore::deleteEditor(int id) {
	if (!plugin_editors.contains(id)) return;
	auto & e(plugin_editors[id]);
	if (!e.main_widget) {
		plugin_editors.remove(id);
		return;
	}
	if (e.center && (e.center != e.main_widget))
		((QMainWindow*)e.main_widget)->setCentralWidget(e.center);
	piDeleteSafety(e.main_widget);
	plugin_editors.remove(id);
}


void SHDesignerCore::deleteEditors() {
	auto keys = plugin_editors.keys();
	for (auto id: keys) {
		deleteEditor(id);
	}
	plugin_editors.clear();
}


void SHDesignerCore::updateByFileWatcher() {
	emit updateFileWatcherRequest();
}


void SHDesignerCore::fileChanged(QString f) {
	QTimer::singleShot(500, this, SLOT(updateByFileWatcher()));
}


void SHDesignerCore::reloadDB() {
	storage.load(base);
	auto bit = storage_plugins.block_types.makeIterator();
	while (bit.next()) {
		BlockType & block(bit.value());
		BlockItem * bi = new BlockItem();
		bi->loadModel(block.model);
		bi->setData(SH_Block_ID, block.id);
		bi->setData(SH_Block_Source, (int)sPlugins);
		assignPinTypes(bi, block);
		storage_plugins.loadBlockPinsProperties(bi, block.pins_properties);
		bi->properties() = storage_plugins.loadBlockProperties(bi, block.properties, block.block_plugin).properties();
		bi->addProperty("block_name", block.name, "", SHS::Hidden);
		storage_plugins.src_blocks[block.id] = bi;
	}
	plugins_api.bus_types = storage.bus_types;
	plugins_api.block_types = storage.block_types;
	plugins_api.src_blocks = storage.src_blocks;
	plugins_api.table_vendors = storage.table_vendors;
	plugins_api.table_groups = storage.table_groups;
	emit changed();
}


SHPluginBaseBlock * SHDesignerCore::blockPlugin(BlockItem * block) {
	if (!block) return 0;
	int id = block->data(SH_Block_ID).toInt();
	return blockPlugin(getStorage(block).block_types[id].block_plugin);
}


SHPluginBaseBlock * SHDesignerCore::blockPlugin(const QString & plugin_name) {
	return (SHPluginBaseBlock * )(RUNTIME->plugins_manager->localInterface().getPlugin(plugin_name, SHPluginBase::Block).plugin);
}


SHPluginBaseScript * SHDesignerCore::scriptPlugin(BlockItem * block) {
	if (!block) return 0;
	int id = block->data(SH_Block_ID).toInt();
	return scriptPlugin(getStorage(block).block_types[id].script_plugin);
}


SHPluginBaseScript * SHDesignerCore::scriptPlugin(const QString & plugin_name) {
	return (SHPluginBaseScript * )(RUNTIME->plugins_manager->localInterface().getPlugin(plugin_name, SHPluginBase::Script).plugin);
}


SHPluginBaseBus * SHDesignerCore::busPlugin(BlockBusItem * bus) {
	if (!bus) return 0;
	return busPlugin(storage.bus_types[bus->busType()].plugin);
}


SHPluginBaseBus * SHDesignerCore::busPlugin(int type) {
	return busPlugin(storage.bus_types[type].plugin);
}


SHPluginBaseBus * SHDesignerCore::busPlugin(const QString & plugin_name) {
	return (SHPluginBaseBus * )(RUNTIME->plugins_manager->localInterface().getPlugin(plugin_name, SHPluginBase::Bus).plugin);
}


SHPluginBasePacket * SHDesignerCore::packetPlugin(BlockItemPin * pin) {
	if (!pin) return 0;
	QString plugin_name = pin->propertyValueByName(SH_Pin_Plugin_name).toString();
	SHPluginsLoader::PluginInfo pi = RUNTIME->plugins_manager->localInterface().getPlugin(plugin_name, SHPluginBase::Packet);
	return (SHPluginBasePacket*)pi.plugin;
}


SHPluginBasePacket * SHDesignerCore::defaultPacketPlugin() {
	return (SHPluginBasePacket *)(RUNTIME->plugins_manager->localInterface().getPlugin("HexLang", SHPluginBase::Packet).plugin);
}


uint SHDesignerCore::packetPluginID(BlockItemPin * pin) {
	SHPluginBasePacket * p = packetPlugin(pin);
	return p ? p->id() : 0;
}


SHPluginBase * SHDesignerCore::packetGateway(BlockItem * block) {
	if (!block) return 0;
	int id = block->data(SH_Block_ID).toInt();
	QString plugin_name = storage.block_types[id].gateway_plugin;
	SHPluginsLoader::PluginInfo pi = RUNTIME->plugins_manager->localInterface().getPlugin(plugin_name, SHPluginBase::Gateway);
	return (SHPluginBase*)pi.plugin;
}


uint SHDesignerCore::packetGatewayID(BlockItem * block) {
	SHPluginBase * p = packetGateway(block);
	return p ? p->id() : 0;
}


void SHDesignerCore::pluginBlockAction(BlockItem * block, const SHPluginBase::PluginAction & action) {
	if (!block) return;
	SHPluginBaseBlock * plugin = blockPlugin(block);
	if (!plugin) return;
	if (action.flags.testFlag(SHPluginBase::GenerateDatabase)) {
		QAD::CursorOverrider curov;
		writeConnectionFile(block, plugin->generatedConnectionFile(block));
		dbgenerator.generate(block, plugin->generatedDatabaseFile(block), project_properties);
	}
	plugin->runAction(block, action, project_properties);
}


void SHDesignerCore::pluginBusAction(BlockBusItem * bus, const SHPluginBase::PluginAction & action) {
	if (!bus) return;
	SHPluginBaseBus * plugin = busPlugin(bus);
	if (!plugin) return;
	else plugin->runAction(bus, action);
}


SHDatabaseStorage & SHDesignerCore::databaseStorage() {
	return storage;
}


SHDatabaseStorage & SHDesignerCore::getStorage(int source) {
	static SHDatabaseStorage null_ds;
	switch (source) {
	case sDatabase: return storage; break;
	case sPlugins : return storage_plugins; break;
	default: break;
	}
	return null_ds;
}


SHDatabaseStorage & SHDesignerCore::getStorage(BlockItem * item) {
	StorageSource s = sDatabase;
	if (item) s = (StorageSource)item->data(SH_Block_Source).toInt();
	return getStorage(s);
}


SHDesignerCore::Editor * SHDesignerCore::blockEditor(BlockItem * block) {
	SHPluginBaseBlock * p = blockPlugin(block);
	if (!p) return 0;
	if (!plugin_editors.contains(p->id())) return 0;
	return &(plugin_editors[p->id()]);
}


void SHDesignerCore::prepareBlock(BlockItem * block) {
	int source = block->data(SH_Block_Source).toInt();
	if (source == sPlugins) {
		assignPinTypes(block, storage_plugins.block_types[block->data(SH_Block_ID).toInt()]);
	}
	getStorage(block).prepareBlock(block);
}


void SHDesignerCore::assignPinTypes(BlockItem * bi, const BlockType & block) {
	for (auto * pin: bi->pins()) {
		QString btn = block.pins_properties[pin->text()].propertyValueByName(SH_Pin_Bus_type).toString();
		QString btp = block.pins_properties[pin->text()].propertyValueByName(SH_Pin_Plugin_name).toString();
		auto _it = storage.bus_types.makeIterator();
		while (_it.next()) {
			if (_it.value().name == btn) {
				pin->setBusType(_it.key());
				break;
			}
		}
	}
}


QString prepareText(QString str, const QMap<QString, QString> & map) {
	if (map.isEmpty() || str.length() < 2) return str;
	QMapIterator<QString, QString> it(map);
	while (it.hasNext()) {
		it.next();
		str.replace(it.key(), it.value());
	}
	return toCName(str);
}

void SHDesignerCore::prepareObject(SHS::Object & o, BlockItem * block) {
	QMap<QString, QString> map;
	foreach (const PropertyStorage::Property & p, block->properties())
		map["%" + p.name] = p.value.toString();
	QMap<QString, SHS::Object> nol;
	QMutableMapIterator<QString, SHS::Object> oit(o.objects);
	while (oit.hasNext()) {
		oit.next();
		prepareObject(oit.value(), block);
		nol[prepareText(oit.key(), map)] = oit.value();
	}
	o.objects = nol;
}


void SHDesignerCore::mergeObject(SHS::Object & src, const SHS::Object & in) {
	QMapIterator<QString, QVariant> pit(in.properties);
	while (pit.hasNext()) {
		pit.next();
		src.properties[pit.key()] = pit.value();
	}
	QMapIterator<QString, SHS::Command> it(in.commands);
	while (it.hasNext()) {
		it.next();
		src.commands[it.key()] = it.value();
	}
	QMapIterator<QString, SHS::Object> oit(in.objects);
	while (oit.hasNext()) {
		oit.next();
		if (!src.objects.contains(oit.key()))
			src.objects[oit.key()] = oit.value();
		else
			mergeObject(src.objects[oit.key()], oit.value());
	}
}


void SHDesignerCore::actualizeProjectProperties(PropertyStorage & ps) {
	auto pl = RUNTIME->plugins_manager->localInterface().loadedPlugins();
	for (auto p: pl) {
		if (!p.plugin) continue;
		auto np = p.plugin->addPropertiesToProject();
		for (const auto & i: np)
			if (!ps.isPropertyExists(i.name))
				ps.addProperty(i);
	}
	//if (!ps.isPropertyExists("cloud_address"))
	//	ps.addProperty("cloud_address", QString("shs.tools:%1").arg(SHNetworkTypes::cloudPort), "PICloud dispatcher address (host:port)");
	//if (!ps.isPropertyExists("cloud_enabled"))
	//	ps.addProperty("cloud_enabled", false, "PICloud dispatcher enable");
}


void SHDesignerCore::writeConnectionFile(const BlockItem * block, const QString & file) {
	if (file.isEmpty()) return;
	QPIConfig f(file, QIODevice::ReadWrite);
	if (!f.isOpen()) return;
	BlockItem::Property pconn = block->propertyByName("_connection_");
	QByteArray cm = pconn.value.toByteArray();
	if (cm.isEmpty()) cm = SHS::blockConnectionModel(block);
	f.setValue("model", cm);
}


void SHDesignerCore::reloadPlugins() {
	QAD::CursorOverrider curov;
	deleteEditors();
	storage_plugins.clear();
	RUNTIME->plugins_manager->localInterface().loadPlugins();
	auto pl = RUNTIME->plugins_manager->localInterface().loadedPlugins();
	for (const auto & p: pl) {
		uint id = p.plugin->id();
		RUNTIME->plugins_props_project[id].updateProperties(p.plugin->projectProperties().properties());
		if (p.plugin->type() == SHPluginBase::Block) {
			SHPluginBaseBlock * bp = ((SHPluginBaseBlock*)p.plugin);
			SHBlockWidgetBase * e = bp->createEditor();
			if (e) {
				e->plugin = p.plugin;
				plugin_editors[id] = Editor::make(e);
			}
			QList<BlockType> blocks = bp->createBlockTypes();
			for (auto & b: blocks) {
				b.id = Q2PIString(QString("%1_%2").arg(p.plugin->name(), b.name)).hash();
				storage_plugins.block_types[b.id] = b;
			}
			if (!blocks.isEmpty())
				qDebug() << "[SHDesignerCore]" << p.manifest.file << ": loaded" << blocks.size() << "blocks";
		}
		if (p.plugin->type() == SHPluginBase::Extra) {
			SHPluginBaseExtra * ep = ((SHPluginBaseExtra*)p.plugin);
			ep->sh_designer_api = &plugins_api;
		}
		connect(p.plugin, SIGNAL(__message(int,QString)), &sprocess, SLOT(addMessage(int,QString)), Qt::DirectConnection);
	}
	emit changedPlugins();
	emit changedPluginsProperties();
	reloadDB();
}


SHDesignerCore::Editor SHDesignerCore::Editor::make(SHBlockWidgetBase * e) {
	SHDesignerCore::Editor ret;
	ret.base = e;
	ret.main_widget = e->widget();
	if (!ret.main_widget) return ret;
	QStyle * fs = QStyleFactory::create("Fusion");
	QWidgetList wl = ret.main_widget->findChildren<QWidget*>();
	for (auto * w: wl) w->setStyle(fs);
	ret.center = ret.main_widget;
	QMainWindow * mw = qobject_cast<QMainWindow*>(ret.main_widget);
	if (mw) {
		ret.center = mw->centralWidget();
		ret.docks = mw->findChildren<QDockWidget*>();
		foreach (auto * d, ret.docks)
			d->setProperty("_area_", (int)(mw->dockWidgetArea(d)));
		if (mw->menuBar()) ret.menus = mw->menuBar()->actions();
	}
	return ret;
}
