#include "script_engine.h"
#include <QDebug>
#include <QMetaObject>
#include <QMetaMethod>
#include <QTimer>
#include <QFile>
#include <QFileInfo>
#include <QDateTime>
#include <QDir>
#include <QWidget>
#include <QPlainTextEdit>
#include <QJSValueIterator>
#include <math.h>
#include <QRegularExpression>
#include <logview.h>
#include "SH_plugins.h"
#include "SH_server_base.h"
#include "SH_runtime.h"
#include "SH_plugins_manager.h"
#include "pitime.h"
#include "button.h"
#include "script_common.h"
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
#  include <QRandomGenerator>
#endif


SHSScriptProxy::SHSScriptProxy(SHSScriptEngine * e): engine(e) {
}


void SHSScriptProxy::timerEvent(QTimerEvent * e) {
	int id = e->timerId();
	killTimer(id);
	QJSValue f = delayed.value(id);
	delayed.remove(id);
	if (f.isCallable())
		engine->checkJSValue(f.call());
}


void SHSScriptProxy::__sendCommand(int id, QVariantList args) {
	engine->sendCommand(id, args.toVector());
}


void SHSScriptProxy::__processCommand(int id, uint pid, QVariantList args) {
	engine->processCommand(id, pid, args.toVector());
}


QVariant SHSScriptProxy::__varGet(int id) {
	return engine->db.variableValue(id);
}


QVariant SHSScriptProxy::__varSet(int id, QVariant v) {
	engine->db.setVariableValue(id, v.toDouble());
	if (!engine->db.is_run) {
		engine->log("VarDB not running!", SHServer::LogWarning);
	}
	if (!engine->db.isHistoryDBRunning()) {
		engine->log("HistoryDB not running!", SHServer::LogWarning);
	}
	return v;
}


QVariant SHSScriptProxy::__propGet(int oid, QString prop) {
	SHS::Object * o = RUNTIME->cache.object_by_id.value(oid, 0);
	if (!o) return QVariant();
	//qDebug() << "__propGet" << oid << prop;
	return o->properties.value(prop);
}


QVariant SHSScriptProxy::__propSet(int oid, QString prop, QVariant v) {
	SHS::Object * o = RUNTIME->cache.object_by_id.value(oid, 0);
	if (!o) return QVariant();
	o->properties[prop] = v;
	SHS::Command * cmd = o->binded_properties.value(prop);
	if (cmd)
		__sendCommand(cmd->id, QVariantList() << v);
	//qDebug() << "__propSet" << oid << prop << v;
	return v;
}


QVariant SHSScriptProxy::__wpropGet(QVariant oid, QString prop) {
	auto * w = engine->widgets_by_ptr.value(oid.toULongLong());
	if (!w) return QVariant();
	return w->property(prop.toLatin1());
}


QVariant SHSScriptProxy::__wpropSet(QVariant oid, QString prop, QVariant v) {
	auto * w = engine->widgets_by_ptr.value(oid.toULongLong());
	if (!w) return QVariant();
	return w->setProperty(prop.toLatin1(), v);
}


void SHSScriptProxy::log(QString s, int type) {
	//qDebug() << "proxy" << "log" << s << type;
	emit engine->log(s, type);
}


void SHSScriptProxy::message(QString s, int type) {
	//qDebug() << "proxy" << "message" << s << type;
	engine->logToWidget(tr("Msg: %1").arg(s));
	emit engine->message(s, type);
}


void SHSScriptProxy::usleep(int us) {
	//qDebug() << "proxy" << "usleep" << us;
	piUSleep(us);
}


QDateTime SHSScriptProxy::lastDate(int y, int M, int d, int h, int m, int s, int z) {
	QDate _d = QDate::currentDate();
	QTime _t = QTime::currentTime();
	if (y > 0) _d = _d.addYears (-y);
	if (M > 0) _d = _d.addMonths(-M);
	if (d > 0) _d = _d.addDays  (-d);
	if (h > 0) _t = _t.addSecs  (-h * 60 * 60);
	if (m > 0) _t = _t.addSecs  (-m * 60);
	if (s > 0) _t = _t.addSecs  (-s);
	if (z > 0) _t = _t.addMSecs (-z);
	return QDateTime(_d, _t);
}


QVector<QPointF> varHistoryToQML(const QList<QPair<QDateTime, double> > & h) {
	QVector<QPointF> ret;
	for (int i = 0; i < h.size(); ++i) {
		ret << QPointF(h[i].first.toMSecsSinceEpoch(), h[i].second);
	}
	return ret;
}


QTimer * SHSScriptProxy::newTimer() {
	return new QTimer();
}


SHSScriptSingleTimer * SHSScriptProxy::newSingleTimer(QJSValue func) {
	auto * ret = new SHSScriptSingleTimer(engine);
	ret->setFunction(func);
	return ret;
}


