#include "script_edit.h"
#include "script_highlighter.h"
#include "SH_runtime.h"
#include "SH_server_base.h"
#include "script_engine.h"
#include "script_common.h"
#include <QMetaMethod>
#include <QMdiSubWindow>


ScriptEdit::ScriptEdit(QWidget * parent): QCodeEdit(parent) {
	Q_INIT_RESOURCE(designer_tools);
	ac_project.setObjectName("Project");
	form_ui = nullptr;
	hid_blocks = hid_widgets = hid_variables = hid_funcs = hid_project = hid_plugins = hid_gvars = hid_classes = 0;
	highlighter = new ScriptHighlighter();
	highlighter->setDocument(document());
	registerAutoCompletitionClass(ScriptCommon::Keyword, QCodeEdit::Keyword, "Keywords", QIcon(":/icons/code-keyword.png"));
	registerAutoCompletitionClass(ScriptCommon::Widget, QCodeEdit::Keyword, "Widgets", QIcon(":/icons/code-widget.png"));
	registerAutoCompletitionClass(ScriptCommon::Class, QCodeEdit::Keyword, "Classes", QIcon(":/icons/code-struct.png"));
	registerAutoCompletitionClass(ScriptCommon::VariableLocal, QCodeEdit::Keyword, "Local variables", QIcon(":/icons/code-local-var.png"));
	registerAutoCompletitionClass(ScriptCommon::VariableGlobal, QCodeEdit::Keyword, "Global variables", QIcon(":/icons/code-glob-var.png"));
	registerAutoCompletitionClass(ScriptCommon::VariableProject, QCodeEdit::Keyword, "Project variables", QIcon(":/icons/code-var.png"));
	registerAutoCompletitionClass(ScriptCommon::Constant, QCodeEdit::Keyword, "Constants", QIcon(":/icons/code-variable.png"));
	registerAutoCompletitionClass(ScriptCommon::Block, QCodeEdit::Keyword, "Blocks", QIcon(":/icons/code-block.png"));
	registerAutoCompletitionClass(ScriptCommon::Namespace, QCodeEdit::Namespace, "Namespaces", QIcon(":/icons/code-union.png"));
	registerAutoCompletitionClass(ScriptCommon::Property, QCodeEdit::Keyword, "Properties", QIcon(":/icons/code-property.png"));
	registerAutoCompletitionClass(ScriptCommon::Function, QCodeEdit::Function, "Functions", QIcon(":/icons/code-function.png"));
	registerAutoCompletitionClass(ScriptCommon::Signal, QCodeEdit::Function, "Signals", QIcon(":/icons/code-signal.png"));
	registerAutoCompletitionClass(ScriptCommon::Connection, QCodeEdit::Keyword, "Connection", QIcon(":/icons/piconnection.png"));
	registerAutoCompletitionClass(ScriptCommon::Project, QCodeEdit::Keyword, "Project", QIcon(":/icons/code-project.png"));
	registerAutoCompletitionClass(ScriptCommon::QtNamespace, QCodeEdit::Keyword, "Qt namespace", QIcon(":/icons/code-qt.png"));
	registerAutoCompletitionClass(ScriptCommon::ScriptPlugin, QCodeEdit::Namespace, "Plugin Objects", QIcon(":/icons/plugin.png"));
	ac_keywords   = ScriptCommon::acKeywords  ();
	ac_functions  = ScriptCommon::acFunctions ();
	ac_objects    = ScriptCommon::acObjects   ();
	ac_namespaces = ScriptCommon::acNamespaces();
	ac_math_const = ScriptCommon::acMathConst ();
	ac_math_func  = ScriptCommon::acMathFunc  ();
	ac_number_const = ScriptCommon::acNumberConst();
	ac_number_func  = ScriptCommon::acNumberFunc ();
	ac_json_func << QCodeEdit::ACEntry("function", "stringify(object)").addHelpHRef(QUrl("https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify"))
				 << QCodeEdit::ACEntry("function", "parse(string)").addHelpHRef(QUrl("https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse"));

	highlighter->addCustomRule(ac_math_func, QColor(64, 128, 128));
	highlighter->addCustomRule(ac_namespaces, QColor(130, 40, 180));
	QTextCharFormat cf;
	cf.setFontItalic(true);
	cf.setFontWeight(QFont::Bold);
	highlighter->addCustomRule(QStringList() << "log" << "message", QColor(114, 114, 222), cf);
	highlighter->addCustomRule(QStringList() << ".log", QColor(114, 114, 222));

	builtin_types["String" ] = ScriptCommon::aclString();
	builtin_types["Array"  ] = ScriptCommon::aclArray();
	builtin_types["Boolean"] = ScriptCommon::aclBoolean();
	builtin_types["Number" ] = ScriptCommon::aclNumber();
}


