#include <QScrollBar>
#include <QMessageBox>
#include <math.h>
#include "kx_pult.h"
#include "ui_kx_pult.h"
#include "piqt.h"
#include "qpiconfig.h"


bool isNormalDouble(const double & v) {
#ifdef WINDOWS
	return true;
#else
	return !isnan(v) && !isinf(v);
#endif
}


XCheck::XCheck(int index): QWidget() {
	index_ = index;
	setProperty("index", index);
	setMouseTracking(true);
	check.setText(QString::number(index + 1) + " ");
	check.setAutoFillBackground(true);
	spin.setMaximum(KX_X_COUNT - 1);
	QBoxLayout * l = new QBoxLayout(QBoxLayout::LeftToRight);
	l->setMargin(0);
	l->setSpacing(2);
	l->addWidget(&check);
	l->addWidget(&spin);
	setLayout(l);
	//check.installEventFilter(this);
	//spin.installEventFilter(this);
	connect(&spin, SIGNAL(valueChanged(int)), this, SLOT(spinChanged(int)));
	connect(&check, SIGNAL(toggled(bool)), this, SLOT(checkChanged_(bool)));
}


bool XCheck::eventFilter(QObject * o, QEvent * e) {
	if (e->type() == QEvent::Enter)
		qApp->postEvent(this, new QEvent(e->type()));
	return QWidget::eventFilter(o, e);
}




KX_Pult::KX_Pult(): QMainWindow(), config_("kx_pult.conf"), name_("x"), config(piqt(config_), QIODevice::ReadWrite), coeffs(config_, "k", true) {
	//cout << sizeof(coeffsK.k_protocol->to_k) << endl;
	ui = new Ui::KX_Pult();
	ui->setupUi(this);
	ui->configWidget->setQPIConfig(&config);
	ui->configWidget->expandAll();
	ui->list->viewport()->installEventFilter(this);
	ui->treeK->viewport()->installEventFilter(this);
	log_menu.addAction(ui->actionClear);
	prot_x = 0;
	show_x = config.getValue("show_x", true);
	if (!show_x)
		ui->tabWidget->removeTab(1);
	session.setFile("session_KX_Pult.conf");
	session.addEntry(this);
	session.addEntry(ui->tabWidget);
	session.addEntry(ui->checkKHideEmpty);
	session.addEntry(ui->checkKHideNormal);
	session.addEntry(ui->checkKHideExpressions);
	session.addEntry(ui->checkKAutoCalculate);
	needWrite = isPause = false;
	timer = 0;
	//x.resize(KX_X_PACKET_NUM);
	//k.resize(K_NUM);
	QPalette pal = palette();
	QColor col;
	ui->graphic->setGraphicsCount(0);
	for (int i = 0; i < KX_X_PACKET_NUM; ++i) {
		XCheck * xc = new XCheck(i);
		xc->installEventFilter(this);
		connect(xc, SIGNAL(valueChanged(int, int)), this, SLOT(changedX(int,int)));
		connect(xc, SIGNAL(checkChanged(int, bool)), this, SLOT(toggledX(int, bool)));
		col = QColor::fromHsv(360 / KX_X_PACKET_NUM * i, 255, 200);
		pal.setColor(QPalette::Button, col);
		pal.setColor(QPalette::Window, col);
		pal.setColor(QPalette::WindowText, invertColor(col));
		xc->check.setPalette(pal);
		ui->graphic->addGraphic(QString::number(i), col);
		ui->graphic->setGraphicVisible(false, i);
		checks << xc;
		((QGridLayout * )(ui->widgetChecks->layout()))->addWidget(xc, (i / 10) * 2, i % 10);
		QLabel * lbl = new QLabel("0"); lbl->setAlignment(Qt::AlignVCenter | Qt::AlignLeft);
		values << lbl;
		((QGridLayout * )(ui->widgetChecks->layout()))->addWidget(lbl, (i / 10) * 2 + 1, i % 10);
		//xc->show();
	}
	renew();
	icon_record = QIcon(":/icons/media-record.png");
	icon_stop = QIcon(":/icons/media-playback-stop.png");
	outdir = dir.absolutePath();
	if (!dir.exists()) dir.mkdir(outdir);
	outdir += "/";
	ui->treeK->setColumnWidth(0, 60);
	ui->treeK->setColumnWidth(1, 250);
	ui->treeK->setColumnWidth(3, 100);
	ui->treeK->setColumnWidth(4, 100);
	//ui->table->setK(coeffsK.k()->data(), coeffsK.count());
	ui->spinSize->setValue(K.size_s());
	addToList(trUtf8("Read K file \"%1\": %2 coeffs, %3 bytes").arg(PI2QString(coeffs.fileName())).arg(K.size_s()).arg(coeffs.k_content.size_s()), Qt::darkMagenta);
	CONNECT(void, &coeffs, sendFailed, this, pip_sendFailed);
	CONNECT(void, &coeffs, sendSucceed, this, pip_sendSucceed);
	CONNECT(void, &coeffs, receiveFailed, this, pip_receiveFailed);
	CONNECT(void, &coeffs, receiveSucceed, this, pip_receiveSucceed);
	connect(this, SIGNAL(q_k_sendFailed()), this, SLOT(k_sendFailed()), Qt::QueuedConnection);
	connect(this, SIGNAL(q_k_sendSucceed()), this, SLOT(k_sendSucceed()), Qt::QueuedConnection);
	connect(this, SIGNAL(q_k_receiveFailed()), this, SLOT(k_receiveFailed()), Qt::QueuedConnection);
	connect(this, SIGNAL(q_k_receiveSucceed()), this, SLOT(k_receiveSucceed()), Qt::QueuedConnection);
	connect(&timer_diag, SIGNAL(timeout()), this, SLOT(updateDiag()));
	connect(&session, SIGNAL(loading(QPIConfig&)), this, SLOT(loading(QPIConfig&)));
	connect(&session, SIGNAL(saving(QPIConfig&)), this, SLOT(saving(QPIConfig&)));
	connect(ui->checkKHideEmpty, SIGNAL(toggled(bool)), this, SLOT(filterTree()));
	connect(ui->checkKHideNormal, SIGNAL(toggled(bool)), this, SLOT(filterTree()));
	connect(ui->checkKHideExpressions, SIGNAL(toggled(bool)), this, SLOT(filterTree()));
	connect(ui->lineKSearch, SIGNAL(textChanged(QString)), this, SLOT(filterTree()));
	session.load();
	updateKDesc();
	timer_diag.start(40);
	timer_update = startTimer(25);
}


