#include <QMessageBox>
#include <QFileDialog>
#include <QInputDialog>
#include <QTextBlock>
#include <QTextDocumentFragment>
#include <QScrollBar>
#include <QKeyEvent>
#include "serverscriptedit.h"
#include "qad_types.h"


ServerScriptEdit::ServerScriptEdit(QWidget * parent): QWidget(parent), Ui::ServerScriptEdit() {
	setupUi(this);
	list_opened = new QListWidget();
	list_opened->setMouseTracking(false);
	list_opened->viewport()->setMouseTracking(false);
	list_opened->setSelectionMode(QAbstractItemView::SingleSelection);
	list_opened->hide();
	list_opened->setParent(this);
	tab_bar = new QTabBar();
	tab_bar->setObjectName("tab_bar_qs");
	tab_bar->setTabsClosable(true);
	tab_bar->setDocumentMode(true);
	tab_bar->setMovable(true);
	tab_bar->setExpanding(false);
	tab_bar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
	layoutTabBar->insertWidget(0, tab_bar);
	connect(tab_bar, SIGNAL(currentChanged(int)), this, SLOT(tabBar_currentChanged(int)));
	connect(tab_bar, SIGNAL(tabCloseRequested(int)), this, SLOT(tabBar_tabCloseRequested(int)));
	connect(CORE, SIGNAL(projectChanged()), this, SLOT(projectChanged()));
	es_error.format.setBackground(QColor(255, 210, 210));
	es_error.format.setProperty(QTextFormat::FullWidthSelection, true);
	es_syntax.format.setBackground(QColor(255, 230, 230));
	es_syntax.format.setProperty(QTextFormat::FullWidthSelection, true);
	icon_main = QIcon(":/icons/run-build.png");
	icon_proj = QIcon(":/icons/code-union.png");
	codeEdit->setDocument(0);
	buttonCancelError->hide();
	_setIconSize();
}


bool ServerScriptEdit::event(QEvent * e) {
	QKeyEvent * ke = ((QKeyEvent*)e);
	switch (e->type()) {
		case QEvent::FontChange:
		case QEvent::Polish:
			_setIconSize();
			break;
		case QEvent::Resize:
			list_opened->setGeometry(width() / 4, height() / 4, width() / 2, height() / 2);
			break;
		case QEvent::FocusOut:
			list_opened->hide();
			break;
		case QEvent::MouseButtonPress:
			if (!list_opened->underMouse())
				list_opened->hide();
			break;
		case QEvent::KeyPress:
			if ((ke->key() == Qt::Key_Tab || ke->key() == Qt::Key_Backtab)
				&& ke->modifiers().testFlag(Qt::ControlModifier)) {
				if (list_opened->isHidden())
					showOpened();
				changeOpened(!ke->modifiers().testFlag(Qt::ShiftModifier));
				return true;
			}
			break;
		case QEvent::KeyRelease:
			if (ke->modifiers() == 0 && list_opened->isVisible()) {
				moveToOpened();
				return true;
			}
			break;
		default: break;
	}
	return QWidget::event(e);
}


ServerScriptEdit::~ServerScriptEdit() {
	qDeleteAll(uu_docs.values());
	uu_docs.clear();
}


void ServerScriptEdit::saveCurrentScript() {
	saveFile(currentDocument());
}


void ServerScriptEdit::saveAllScripts() {
	for (int i = 0; i < tab_bar->count(); ++i) {
		QTextDocument * d = qobject_cast<QTextDocument * >((QObject*)tab_bar->tabData(i).toLongLong());
		if (d)
			if (d->isModified())
				saveFile(d);
	}

}


void ServerScriptEdit::saveAsCurrentScript() {
	saveAsFile(currentDocument());
}


void ServerScriptEdit::closeCurrentScript() {
	QTextDocument * doc = currentDocument();
	for (int i = 0; i < tab_bar->count(); ++i) {
		QTextDocument * d = (QTextDocument *)(tab_bar->tabData(i).toLongLong());
		if (d && (d == doc)) {
			tabBar_tabCloseRequested(i);
			return;
		}
	}
}


void ServerScriptEdit::openScript() {
	QString f = QFileDialog::getOpenFileName(this, "Select file", "", "Script files(*.qs);;Any file(*)");
	if (f.isEmpty()) return;
	loadFile(QDir::current().relativeFilePath(f));
}