SHSScriptQuery * SHSScriptProxy::newQuery(QVariantMap props) {
	auto * ret = new SHSScriptQuery(engine);
	if (props.contains("queryTimeout")) ret->setQueryTimeout(props["queryTimeout"].toInt());
	if (props.contains("tryTimeout")) ret->setTryTimeout(props["tryTimeout"].toInt());
	if (props.contains("maxTries")) ret->setMaxTries(props["maxTries"].toInt());
	if (props.contains("maxMissQueries")) ret->setMaxMissQueries(props["maxMissQueries"].toInt());
	return ret;
}


SHSScriptTimeMeasurer * SHSScriptProxy::newTimeMeasurer() {
	return new SHSScriptTimeMeasurer(engine);
}


uint SHSScriptProxy::randomInt(uint max) {
	uint r = 0;
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
	r = QRandomGenerator::global()->generate();
#else
	r = randomi();
#endif
	return max == 0 ? r : r % max;
}


double SHSScriptProxy::randomFloat() {
	return randomd() / 2. + 0.5;
}


void SHSScriptProxy::save(int id, QVariant v) {
	engine->db.setScriptVariableValue(id, v);
	if (!engine->db.is_run) {
		engine->log("VarDB not running!", SHServer::LogWarning);
	}
}


QVariant SHSScriptProxy::load(int id) {
	return engine->db.scriptVariableValue(id);
}


QString SHSScriptProxy::getScript(QString name) {
	return engine->files_content.value(name);
}


QByteArray SHSScriptProxy::getResource(QString name) {
	QFile f(RUNTIME->server_data.res_path + "/" + name);
	if (!f.open(QIODevice::ReadOnly)) {
		qDebug() << "Can`t open" << f.fileName();
		return QByteArray();
	}
	return f.readAll();
}


QString SHSScriptProxy::formJSON() {
	return RUNTIME->json_ui;
}


QVariant SHSScriptProxy::getVariable(QString name) {
	return __varGet(engine->db.variableID(name));
}


void SHSScriptProxy::setVariable(QString name, QVariant v) {
	__varSet(engine->db.variableID(name), v);
}


QObject * SHSScriptProxy::getWidget(QString name) {
	return engine->widgets.value(name, nullptr);
}


int SHSScriptProxy::delayCall(int msec, QJSValue func) {
	int id = startTimer(msec);
	delayed[id] = func;
	return id;
}


bool SHSScriptProxy::cancelDelayCall(int id) {
	if (!delayed.contains(id)) return false;
	delayed.remove(id);
	killTimer(id);
	return true;
}


bool SHSScriptProxy::isTest() const {
	return RUNTIME->is_test;
}




SHSScriptSingleTimer::SHSScriptSingleTimer(SHSScriptEngine * e): engine(e) {
}


void SHSScriptSingleTimer::timerEvent(QTimerEvent * e) {
	killTimer(e->timerId());
	id = -1;
	if (func_.isCallable())
		engine->checkJSValue(func_.call());
}


void SHSScriptSingleTimer::start() {
	stop();
	id = startTimer(msec_);
	tm.restart();
}


void SHSScriptSingleTimer::stop() {
	if (id < 0) return;
	killTimer(id);
	id = -1;
}


void SHSScriptSingleTimer::setDelay(int msec) {
	msec_ = msec;
	if (!isStarted()) return;
	stop();
	id = startTimer(piMaxi((quint64)msec_ - tm.elapsed(), 0));
}




SHSScriptQuery::SHSScriptQuery(SHSScriptEngine * e): engine(e) {
}


void SHSScriptQuery::timerEvent(QTimerEvent * e) {
	if (e->timerId() == timer_query) {
		startQuery();
		return;
	}
	if (e->timerId() == timer_try) {
		if (++cur_try >= max_tries) {
			destroyTimer(timer_try);
			if (!cur_accepted) {
				if (++cur_miss >= max_miss)
					setMissed(true);
			}
			return;
		}
		query();
		return;
	}
}


void SHSScriptQuery::destroyTimer(int & id) {
	if (id < 0) return;
	killTimer(id);
	id = -1;
}


void SHSScriptQuery::query() {
	if (cur_func >= qfuncs_.size()) return;
	//qDebug() << "SHSScriptQuery::query" << cur_func << qfuncs_.size();
	if (qfuncs_[cur_func].isCallable())
		engine->checkJSValue(qfuncs_[cur_func].call());
}


void SHSScriptQuery::startQuery() {
	destroyTimer(timer_try);
	cur_accepted = false;
	cur_try = cur_func = 0;
	query();
	timer_try = startTimer(try_timeout);
}


void SHSScriptQuery::setMissed(bool yes) {
	if (is_missed != yes) {
		if (yes) emit missed();
		else emit accepted();
	}
	is_missed = yes;
}