KX_Pult::~KX_Pult() {
	session.save();
}


void KX_Pult::loading(QPIConfig & conf) {
	kdesc_file = conf.getValue("kdesc_file").stringValue();
}


void KX_Pult::saving(QPIConfig & conf) {
	conf.setValue("kdesc_file", kdesc_file);
}


bool KX_Pult::eventFilter(QObject * o, QEvent * e) {
	if (o == ui->list->viewport()) {
		if (e->type() == QEvent::ContextMenu) {
			clear_target = 0;
			log_menu.popup(((QContextMenuEvent*)e)->globalPos());
		}
		return QMainWindow::eventFilter(o, e);
	}
	if (o == ui->treeK->viewport()) {
		if (e->type() == QEvent::ContextMenu) {
			clear_target = 1;
			log_menu.popup(((QContextMenuEvent*)e)->globalPos());
		}
		return QMainWindow::eventFilter(o, e);
	}
	int ind = o->property("index").toInt();
	//qDebug() << "event" << i << e->type();
	switch (e->type()) {
	case QEvent::Enter:
		ui->graphic->setAutoUpdate(false);
		for (int i = 0; i < KX_X_PACKET_NUM; ++i)
			ui->graphic->setGraphicLineWidth(ind == i ? 3. : 1., i);
		ui->graphic->setAutoUpdate(true);
		ui->graphic->update();
		break;
	case QEvent::Leave:
		ui->graphic->setAutoUpdate(false);
		for (int i = 0; i < KX_X_PACKET_NUM; ++i)
			ui->graphic->setGraphicLineWidth(1., i);
		ui->graphic->setAutoUpdate(true);
		ui->graphic->update();
		break;
	default: break;
	}
	return QMainWindow::eventFilter(o, e);
}