void ServerScriptEdit::loadFiles(const QStringList & path) {
	foreach (const QString & p, path)
		loadFile(QDir::current().relativeFilePath(p));
}


void ServerScriptEdit::loadFile(const QString & path) {
	if (path.isEmpty()) return;
	for (int i = 0; i < tab_bar->count(); ++i) {
		QTextDocument * d = (QTextDocument *)(tab_bar->tabData(i).toLongLong());
		if (ScriptEdit::docPath(d) == path) {
			tab_bar->setCurrentIndex(i);
			return;
		}
	}
	QFile file(path);
	if (!file.open(QIODevice::ReadOnly)) {
		QMessageBox::critical(this, "Open file", "Can`t open file \"" + path + "\"!");
		return;
	}
	QString dt = QFileInfo(path).baseName();
	QTextDocument * d = 0;
	if (uu_docs.contains(path))
		d = uu_docs.take(path);
	else {
		d = new QTextDocument();
		ScriptEdit::setDocPath(d, path);
		d->setPlainText(QString::fromUtf8(file.readAll()));
	}
	d->setModified(false);
	addDocTab(d, dt, path);
}


void ServerScriptEdit::saveCurrentScriptTo(QString path) {
	if (path.isEmpty()) return;
	if (!path.endsWith(".qs")) path += ".qs";
	QTextDocument * doc = currentDocument();
	ScriptEdit::setDocPath(doc, QDir::current().relativeFilePath(path));
	markTabs();
	emit titleChangeRequest(ScriptEdit::docTitle(doc));
	emit currentScriptChanged(ScriptEdit::docPath(doc));
	saveFile(doc);
}


void ServerScriptEdit::clearError() {
	labelSyntax->setText("No syntax problems");
	es_error.cursor = QTextCursor();
	buttonCancelError->hide();
	applyExtraSelection();
}


void ServerScriptEdit::closeAll() {
	setDocument(0);
	while (tab_bar->count() > 0) {
		QTextDocument * d = (QTextDocument * )(tab_bar->tabData(0).toLongLong());
		if (d)
			d->deleteLater();
		tab_bar->removeTab(0);
	}
	last_opened.clear();
	prepareUnusedDocs();
}


QString ServerScriptEdit::currentFile() {
	return ScriptEdit::docPath(currentDocument());
}


QString ServerScriptEdit::currentTitle() {
	return ScriptEdit::docTitle(currentDocument());
}


void ServerScriptEdit::setProject(const SHS::ServerProject & proj) {
	project = proj;
	markTabs();
	prepareUnusedDocs();
	emit currentScriptChanged(currentFile());
}


void ServerScriptEdit::clearForm() {
	codeEdit->form_ui = nullptr;
	codeEdit->updateACObjects();
}


void ServerScriptEdit::setDocument(QTextDocument * doc) {
	es_error.cursor = QTextCursor();
	applyExtraSelection();
	codeEdit->setDocument(doc);
	emit titleChangeRequest(ScriptEdit::docTitle(doc));
	emit currentScriptChanged(ScriptEdit::docPath(doc));
}


QTextDocument * ServerScriptEdit::currentDocument() {
	int ci = tab_bar->currentIndex();
	if (ci < 0 || ci >= tab_bar->count()) return 0;
	return (QTextDocument * )(tab_bar->tabData(ci).toLongLong());
}


void ServerScriptEdit::addDocTab(QTextDocument * doc, const QString & title, const QString & path) {
	tab_bar->blockSignals(true);
	tab_bar->addTab(title);
	int ci = tab_bar->count() - 1;
	tab_bar->setTabToolTip(ci, path);
	tab_bar->setTabData(ci, (qlonglong)doc);
	tab_bar->setCurrentIndex(ci);
	tab_bar->blockSignals(false);
	tabBar_currentChanged(ci);
	connect(doc, SIGNAL(modificationChanged(bool)), this, SLOT(markTabs()));
	markTabs();
	codeEdit->setFocus();
}


void ServerScriptEdit::_setIconSize() {
	tab_bar->setIconSize(preferredIconSize(1.25, this));
}


