#include <QApplication>
#include <QFileInfo>
#include <QMetaMethod>
#include <QTimer>
#if QT_VERSION < 0x050000
#  include <QDesktopWidget>
#else
#  include <QScreen>
#endif
#include "qad_locations.h"
#include "qad_types.h"
#include "session_manager.h"


SessionManager::SessionManager(QString file) {
	if (file.isEmpty() && !QCoreApplication::applicationName().isEmpty()) {
		file = QAD::userPath(QAD::ltConfig, "session");
	}
	setFile(file);
}


void SessionManager::setFile(const QString & file) {
	if (file.isEmpty()) {
		file_.clear();
		return;
	}
	QFileInfo fi(file);
	if (fi.isAbsolute()) {
		file_ = file;
		return;
	}
	file_ = QApplication::applicationDirPath() + "/" + file;
}


void SessionManager::removeMainWidget(QWidget * e) {
	for (int i = 0; i < widgets.size(); ++i) {
		if (widgets[i].first == e->objectName()) {
			widgets.remove(i);
			--i;
		}
	}
}


void SessionManager::addEntry(const QString & name, QMainWindow * e) {
	mwindows.push_back(QPair<QString, QMainWindow *>(name, e));
	mw_splitters[e] = e->findChildren<QSplitter *>();
}


void SessionManager::save() {
	if (file_.isEmpty()) {
		qDebug() << "[SessionManager] filename is empty";
		return;
	}
	QPIConfig sr(file_);
	QObjectList tsc;
	for (int i = 0; i < mwindows.size(); ++i) {
		tsc << mwindows[i].second;
		sr.setValue(mwindows[i].first + " state", mwindows[i].second->saveState(), false);
		sr.setValue(mwindows[i].first + " window state", (int)mwindows[i].second->windowState(), false);
		sr.setValue(mwindows[i].first + " geometry " + QString::number((int)mwindows[i].second->windowState()),
		            mwindows[i].second->saveGeometry(),
		            false);
		QList<QSplitter *> spl = mw_splitters[mwindows[i].second];
		foreach(QSplitter * s, spl)
			sr.setValue(mwindows[i].first + " splitter " + s->objectName(), s->saveState(), false);
	}
	for (int i = 0; i < widgets.size(); ++i) {
		tsc << widgets[i].second;
		sr.setValue(widgets[i].first + " geometry " + QString::number((int)widgets[i].second->windowState()),
		            widgets[i].second->saveGeometry(),
		            false);
		sr.setValue(widgets[i].first + " window state", (int)widgets[i].second->windowState(), false);
	}
	for (int i = 0; i < checks.size(); ++i)
		sr.setValue(checks[i].first, checks[i].second->isChecked(), false);
	for (int i = 0; i < lines.size(); ++i)
		sr.setValue(lines[i].first, lines[i].second->text(), "s", false);
	for (int i = 0; i < combos.size(); ++i)
		sr.setValue(combos[i].first, combos[i].second->currentIndex(), false);
	for (int i = 0; i < dspins.size(); ++i)
		sr.setValue(dspins[i].first, dspins[i].second->value(), false);
	for (int i = 0; i < spins.size(); ++i)
		sr.setValue(spins[i].first, spins[i].second->value(), false);
	for (int i = 0; i < spinsliders.size(); ++i)
		sr.setValue(spinsliders[i].first, spinsliders[i].second->value(), false);
	for (int i = 0; i < evals.size(); ++i)
		sr.setValue(evals[i].first, evals[i].second->expression(), "s", false);
	for (int i = 0; i < tabs.size(); ++i)
		sr.setValue(tabs[i].first, tabs[i].second->currentIndex(), false);
	for (int i = 0; i < groups.size(); ++i)
		sr.setValue(groups[i].first, groups[i].second->isChecked(), false);
	for (int i = 0; i < buttons.size(); ++i)
		sr.setValue(buttons[i].first, buttons[i].second->isChecked(), false);
	for (int i = 0; i < stacks.size(); ++i)
		sr.setValue(stacks[i].first, stacks[i].second->currentIndex(), false);
	for (int i = 0; i < sliders.size(); ++i)
		sr.setValue(sliders[i].first, sliders[i].second->value(), false);
	for (int i = 0; i < actions.size(); ++i)
		sr.setValue(actions[i].first, actions[i].second->isChecked(), false);
	for (int i = 0; i < stringlists.size(); ++i)
		sr.setValue(stringlists[i].first, *stringlists[i].second, false);
	for (int i = 0; i < strings.size(); ++i)
		sr.setValue(strings[i].first, *strings[i].second, "s", false);
	for (int i = 0; i < colors.size(); ++i)
		sr.setValue(colors[i].first, *colors[i].second, false);
	for (int i = 0; i < bools.size(); ++i)
		sr.setValue(bools[i].first, *bools[i].second, false);
	for (int i = 0; i < ints.size(); ++i)
		sr.setValue(ints[i].first, *ints[i].second, false);
	for (int i = 0; i < floats.size(); ++i)
		sr.setValue(floats[i].first, *floats[i].second, false);
	QSet<QObject *> all_list;
	foreach(QObject * c, tsc) {
		all_list |= QList2QSet(c->findChildren<QObject *>());
		all_list |= c;
	}
	QMap<const QMetaObject *, QByteArray> funcs = metaFunctions(all_list, "sessionSave");
	// qDebug() << "check for save" << all_list.size();
	foreach(QObject * o, all_list) {
		const QMetaObject * mo = o->metaObject();
		QByteArray fn          = funcs.value(mo);
		if (!mo || fn.isEmpty()) continue;
		QByteArray value;
		// qDebug() << "save" << o->objectName();
		mo->invokeMethod(o, fn.constData(), Q_ARG(QByteArray *, &value));
		sr.setValue(o->objectName(), value, false);
	}
	// qDebug() << mcl.size();
	emit saving(sr);
	sr.writeAll();
}