void SHSScriptQuery::start() {
	startOnce();
	timer_query = startTimer(query_timeout);
}


void SHSScriptQuery::startOnce() {
	stop();
	startQuery();
}


void SHSScriptQuery::stop() {
	destroyTimer(timer_try);
	destroyTimer(timer_query);
	cur_try = cur_func = 0;
	cur_miss = 0;
	is_missed = true;
	cur_accepted = false;
}


void SHSScriptQuery::accept() {
	cur_miss = 0;
	if (++cur_func < qfuncs_.size())
		cur_try = 0;
	else {
		destroyTimer(timer_try);
		cur_accepted = true;
		setMissed(false);
		cur_func = 0;
	}
}




SHSScriptTimeMeasurer::SHSScriptTimeMeasurer(SHSScriptEngine * e): engine(e) {
}


int SHSScriptTimeMeasurer::elapsed() {
	return piRound(tm.elapsed_m());
}


void SHSScriptTimeMeasurer::reset() {
	tm.reset();
}





QJSValue QPolygonFtoScriptValue(QJSEngine * e, const QVector<QPointF> & s) {
	QJSValue ret = e->newArray();
	for (int i = 0; i < s.size(); ++i) {
		QJSValue v = e->newObject();
		v.setProperty("time", e->toScriptValue(QDateTime::fromMSecsSinceEpoch(s[i].x())));
		v.setProperty("value", s[i].y());
		ret.setProperty(i, v);
	}
	return ret;
}

void QPolygonFfromScriptValue(const QJSValue & o, QVector<QPointF> & s) {
	s.clear();
	QJSValueIterator it(o);
	while (it.hasNext()) {
		it.next();
		if (it.hasNext())
			s << QPointF(it.value().property("time").toDateTime().toMSecsSinceEpoch(),
						it.value().property("value").toNumber());
	}
}




SHSScriptConsole::SHSScriptConsole(SHSScriptEngine * e): engine(e) {
}


void SHSScriptConsole::clear() {
	engine->clearLogWidget();
}




SHSDebugHandler::SHSDebugHandler() {
	qInstallMessageHandler(handler);
}


SHSDebugHandler * SHSDebugHandler::instance() {
	static SHSDebugHandler ret;
	return &ret;
}


void SHSDebugHandler::handler(QtMsgType type, const QMessageLogContext & ctx, const QString & msg) {
	piCout << Q2PIString(msg);
	if (QLatin1String(ctx.category) == QLatin1String("qml")) {
		emit instance()->printed(msg);
	}
}




SHSScriptEngine::SHSScriptEngine(QObject * parent): QObject(parent), SHSScriptEngineBase() {
	project.setObjectName("Project");
	connect(&conn_, SIGNAL(received(SHS::Command*, QVariantMap)), this, SLOT(eventReceived(SHS::Command*, QVariantMap)));
	connect(&conn_, SIGNAL(logIOEvent(QString)), this, SIGNAL(logIO(QString)));
	connect(SHSDebugHandler::instance(), SIGNAL(printed(QString)), this, SLOT(logToWidget(QString)));
	connect(RUNTIME, SIGNAL(invokeCommand(SHS::Command*, QVariantList, QVariant*)), this, SLOT(invokeCommand(SHS::Command*, QVariantList, QVariant*)));
	connect(RUNTIME, SIGNAL(sendBroadcast(QByteArray)), this, SLOT(sendBroadcast(QByteArray)));
	new_mol << QTimer::staticMetaObject << SHSScriptSingleTimer::staticMetaObject
			<< SHSScriptQuery::staticMetaObject << SHSScriptTimeMeasurer::staticMetaObject;
	engine_ = 0;
	logview = 0;
	no_graphics = false;
	startTimer(5000);
}


SHSScriptEngine::~SHSScriptEngine() {
	remove();
}