void ScriptEdit::updateACObjects() {
	QTextCharFormat tcf;
	tcf.setFontWeight(QFont::Bold);
	QSet<QString> smo;
	QStringList bnl;
	auto_comp_enums.clear();
	foreach (QObject * w, RUNTIME->widget_list) {
		//qDebug() << llong(w);
		//qDebug() << llong(qobject_cast<QObject*>(w));
		if (w->objectName().startsWith("qt_")) continue;
		QString cname = w->metaObject()->className();
		bnl << w->objectName();
		if (smo.contains(cname)) continue;
		smo << cname;
		QList<QMetaEnum> el;
		for (int i = 0; i < w->metaObject()->enumeratorCount(); ++i)
			el << w->metaObject()->enumerator(i);
		if (!el.isEmpty())
			auto_comp_enums[cname] = el;
	}
	highlighter->removeCustomRule(hid_widgets);
	hid_widgets = highlighter->addCustomRule(bnl, QColor(100, 100, 10));

	bnl.clear();
	QList<QByteArray> dpnl = ac_project.dynamicPropertyNames();
	foreach (QByteArray n, dpnl)
		ac_project.setProperty(n, QVariant());
	foreach (const auto & p, RUNTIME->server_data.project) {
		ac_project.setProperty(p.name.toLatin1(), p.value);
	}
	bnl << ac_project.objectName();

	highlighter->removeCustomRule(hid_project);
	hid_project = highlighter->addCustomRule(bnl, QColor(0, 100, 0), tcf);

	rehighlight();
}


void ScriptEdit::updateACPlugins() {
	QStringList bnl;
	foreach (QObject * o, ac_plugins) {
		bnl << ((SHPluginBase*)o)->name();
	}
	highlighter->removeCustomRule(hid_plugins);
	QTextCharFormat cf;
	cf.setFontWeight(QFont::Bold);
	hid_plugins = highlighter->addCustomRule(bnl, QColor(180, 30, 90), cf);
	rehighlight();
}


void ScriptEdit::setBlockObjects() {
	QStringList bnl;
	foreach (const SHS::Object & o, RUNTIME->server_data.blocks.objects)
		bnl << o.name;
	highlighter->removeCustomRule(hid_blocks);
	QTextCharFormat tcf;
	//tcf.setFontWeight(QFont::Bold);
	hid_blocks = highlighter->addCustomRule(bnl, QColor(21, 130, 117), tcf);
	rehighlight();
}


void ScriptEdit::setCreatableObjects(QList<QMetaObject> col) {
	new_mol = col;
}


void ScriptEdit::setVariables(const QStringList & vl) {
	variables = vl;
	highlighter->removeCustomRule(hid_variables);
	QTextCharFormat tcf;
	tcf.setFontWeight(QFont::Bold);
	hid_variables = highlighter->addCustomRule(vl, QColor(140, 80, 140), tcf);
	rehighlight();
}


void ScriptEdit::setPlugins(const QMap<QString, SHS::Object> & pol) {
	plugins_objects = pol;
}


void ScriptEdit::rehighlight() {
	highlighter->rehighlight();
}