void restoreWindowState(QWidget * w, QPIConfig & sr, QString name) {
	int wstate = sr.getValue(name + " window state").toInt();
	w->setWindowState(Qt::WindowNoState);
	w->restoreGeometry(sr.getValue(name + " geometry 0").toByteArray());
	if (wstate == Qt::WindowMaximized) {
		QTimer::singleShot(0, w, [w, wstate]() { w->setWindowState((Qt::WindowState)wstate); });
	}
	bool wnd_ok = false;
	QRect srect;
#if QT_VERSION < 0x050000
	wnd_ok = (QApplication::desktop()->screenNumber(w) >= 0);
	if (!wnd_ok) {
		srect = QApplication::desktop()->geometry();
	}
#else
	QList<QScreen *> sl = QApplication::screens();
	foreach(QScreen * s, sl) {
		if (s->geometry().contains(w->geometry())) {
			wnd_ok = true;
			break;
		}
	}
	srect = QApplication::primaryScreen()->geometry();
#endif
	if (!wnd_ok) {
		w->setWindowState(Qt::WindowNoState);
		w->setGeometry(srect.x() + (srect.width() - w->width()) / 2,
		               srect.y() + (srect.height() - w->height()) / 2,
		               w->width(),
		               w->height());
	}
}


void SessionManager::load(bool onlyMainwindow) {
	if (file_.isEmpty()) {
		qDebug() << "[SessionManager] filename is empty";
		return;
	}
	QPIConfig sr(file_);
	QObjectList tsc;
	for (int i = 0; i < mwindows.size(); ++i) {
		QMainWindow * mw = mwindows[i].second;
		tsc << mw;
		QByteArray ba = sr.getValue(mwindows[i].first + " state").toByteArray();
		if (!ba.isEmpty()) mw->restoreState(ba);
		QList<QSplitter *> spl = mw_splitters[mw];
		foreach(QSplitter * s, spl) {
			ba = sr.getValue(mwindows[i].first + " splitter " + s->objectName()).toByteArray();
			if (!ba.isEmpty()) s->restoreState(ba);
		}
		restoreWindowState(mw, sr, mwindows[i].first);
	}
	for (int i = 0; i < widgets.size(); ++i) {
		QWidget * mw = widgets[i].second;
		tsc << mw;
		restoreWindowState(mw, sr, widgets[i].first);
	}
	if (onlyMainwindow) return;
	for (int i = 0; i < checks.size(); ++i)
		checks[i].second->setChecked(sr.getValue(checks[i].first, checks[i].second->isChecked()).toBool());
	for (int i = 0; i < lines.size(); ++i)
		lines[i].second->setText(sr.getValue(lines[i].first, lines[i].second->text()).toString());
	for (int i = 0; i < combos.size(); ++i) {
		QComboBox * c = combos[i].second;
		int v         = sr.getValue(combos[i].first, c->currentIndex()).toInt();
		if (v >= 0 && v < c->count()) c->setCurrentIndex(v);
	}
	for (int i = 0; i < dspins.size(); ++i)
		dspins[i].second->setValue(sr.getValue(dspins[i].first, dspins[i].second->value()).toDouble());
	for (int i = 0; i < spins.size(); ++i)
		spins[i].second->setValue(sr.getValue(spins[i].first, spins[i].second->value()).toInt());
	for (int i = 0; i < spinsliders.size(); ++i)
		spinsliders[i].second->setValue(sr.getValue(spinsliders[i].first, spinsliders[i].second->value()).toDouble());
	for (int i = 0; i < evals.size(); ++i)
		evals[i].second->setExpression(sr.getValue(evals[i].first, evals[i].second->expression()).toString());
	for (int i = 0; i < tabs.size(); ++i) {
		QTabWidget * t = tabs[i].second;
		int v          = sr.getValue(tabs[i].first, t->currentIndex()).toDouble();
		if (v >= 0 && v < t->count()) t->setCurrentIndex(v);
	}
	for (int i = 0; i < groups.size(); ++i)
		groups[i].second->setChecked(sr.getValue(groups[i].first, groups[i].second->isChecked()).toBool());
	for (int i = 0; i < buttons.size(); ++i)
		buttons[i].second->setChecked(sr.getValue(buttons[i].first, buttons[i].second->isChecked()).toBool());
	for (int i = 0; i < stacks.size(); ++i)
		stacks[i].second->setCurrentIndex(
			qMin<int>(sr.getValue(stacks[i].first, stacks[i].second->currentIndex()).toInt(), stacks[i].second->count()));
	for (int i = 0; i < sliders.size(); ++i)
		sliders[i].second->setValue(sr.getValue(sliders[i].first, sliders[i].second->value()).toInt());
	for (int i = 0; i < actions.size(); ++i)
		actions[i].second->setChecked(sr.getValue(actions[i].first, actions[i].second->isChecked()).toBool());
	for (int i = 0; i < stringlists.size(); ++i)
		*stringlists[i].second = sr.getValue(stringlists[i].first, *stringlists[i].second).toStringList();
	for (int i = 0; i < strings.size(); ++i)
		*strings[i].second = sr.getValue(strings[i].first, *strings[i].second).toString();
	for (int i = 0; i < colors.size(); ++i)
		*colors[i].second = sr.getValue(colors[i].first, *colors[i].second).toColor();
	for (int i = 0; i < bools.size(); ++i)
		*bools[i].second = sr.getValue(bools[i].first, *bools[i].second).toBool();
	for (int i = 0; i < ints.size(); ++i)
		*ints[i].second = sr.getValue(ints[i].first, *ints[i].second).toInt();
	for (int i = 0; i < floats.size(); ++i)
		*floats[i].second = sr.getValue(floats[i].first, *floats[i].second).toFloat();
	QSet<QObject *> all_list;
	foreach(QObject * c, tsc) {
		all_list |= QList2QSet(c->findChildren<QObject *>());
		all_list |= c;
	}
	QMap<const QMetaObject *, QByteArray> funcs = metaFunctions(all_list, "sessionLoad");
	// qDebug() << "check for load" << all_list.size();
	foreach(QObject * o, all_list) {
		const QMetaObject * mo = o->metaObject();
		QByteArray fn          = funcs.value(mo);
		if (!mo || fn.isEmpty()) continue;
		QByteArray value = sr.getValue(o->objectName()).toByteArray();
		// qDebug() << "load" << o->objectName();
		mo->invokeMethod(o, fn.constData(), Q_ARG(QByteArray *, &value));
	}
	emit loading(sr);
}