void KX_Pult::timerEvent(QTimerEvent * e) {
	if (e->timerId() == timer_update) {
		if (need_update) {
			need_update = false;
			ui->graphic->updateGraphics();
		}
	}
	if (e->timerId() == timer) {
		static QString sPI = QString::number(atan(1) * 4., 'f', 14).leftJustified(14);
		static int cnt = 0;
		int si = qMax<int>(cnt - 6, 0);
		++cnt;
		cnt %= 23;
		ui->labelWait->setText(QString(si, QChar(' ')) + sPI.mid(cnt - 6, 6).trimmed());
		if (!coeffs.isReady()) return;
		//ui->table->setK(coeffsK.k()->data(), coeffsK.count());
		//ui->table->showK();
	}
}


void KX_Pult::setControlsEnable(bool enable) {
	foreach (XCheck * i, checks)
		i->setEnabled(enable);
	ui->buttonShowAll->setEnabled(enable);
	ui->buttonHideAll->setEnabled(enable);
}


void KX_Pult::setX(const KX_X_Data & data) {
	if (!show_x) return;
	ui->graphic->lock();
	for (int i = 0; i < KX_X_PACKET_NUM; ++i) {
		if (!isNormalDouble(data.x_data[i])) continue;
		ui->graphic->addPoint(data.x_data[i], i, false);
		values[i]->setText(QString("(%1): %2").arg(data.x_num[i]).arg(data.x_data[i]));
	}
	ui->graphic->unlock();
	if (!isPause) {
		need_update = true;
	}
	if (!needWrite) return;
	stream << QString::number(tm.elapsed() / 1000., 'f', 3) << " " << QTime::currentTime().toString("hh:mm:ss") << " " << wcnt++;
	for (int i = 0; i < KX_X_PACKET_NUM; ++i)
		stream << " " << QString::number(data.x_data[i], 'f', 4);
	stream << "\n";
}


void KX_Pult::addToList(const QString & s, const QColor & c) {
	ui->list->addItem(QDateTime::currentDateTime().toString("dd/MM/yyyy  hh:ss  -  ") + s);
	ui->list->item(ui->list->count() - 1)->setTextColor(c);
	ui->list->scrollToBottom();
}


void KX_Pult::on_buttonRecord_clicked() {
	static bool isRec = false;
	int cinc = 0;
	QString str;
	isRec = !isRec;
	setControlsEnable(!isRec);
	if (isRec) {
		tm.restart();
		file.close();
		file.setFileName(outdir + getNewFileName(cinc));
		while (file.exists())
			file.setFileName(outdir + getNewFileName(cinc++));
		file.open(QIODevice::ReadWrite);
		stream.setDevice(&file);
		stream << "T V C";
		for (int i = 0; i < KX_X_PACKET_NUM; ++i)
			stream << " X" + QString::number(checks[i]->spin.value());
		stream << "\n";
		wcnt = 0;
		needWrite = true;
		ui->buttonRecord->setText(trUtf8("Finish record"));
		ui->buttonRecord->setIcon(icon_stop);
		emit recordStarted(QFileInfo(file).completeBaseName());
	} else {
		needWrite = false;
		stream.setDevice(0);
		file.close();
		ui->buttonRecord->setText(trUtf8("Start record"));
		ui->buttonRecord->setIcon(icon_record);
		emit recordStopped(QFileInfo(file).completeBaseName());
	}
}


void KX_Pult::on_actionClear_triggered() {
	switch (clear_target) {
	case 0:
		ui->list->clear();
		break;
	case 1:
		clearSelected();
		break;
	default: break;
	}
}


void KX_Pult::clearSelected() {
	QList<QTreeWidgetItem * > si = ui->treeK->selectedItems();
	ui->treeK->setUpdatesEnabled(false);
	ui->treeK->blockSignals(true);
	foreach (QTreeWidgetItem * i, si) {
		int ki = i->text(0).toInt();
		i->setText(2, "");
		coeffs.setFormula(ki, "");
	}
	ui->treeK->blockSignals(false);
	ui->treeK->setUpdatesEnabled(true);
	if (ui->checkKAutoCalculate->isChecked()) {
		QApplication::processEvents();
		calculate();
	}
}


QString KX_Pult::typeName(const QString & n) const {
	if (n.isEmpty()) return "";
	switch (n[0].toLatin1()) {
	case 'l': return trUtf8("list"); break;
	case 'b': return trUtf8("bool"); break;
	case 'n': return trUtf8("int"); break;
	case 'f': return trUtf8("double"); break;
	case 'c': return trUtf8("color"); break;
	case 'r': return trUtf8("rect"); break;
	case 'a': return trUtf8("rect"); break;
	case 'p': return trUtf8("point"); break;
	case 'v': return trUtf8("vector"); break;
	case 'i': return trUtf8("IP"); break;
	case 'e': return trUtf8("enum"); break;
	case 'F': return trUtf8("file"); break;
	case 'D': return trUtf8("dir"); break;
	}
	return "";
}