QCodeEdit::ACList ScriptEdit::autoCompletitionList(const QStringList & scope, const QString & written) const {
	QCodeEdit::ACList ret;
	ACSection ac;
	bool is_block = false;
	if (scope.isEmpty()) {

		ac.first = ScriptCommon::VariableGlobal; ac.second.clear();
		foreach (const ACEntry & v, ac_glob_vars) {
			if (!matchWritten(v.name, written)) continue;
			ac.second << v;
		}
		ret << ac;

		ac.first = ScriptCommon::Widget; ac.second.clear();
		foreach (QObject * w, RUNTIME->widget_list) {
			const QMetaObject * mo = w->metaObject();
			if (w->objectName().isEmpty()) continue;
			if (!matchWritten(w->objectName(), written)) continue;
			ac.second << ACEntry(mo->className(), w->objectName());
		}
		if (form_ui) {
			const QMetaObject * mo = form_ui->metaObject();
			if (matchWritten("ui", written))
				ac.second << ACEntry(mo->className(), "ui");
		}
		ret << ac;
		
		ac.first = ScriptCommon::Block; ac.second.clear();
		foreach (const SHS::Object & o, RUNTIME->server_data.blocks.objects) {
			if (!matchWritten(o.name, written)) continue;
			ac.second << ACEntry("block", o.name, o.desc);
		}
		ret << ac;

		ac.first = ScriptCommon::ScriptPlugin; ac.second.clear();
		foreach (QObject * o, ac_plugins) {
			SHPluginBase * plugin = ((SHPluginBase*)o);
			QString pn = plugin->name();
			if (!matchWritten(pn, written)) continue;
			ac.second << ACEntry(plugin->version(), pn, plugin->description());
		}
		ret << ac;

		ac.first = ScriptCommon::Function; ac.second.clear();
		foreach (const ACEntry & s, ac_functions) {
			if (!matchWritten(s.name, written)) continue;
			ac.second << s;
		}
		foreach (const ACEntry & s, ac_rt_functions) {
			if (!matchWritten(s.name, written)) continue;
			ac.second << s;
		}
		ret << ac;

		ac.first = ScriptCommon::Class; ac.second.clear();
		foreach (QMetaObject o, new_mol) {
			if (!matchWritten(o.className(), written)) continue;
			ac.second << ACEntry("class", o.className());
		}
		foreach (const ACEntry & s, ac_classes) {
			if (!matchWritten(s.name, written)) continue;
			ac.second << s;
		}
		ret << ac;
		
		ac.first = ScriptCommon::VariableProject; ac.second.clear();
		foreach (const QString & v, variables) {
			if (!matchWritten(v, written)) continue;
			ac.second << ACEntry("var", v);
		}
		ret << ac;
		
		ac.first = ScriptCommon::Namespace; ac.second.clear();
		foreach (const ACEntry & s, ac_namespaces) {
			if (!matchWritten(s.name, written)) continue;
			ac.second << s;
		}
		QList<QString> el = auto_comp_enums.keys();
		foreach (const QString & s, el) {
			if (!matchWritten(s, written)) continue;
			ac.second << ACEntry("enum", s);
		}
		ret << ac;
		
		ac.first = ScriptCommon::Keyword; ac.second.clear();
		foreach (const ACEntry & s, ac_keywords) {
			if (!matchWritten(s.name, written)) continue;
			ac.second << s;
		}
		foreach (const ACEntry & s, ac_objects) {
			if (!matchWritten(s.name, written)) continue;
			ac.second << s;
		}
		ret << ac;
		
		if (!RUNTIME->connection.objectName().isEmpty()) {
			ac.first = ScriptCommon::Connection; ac.second.clear();
			if (matchWritten(RUNTIME->connection.objectName(), written))
				ac.second << ACEntry(RUNTIME->connection.className(), RUNTIME->connection.objectName());
			ret << ac;
		}

		ac.first = ScriptCommon::Project; ac.second.clear();
		if (matchWritten(ac_project.objectName(), written))
			ac.second << ACEntry("proj", ac_project.objectName());
		ret << ac;
		
	} else {
		
		if (scope.size() == 1) {
			if (scope[0] == "Qt") {
				ac.first = ScriptCommon::QtNamespace; ac.second.clear();
				for (int e = 0; e < getQtMetaObject()->enumeratorCount(); ++e) {
					QMetaEnum me = getQtMetaObject()->enumerator(e);
					for (int i = 0; i < me.keyCount(); ++i) {
						if (!matchWritten(me.key(i), written)) continue;
						ac.second << ACEntry(me.name(), me.key(i));
					}
				}
				ret << ac;
				return ret;
			}
			if (scope[0] == "Math") {
				ac.first = ScriptCommon::Function; ac.second.clear();
				foreach (const ACEntry & s, ac_math_func) {
					if (!matchWritten(s.name, written)) continue;
					ac.second << s;
				}
				ret << ac;
				ac.first = ScriptCommon::Constant; ac.second.clear();
				foreach (const ACEntry & s, ac_math_const) {
					if (!matchWritten(s.name, written)) continue;
					ac.second << s;
				}
				ret << ac;

				return ret;
			}
			if (scope[0] == "Number") {
				ac.first = ScriptCommon::Function; ac.second.clear();
				foreach (const ACEntry & s, ac_number_func) {
					if (!matchWritten(s.name, written)) continue;
					ac.second << s;
				}
				ret << ac;
				ac.first = ScriptCommon::Constant; ac.second.clear();
				foreach (const ACEntry & s, ac_number_const) {
					if (!matchWritten(s.name, written)) continue;
					ac.second << s;
				}
				ret << ac;

				return ret;
			}
			if (scope[0] == "SHServer") {
				return compFromMeta(SHServer::staticMetaObject, written);
			}
			if (scope[0] == "SHS") {
				return compFromMeta(SHSScriptProxy::staticMetaObject, written);
			}
			if (scope[0] == "console") {
				return compFromMeta(SHSScriptConsole::staticMetaObject, written);
			}
			if (scope[0] == "JSON") {
				ac.first = ScriptCommon::Function; ac.second.clear();
				foreach (const ACEntry & s, ac_json_func) {
					if (!matchWritten(s.name, written)) continue;
					ac.second << s;
				}
				ret << ac;
			}
			QList<QMetaEnum> el = auto_comp_enums.value(scope[0]);
			if (!el.isEmpty()) {
				ac.first = ScriptCommon::Constant; ac.second.clear();
				foreach (const QMetaEnum & e, el) {
					for (int i = 0; i < e.keyCount(); ++i) {
						if (!matchWritten(e.key(i), written)) continue;
						ac.second << ACEntry(e.name(), e.key(i));
					}
				}
				ret << ac;
			}
		}
		
		const QObject * co = 0;
		const QMetaObject * mo = 0;
		SHS::Object obj;
		if (RUNTIME->connection.objectName() == scope[0]) {
			co = &RUNTIME->connection;
		} else {
			if (ac_project.objectName() == scope[0]) {
				co = &ac_project;
			} else {
				bool br = false;
				foreach (ACEntry v, ac_glob_vars) {
					if (v.name == scope[0]) {
						foreach (const QMetaObject & o, new_mol) {
							if (v.type == o.className()) {
								mo = &o;
								br = true;
								break;
							}
						}
						QMapIterator<QString, QList<ACSection> > btit(builtin_types);
						while (btit.hasNext()) {
							btit.next();
							if (v.type == btit.key()) {
								return compFromBuiltin(btit.value(), written);
							}
						}
						if (br) break;
						break;
					}
				}
				if (!br) {
					for (int i = 0; i < scope.size(); ++i) {
						if (i == 0) {
							if (scope[0] == "ui") co = form_ui;
							else {
								foreach (QObject * w, RUNTIME->widget_list)
									if (w->objectName() == scope[0]) {
										co = w;
										break;
									}
								if (!co) {
									foreach (const SHS::Object & o, RUNTIME->server_data.blocks.objects) {
										if (o.name == scope[0]) {
											obj = o;
											is_block = true;
											break;
										}
									}
									foreach (const QObject * o, ac_plugins) {
										if (scope[0] == ((SHPluginBase*)o)->name()) {
											co = o;
											obj = plugins_objects.value(scope[0]);
											is_block = true;
											break;
										}
									}
								}
							}
						} else {
							if (is_block) {
								if (obj.command(scope[i]).isValid()) {
									if (!matchWritten("connect", written))
										return ret;
									ac.first = ScriptCommon::Function; ac.second.clear();
									ac.second << ACEntry("function", "connect(function)", "");
									ret << ac;
									return ret;
								} else
									obj = obj.objects.value(scope[i]);
							} else {
								if (co == 0) break;
								for (int k = 0; k < co->metaObject()->methodCount(); ++k) {
									QMetaMethod mm = co->metaObject()->method(k);
									if (mm.methodType() == QMetaMethod::Signal) {
										if (mm.name() == scope[i]) {
											ac.first = ScriptCommon::Function; ac.second.clear();
											ac.second << ACEntry("function", "connect(function)", "");
											ret << ac;
											return ret;
										}
									}
								}
								co = co->findChild<QObject*>(scope[i]);
							}
						}
					}
				}
			}
		}

		ACSection ac2;
		ac.first = ScriptCommon::Function; ac.second.clear();
		ac2.first = ScriptCommon::Signal; ac2.second.clear();
		if (is_block) {
			QMapIterator<QString, SHS::Command> cit(obj.commands);
			while (cit.hasNext()) {
				cit.next();
				if (cit.key().startsWith("_")) continue;
				if (!matchWritten(cit.key(), written)) continue;
				const SHS::Command & cmd(cit.value());
				QString fn = QString("%1(%2)").arg(cmd.name, cmd.user_arguments.join(", "));
				if (cmd.flags.testFlag(SHS::Output)) ac.second << ACEntry("function", fn, cmd.desc);
				if (cmd.flags.testFlag(SHS::Input)) ac2.second << ACEntry("signal", fn, cmd.desc);
			}
			ret << ac;
			ret << ac2;

			ac.first = ScriptCommon::Property; ac.second.clear();
			QMapIterator<QString, QVariant> pit(obj.properties);
			while (pit.hasNext()) {
				pit.next();
				if (pit.key().startsWith("_")) continue;
				if (!matchWritten(pit.key(), written)) continue;
				ac.second << ACEntry(pit.value().typeName(), pit.key(), pit.value().toString());
			}
			ret << ac;

			ac.first = ScriptCommon::Block; ac.second.clear();
			QMapIterator<QString, SHS::Object> oit(obj.objects);
			while (oit.hasNext()) {
				oit.next();
				if (!matchWritten(oit.key(), written)) continue;
				ac.second << ACEntry("Object", oit.key(), oit.value().desc);
			}
			ret << ac;
		}

		if (co || mo) {
			
			if (co) {
				mo = co->metaObject();
				ac.first = ScriptCommon::Widget; ac.second.clear();
				foreach (const QObject * o, co->children()) {
					if (o->objectName().isEmpty()) continue;
					if (!matchWritten(o->objectName(), written)) continue;
					ac.second << ACEntry(o->metaObject()->className(), o->objectName());
				}
				ret << ac;
			}

			ret << compFromMeta(*mo, written);

			if (co) {
				ac.first = ScriptCommon::Property; ac.second.clear();
				QList<QByteArray> dpn = co->dynamicPropertyNames();
				foreach (const QByteArray & n, dpn) {
					QString pn(n);
					if (pn.startsWith("__")) continue;
					if (pn.startsWith("h__")) continue;
					if (pn.startsWith("_q_")) continue;
					if (!isCName(pn)) continue;
					if (!matchWritten(pn, written)) continue;
					ac.second << ACEntry(co->property(n).typeName(), pn);
				}
			}
			ret << ac;
		}
		
	}
	return ret;
}