void SessionManager::clear(bool with_filename) {
	mwindows.clear();
	widgets.clear();
	checks.clear();
	lines.clear();
	combos.clear();
	dspins.clear();
	spins.clear();
	spinsliders.clear();
	evals.clear();
	tabs.clear();
	buttons.clear();
	stacks.clear();
	sliders.clear();
	actions.clear();
	stringlists.clear();
	strings.clear();
	colors.clear();
	bools.clear();
	ints.clear();
	floats.clear();
	mw_splitters.clear();
	if (with_filename) setFile("");
}


QMap<const QMetaObject *, QByteArray> SessionManager::metaFunctions(const QSet<QObject *> & objects, QByteArray fname) {
	QMap<const QMetaObject *, QByteArray> funcs;
	foreach(QObject * o, objects) {
		const QMetaObject * mo = o->metaObject();
		if (!mo) continue;
		QByteArray fn;
		if (!funcs.contains(mo)) {
			for (int i = 0; i < mo->methodCount(); ++i) {
				QMetaMethod mm = mo->method(i);
				QByteArray mmn =
#if QT_VERSION >= 0x050000
					mm.name();
#else
					mm.signature();
				mmn = mmn.left(mmn.indexOf('('));
#endif

				if (mmn == fname) {
					if (mm.parameterTypes().size() > 0) {
						if (mm.parameterTypes()[0] == "QByteArray*") {
							fn = mmn;
						}
					}
				}
			}
			funcs[mo] = fn;
		} else
			fn = funcs[mo];
	}
	return funcs;
}