void SHSScriptEngine::create(bool force) {
	if (force) remove();
	if (engine_ == 0) {
		engine_ = new QQmlEngine();
		connect(engine_, SIGNAL(warnings(const QList<QQmlError> &)), this, SLOT(engineWarnings(const QList<QQmlError> &)));
		RUNTIME->engine_ = engine_;
		RUNTIME->shs_engine_ = this;
		QJSValue root_SHS = engine_->newQObject(new SHSScriptProxy(this));
		QJSValue root_console = engine_->newQObject(new SHSScriptConsole(this));
		engine_->setProperty("_parent", quintptr(this));
#if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
		engine_->globalObject().setProperty("QTimer", engine_->newQMetaObject(&QTimer::staticMetaObject));
		engine_->globalObject().setProperty("Qt", engine_->newQMetaObject(getQtMetaObject()));
		engine_->globalObject().setProperty("SHServer", engine_->newQMetaObject(&SHServer::staticMetaObject));
#else
		engine_->globalObject().setProperty("QTimer", engine_->newQObject(new QTimer()));
		//engine_->globalObject().setProperty("Qt", engine_->newQObject(&staticQtMetaObject));
		engine_->globalObject().setProperty("SHServer", engine_->newQObject(new SHServer()));
#endif
		qmlRegisterAnonymousType<QTimer>("SHS", 0);
		qmlRegisterAnonymousType<SHSScriptSingleTimer>("SHS", 0);
		qmlRegisterAnonymousType<SHSScriptQuery>("SHS", 0);
		qmlRegisterAnonymousType<SHSScriptTimeMeasurer>("SHS", 0);

		engine_->globalObject().setProperty("SHS", root_SHS);
		engine_->globalObject().setProperty("SHSConsole", root_console);
		QJSValue co = engine_->globalObject().property("console");
		co.setProperty("clear", root_console.property("clear"));
		engine_->globalObject().setProperty("console", co);

		QStringList shs_funcs;
		shs_funcs << "usleep" << "msleep" << "sleep" << "message" << "log" << "lastDate";
		foreach (QString f, shs_funcs) {
			engine_->globalObject().setProperty(f, root_SHS.property(f));
		}

		auto pl = RUNTIME->plugins_manager->localInterface().getPlugins(SHPluginBase::Script);
		for (const auto & i: pl) {
			SHPluginBaseScript * p = (SHPluginBaseScript *)i.plugin;
			QJSValue jv = engine_->newQObject(p);
			for (int j = p->metaObject()->enumeratorOffset(); j < p->metaObject()->enumeratorCount(); ++j) {
				QMetaEnum me = p->metaObject()->enumerator(j);
				for (int m = 0; m < me.keyCount(); ++m) {
					jv.setProperty(me.key(m), me.value(m));
				}
			}
			engine_->globalObject().setProperty(p->name(), jv);
			QQmlEngine::setObjectOwnership(p, QQmlEngine::CppOwnership);
		}

	}

	// Assign project properties
	QList<QByteArray> dpnl = project.dynamicPropertyNames();
	foreach (QByteArray n, dpnl)
		project.setProperty(n, QVariant());
	QJSValue proj_jo = engine_->newQObject(&project);
	foreach (const auto & p, RUNTIME->server_data.project) {
		project.setProperty(p.name.toLatin1(), p.value);
		proj_jo.setProperty(p.name, engine_->toScriptValue(p.value));
	}
	auto apl = RUNTIME->plugins_manager->localInterface().loadedPlugins();
	for (const auto & p: apl) {
		if (p.plugin) {
			p.plugin->project_properties = RUNTIME->server_data.project;
			p.plugin->server_block = RUNTIME->server_data.server;
		}
	}
	engine_->globalObject().setProperty(project.objectName(), proj_jo);
	QQmlEngine::setObjectOwnership(&project, QQmlEngine::CppOwnership);
}


void SHSScriptEngine::timerEvent(QTimerEvent * e) {
	if (!engine_) return;
	engine_->collectGarbage();
	//piCout << "collectGarbage";
}


void SHSScriptEngine::remove() {
	conn_.stop();
	abort();
	auto apl = RUNTIME->plugins_manager->localInterface().loadedPlugins();
	for (const auto & p: apl) {
		if (p.plugin)
			p.plugin->destroy();
	}
	db.stop();
	RUNTIME->cache.clear();
	RUNTIME->engine_ = 0;
	if (engine_)
		delete engine_;
	engine_ = 0;
}


void SHSScriptEngine::abort() {
	if (!engine_) return;
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
	engine_->setInterrupted(true);
#endif
}


void SHSScriptEngine::assignForm(QWidget * form) {
	widgets.clear();
	widgets_by_ptr.clear();
	if (!form) return;
	if (!engine_) create();
	engine_->globalObject().setProperty("ui", engine_->newQObject(form));
	QQmlEngine::setObjectOwnership(form, QQmlEngine::CppOwnership);
	QList<QWidget * > wl = form->findChildren<QWidget * >();
	QSet<QString> smo;
	foreach (QWidget * w, wl) {
		if (w->objectName().startsWith("qt_")) continue;
		QString cname = w->metaObject()->className();
		if (!smo.contains(cname)) {
			smo << cname;
#if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
			engine_->globalObject().setProperty(cname, engine_->newQMetaObject(w->metaObject()));
#else
			engine_->globalObject().setProperty(cname, engine_->newQObject(w));
#endif
		}
		if (!w->objectName().isEmpty()) {
			widgets[w->objectName()] = w;
			engine_->globalObject().setProperty(w->objectName(), engine_->newQObject(w));
			widgets_by_ptr[(quintptr)w] = w;
			QQmlEngine::setObjectOwnership(w, QQmlEngine::CppOwnership);
			for (auto p: w->dynamicPropertyNames()) {
				QString fgs = QString("Object.defineProperty(%1, \"%2\", {"
					"get: function( ) {return SHS.__wpropGet(%3, \"%2\");},"
					"set: function(v) {return SHS.__wpropSet(%3, \"%2\", v);}"
					"});").arg(w->objectName()).arg(QLatin1String(p)).arg((quintptr)w);
				QJSValue res = engine_->evaluate(fgs);
				if (res.isError())
					qDebug() << res.toString();
			}
		}
		SHSButton * b = qobject_cast<SHSButton * >(w);
		if (b) {
			connect(b, SIGNAL(clicked(bool)), this, SLOT(buttonClicked()));
		}
	}
}