void ScriptEdit::parse() {
	if (!document()) return;
	parsed_includes.clear();
	ac_glob_vars.clear();
	ac_rt_functions.clear();
	ac_classes.clear();
	parseDocument(document());

	QStringList hsl;
	foreach (const auto & e, ac_glob_vars)
		hsl << e.name;
	highlighter->removeCustomRule(hid_gvars);
	hid_gvars = highlighter->addCustomRule(hsl, QColor(100, 20, 20));

	hsl.clear();
	foreach (const auto & e, ac_rt_functions)
		hsl << e.name;
	highlighter->removeCustomRule(hid_funcs);
	hid_funcs = highlighter->addCustomRule(hsl, highlighter->formatForFunction().foreground().color());

	hsl.clear();
	foreach (const auto & e, ac_classes)
		hsl << e.name;
	foreach (const auto & e, new_mol)
		hsl << e.className();
	highlighter->removeCustomRule(hid_classes);
	hid_classes = highlighter->addCustomRule(hsl, Qt::darkMagenta);

	rehighlight();
}


void ScriptEdit::documentUnset() {
}


void ScriptEdit::documentChanged(QTextDocument * d) {
	if (!d) return;
	highlighter->setDocument(d);
}


void ScriptEdit::linkClicked(QCodeEdit::ACEntry e) {
	//qDebug() << "link" << e.type << e.name << e.declaration_pos << e.declaration_file;
	if (e.name == ScriptCommon::includeACName()) {
		QTextCursor tc(textCursor());
		tc.select(QTextCursor::LineUnderCursor);
		QString fn = ScriptCommon::includeExpression().match(tc.selectedText()).captured(1);
		emit gotoDoc(QDir::current().relativeFilePath(fn), -1);
	} else {
		emit gotoDoc(e.declaration_file, e.declaration_pos);
	}
}