void KX_Pult::received(bool ok) {
	if (!ok) return;
	setX(prot_x->from_x);
}


void KX_Pult::on_treeK_itemClicked(QTreeWidgetItem * item, int column) {
	Qt::ItemFlags f = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
	if (column == 2) f |= Qt::ItemIsEditable;
	item->setFlags(f);
}


void KX_Pult::on_treeK_itemChanged(QTreeWidgetItem * item, int column) {
	if (column != 2) return;
	int ki = item->text(0).toInt();
	coeffs.setFormula(ki, piqt(item->text(column)));
	if (ui->checkKAutoCalculate->isChecked())
		calculate();
}


QString KX_Pult::getNewFileName(int inc) {
	dir.refresh();
	dir.setNameFilters(QStringList("Experiment_*.txt"));
	return "Experiment_" + QDateTime::currentDateTime().toString("dd_MM_yy__hh_mm_ss") + "__" +
			QString::number(dir.entryList().count() + inc) + ".txt";
}


void KX_Pult::on_buttonSendK_clicked() {
	on_buttonWrite_clicked();
	coeffs.sendCoeffs();
	if (timer != 0) killTimer(timer);
	timer = startTimer(100);
}


void KX_Pult::on_buttonReceiveK_clicked() {
	coeffs.receiveCoeffs();
	if (timer != 0) killTimer(timer);
	timer = startTimer(100);
}


void KX_Pult::on_buttonShowAll_clicked() {
	for (int i = 0; i < KX_X_PACKET_NUM; ++i) {
		checks[i]->check.setChecked(true);
		//ui->graphic->setGraphicVisible(true, i);
	}
}


void KX_Pult::on_buttonHideAll_clicked() {
	for (int i = 0; i < KX_X_PACKET_NUM; ++i) {
		checks[i]->check.setChecked(false);
		//ui->graphic->setGraphicVisible(false, i);
	}
}


void KX_Pult::on_buttonRead_clicked() {
	coeffs.readCoeffs();
	addToList(trUtf8("Read K file \"%1\": %2 coeffs, %3 bytes").arg(PI2QString(coeffs.fileName())).arg(K.size_s()).arg(coeffs.k_content.size_s()), Qt::darkMagenta);
	updateTree();
}


void KX_Pult::on_buttonWrite_clicked() {
	coeffs.writeCoeffs();
	addToList(trUtf8("Write K file \"%1\": %2 coeffs, %3 bytes").arg(PI2QString(coeffs.fileName())).arg(K.size_s()).arg(coeffs.k_content.size_s()), Qt::darkMagenta);
}


void KX_Pult::on_buttonResize_clicked() {
	K.resize(ui->spinSize->value());
	coeffs.formulas.resize(ui->spinSize->value());
	ui->spinSize->setStyleSheet("");
	updateTree();
}


void KX_Pult::on_buttonSetDesc_clicked() {
	QString ret = QFileDialog::getOpenFileName(this, trUtf8("Select *.h file with K description"), kdesc_file, "C/C++ header files(*.h *.hpp);;All files(*)");
	if (ret.isEmpty()) return;
	kdesc_file = QDir::current().relativeFilePath(ret);
	updateKDesc(true);
}


void KX_Pult::on_spinSize_valueChanged(int) {
	ui->spinSize->setStyleSheet("");
}


void KX_Pult::k_sendFailed() {
	stopWait();
	addToList(trUtf8("K not sended"), Qt::darkRed);
}


void KX_Pult::k_sendSucceed() {
	stopWait();
	addToList(trUtf8("K sended"), Qt::darkGreen);
}


void KX_Pult::k_receiveFailed() {
	stopWait();
	addToList(trUtf8("K not received"), Qt::darkRed);
}


void KX_Pult::k_receiveSucceed() {
	stopWait();
	addToList(trUtf8("K received"), Qt::darkGreen);
	addToList(trUtf8("Write K file \"%1\": %2 coeffs, %3 bytes").arg(PI2QString(coeffs.fileName())).arg(K.size_s()).arg(coeffs.k_content.size_s()), Qt::darkMagenta);
	ui->spinSize->setValue(K.size_s());
	updateTree();
	//ui->table->setK(coeffsK.k()->data(), coeffsK.count());
}


