#include "SH_plugins_manager.h"
#include "SH_runtime.h"
#include <QTimer>


// SHPluginsManager::Plugin

void SHPluginsManager::Plugin::check(const SHPluginsLoader & loader) {
	flags = 0;
	if (local.isValid()) flags |= Installed;
	if (web.isValid())   flags |= Available;
	if (local.isValid() && web.isValid()) {
		int vc = versionCompare(web.comparableVersion(), local.comparableVersion());
		if (vc == 0) flags |= UpToDate;
		else flags |= vc > 0 ? Upgradable : Downgradable;
	}
	if (local.isValid()) {
		const auto & l(loader.readedLibraries().value(local.name));
		if (l.isDisabled()) flags |= Disabled;
		if (l.isLoaded()) flags |= Loaded;
		if (l.isError()) {
			flags |= Error;
			error = l.load_error;
		}
	}
}


QString SHPluginsManager::Plugin::displayVersion() const {
	if (flags[UpToDate]) return local.fullVersion();
	QString ret;
	if (local.isValid()) ret = local.fullVersion();
	if (web.isValid()) {
		if (!ret.isEmpty()) ret += QString::fromUtf8(" ➔ ");
		ret += web.fullVersion();
	}
	return ret;
}


SHPluginManifest SHPluginsManager::Plugin::validManifest() const {
	if (web.isValid()) return web;
	if (local.isValid()) return local;
	return {};
}


// SHPluginsManager

SHPluginsManager::SHPluginsManager() {
	connect(&web, &SHPluginsWeb::downloadFinished, this, [this](){updateInfo();});
	connect(&loader, &SHPluginsLoader::loaded, this, [this](){updateInfo();});
}


SHPluginsManager::~SHPluginsManager() {
	loader.unloadPlugins();
}


void SHPluginsManager::update(const QStringList & names) {
	QAD::CursorOverrider curov;
	QStringList to_load;
	for (const auto & n: names) {
		if (!plugin_by_name.contains(n)) continue;
		auto p = plugin_by_name[n];
		if (p.flags[Plugin::UpToDate]) continue;
		if (p.flags[Plugin::Installed])
			loader.unloadPlugin(p.local.name);
		web.downloadPlugin(p.web, loader.webDirectory());
		to_load << n;
	}
	loader.readManifests();
	auto local_libs = loader.readedLibraries();
	for (const auto & l: to_load)
		if (local_libs.contains(l))
			loader.loadPlugin(l);
	updateInfo();
}


void SHPluginsManager::remove(const QStringList & names) {
	QAD::CursorOverrider curov;
	for (const auto & n: names) {
		if (!plugin_by_name.contains(n)) continue;
		auto p = plugin_by_name[n];
		if (!p.flags[Plugin::Installed]) continue;
		loader.unloadPlugin(p.local.name);
		QFile::remove(p.local.manifestPath());
		QFile::remove(p.local.libraryPath());
		loader.removePlugin(n);
	}
	loader.readManifests();
	updateInfo();
}


void SHPluginsManager::load(const QStringList & names) {
	QAD::CursorOverrider curov;
	for (const auto & n: names) {
		if (!plugin_by_name.contains(n)) continue;
		auto p = plugin_by_name[n];
		if (!p.flags[Plugin::Installed]) continue;
		loader.loadPlugin(p.local.name);
	}
	updateInfo();
}


void SHPluginsManager::unload(const QStringList & names) {
	QAD::CursorOverrider curov;
	for (const auto & n: names) {
		if (!plugin_by_name.contains(n)) continue;
		auto p = plugin_by_name[n];
		if (!p.flags[Plugin::Installed]) continue;
		loader.unloadPlugin(p.local.name);
	}
	updateInfo();
}


void SHPluginsManager::enable(const QStringList & names) {
	QAD::CursorOverrider curov;
	for (const auto & n: names) {
		if (!plugin_by_name.contains(n)) continue;
		loader.enablePlugin(plugin_by_name[n].local.name);
	}
	updateInfo();
}


void SHPluginsManager::disable(const QStringList & names) {
	QAD::CursorOverrider curov;
	for (const auto & n: names) {
		if (!plugin_by_name.contains(n)) continue;
		loader.disablePlugin(plugin_by_name[n].local.name);
	}
	updateInfo();
}


bool SHPluginsManager::loadMandatory(const QStringList & names) {
	QAD::CursorOverrider curov;
	remain_plugins.clear();
	loader.readManifests();
	remain_plugins = names;
	for (const auto & n: names) {
		if (loader.loadPlugin(n, true))
			remain_plugins.removeAll(n);
	}
	if (remain_plugins.isEmpty()) return true;
	qDebug() << "[SHPluginsManager::loadMandatory]" << remain_plugins << "plugins missed, try to download ...";
	web.requestPluginsList();
	if (!web.waitPluginsList()) return false;
	updateInfo();
	qDebug() << "[SHPluginsManager::loadMandatory]" << "Got info from server ...";
	for (const auto & n: remain_plugins) {
		auto p = plugin_by_name[n];
		loader.unloadPlugin(p.local.name);
		if (p.web.isInvalid()) {
			qDebug() << "[SHPluginsManager::loadMandatory]" << "Can`t find" << n << "on server";
			return false;
		}
		if (!web.downloadPlugin(p.web, loader.webDirectory())) {
			qDebug() << "[SHPluginsManager::loadMandatory]" << "Can`t download" << p.web.name;
			return false;
		}
	}
	loader.readManifests();
	for (const auto & n: remain_plugins) {
		if (!loader.loadPlugin(n, true)) {
			qDebug() << "[SHPluginsManager::loadMandatory]" << "Can`t load" << n;
			return false;
		}
	}
	qDebug() << "[SHPluginsManager::loadMandatory]" << "Plugins updated";
	return true;
}


void SHPluginsManager::updateAll() {
	QAD::CursorOverrider curov;
	QStringList to_update;
	auto it = plugin_by_name.makeIterator();
	while (it.next()) {
		Plugin & p(it.value());
		if (!p.flags[Plugin::Upgradable]) continue;
		to_update << p.local.name;
	}
	update(to_update);
}


QString SHPluginsManager::pluginsStat(const PIVector<SHPluginsLoader::PluginInfo> & list) {
	QString ret;
	PIMap<int, int> counts;
	for (const auto & p: list)
		counts[p.type]++;
	auto me = getMetaEnum<SHPluginBase>("PluginType");
	auto it = counts.makeIterator();
	while (it.next()) {
		if (!ret.isEmpty()) ret += ", ";
		ret += QString::number(it.value()) + " " + me.valueToKey(it.key());
	}
	return ret;
}


void SHPluginsManager::updateInfo() {
	plugin_by_name.clear();
	auto it = loader.readedLibraries().makeIterator();
	while (it.next()) {
		auto m = it.value().manifest;
		auto & p(plugin_by_name[m.name]);
		p.plugins_list = pluginsStat(it.value().plugins);
		p.local = m;
	}
	for (const auto & m: web.manifests()) {
		if (m.isInvalid()) continue;
		auto & p(plugin_by_name[m.name]);
		p.web = m;
	}

	auto pit = plugin_by_name.makeIterator();
	while (pit.next())
		pit.value().check(loader);
	emit infoChanged();
}