bool ScriptEdit::linkValidate(QCodeEdit::ACEntry e) {
	if (e.declaration_pos < 0 || !document()) return false;
	return true;
}


void ScriptEdit::parseDocument(const QTextDocument * d) {
	if (!d) return;
	QString dp = docPath(d);
	if (parsed_includes.contains(dp)) return;
	parsed_includes << dp;
	QString doc_text = d->toPlainText();
	ac_glob_vars    << findVariables(doc_text, dp);
	ac_rt_functions << findFunctions(doc_text, dp);
	ac_classes      << findClasses  (doc_text, dp);
	QRegularExpressionMatchIterator i = ScriptCommon::includeExpression().globalMatch(doc_text);
	while (i.hasNext()) {
		QString inc = i.next().captured(1);
		QTextDocument * idoc = 0;
		emit requestDocByPath(inc, &idoc);
		//qDebug() << "requested doc" << inc << idoc;
		bool is_new = false;
		if (!idoc) {
			QFile f(findFile(inc, "qs", dp));
//			qDebug() << "include" << inc << dp << findFile(inc, "qs", dp);
			if (f.open(QIODevice::ReadOnly)) {
				idoc = new QTextDocument();
				idoc->setPlainText(QString::fromUtf8(f.readAll()));
				setDocPath(idoc, QDir::current().relativeFilePath(f.fileName()));
				is_new = true;
//				qDebug() << "open" << inc;
			}
		}
//		qDebug() << "----" << inc;
		if (!idoc) continue;
//		qDebug() << "parse" << inc;
		parseDocument(idoc);
		if (is_new) delete idoc;
	}
}