bool ServerScriptEdit::saveFile(QTextDocument * doc) {
	if (!doc) return false;
	QString path = ScriptEdit::docPath(doc);
	if (path.isEmpty())
		return saveAsFile(doc);
	QFile f(path);
	if (!f.open(QIODevice::WriteOnly)) {
		QMessageBox::critical(this, "Save file", "Can`t open file \"" + path + "\"!");
		return false;
	}
	f.resize(0);
	f.write(doc->toPlainText().toUtf8());
	ScriptEdit::setDocPath(doc, path);
	doc->setModified(false);
	return true;
}


bool ServerScriptEdit::saveAsFile(QTextDocument * doc) {
	if (!doc) return false;
	QString p = QFileDialog::getSaveFileName(this, "Save script", ScriptEdit::docPath(doc), "Script files(*.qs)");
	if (p.isEmpty()) return false;
	if (!p.endsWith(".qs")) p += ".qs";
	ScriptEdit::setDocPath(doc, QDir::current().relativeFilePath(p));
	if (doc == currentDocument()) {
		markTabs();
		emit titleChangeRequest(ScriptEdit::docTitle(doc));
		emit currentScriptChanged(ScriptEdit::docPath(doc));
	}
	return saveFile(doc);
}


void ServerScriptEdit::applyExtraSelection() {
	codeEdit->setCustomExtraSelection(QList<QTextEdit::ExtraSelection>() << es_syntax << es_error);
}


void ServerScriptEdit::prepareUnusedDocs() {
	qDeleteAll(uu_docs.values());
	uu_docs.clear();
	QSet<QString> up;
	for (int i = 0; i < tab_bar->count(); ++i) {
		QTextDocument * d = (QTextDocument *)(tab_bar->tabData(i).toLongLong());
		if (!d) continue;
		up << ScriptEdit::docPath(d);
	}
	up.remove("");
	foreach (QString f, project.qs) {
		if (up.contains(f)) continue;
		QFile file(f);
		if (!file.open(QIODevice::ReadOnly)) continue;
		QTextDocument * d = new QTextDocument();
		ScriptEdit::setDocPath(d, f);
		d->setPlainText(QString::fromUtf8(file.readAll()));
		d->setModified(false);
		uu_docs[f] = d;
	}
}


QTextCursor ServerScriptEdit::findInDocs(const QString & s, QTextDocument *& doc) {
	QList<QTextDocument * > docs;
	for (int i = 0; i < tab_bar->count(); ++i) {
		QTextDocument * d = (QTextDocument *)(tab_bar->tabData(i).toLongLong());
		if (d) docs << d;
	}
	docs << uu_docs.values();
	foreach (QTextDocument * d, docs) {
		QTextCursor tc = d->find(s);
		if (!tc.isNull()) {
			doc = d;
			return tc;
		}
	}
	doc = currentDocument();
	return QTextCursor();
}


void ServerScriptEdit::setTextCursor(QTextCursor tc) {
	if (!tc.document() || tc.isNull()) return;
	loadFile(ScriptEdit::docPath(tc.document()));
	codeEdit->setTextCursor(tc);
	codeEdit->centerCursor();
	codeEdit->setFocus();
}


void ServerScriptEdit::showOpened() {
	list_opened->clear();
	//for (int i = 0; i < tab_bar->count(); ++i) {
	foreach (QTextDocument * d, last_opened) {
		if (!d) continue;
		QListWidgetItem * li = new QListWidgetItem();
		li->setText(ScriptEdit::docTitle(d));
		li->setData(Qt::UserRole, qulonglong(d));
		list_opened->addItem(li);
	}
	if (list_opened->count() > 0) {
		list_opened->setCurrentRow(0);
		list_opened->show();
	}
}


void ServerScriptEdit::moveToOpened() {
	QListWidgetItem * li = list_opened->currentItem();
	list_opened->hide();
	if (li) {
		QTextDocument * sd = (QTextDocument *)(li->data(Qt::UserRole).toULongLong());
		for (int i = 0; i < tab_bar->count(); ++i) {
			QTextDocument * d = (QTextDocument *)(tab_bar->tabData(i).toLongLong());
			if (sd == d) {
				tab_bar->setCurrentIndex(i);
				break;
			}
		}
	}
}


