#include <QApplication>
#include <QCryptographicHash>
#include <QMessageBox>
#include <QStandardPaths>
#include <QDir>
#include <picrypt.h>
#include <piqt_connection_edit.h>
#include <piqt.h>
#include <aboutwindow.h>
#include <pip_version.h>
#include <qad_version.h>
#include "SH_runtime.h"
#include "SH_version_gen.h"


const int SDPVersion = 1;
const char SDPMagic[] = "SDPF";


SHS::ServerProject::ServerProject() {
	version = SDPVersion;
}


bool SHS::ServerProject::load(const QString & path, bool messages) {
	if (path.isEmpty()) return false;
	QFile f(path);
	if (!f.open(QIODevice::ReadOnly)) {
		if (messages) QMessageBox::critical(QApplication::activeWindow(), "Server Project", QString("Can`t open \"%1\"!").arg(path));
		return false;
	}
	QDataStream fs(&f);
	char fmagic[4];
	fs.readRawData(fmagic, 4);
	if (memcmp(SDPMagic, fmagic, 4) != 0) {
		if (messages) QMessageBox::critical(QApplication::activeWindow(), "Server Project", "This file is not Server Designer project!");
		return false;
	}
	ServerProject p;
	fs >> p;
	if (p.version < SDPVersion) {
		if (messages) QMessageBox::critical(QApplication::activeWindow(), "Server Project", "File version doesn`t match!");
		return false;
	}
	*this = p;
	if (roles.isEmpty()) {
		SHS::UserRole r;
		r.name = "guest";
		r.privilege = 0;
		roles << r;
	}
	for (int i = 0; i < roles.size(); ++i)
		roles[i].index = i;
	return true;
}


bool SHS::ServerProject::save(const QString & path) const {
	if (path.isEmpty()) return false;
	QFile f(path);
	if (!f.open(QIODevice::ReadWrite)) {
		QMessageBox::critical(QApplication::activeWindow(), "Server Project", QString("Can`t open \"%1\"!").arg(path));
		return false;
	}
	f.resize(0);
	QDataStream fs(&f);
	fs.writeRawData(SDPMagic, 4);
	fs << (*this);
	return true;
}


void SHS::ServerProject::makeEmptyNames(QString base_filename) {
	base_filename.chop(QFileInfo(base_filename).completeSuffix().size());
	if (base_filename.endsWith(".")) base_filename.chop(1);
	if (ui.isEmpty())  ui  = base_filename + ".ui";
	if (dat.isEmpty()) dat = base_filename + ".dat";
	if (var.isEmpty()) var = base_filename + ".var";
	if (cm.isEmpty())  cm  = base_filename + ".cm";
}


QStringList SHS::ServerProject::getAssFiles() const {
	QStringList ret;
	ret << qs << dat << var << cm;
	return ret;
}


bool SHS::ServerProject::addScriptFile(const QString & p) {
	if (qs.contains(p) || p.isEmpty()) return false;
	qs << p;
	if (qs_main.isEmpty())
		qs_main = qs[0];
	return true;
}


void SHS::ServerProject::removeScriptFile(const QString & p) {
	if (!qs.contains(p) || p.isEmpty()) return;
	qs.removeAll(p);
	if (qs_main == p) {
		qs_main = "";
		if (!qs.isEmpty())
			qs_main = qs[0];
	}
}


QString SHS::ServerProject::mainScriptText(QDir dir) const {
	if (qs_main.isEmpty()) return QString();
	QFile f(dir.absoluteFilePath(qs_main));
	if (!f.open(QIODevice::ReadOnly)) return QString();
	return QString::fromUtf8(f.readAll());
}


SHS::UserRole SHS::ServerProject::roleByIndex(int index) const {
	if (index < 0 || roles.size() <= index) return {};
	return roles[index];
}


SHS::DeviceParameters SHS::ServerProject::deviceParameterByName(QString n) const {
	for (const auto & p: device_parameters)
		if (p.name == n)
			return p;
	return DeviceParameters();
}




QByteArray SHS::blockConnectionModel(const BlockItem * block) {
	//QApplication::setOverrideCursor(Qt::BusyCursor);
	ConnectionEdit ce;
	if (ce.name().isEmpty()) ce.setName("conn");
	foreach (BlockItemPin * bp, block->pins()) {
		QString dev = bp->propertyValueByName("device").toString();
		if (dev.isEmpty()) continue;
		PIIODevice * d = PIIODevice::createFromFullPath(Q2PIString(dev));
		if (!d) continue;
		if (d->path().isEmpty()) continue;
		ce.addDevice(bp->text(), dev);
// 			QStringList sl = bp->propertyValueByName("Sockets pathes").toStringList();
// 			int i=0;
// 			foreach(QString s, sl) ce.addDevice(bp->text() + "_" + QString::number(i++), s);
	}
	//QApplication::restoreOverrideCursor();
	return ce.model();
}




void SHS::UserRole::prepare() {
	password_hash = QCryptographicHash::hash(password.toUtf8(), QCryptographicHash::Sha256);
}


QString SHS::UserRole::displayName() const {
	if (!display_name.isEmpty()) return display_name;
	return name;
}




void SHS::ScriptVariable::parseStore() {
	store_msecs = 0;
	if (store == "inf") return;
	QStringList dl = store.split(" ");
	dl.removeAll("");
	dl.removeAll(" ");
	store_msecs = 1;
	if (dl.size() != 2) return;
	double dv = dl[0].toDouble();
	switch (dl[1][0].toLatin1()) {
	case 'y': dv *= 12.  ;
	case 'M': dv *= 30.  ;
	case 'd': dv *= 24.  ;
	case 'h': dv *= 60.  ;
	case 'm': dv *= 60.  ;
	case 's': dv *= 1000.;
	}
	store_msecs = dv;
	//qDebug() << d << "=" << dv << "ms";
}