void SHSScriptEngine::assignConnection(QPIConnection * conn) {
	conn_.assignConnection(conn);
	if (!conn) return;
	engine_->globalObject().setProperty(conn->objectName(), engine_->newQObject(conn));
	QQmlEngine::setObjectOwnership(conn, QQmlEngine::CppOwnership);
}


void SHSScriptEngine::assignVariables(QList<SHS::ScriptVariable> vars) {
	if (!engine_) create();
	db.assignEngine(this, vars);
	for (const auto & v: db.vars) {
		QString fgs = QString("Object.defineProperty(this, \"%1\", {"
			"get: function( ) {return SHS.__varGet(%2);},"
			"set: function(v) {return SHS.__varSet(%2, v);}"
			"});").arg(v.name).arg(v.id);
		QJSValue res = engine_->evaluate(fgs);
		if (res.isError())
			qDebug() << res.toString();
	}
	//qDebug() << vars_id;
}


bool SHSScriptEngine::loadFromProject(const SHS::ServerProject & p) {
	bool ret = loadFromText(p.mainScriptText(dir), p.qs_main);
	for (auto i: p.qs) {
		if (!files_content.contains(i)) {
			QFile f(findFile(i, "qs", engine_->property("_dir").toString()));
			if (f.open(QIODevice::ReadOnly)) {
				files_content[i] = QString::fromUtf8(f.readAll());
			} else {
				qDebug() << "[SHSScriptEngine::loadFromProject] Can`t open" << i;
				ret = false;
			}
		}
	}
	return ret;
}


bool SHSScriptEngine::loadFromText(const QString & text, const QString & filename) {
	bool ret = true;
	if (!engine_) create();
	files_content.clear();
	engine_->setProperty("_dir", dir.absolutePath());
	files_content[filename] = text;
	script_file = filename;
	script_content = processScriptIncludes(text, filename, ret);
	script_content = prepareScript(script_content);
	return ret;
}


bool SHSScriptEngine::loadFromFile(const QString & file) {
	script_file.clear();
	script_content.clear();
	QFile f(file);
	if (!f.open(QIODevice::ReadOnly)) return false;
	return loadFromText(QString::fromUtf8(f.readAll()), file);
}


bool SHSScriptEngine::start() {
	//qDebug() << "start";
	auto apl = RUNTIME->plugins_manager->localInterface().loadedPlugins();
	for (const auto & p: apl) {
		if (p.plugin)
			p.plugin->init();
	}
	svalue = engine_->evaluate(script_content, script_file);
	if (!checkJSValue(svalue))
		return false;
	connectToWidgets();
	return true;
}


void SHSScriptEngine::preparePlugins() {
	QMap<uint, SHPluginBase*> pm;
	auto apl = RUNTIME->plugins_manager->localInterface().loadedPlugins();
	for (const auto & p: apl) {
		if (p.plugin)
			pm[p.plugin->id()] = p.plugin;
	}
	for (const auto & p: apl) {
		if (p.plugin)
			p.plugin->_plugins = pm;
	}
}


QString SHSScriptEngine::prepareScript(const QString & t) {
	QString ret("'use strict'; var global = this; " + t);
	QStringList kw;
	kw << "historyAll" << "historyLast" << "historyPredict";
	foreach (QString k, kw) {
		int i = ret.indexOf(k);
		while (i >= 0) {
			if (ret.mid(i - 9, 9).trimmed() != "function") {
				int bs = ret.indexOf("(", i + 1);
				int bf = ret.indexOf(")", i + 1);
				int bc = ret.indexOf(",", i + 1);
				if (bc > 0 && bc < bf)
					bf = bc;
				if (bs < 0 || bf < 0) break;
				ret.insert(bf, "\"");
				ret.insert(bs + 1, "\"");
			}
			i = ret.indexOf(k, i + 1);
		}
	}
	ret = updateScript(ret);
	//piCout << Q2PIString(ret);
	return ret;
}