void ServerScriptEdit::changeOpened(bool next) {
	if (list_opened->count() == 0) return;
	int r = list_opened->currentRow();
	if (next) r = (r + 1) % list_opened->count();
	else r = (r == 0 ? list_opened->count() - 1 : r - 1);
	list_opened->setCurrentRow(r);
}


void ServerScriptEdit::gotoLine(QString file, int line) {
	on_codeEdit_gotoDoc(file, 0);
	QTextCursor tc(currentDocument());
	tc.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, line - 1);
	setTextCursor(tc);
}


void ServerScriptEdit::markTabs() {
	bool ch = false;
	for (int i = 0; i < tab_bar->count(); ++i) {
		QTextDocument * d = (QTextDocument *)(tab_bar->tabData(i).toLongLong());
		if (!d) continue;
		QString tn = ScriptEdit::docTitle(d);
		QString p = ScriptEdit::docPath(d);
		if (p.isEmpty()) continue;
		if (d->isModified()) {
			tn += "*";
			ch = true;
		}
		QIcon ti;
		if (project.qs.contains(p)) {
			//tn += " [P";
			if (project.qs_main == p) {
				//tn += ", Main";
				ti = icon_main;
			} else
				ti = icon_proj;
			//tn += "]";
		}
		tab_bar->setTabIcon(i, ti);
		tab_bar->setTabText(i, tn);
		tab_bar->setTabToolTip(i, QFileInfo(p).absoluteFilePath());
	}
	emit documentsChanged(ch);
}


void ServerScriptEdit::projectChanged() {
	setProject(RUNTIME->server_project);
	titleChangeRequest(currentTitle());
}


void ServerScriptEdit::on_codeEdit_textChanged() {
	if (!currentDocument()) return;
	es_syntax.cursor = QTextCursor();
	/*QScriptSyntaxCheckResult res = QJSEngine::checkSyntax(codeEdit->text());
	//qDebug() << res.errorLineNumber() << res.state() << res.errorMessage();
	if (res.state() == QScriptSyntaxCheckResult::Error) {
		es_syntax.cursor = QTextCursor(currentDocument()->begin());
		es_syntax.cursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, res.errorLineNumber() - 1);
		es_syntax.cursor.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor);
		applyExtraSelection();
		labelSyntax->setText(QString("Line %1: %2").arg(res.errorLineNumber()).arg(res.errorMessage()));
	} else {
		applyExtraSelection();
		labelSyntax->setText("No syntax problems");
	}*/ /// TODO
}


void ServerScriptEdit::on_codeEdit_requestDocByPath(QString path, QTextDocument ** ret) {
	if (!ret) return;
	for (int i = 0; i < tab_bar->count(); ++i) {
		QTextDocument * d = (QTextDocument *)(tab_bar->tabData(i).toLongLong());
		if (!d) continue;
		if (ScriptEdit::docPath(d) == path) {
			*ret = d;
			return;
		}
	}
	foreach (QTextDocument * d, uu_docs) {
		if (ScriptEdit::docPath(d) == path) {
			*ret = d;
			return;
		}
	}
}


void ServerScriptEdit::on_codeEdit_gotoDoc(QString path, int pos) {
	if (path.isEmpty()) return;
	loadFile(path);
	if (pos >= 0) {
		QTextCursor tc(currentDocument());
		tc.setPosition(pos);
		setTextCursor(tc);
	}
}


void ServerScriptEdit::on_labelSyntax_linkActivated(QString link) {
	QStringList sl = link.split(":");
	if (sl.size() < 2) return;
	gotoLine(sl[0], sl[1].toInt());
}


void ServerScriptEdit::newScript() {
	addDocTab(new QTextDocument(), "[new]");
}


void ServerScriptEdit::tabBar_currentChanged(int index) {
	if (index < 0)
		setDocument(0);
	else {
		QTextDocument * d = (QTextDocument * )(tab_bar->tabData(index).toLongLong());
		last_opened.removeAll(d);
		last_opened.prepend(d);
		setDocument(d);
	}
}