void KX_Pult::on_spinBuffer_editingFinished() {
	ui->graphic->setHistorySize(ui->spinBuffer->value());
}


void KX_Pult::stopWait() {
	killTimer(timer);
	timer = 0;
	ui->labelWait->setText(" ");
}


void KX_Pult::updateGraph() {
	ui->graphic->updateGraphics();
}


void KX_Pult::updateDiag() {
	
	ui->labelKReceiver->setText(piqt(coeffs.k_protocol->receiverDeviceName() + " - " + coeffs.k_protocol->receiverDeviceState()));
	ui->labelKSender->setText(piqt(coeffs.k_protocol->senderDeviceName()));
	ui->spinKSended->setValue(coeffs.k_protocol->sendCount());
	ui->spinKReceived->setValue(coeffs.k_protocol->receiveCount());
	ui->spinKWrong->setValue(coeffs.k_protocol->wrongCount());
	ui->spinKMissed->setValue(coeffs.k_protocol->missedCount());
	ui->labelKType->setText("0x" + QString::number(coeffs.k_protocol->from_k.type, 16).toUpper().rightJustified(2, '0'));
	ui->labelKAddrPult->setText("0x" + QString::number(coeffs.k_protocol->from_k.addr_to, 16).toUpper().rightJustified(2, '0'));
	ui->labelKAddr->setText("0x" + QString::number(coeffs.k_protocol->to_k.addr_to, 16).toUpper().rightJustified(2, '0'));
	
	ui->labelXReceiver->setText(piqt(prot_x->receiverDeviceName() + " - " + prot_x->receiverDeviceState()));
	ui->labelXSender->setText(piqt(prot_x->senderDeviceName()));
	ui->spinXSended->setValue(prot_x->sendCount());
	ui->spinXReceived->setValue(prot_x->receiveCount());
	ui->spinXWrong->setValue(prot_x->wrongCount());
	ui->spinXMissed->setValue(prot_x->missedCount());
	ui->labelXType->setText("0x" + QString::number(prot_x->from_x.type, 16).toUpper().rightJustified(2, '0'));
	ui->labelXAddrPult->setText("0x" + QString::number(prot_x->from_x.addr_to, 16).toUpper().rightJustified(2, '0'));
	ui->labelXAddr->setText("0x" + QString::number(prot_x->to_x.addr_to, 16).toUpper().rightJustified(2, '0'));
	
}