QString SHSScriptEngine::processScriptIncludes(const QString & t, const QString & filename, bool & ok) {
//	qDebug() << "processScriptIncludes" << t;
	QRegularExpression expr = ScriptCommon::includeExpression();
	expr.optimize();
	QString text(t);
	int cnt = 0;
	inc_lines.clear();
	inc_lines << (IncPart){1, 0, filename};
	while(cnt < 100) {
		QRegularExpressionMatch m = expr.match(text);
		if (!m.isValid() || m.captured().isEmpty()) break;
		QString fp = m.captured(1);
//		qDebug() << m.captured();

		QFile f(findFile(fp, "qs", engine_->property("_dir").toString()));
		QString fname = f.fileName();
		if (fname.isEmpty()) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)
			engine_->throwError(QJSValue::URIError, QString("Can`t find file \"%1\"").arg(fp));
#endif
			qDebug() << QString("Can`t find file \"%1\"").arg(fp);
			ok = false;
			return text;
		}
		if (!f.open(QIODevice::ReadOnly)) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)
			engine_->throwError(QJSValue::URIError, QString("Can`t open file \"%1\"").arg(fname));
#endif
			qDebug() << QString("Can`t open file \"%1\"").arg(fname);
			ok = false;
			return text;
		}
		QString it = QString::fromUtf8(f.readAll());
		files_content[fname] = it;
		int nlc = it.count('\n');
		int inc_loc = text.left(m.capturedStart()).count('\n') + 1;
		int ii = 0;
		for (ii = 0; ii < inc_lines.size(); ++ii) {
			if (inc_lines[ii].start > inc_loc)
				break;
		}
		for (int i = ii; i < inc_lines.size(); ++i) {
			inc_lines[i].start += nlc;
			inc_lines[i].reduce += nlc;
		}
		IncPart old_ip = inc_lines[piClampi(ii - 1, 0, inc_lines.size() - 1)];
		inc_lines.insert(ii, (IncPart){inc_loc + nlc + 1, old_ip.reduce + nlc, old_ip.file});
		inc_lines.insert(ii, (IncPart){inc_loc, inc_loc - 1, fname});
		text.replace(m.capturedStart(), m.capturedLength(), it);
		cnt++;
	}
	return text;
}


QString SHSScriptEngine::updateScript(const QString & content) {
	QString ret(content);
	QList<SSPair> drl;
	drl << SSPair("new QTimer()", "SHS.newTimer()");
	foreach (SSPair i, drl) {
		int ind = -1;
		for (;;) {
			ind = ret.indexOf(i.first, ind + 1);
			if (ind < 0) break;
			ret.replace(ind, i.first.size(), i.second);
		}
	}
	int ind = -1;
	for (;;) {
		ind = ret.indexOf("\"].connect(", ind + 1);
		if (ind < 1) break;
		int si = ind - 1;
		while (si > 0) {
			si--;
			if (ret[si] == '(') {
				ret.remove(si, ind - si);
				ind += 10;
				break;
			}
			if (ret[si] == '\"') {
				ind += 10;
				break;
			}
		}
	}
	return ret;
}


QJSValue SHSScriptEngine::createScriptObject(SHS::Object & o, uint script_plugin_id) {
	SHS::PacketFlag fcmd = SHS::Output, fev = SHS::Input;
	if (script_plugin_id != 0)
		piSwap(fcmd, fev);
	QJSValue ret = engine_->newObject();

	QMutableMapIterator<QString, SHS::Command> it(o.commands);
	while (it.hasNext()) {
		it.next();
		QJSValue so;
		SHS::Command & cmd(it.value());
		cmd.name = it.key();
		cmd.parent = &o;
		cmd.root = _cur_root;

		if (!cmd.cmd.isEmpty()) {
			cmd.packet_cmd = cmd.cmd[0];
			cmd.packet_cmd.plugin = RUNTIME->plugins_manager->localInterface().getPlugin(cmd.packet_cmd.plugin_id).plugin;
			//qDebug() << o.name << cmd.name << cmd.packet_cmd.plugin_id << cmd.packet_cmd.plugin;
			for (int i = 1; i < cmd.cmd.size(); ++i) {
				cmd.gw_cmd << cmd.cmd[i];
				cmd.gw_cmd.back().plugin = RUNTIME->plugins_manager->localInterface().getPlugin(cmd.gw_cmd.back().plugin_id).plugin;
			}
			if (cmd.packet_cmd.plugin) {
				//qDebug() << "prepare" << cmd.name << cmd.packet_cmd.plugin;
				((SHPluginBasePacket * )cmd.packet_cmd.plugin)->prepareCommand(*(cmd.root), o, cmd);
			}
		}

		if (cmd.flags.testFlag(fcmd)) {
			QString script;
			if (script_plugin_id == 0)
				script = QString("(function(%1) {SHS.__sendCommand(%2, [%1]);})")
						.arg(cmd.user_arguments.join(",")).arg(cmd.id);
			else
				script = QString("(function(%1) {SHS.__processCommand(%2, %3, [%1]);})")
						.arg(cmd.user_arguments.join(",")).arg(cmd.id).arg(script_plugin_id);
			so = engine_->evaluate(script);
		}
		if (cmd.flags.testFlag(fev)) {
			so = engine_->newObject();
			so.setProperty("targets", engine_->newArray());
			so.setProperty("connect", engine_->evaluate("(function(f) {this.targets.push(f);})"));
			so.setProperty("call", engine_->evaluate("(function(o, ...args) {for (var i in o.targets) o.targets[i].apply(null, args);})"));
		}
		if (so.isError()) {
			qDebug() << so.toString();
		} else {
			ret.setProperty(it.key(), so);
		}
	}

	QMutableMapIterator<QString, SHS::Object> oit(o.objects);
	while (oit.hasNext()) {
		oit.next();
		ret.setProperty(oit.key(), createScriptObject(oit.value(), script_plugin_id));
	}

	return ret;
}