void ServerScriptEdit::tabBar_tabCloseRequested(int index) {
	QTextDocument * doc = (QTextDocument * )(tab_bar->tabData(index).toLongLong());
	if (!doc) return;
	if (doc->isModified()) {
		int ret = QMessageBox::question(this, "Close script", QString("Save changes in \"%1\"?").arg(ScriptEdit::docTitle(doc)),
										QMessageBox::Yes, QMessageBox::No, QMessageBox::Cancel);
		if (ret == QMessageBox::Cancel) return;
		if (ret == QMessageBox::Yes)
			if (!saveFile(doc))
				return;
	}
	if (currentDocument() == doc)
		setDocument(0);
	QString dp = ScriptEdit::docPath(doc);
	if (project.qs.contains(dp)) {
		QFile file(dp);
		file.open(QIODevice::ReadOnly);
		doc->setPlainText(QString::fromUtf8(file.readAll()));
		uu_docs[dp] = doc;
	} else
		delete doc;
	last_opened.removeAll(doc);
	tab_bar->removeTab(index);
}


void ServerScriptEdit::scriptError(int line, QString file, QString message) {
	gotoLine(file, line);
	es_error.cursor = QTextCursor(codeEdit->document()->begin());
	es_error.cursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, line - 1);
	es_error.cursor.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor);
	labelSyntax->setText(message);
	buttonCancelError->show();
	applyExtraSelection();
}


void ServerScriptEdit::addSignal(QMetaMethod mm, QString signal_src) {
	//qDebug() << mm.parameterNames() << mm.parameterTypes();// << a->text();
	QString al, mn = mm.methodSignature();
	mn = mn.left(mn.indexOf("("));
	for (int i = 0; i < mm.parameterNames().size(); ++i) {
		if (!al.isEmpty()) al += ", ";
		QString pn = mm.parameterNames()[i];
		if (pn.isEmpty()) pn = QString("a%1").arg(i);
		al += pn;
	}
	/*for (int i = 0; i < mm.parameterTypes().size(); ++i) {
		if (!tl.isEmpty()) tl += ", ";
		tl += mm.parameterTypes()[i];
	}*/
	QString func = QString("function on_%1_%2(%3)").arg(signal_src, mn, al);
	QTextDocument * doc = 0;
	QTextCursor tc = findInDocs(func, doc);
	if (!doc) return;
	if (!tc.isNull()) {
		tc.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
		tc.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor);
		tc.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor);
	} else {
		tc = QTextCursor(doc);
		tc.beginEditBlock();
		tc.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
		tc.insertText(QString("\n" + func + " {\n\t\n}\n"));
		tc.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
		tc.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, 3);
		tc.endEditBlock();
	}
	setTextCursor(tc);
}


void ServerScriptEdit::insertText(const QString & t) {
	QTextCursor tc = codeEdit->textCursor();
	tc.removeSelectedText();
	tc.insertText(t);
	codeEdit->setTextCursor(tc);
}


void ServerScriptEdit::insertEvent(const QString & func_name) {
	QString func = func_name;
	QString args = func.right(func.size() - func.indexOf("("));
	func.chop(args.size());
	QTextDocument * doc = 0;
	QTextCursor tc = findInDocs(func, doc);
	//qDebug() << "found in" << doc << tc.position();
	if (!doc) return;
	if (!tc.isNull()) {
		tc.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
		tc.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor);
		tc.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor);
	} else {
		tc = QTextCursor(doc);
		tc.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
		tc.insertText(QString("\n" + func + args + " {\n\t\n}\n"));
		tc.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
		tc.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, 3);
	}
	setTextCursor(tc);
}


void ServerScriptEdit::insertFunction(const QString & func_name) {
	QString func = func_name;
	QString args = func.right(func.size() - func.indexOf("("));
	func.chop(args.size());
	QTextCursor tc = codeEdit->textCursor();
	tc.removeSelectedText();
	tc.insertText(func + "()");
	if (args.size() > 2)
		tc.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor);
	codeEdit->setTextCursor(tc);
}


void ServerScriptEdit::insertInclude(const QString & inc_name) {
	QTextDocument * doc = currentDocument();
	if (!findInDocs(inc_name, doc).isNull()) return;
	QTextCursor tc(doc);
	tc.insertText(inc_name + "\n");
	tc.movePosition(QTextCursor::Left);
	codeEdit->setTextCursor(tc);
	codeEdit->setFocus();
}
