#include "qcd_core.h"
#include "cdutils_k.h"
#include "cdutils_core.h"
#include "piqt.h"
#include <QWidget>
#include <QCheckBox>
#include <QGroupBox>
#include <QSpinBox>
#include <QSlider>
#include <QScrollBar>
#include <QDoubleSpinBox>
#include <QLineEdit>
#include <spinslider.h>
#include <clineedit.h>
#include <evalspinbox.h>
#include <qvariantedit.h>
#include <qcd_view.h>

using namespace CDUtils;


int __QCore_Initializer__::count_(0);
QCDCore * __QCore_Initializer__::__instance__(0);


__QCore_Initializer__::__QCore_Initializer__() {
	count_++;
	if (count_ > 1) return;
	__instance__ = new QCDCore();
}


__QCore_Initializer__::~__QCore_Initializer__() {
	count_--;
	if (count_ > 0) return;
	if (__instance__ != 0) {
		delete __instance__;
		__instance__ = 0;
	}
}




QCDCore::QCDCore() {
	setObjectName("QCDCore");
	setName("QCDCore");
	CONNECTU(&K, changedGlobal, this, K_ChangedGlobal);
	updating = direct_on = false;
}


QCDCore::~QCDCore() {
}


void QCDCore::K_ChangedGlobal() {
	QMetaObject::invokeMethod(this, "updateBindedWidgets", Qt::QueuedConnection);
}


void QCDCore::slotBool(bool v) {
	QWidget * w = (QWidget*)sender();
	if (!w || updating) return;
	QList<PIDeque<int> > pathes = binded_widgets.values(w);
	foreach (const PIDeque<int> & path, pathes)
		K[path].setValue(PIString::fromBool(v));
	K.calculate();
	emit updateViewRequest();
}


void QCDCore::slotInt(int v) {
	QWidget * w = (QWidget*)sender();
	if (!w || updating) return;
	QList<PIDeque<int> > pathes = binded_widgets.values(w);
	foreach (const PIDeque<int> & path, pathes)
		K[path].setValue(PIString::fromNumber(v));
	finishEdit(pathes);
}


void QCDCore::slotDouble(double v) {
	QWidget * w = (QWidget*)sender();
	if (!w || updating) return;
	QList<PIDeque<int> > pathes = binded_widgets.values(w);
	foreach (const PIDeque<int> & path, pathes)
		K[path].setValue(PIString::fromNumber(v));
	finishEdit(pathes);
}


void QCDCore::slotText(QString v) {
	QWidget * w = (QWidget*)sender();
	if (!w || updating) return;
	QList<PIDeque<int> > pathes = binded_widgets.values(w);
	foreach (const PIDeque<int> & path, pathes)
		K[path].setValue(Q2PIString(v));
	finishEdit(pathes);
}


void QCDCore::slotVariant(QVariant v) {
	QWidget * w = (QWidget*)sender();
	if (!w || updating) return;
	QList<PIDeque<int> > pathes = binded_widgets.values(w);
	foreach (const PIDeque<int> & path, pathes)
		K[path].setVariantValue(Q2PIVariant(v));
	finishEdit(pathes);
}


void QCDCore::slotDestroyed(QObject * o) {
	if (!o) return;
	if (!binded_widgets.contains((QWidget*)o)) return;
	binded_widgets.remove((QWidget*)o);
}


int QCDCore::bindWindow(QWidget * wnd) {
	if (!wnd) return 0;
	//K.root().makePath();
	return bindWidgets(wnd->findChildren<QWidget * >());
}


int QCDCore::bindWidgets(QList<QWidget * > wl) {
	int ret = 0;
	foreach (QWidget * w, wl)
		if (bindWidget(w)) ++ret;
	return ret;
}


bool QCDCore::bindWidget(QWidget * w) {
	if (!w) return false;
	QString on = w->objectName();
	QString cn = w->metaObject()->className();
	if (cn == "CDView") {
		bindView(w);
		return false;
	}
	PIVector<CDType * > ak = K.root().children();
	piForeachC (CDType * k, ak) {
		if (!on.endsWith(PI2QString(k->pathString().join("_")))) continue;
		if (bindWidget(w, *k)) return true;
	}
	return false;
}


bool QCDCore::bindWidget(QWidget * w, const CDType & k) {
	if (!w) return false;
	//piCout << "bind..." << k.name() << k.path();
	QString cn = w->metaObject()->className();
	bool ok = false;
	if (cn == "QCheckBox" || cn == "QGroupBox") {
		connect(w, SIGNAL(toggled(bool)), this, SLOT(slotBool(bool)), Qt::UniqueConnection);
		ok = true;
	}
	if (cn == "QSpinBox" || cn == "QSlider" || cn == "QScrollBar") {
		connect(w, SIGNAL(valueChanged(int)), this, SLOT(slotInt(int)), Qt::UniqueConnection);
		ok = true;
	}
	if (cn == "QDoubleSpinBox" || cn == "SpinSlider" || cn == "EvalSpinBox") {
		connect(w, SIGNAL(valueChanged(double)), this, SLOT(slotDouble(double)), Qt::UniqueConnection);
		ok = true;
	}
	if (cn == "QLineEdit" || cn == "CLineEdit") {
		connect(w, SIGNAL(textChanged(QString)), this, SLOT(slotText(QString)), Qt::UniqueConnection);
		ok = true;
	}
	if (cn == "QVariantEdit") {
		connect(w, SIGNAL(valueChanged(QVariant)), this, SLOT(slotVariant(QVariant)), Qt::UniqueConnection);
		ok = true;
	}
	if (cn == "CDView") {
		bindView(w);
	}
	connect(w, SIGNAL(destroyed(QObject*)), this, SLOT(slotDestroyed(QObject*)), Qt::UniqueConnection);
	setWidgetValue(w, k);
	if (!ok) return false;
	//piCout << k.name() << k.path() << "ok";
	binded_widgets.insert(w, k.path());
	return true;
}