void SHSScriptEngine::createScriptObjectProperties(const SHS::Object & o, const QString & path) {
	QMapIterator<QString, QVariant> pit(o.properties);
	while (pit.hasNext()) {
		pit.next();
		QString fgs = QString("Object.defineProperty(%1, \"%2\", {"
			"get: function( ) {return SHS.__propGet(%3, \"%2\");},"
			"set: function(v) {return SHS.__propSet(%3, \"%2\", v);}"
			"});").arg(path).arg(pit.key()).arg(o.id);
		QJSValue res = engine_->evaluate(fgs);
		if (res.isError())
			qDebug() << res.toString();
	}

	QMapIterator<QString, SHS::Object> oit(o.objects);
	while (oit.hasNext()) {
		oit.next();
		createScriptObjectProperties(oit.value(), path + "." + oit.key());
	}
}


void SHSScriptEngine::createBlockObjects() {
	if (!engine_) create();
	RUNTIME->cache.clear();
	RUNTIME->cache.build(RUNTIME->server_data.blocks);

	QMutableMapIterator<QString, SHS::Object> oit(RUNTIME->server_data.blocks.objects);
	while (oit.hasNext()) {
		oit.next();
		SHS::Object & o(oit.value());
		_cur_root = &o;
		SHPluginBaseBlock * block_plugin = (SHPluginBaseBlock*)RUNTIME->plugins_manager->localInterface().getPlugin(o.block_plugin_id).plugin;
		if (block_plugin)
			o.applyBlockPlugin(block_plugin);

		engine_->globalObject().setProperty(oit.key(), createScriptObject(o));
		createScriptObjectProperties(o, oit.key());
	}
	RUNTIME->server_data.blocks.applyRuntime(RUNTIME);

	auto pl = RUNTIME->plugins_manager->localInterface().getPlugins(SHPluginBase::Script);
	for (const auto & p: pl) {
		SHPluginBaseScript * plugin = (SHPluginBaseScript *)p.plugin;
		plugin->object = plugin->createObject();
		plugin->object.applyRuntime(RUNTIME);
		_cur_root = &plugin->object;
		RUNTIME->cache.build(plugin->object, plugin->name());
		uint pid = plugin->id();
		QMutableMapIterator<QString, SHS::Object> oit(plugin->object.objects);
		while (oit.hasNext()) {
			oit.next();
			engine_->globalObject().property(plugin->name()).setProperty(oit.key(), createScriptObject(oit.value(), pid));
		}
	}
}


QList<QMetaObject> SHSScriptEngine::creatableObjects() const {
	return new_mol;
}


void SHSScriptEngine::sendCommand(int id, const QVector<QVariant> & args) {
	//qDebug() << "sendCommand" << id << args;
	conn_.sendCommand(id, args);
}


void SHSScriptEngine::processCommand(int id, uint pid, const QVector<QVariant> & args) {
	SHPluginBaseScript * plugin = (SHPluginBaseScript *)(RUNTIME->plugins_manager->localInterface().getPlugin(pid).plugin);
	if (!plugin) return;
	SHS::Command * src_cmd = RUNTIME->cache.command_by_id.value(id, 0);
	if (!src_cmd) return;

	//qDebug() << "sendCommand" << id << args << cmd << conn_ << plugin;

	QVariantMap user_args;
	int _msz = piMini(args.size(), src_cmd->user_arguments.size());
	for (int i = 0; i < _msz; ++i)
		user_args[src_cmd->user_arguments[i]] = args[i];

	SHS::Command cmd(*src_cmd);
	addToMapOverwrite(cmd.arguments, user_args);
	plugin->processCommand(*(cmd.root), *(cmd.parent), cmd);
}