void KX_Pult::updateKDesc(bool ask_move) {
	kdesc.clear();
	QFile f(kdesc_file);
	if (!f.open(QIODevice::ReadOnly)) {
		updateTree();
		addToList(trUtf8("Update descriptions from \"%1\": error").arg(kdesc_file), Qt::darkRed);
		return;
	}
	addToList(trUtf8("Update descriptions from \"%1\"").arg(kdesc_file), Qt::darkMagenta);
	QTextStream s(&f);
	int cind = -1;
	bool found = false;
	//qDebug() << "\nparse" << kdesc_file;
	while (!s.atEnd()) {
		QString line = s.readLine().trimmed(), num, name, type, comment;
		int i = line.indexOf("//");
		if (i >= 0) {
			comment = line.right(line.length() - i - 2);
			type = comment.left(1);
			comment = comment.right(comment.length() - 1).trimmed();
			line = line.left(i).trimmed();
		}
		if (line.isEmpty()) continue;
		if (line.contains("enum")) {
			found = true;
			continue;
		}
		if (!found) continue;
		if (line.contains('}'))
			break;
		line.remove(',').remove(' ').remove('\t');
		i = line.indexOf("=");
		if (i >= 0) {
			num = line.right(line.length() - i - 1).trimmed();
			line = line.left(i).trimmed();
		}
		name = line;
		if (num.isEmpty())
			++cind;
		else
			cind = Q2PIString(num).toInt();
		KDesc kd;
		kd.index = cind;
		kd.name = name;
		kd.type = type;
		kd.comment = comment;
		kdesc[kd.index] = kd;
		//qDebug() << name << cind << type << comment;
	}
	cind++;
	if (K.size_s() < cind) {
		ui->spinSize->setValue(cind);
		ui->spinSize->setStyleSheet("background-color: rgb(220, 220, 255);");
	}
	bool move = false;
	if (ask_move)
		move = (QMessageBox::question(this, "KX Pult", "Save values at associated names?", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes);
	updateTree(move);
}


bool stringComp(const QString & s1, const QString & s2) {
	if (s1.size() != s2.size())
		return s1.size() > s2.size();
	return s1 > s2;
}


void KX_Pult::updateTree(bool move) {
	int sp = ui->treeK->verticalScrollBar()->value();
	QApplication::setOverrideCursor(Qt::WaitCursor);
	//qDebug() << "fill tree ...";
	QMap<QString, QString> prev_val;
	if (move) {
		for (int i = 0; i < ui->treeK->topLevelItemCount(); ++i) {
			QTreeWidgetItem * ti = ui->treeK->topLevelItem(i);
			if (!ti->text(1).isEmpty())
				prev_val[ti->text(1)] = ti->text(2);
		}
	}
	ui->treeK->clear();
	ui->treeK->setUpdatesEnabled(false);
	eval.clearCustomVariables();
	for (int i = 0; i < K.size_s(); ++i) {
		QTreeWidgetItem * ti = new QTreeWidgetItem();
		KDesc kd = kdesc[i];
		QString kn = QString("k%1").arg(i);
		knames[kn] = i;
		knames_sort << kn;
		if (eval.content.findVariable(kn) < 0)
			eval.content.addVariable(kn, 0., false);
		if (!kd.name.isEmpty()) {
			knames[kd.name] = i;
			knames_sort << kd.name;
			eval.content.addVariable(kd.name, 0., false);
		}
		if (move && !kd.name.isEmpty()) {
			if (prev_val.contains(kd.name))
				coeffs.setFormula(i, Q2PIString(prev_val[kd.name]));
		}
		ti->setText(0, QString::number(i));
		ti->setText(1, kd.name);
		ti->setText(2, PI2QString(coeffs.formula(i)));
		ti->setText(3, QString::number(K[i]));
		ti->setText(4, typeName(kd.type));
		ti->setText(5, kd.comment);
		ui->treeK->addTopLevelItem(ti);
	}
	eval.content.sortVariables();
	//qDebug() << "fill tree ok";
	//qDebug() << "sort ...";
	qSort(knames_sort.begin(), knames_sort.end(), stringComp);
	//qDebug() << "names" << knames_sort;
	//qDebug() << "sort ok";
	QApplication::restoreOverrideCursor();
	ui->treeK->setUpdatesEnabled(true);
	ui->treeK->verticalScrollBar()->setValue(sp);
	calculate();
	filterTree();
}


void KX_Pult::filterTree() {
	bool he = ui->checkKHideEmpty->isChecked();
	bool hn = ui->checkKHideNormal->isChecked();
	bool hs = ui->checkKHideExpressions->isChecked();
	bool ok = false;
	QString fl = ui->lineKSearch->text();
	int lc = ui->treeK->topLevelItemCount();
	for (int i = 0; i < lc; ++i) {
		QTreeWidgetItem * ti = ui->treeK->topLevelItem(i);
		if (ti->text(1).isEmpty() && he)
			ti->setHidden(true);
		else
			if (fl.isEmpty())
				ti->setHidden(false);
			else
				ti->setHidden(!ti->text(0).contains(fl, Qt::CaseInsensitive) &&
							  !ti->text(1).contains(fl, Qt::CaseInsensitive) &&
							  !ti->text(2).contains(fl, Qt::CaseInsensitive) &&
							  !ti->text(3).contains(fl, Qt::CaseInsensitive) &&
							  !ti->text(4).contains(fl, Qt::CaseInsensitive));
		if (hn)
			if (ti->data(0, Qt::UserRole).toBool())
				ti->setHidden(true);
		if (hs) {
			ti->data(2, Qt::DisplayRole).toDouble(&ok);
			if (!ok)
				ti->setHidden(true);
		}
	}
}


void KX_Pult::calculate() {
	calculated.clear();
	ui->treeK->setUpdatesEnabled(false);
	ui->treeK->blockSignals(true);
	QApplication::setOverrideCursor(Qt::WaitCursor);
	progress(0, 100);
	ui->buttonCalculate->setEnabled(false);
	QApplication::processEvents();
	for (int i = 0; i < K.size_s(); ++i) {
		ui->treeK->topLevelItem(i)->setToolTip(2, QString());
		ui->treeK->topLevelItem(i)->setToolTip(3, QString());
	}
	for (int i = 0; i < K.size_s(); ++i) {
		progress(i, K.size_s());
		calculateExpression(i, QVector<int>());
	}
	ui->buttonCalculate->setEnabled(true);
	ui->progress->setValue(100);
	QApplication::restoreOverrideCursor();
	ui->treeK->blockSignals(false);
	ui->treeK->setUpdatesEnabled(true);
}


bool KX_Pult::calculateExpression(int i, QVector<int> trace) {
	if (calculated.contains(i)) return true;
	trace << i;
	QTreeWidgetItem * ti = ui->treeK->topLevelItem(i);
	QString expr = ti->text(2);
	if (expr.isEmpty() || expr == "0" || expr == "0,00000000" || expr == "0.00000000") {
		markNormal(ti);
		calculated << i;
		K[i] = 0.;
		ti->setText(3, "0");
		return true;
	}
	//ti->setToolTip(2, QString());
	if (!eval.check(expr)) {
		markError(ti, eval.error());
		return false;
	}
	foreach (const QString & n, knames_sort) {
		if (expr.contains(n)) {
			int ki = knames.value(n, -1);
			if (trace.contains(ki)) {
				QString strace;
				trace << ki;
				for (int j = 0; j < trace.size(); ++j) {
					//calculated << trace[j];
					if (j > 0) strace += " -> ";
					strace += "k" + QString::number(trace[j]);
				}
				for (int j = 0; j < trace.size(); ++j) {
					QTreeWidgetItem * pti = ui->treeK->topLevelItem(trace[j]);
					markError(pti, QString("Circular dependency: %1!").arg(strace));
				}
				return false;
			}
			if (ki < 0) {
				markError(ti);
				return false;
			}
			if (calculated.contains(ki)) {
				eval.setVariable(n, K[ki]);
			} else {
				if (calculateExpression(ki, trace))
					eval.setVariable(n, K[ki]);
				else {
					markError(ti);
					return false;
				}
			}
		}
	}
	markNormal(ti);
	calculated << i;
	complexd ret = eval.evaluate();
	K[i] = ret.real();
	ti->setText(3, QString::number(K[i]));
	return true;
}


void KX_Pult::markError(QTreeWidgetItem * item, const QString & tool_tip) {
	int cc = item->columnCount();
	for (int i = 0; i < cc; ++i)
		item->setBackgroundColor(i, QColor(255, 200, 200));
	if (item->toolTip(2).isEmpty())
		item->setToolTip(2, tool_tip);
	if (item->toolTip(3).isEmpty())
		item->setToolTip(3, tool_tip);
	item->setData(0, Qt::UserRole, false);
	item->setText(3, "Error");
}


void KX_Pult::markNormal(QTreeWidgetItem * item) {
	int cc = item->columnCount();
	for (int i = 0; i < cc; ++i)
		item->setBackground(i, Qt::NoBrush);
	item->setToolTip(2, QString());
	item->setToolTip(3, QString());
	item->setData(0, Qt::UserRole, true);
}


void KX_Pult::progress(int val, int max) {
	if (ctm.elapsed() < 50) return;
	ctm.restart();
	ui->progress->setValue(qRound(val * 100. / max));
	QApplication::processEvents();
}


void KX_Pult::renew(bool write) {
	addToList(trUtf8("Update settings from \"%1\"").arg(PI2QString(config_)), Qt::darkMagenta);
	dir.setPath(config.getValue("x.output_dir", "./Experiments/").stringValue());
	setWindowTitle(config.getValue("title", "Noname").stringValue() + trUtf8(" - KX Pult"));
	//if (write) ui->configWidget->write();
	if (prot_x != 0) {
		prot_x->stop();
		delete prot_x;
	}
	prot_x = new __KX_Protocol_X(config_, name_);
	ui->graphic->setAutoXIncrement(prot_x->expectedFrequency() > 0. ? 1. / prot_x->expectedFrequency() : 1.);
	coeffs.renew();
	CONNECT1(void, bool, prot_x, received, this, received);
}


void KX_Pult::toggledX(int index, bool on) {
	ui->graphic->setGraphicVisible(on, index);
}


void KX_Pult::changedX(int index, int num) {
	prot_x->to_x.x_num[index] = num;
}