void SHS::Command::swapIOFlag() {
	bool f[2] = {flags.testFlag(SHS::Input), flags.testFlag(SHS::Output)};
	flags.setFlag(SHS::Input , f[1]);
	flags.setFlag(SHS::Output, f[0]);
}


void SHS::Command::setDirectionFlag(PacketFlag f) {
	flags.setFlag(Input , f == Input );
	flags.setFlag(Output, f == Output);
}


QVariant SHS::Command::invoke(QVariantMap args) {
	if (!_rt) return QVariant();
	QVariant ret;
	QVariantList arglist;
	foreach (const QString & n, user_arguments)
		arglist << args.value(n);
	emit _rt->invokeCommand(this, arglist, &ret);
	return ret;
}


QVector<QVariant> SHS::Command::obtainUserArguments() const {
	QVector<QVariant> ret;
	foreach (const QString & a, user_arguments)
		ret << arguments.value(a);
	return ret;
}


void SHS::Command::applyUserArguments(const QVector<QVariant> & ua) {
	for (int i = 0; i < piMini(ua.size(), user_arguments.size()); ++i)
		arguments[user_arguments[i]] = ua[i];
}


SHS::Object & SHS::Object::addProperty(const QString & n, const QVariant & v) {
	properties[n] = v;
	return *this;
}


SHS::Object & SHS::Object::addObject(const QString & n, const SHS::Object & v) {
	objects   [n] = v;
	return *this;
}


SHS::Object & SHS::Object::addCommand(const QString & n, const SHS::Command & v) {
	command(n, v, Input);
	return *this;
}


SHS::Object & SHS::Object::addCommand(const QString & n, const QStringList & user_args, const SHS::Command & v) {
	command(n, v, Input).user_arguments = user_args;
	return *this;
}


SHS::Object & SHS::Object::addEvent(const QString & n, const SHS::Command & v) {
	command(n, v, Output);
	return *this;
}


SHS::Object & SHS::Object::addEvent(const QString & n, const QStringList & user_args, const SHS::Command & v) {
	command(n, v, Output).user_arguments = user_args;
	return *this;
}


SHS::Command & SHS::Object::command(const QString & n, const Command & v, PacketFlag dir) {
	SHS::Command & ret(commands[n]);
	ret = v;
	ret.name = n;
	ret.setDirectionFlag(dir);
	return ret;
}


QVariant SHS::Object::property(const QString & n) {
	return properties.value(n);
}


SHS::Object SHS::Object::object(const QString & n) {
	return objects   .value(n);
}


SHS::Command SHS::Object::command(const QString & n) {
	Command ret = commands.value(n);
	if (ret.isValid() && (ret.flags ==  Input))
		return ret;
	return Command();
}


SHS::Command SHS::Object::event(const QString & n) {
	Command ret = commands.value(n);
	if (ret.isValid() && (ret.flags == Output))
		return ret;
	return Command();
}


void SHS::Object::bindProperty(const QString & prop, const QString & cmd) {
	if (!commands.contains(cmd)) return;
	commands[cmd].binded_property = prop;
}


void SHS::Object::applyPathes(const PIStringList & p, SHS::PacketFlag flag) {
	PIStringList pathes;
	piForeachC (PIString & i, p)
		pathes << PIIODevice::normalizeFullPath(i);
	QMutableMapIterator<QString, Command> cit(commands);
	while (cit.hasNext()) {
		cit.next();
		if (cit.value().flags.testFlag(flag))
			cit.value().devices = pathes;
	}
	QMutableMapIterator<QString, Object> oit(objects);
	while (oit.hasNext()) {
		oit.next().value().applyPathes(pathes, flag);
	}
}


void SHS::Object::applyPluginCommands(const QVector<SHS::PluginCommand> & c, SHS::PacketFlag flag) {
	QMutableMapIterator<QString, Command> cit(commands);
	while (cit.hasNext()) {
		cit.next();
		if (cit.value().flags.testFlag(flag))
			cit.value().cmd = c;
	}
	QMutableMapIterator<QString, Object> oit(objects);
	while (oit.hasNext()) {
		oit.next().value().applyPluginCommands(c, flag);
	}
}


void SHS::Object::applyBlockPlugin(SHPluginBase * p) {
	QMutableMapIterator<QString, Command> cit(commands);
	while (cit.hasNext()) {
		cit.next();
		cit.value().block_plugin = p;
	}
	QMutableMapIterator<QString, Object> oit(objects);
	while (oit.hasNext()) {
		oit.next().value().applyBlockPlugin(p);
	}
}


void SHS::Object::applyRuntime(SHRuntime * rt) {
	QMutableMapIterator<QString, Command> cit(commands);
	while (cit.hasNext()) {
		cit.next();
		cit.value()._rt = rt;
	}
	QMutableMapIterator<QString, Object> oit(objects);
	while (oit.hasNext()) {
		oit.next().value().applyRuntime(rt);
	}
}


void SHS::Object::swapIOFlags() {
	QMutableMapIterator<QString, Command> cit(commands);
	while (cit.hasNext()) {
		cit.next().value().swapIOFlag();
	}
	QMutableMapIterator<QString, Object> oit(objects);
	while (oit.hasNext()) {
		oit.next().value().swapIOFlags();
	}
}


void SHS::Object::renameObject(const QString & name, const QString & new_name) {
	if (name == new_name) return;
	if (!objects.contains(name)) return;
	objects[new_name] = objects.take(name);
}