void SHSScriptEngine::logToWidget(const QString & msg) {
	debug(msg);
	if (!logview || no_graphics) return;
	logview->addText(msg);
}


void SHSScriptEngine::clearLogWidget() {
	if (!logview) return;
	logview->clear();
}


void SHSScriptEngine::connectToWidgets() {
	for (const QObject * w: RUNTIME->widget_list) {
		const QMetaObject * mo = w->metaObject();
		QSet<QString> in_list;
		QString signal_str;
		for (int i = 0; i < mo->methodCount(); ++i) {
			QMetaMethod mm = mo->method(i);
			if (mm.methodType() == QMetaMethod::Signal) {
				if (in_list.contains(mm.name())) continue;
				in_list << mm.name();
				signal_str = QString("on_%1_%2").arg(w->objectName(), QString::fromLatin1(mm.name()));
				//qDebug() << "check" << signal_str;
				QJSValue jv = engine_->globalObject().property(signal_str);
				if (jv.isCallable()) {
					//qDebug() << "found" << signal_str;
					engine_->evaluate(QString("%1.%2.connect(%3);").arg(w->objectName(), mm.name(), signal_str));
				}
			}
		}
	}
}


bool SHSScriptEngine::checkJSValue(QJSValue v) {
	if (v.isError()) {
		emitError(v.property("lineNumber").toInt(), v.toString(), v.property("stack").toString());
		return false;
	}
	return true;
}


void SHSScriptEngine::emitError(int line, const QString & msg, const QString & stack) {
	QString file("?");
	if (!inc_lines.isEmpty()) {
		int ii = 0;
		for (ii = 0; ii < inc_lines.size(); ++ii) {
			if (inc_lines[ii].start > line)
				break;
		}
		ii = piClampi(ii - 1, 0, inc_lines.size() - 1);
		line -= inc_lines[ii].reduce;
		file = inc_lines[ii].file;
	}
	QMetaObject::invokeMethod(this, [=](){error(line, file, msg, stack);}, Qt::QueuedConnection);
}


QVariant SHSScriptEngine::call(const QString & function, const QVariantList & args) {
	if (!engine_) return QVariant();
	QJSValue f = engine_->globalObject().property(function);
	if (f.isCallable()) {
		QJSValueList jvl;
		for (const auto & a: args) jvl << engine_->toScriptValue(a);
		QJSValue ret = f.call(jvl);
		checkJSValue(ret);
		return ret.toVariant();
	}
	return QVariant();
}


void SHSScriptEngine::prepare() {
	preparePlugins();
	conn_.prepareConnection();
}


void SHSScriptEngine::started() {
	call("server_started");
}


void SHSScriptEngine::prepareForRole(SHS::UserRole role) {
	call("server_prepare_ui_for_role", {role.name});
}


void SHSScriptEngine::eventReceived(SHS::Command * cmd, QVariantMap args) {
	if (!engine_ || !cmd) return;
	cmd->invoke(args);
	QVariantMap old_args = cmd->arguments;
	addToMapOverwrite(cmd->arguments, args);
	if (cmd->block_plugin)
		((SHPluginBaseBlock*)cmd->block_plugin)->applyCommand(*(cmd->root), *(cmd->parent), *cmd);
	cmd->arguments = old_args;
}


void SHSScriptEngine::invokeCommand(SHS::Command * cmd, QVariantList args, QVariant * ret) {
	if (!engine_ || !cmd) return;
	QString fname = cmd->object_path.join(".") + "." + cmd->name;
	//qDebug() << "invokeCommand" << fname << args;
	QJSValueList fa;
	foreach (QVariant i, args) fa << engine_->toScriptValue(i);
	QJSValue fv;

	fv = engine_->globalObject().property("on_" + cmd->object_path.join("_") + "_" + cmd->name);
	if (fv.isCallable())
		checkJSValue(fv.call(fa));

	fv = engine_->globalObject();
	foreach (const QString & i, cmd->object_path)
		fv = fv.property(i);
	fv = fv.property(cmd->name);
	fa.prepend(fv);
	checkJSValue(fv.property("call").call(fa));
}


void SHSScriptEngine::sendBroadcast(const QByteArray & data) {
	conn_.sendToAll(data);
}


void SHSScriptEngine::buttonClicked() {
	if (!engine_) return;
	SHSButton * b = qobject_cast<SHSButton * >(sender());
	if (!b) return;
	if (b->command().isEmpty()) return;
	//qDebug() << "button exec" << b->command();
	checkJSValue(engine_->evaluate(b->command(), QString("SHSButton(%1)").arg(b->objectName())));
}


void SHSScriptEngine::engineWarnings(const QList<QQmlError> & w) {
	if (w.isEmpty()) return;
	emitError(w[0].line(), w[0].toString(), "");
}