QList<QCodeEdit::ACEntry> ScriptEdit::findFunctions(const QString & content, const QString & docpath) {
	QList<ACEntry> ret;
	QRegularExpression expr("(?:\\/\\/\\s*)?(.*\\n)?(?:\\bfunction\\b)\\s*(\\b\\w+\\b)\\s*(\\((?:[^;()]*?)\\))");
	QRegularExpressionMatchIterator i = expr.globalMatch(content);
	while (i.hasNext()) {
		QRegularExpressionMatch m = i.next();
		ACEntry e("function", m.captured(2) + m.captured(3).replace("\n","").trimmed(), m.captured(1).replace("\n","").trimmed());
		e.declaration_pos = m.capturedStart(2);
		e.declaration_file = docpath;
		ret << e;
	}
	return ret;
}


QList<QCodeEdit::ACEntry> ScriptEdit::findVariables(const QString & content, const QString & docpath) {
	QList<ACEntry> ret;
	QRegularExpression gexpr("[^{}]+|({(?R)*})");
	QRegularExpressionMatchIterator i = gexpr.globalMatch(content);
	while (i.hasNext()) {
		QRegularExpressionMatch m = i.next();
		if (m.captured(1).isEmpty()) {
			QRegularExpression vexpr("(?<=(?:\\bvar\\b)|(?:\\blet\\b)|(?:\\bconst\\b))\\s*(\\b\\w+\\b)(?:\\s*=?\\s*)("
"{"
"|\\["
"|\""
"|'"
"|`"
"|[0-9.\\-+]"
"|\\btrue\\b"
"|\\bfalse\\b"
"|\\bSHS\\.newTimer\\b"
"|\\bSHS\\.newSingleTimer\\b"
"|\\bSHS\\.newQuery\\b"
"|\\bSHS\\.newTimeMeasurer\\b"
")?(?:new)?(?:\\s*(\\b[A-Za-z]+\\b)(?:[(.]))?");
			QRegularExpressionMatchIterator j = vexpr.globalMatch(m.captured()+"{");
			while (j.hasNext()) {
				QRegularExpressionMatch n = j.next();
				QString t = n.captured(2);
				if (t == "{") t = "Object";
				else if (t == "[") t = "Array";
				else if (t == "\"" || t == "'" || t == "`") t = "String";
				else if (t == "true" || t == "false") t = "Boolean";
				else if (t == "SHS.newTimer") t = "QTimer";
				else if (t == "SHS.newSingleTimer") t = "SHSScriptSingleTimer";
				else if (t == "SHS.newQuery") t = "SHSScriptQuery";
				else if (t == "SHS.newTimeMeasurer") t = "SHSScriptTimeMeasurer";
				else if (!t.isEmpty()) t = "Number";
				else t = n.captured(3);
				if (t.isEmpty()) t = "???";
				ACEntry e(t, n.captured(1));
				e.declaration_pos = m.capturedStart() + n.capturedStart(1);
				e.declaration_file = docpath;
				//qDebug() << docpath << e.type << e.name << m.capturedStart() << n.capturedStart(1);
				ret << e;
			}
		}
	}
	return ret;
}