void QCDCore::updateBindedWidgets() {
	QMapIterator<QWidget * , PIDeque<int> > it(binded_widgets);
	QWidgetList to_remove;
	updating = true;
	while (it.hasNext()) {
		QWidget * w = it.next().key();
		if (!K.exists(it.value()))
			to_remove << w;
		else
			setWidgetValue(w, K[it.value()]);
	}
	foreach (QWidget * w, to_remove)
		unbindWidget(w);
	updating = false;
}


void QCDCore::bindView(QWidget * v) {
	CDView * w = qobject_cast<CDView * >(v);
	if (!w) return;
	connect(this, SIGNAL(updateViewRequest()), w, SLOT(refreshValues()), Qt::UniqueConnection);
}


void QCDCore::setWidgetValue(QWidget * w, const CDType & k) {
	if (!w) return;
	QString cn = w->metaObject()->className();
	if (cn == "QCheckBox")      qobject_cast<QCheckBox*>(w)->setChecked(k.toBool());
	if (cn == "QGroupBox")      qobject_cast<QGroupBox*>(w)->setChecked(k.toBool());
	if (cn == "QSpinBox")       qobject_cast<QSpinBox*>(w)->setValue(k.toInt());
	if (cn == "QSlider")        qobject_cast<QSlider*>(w)->setValue(k.toInt());
	if (cn == "QScrollBar")     qobject_cast<QScrollBar*>(w)->setValue(k.toInt());
	if (cn == "QDoubleSpinBox") qobject_cast<QDoubleSpinBox*>(w)->setValue(k.toDouble());
	if (cn == "SpinSlider")     qobject_cast<SpinSlider*>(w)->setValue(k.toDouble());
	if (cn == "QLineEdit")      qobject_cast<QLineEdit*>(w)->setText(PI2QString(k.value()));
	if (cn == "CLineEdit")      qobject_cast<CLineEdit*>(w)->setText(PI2QString(k.value()));
	if (cn == "EvalSpinBox")    qobject_cast<EvalSpinBox*>(w)->setValue(k.toDouble());
	if (cn == "QVariantEdit")   qobject_cast<QVariantEdit*>(w)->setValue(PI2QVariant(k.variantValue()));
}


void QCDCore::finishEdit(const QList<PIDeque<int> > & pathes) {
	K.calculate();
	if (direct_on) {
		foreach (const PIDeque<int> & path, pathes)
			K.directChange(K[path]);
	}
	emit updateViewRequest();
}


int QCDCore::unbindWindow(QWidget * wnd) {
	if (!wnd) return 0;
	return unbindWidgets(wnd->findChildren<QWidget * >());
}


int QCDCore::unbindWidgets(QList<QWidget * > wl) {
	int ret = 0;
	foreach (QWidget * w, wl)
		if (unbindWidget(w)) ++ret;
	return ret;
}


bool QCDCore::unbindWidget(QWidget * w) {
	if (!w) return false;
	//qDebug() << "unbind" << w;
	if (!binded_widgets.contains(w)) return false;
	QString cn = w->metaObject()->className();
	if (cn == "QCheckBox" || cn == "QGroupBox")
		disconnect(w, SIGNAL(toggled(bool)), this, SLOT(slotBool(bool)));
	if (cn == "QSpinBox" || cn == "QSlider" || cn == "QScrollBar")
		disconnect(w, SIGNAL(valueChanged(int)), this, SLOT(slotInt(int)));
	if (cn == "QDoubleSpinBox" || cn == "SpinSlider" || cn == "EvalSpinBox")
		disconnect(w, SIGNAL(valueChanged(double)), this, SLOT(slotDouble(double)));
	if (cn == "QLineEdit" || cn == "CLineEdit")
		disconnect(w, SIGNAL(textChanged(QString)), this, SLOT(slotText(QString)));
	if (cn == "QVariantEdit")
		disconnect(w, SIGNAL(valueChanged(QVariant)), this, SLOT(slotVariant(QVariant)));
	//qDebug() << "remove b" << binded_widgets.size();
	binded_widgets.remove(w);
	//qDebug() << "remove a" << binded_widgets.size();
	return true;
}


void QCDCore::unbindAllWidgets() {
	QMap<QWidget * , PIDeque<int> > bwm = binded_widgets;
	QMapIterator<QWidget * , PIDeque<int> > it(bwm);
	while (it.hasNext()) {
		QWidget * w = it.next().key();
		unbindWidget(w);
	}
	binded_widgets.clear();
}


void QCDCore::updateBindedWidget(const CDType & k_) {
	QMapIterator<QWidget * , PIDeque<int> > it(binded_widgets);
	updating = true;
	while (it.hasNext()) {
		QWidget * w = it.next().key();
		const CDType & k(K[it.value()]);
		if (k.path() != k_.path()) continue;
		setWidgetValue(w, k);
	}
	updating = false;
}