QList<QCodeEdit::ACEntry> ScriptEdit::findClasses(const QString & content, const QString & docpath) {
	QList<ACEntry> ret;
	QRegularExpression expr("(?:\\/\\/\\s*)?(.*\\n)?(?:\\bclass\\b)\\s*(\\b\\w+\\b)");
	QRegularExpressionMatchIterator i = expr.globalMatch(content);
	while (i.hasNext()) {
		QRegularExpressionMatch m = i.next();
		ACEntry e("class", m.captured(2), m.captured(1).replace("\n","").trimmed());
		e.declaration_pos = m.capturedStart(2);
		e.declaration_file = docpath;
		ret << e;
	}
	return ret;
}


QList<QCodeEdit::ACSection> ScriptEdit::compFromMeta(QMetaObject mo, QString written) const {
	QList<ACSection> ret;
	ACSection ac, ac2;
	ac.first = ScriptCommon::Function; ac.second.clear();
	ac2.first = ScriptCommon::Signal; ac2.second.clear();
	QSet<QString> in_list;
	for (int i = 0; i < mo.methodCount(); ++i) {
		QMetaMethod mm = mo.method(i);
		if (mm.methodType() != QMetaMethod::Signal && mm.methodType() != QMetaMethod::Slot)
			continue;
		if (mm.access() != QMetaMethod::Public) continue;
		QString fmn = mm.methodSignature(), rtn(mm.typeName());
		if (fmn.startsWith("_def_")) fmn.remove(0, 5);
		if (fmn.startsWith("_") || fmn.startsWith("deleteLater")) continue;
		fmn = fmn.left(fmn.indexOf("("));
		if (in_list.contains(fmn)) continue;
		in_list << fmn;
		if (!matchWritten(fmn, written)) continue;
		fmn += "(";
		for (int i = 0; i < mm.parameterNames().size(); ++i) {
			if (i > 0) fmn += ", ";
			fmn += mm.parameterNames()[i];
		}
		fmn += ")";
		if (rtn.isEmpty()) rtn = "function";
		if (mm.methodType() == QMetaMethod::Slot) ac.second << ACEntry(rtn, fmn);
		else ac2.second << ACEntry(rtn, fmn);
	}
	ret << ac << ac2;
	ac.first = ScriptCommon::Property; ac.second.clear();
	for (int i = 0; i < mo.propertyCount(); ++i) {
		QMetaProperty mp = mo.property(i);
		if (QLatin1String(mp.name()).startsWith(QLatin1String("_"))) continue;
		if (!matchWritten(mp.name(), written)) continue;
		ac.second << ACEntry(mp.typeName(), mp.name());
	}
	ret << ac;
	ac.first = ScriptCommon::Constant; ac.second.clear();
	for (int e = 0; e < mo.enumeratorCount(); ++e) {
		QMetaEnum me = mo.enumerator(e);
		for (int i = 0; i < me.keyCount(); ++i) {
			if (!matchWritten(me.key(i), written)) continue;
			ac.second << ACEntry(me.name(), me.key(i));
		}
	}
	ret << ac;
	return ret;
}


QList<QCodeEdit::ACSection> ScriptEdit::compFromBuiltin(const QList<ACSection> & aclist, QString written) const {
	QList<ACSection> ret;
	ACSection ac;
	for (const auto & acs: aclist) {
		ac.first = acs.first; ac.second.clear();
		for (const ACEntry & s: acs.second) {
			if (!matchWritten(s.name, written)) continue;
			ac.second << s;
		}
		if (!ac.second.isEmpty())
			ret << ac;
	}
	return ret;
}


QString ScriptEdit::docPath(const QTextDocument * doc) {
	if (!doc) return QString();
	return doc->property("_path").toString();
}


QString ScriptEdit::docTitle(const QTextDocument * doc) {
	if (!doc) return QString();
	return doc->property("_title").toString();
}


void ScriptEdit::setDocPath(QTextDocument * doc, const QString & path) {
	if (!doc) return;
	doc->setProperty("_path", path);
	doc->setProperty("_title", QFileInfo(path).baseName());
}
