#include "piqt_connection_edit.h"

#include "picodeinfo.h"
#include "piqt.h"
#include "piqt_connection_view.h"
#include "piqt_highlighter.h"
#include "ui_piqt_connection_edit.h"

#include <QCheckBox>
#include <QMessageBox>


ConnectionEdit::ConnectionEdit(QWidget * parent): QDialog(parent) {
	ui = new Ui::ConnectionEdit();
	ui->setupUi(this);
	new ConfigHighlighter(ui->codeEdit->document());
	loading = false;
	connect(ui->blockView, SIGNAL(schemeAction(BlockItemBase::Action, QList<QGraphicsItem *>)), this, SLOT(recreateRequest()));
	connect(ui->blockView->scene(), SIGNAL(selectionChanged()), this, SLOT(selectionChanged()));
	conn                      = 0;
	PICodeInfo::EnumInfo * ei = PICODEINFO::enums().value("PIIODevice::DeviceMode");
	if (ei) {
		for (const auto & e: ei->members)
#if PIP_VERSION >= PIP_MAKE_VERSION(2, 39, 0)
			ui->comboMode->addItem(PI2QString(e.name.toString() + " (" + PIString::fromNumber(e.value) + ")"),
			                       QVariant::fromValue<int>(e.value));
#else
			ui->comboMode->addItem(PI2QString(e.name + " (" + PIString::fromNumber(e.value) + ")"), QVariant::fromValue<int>(e.value));
#endif
	}
	ui->comboMode->setCurrentIndex(ui->comboMode->count() - 1);
	ei = PICODEINFO::enums().value("PIIODevice::DeviceOption");
	if (ei) {
		for (const auto & e: ei->members) {
			QCheckBox * cb = new QCheckBox();
#if PIP_VERSION >= PIP_MAKE_VERSION(2, 39, 0)
			cb->setText(PI2QString(e.name.toString() + " (" + PIString::fromNumber(e.value) + ")"));
#else
			cb->setText(PI2QString(e.name + " (" + PIString::fromNumber(e.value) + ")"));
#endif
			cb->setProperty("__value", e.value);
			ui->layoutOptions->addWidget(cb);
		}
	}
	ei = PICODEINFO::enums().value("PIPacketExtractor::SplitMode");
	if (ei) {
		for (const auto & e: ei->members)
#if PIP_VERSION >= PIP_MAKE_VERSION(2, 39, 0)
			ui->comboSplit->addItem(PI2QString(e.name.toString() + " (" + PIString::fromNumber(e.value) + ")"),
			                        QVariant::fromValue<int>(e.value));
#else
			ui->comboSplit->addItem(PI2QString(e.name + " (" + PIString::fromNumber(e.value) + ")"), QVariant::fromValue<int>(e.value));
#endif
	}
	udevicenum = 0;
}


ConnectionEdit::~ConnectionEdit() {
	if (conn) {
		conn->stop();
		delete conn;
	}
}


void ConnectionEdit::accept() {
	// bool ok = false;
	QList<BlockItem *> bl = ui->blockView->allDevices();
	foreach(BlockItem * i, bl)
		foreach(BlockItem * j, bl)
			if (i != j)
				if (((DeviceItem *)i)->name() == ((DeviceItem *)j)->name()) {
					QMessageBox::critical(this,
					                      windowTitle() + " - " + tr("error") + "!",
					                      tr("Equal devices names: \"%1\"!").arg(((DeviceItem *)i)->name()));
					return;
				}
	QDialog::accept();
}


QString ConnectionEdit::configuration() const {
	if (!conn) return QString();
	return PI2QString(conn->makeConfig());
}


QByteArray ConnectionEdit::model() const {
	QByteArray ret;
	QDataStream s(&ret, QIODevice::ReadWrite);
	QString cn = PI2QString(conn ? conn->name() : PIString());
	s << cn;
	QList<BlockBusItem *> busl = ui->blockView->buses();
	s << busl.size();
	foreach(BlockBusItem * b, busl)
		s << b->save();
	QList<BlockItem *> blockl = ui->blockView->blocks();
	s << blockl.size();
	foreach(BlockItem * b, blockl) {
		int type = b->propertyByName("__type").value.toInt();
		s << type;
		switch (type) {
		case __CV_Device:
		case __CV_Filter:
		case __CV_Sender: s << b->save(); break;
		}
	}
	return ret;
}


QString ConnectionEdit::name() const {
	if (!conn) return QString();
	return PI2QString(conn->name());
}


void ConnectionEdit::setName(const QString & name) {
	ui->lineName->setText(name);
	recreateConnection();
}


void ConnectionEdit::addDevice(const QString & name, const QString & path) {
	ui->blockView->addDevice(name, path);
}


void ConnectionEdit::setModel(const QByteArray & m) {
	loading = true;
	ui->blockView->removeAll();
	QDataStream s(m);
	QString cn;
	s >> cn;
	if (conn) delete conn;
	conn = new PIConnection(Q2PIString(cn));
	ui->lineName->setText(cn);
	int sz;
	s >> sz;
	for (int i = 0; i < sz; ++i) {
		BlockBusItem * b = new BlockBusItem();
		QByteArray ba;
		s >> ba;
		b->load(ba);
		ui->blockView->addItem(b);
	}
	s >> sz;
	for (int i = 0; i < sz; ++i) {
		int type(0);
		BlockItem * b(0);
		QByteArray ba;
		s >> type;
		switch (type) {
		case __CV_Device:
			b = new DeviceItem();
			s >> ba;
			b->load(ba);
			if (!b->isPropertyExists("bufferSize")) ((DeviceItem *)b)->setBufferSize(4096);
			((DeviceItem *)b)->rename();
			break;
		case __CV_Filter:
			b = new FilterItem();
			s >> ba;
			b->load(ba);
			if (!b->isPropertyExists("bufferSize")) ((FilterItem *)b)->setBufferSize(65536);
			((FilterItem *)b)->rename();
			break;
		case __CV_Sender:
			b = new SenderItem();
			s >> ba;
			b->load(ba);
			((SenderItem *)b)->rename();
			break;
		}
		if (b) ui->blockView->addItem(b);
	}
	ui->blockView->reconnectAll();
	loading = false;
	recreateConnection();
}


void ConnectionEdit::selectionChanged() {
	QList<QGraphicsItem *> si = ui->blockView->scene()->selectedItems();
	ui->buttonRemove->setEnabled(!si.isEmpty());
	ui->buttonDeviceModify->setEnabled(false);
	ui->buttonFilterModify->setEnabled(false);
	ui->buttonSenderModify->setEnabled(false);
	if (si.size() != 1) return;
	BlockItem * b = qgraphicsitem_cast<BlockItem *>(si[0]);
	if (!b) return;
	int type = b->propertyByName("__type").value.toInt();
	if (type == __CV_Device) {
		ui->tabWidget->setCurrentIndex(0);
		DeviceItem * di = (DeviceItem *)b;
		ui->buttonDeviceModify->setEnabled(true);
		for (int i = 0; i < ui->comboMode->count(); ++i)
			if (ui->comboMode->itemData(i).toInt() == di->mode()) {
				ui->comboMode->setCurrentIndex(i);
				break;
			}
		ui->lineDevice->setText(di->name());
		ui->linePath->setEditText(di->path());
		ui->spinDeviceDT->setValue(di->disconnectTimeout());
		ui->spinDeviceBS->setValue(di->bufferSize());
		setOptions(di->options());
	}
	if (type == __CV_Filter) {
		ui->tabWidget->setCurrentIndex(1);
		FilterItem * fi = (FilterItem *)b;
		ui->buttonFilterModify->setEnabled(true);
		for (int i = 0; i < ui->comboSplit->count(); ++i)
			if (ui->comboSplit->itemData(i).toInt() == fi->mode()) {
				ui->comboSplit->setCurrentIndex(i);
				break;
			}
		ui->lineFilter->setText(fi->name());
		ui->lineHeader->setText(fi->header());
		ui->lineFooter->setText(fi->footer());
		ui->spinTimeout->setValue(fi->timeout());
		ui->spinSize->setValue(fi->packetSize());
		ui->spinFilterDT->setValue(fi->disconnectTimeout());
		ui->spinFilterBS->setValue(fi->bufferSize());
	}
	if (type == __CV_Sender) {
		ui->tabWidget->setCurrentIndex(2);
		SenderItem * si = (SenderItem *)b;
		ui->buttonSenderModify->setEnabled(true);
		ui->lineSender->setText(si->name());
		ui->lineData->setText(si->data());
		ui->spinFrequency->setValue(si->frequency());
	}
}


void ConnectionEdit::applyFilter(FilterItem * b) {
	b->setName(ui->lineFilter->text());
	b->setMode(PIPacketExtractor::SplitMode(ui->comboSplit->itemData(ui->comboSplit->currentIndex()).toInt()));
	b->setHeader(ui->lineHeader->text());
	b->setFooter(ui->lineFooter->text());
	b->setTimeout(ui->spinTimeout->value());
	b->setPacketSize(ui->spinSize->value());
	b->setDisconnectTimeout(ui->spinFilterDT->value());
	b->setBufferSize(ui->spinFilterBS->value());
	recreateConnection();
}


void ConnectionEdit::applyDevice(DeviceItem * b) {
	QString n = ui->lineDevice->text();
	if (n.isEmpty()) {
		n = "device" + QString::number(udevicenum);
	}
	b->setName(n);
	b->setMode(PIIODevice::DeviceMode(ui->comboMode->itemData(ui->comboMode->currentIndex()).toInt()));
	b->setOptions(PIIODevice::DeviceOptions(getOptions()));
	b->setPath(ui->linePath->currentText());
	b->setDisconnectTimeout(ui->spinDeviceDT->value());
	b->setBufferSize(ui->spinDeviceBS->value());
	recreateConnection();
}


void ConnectionEdit::applySender(SenderItem * b) {
	b->setName(ui->lineSender->text());
	b->setData(ui->lineData->text());
	b->setFrequency(ui->spinFrequency->value());
	recreateConnection();
}


int ConnectionEdit::getOptions() const {
	int ret(0);
	for (int i = 0; i < ui->layoutOptions->count(); ++i) {
		QCheckBox * cb = qobject_cast<QCheckBox *>(ui->layoutOptions->itemAt(i)->widget());
		if (!cb) continue;
		if (cb->isChecked()) ret |= cb->property("__value").toInt();
	}
	return ret;
}


void ConnectionEdit::setOptions(int o) {
	for (int i = 0; i < ui->layoutOptions->count(); ++i) {
		QCheckBox * cb = qobject_cast<QCheckBox *>(ui->layoutOptions->itemAt(i)->widget());
		if (!cb) continue;
		int cbf = cb->property("__value").toInt();
		// qDebug() << cbf;
		cb->setChecked((o & cbf) == cbf);
	}
}


void ConnectionEdit::recreateConnection() {
	// qDebug() << "recreate";
	if (loading) return;
	ui->blockView->reconnectAll();
	if (conn) delete conn;
	PIString cn = Q2PIString(ui->lineName->text());
	if (cn.isEmpty())
		conn = new PIConnection();
	else
		conn = new PIConnection(cn);
	QList<BlockItem *> devs = ui->blockView->allDevices();
	foreach(BlockItem * b, devs) {
		DeviceItem * di  = (DeviceItem *)b;
		// qDebug() << di->path();
		PIIODevice * dev = conn->addDevice(Q2PIString(di->path()), di->mode());
		if (!dev) continue;
		dev->setOptions(di->options());
		dev->setThreadedReadBufferSize(di->bufferSize());
		conn->setDeviceName(dev, Q2PIString(di->name()));
		PIDiagnostics * diag = conn->diagnostic(dev);
		if (diag) diag->setDisconnectTimeout(PISystemTime::fromSeconds(di->disconnectTimeout()));
	}
	foreach(BlockItem * b, devs) {
		DeviceItem * di  = (DeviceItem *)b;
		PIIODevice * dev = conn->deviceByName(Q2PIString(di->name()));
		if (!dev) continue;
		BlockItemPin * p = b->pinByText("read");
		if (!p) continue;
		QList<BlockBusItem *> buses = p->connectedBuses(), nbuses;
		QSet<BlockBusItem *> pbuses;
		while (!buses.isEmpty()) {
			nbuses.clear();
			foreach(BlockBusItem * bus, buses) {
				QList<BlockItem *> cb = bus->connectedBlocks();
				if (cb.size() != 2) continue;
				FilterItem * fi_t(0);
				BlockItem * bi_f(0);
				if (cb[0]->pinAtBus(bus)->text() == "in") {
					fi_t = (FilterItem *)(cb[0]);
					bi_f = (cb[1]);
				} else if (cb[1]->pinAtBus(bus)->text() == "in") {
					fi_t = (FilterItem *)(cb[1]);
					bi_f = (cb[0]);
				}
				if (!fi_t || !bi_f) continue;
				PIString name_from;
				int type = bi_f->propertyByName("__type").value.toInt();
				if (type == __CV_Device) name_from = Q2PIString(((DeviceItem *)bi_f)->name());
				if (type == __CV_Filter) name_from = Q2PIString(((FilterItem *)bi_f)->name());
				if (name_from.isEmpty()) continue;
				PIPacketExtractor * pe = conn->addFilter(Q2PIString(fi_t->name()), conn->deviceByName(name_from), fi_t->mode());
				if (!pe) continue;
				pe->setHeader(PIByteArray::fromUserInput(Q2PIString(fi_t->header())));
				pe->setFooter(PIByteArray::fromUserInput(Q2PIString(fi_t->footer())));
				pe->setTimeout(PISystemTime::fromMilliseconds(fi_t->timeout()));
				pe->setPayloadSize(fi_t->packetSize());
				pe->setThreadedReadBufferSize(fi_t->bufferSize());
				PIDiagnostics * diag = conn->diagnostic(pe);
				if (diag) diag->setDisconnectTimeout(PISystemTime::fromSeconds(fi_t->disconnectTimeout()));
				QList<BlockBusItem *> nb = fi_t->pinByText("out")->connectedBuses();
				foreach(BlockBusItem * b_, nb)
					if (!pbuses.contains(b_)) {
						pbuses << b_;
						nbuses << b_;
					}
			}
			buses = nbuses;
		}
	}
	foreach(BlockItem * b, devs) {
		BlockItemPin * p = b->pinByText("write");
		if (!p) continue;
		QList<BlockBusItem *> buses = p->connectedBuses();
		foreach(BlockBusItem * bus, buses) {
			QList<BlockItem *> cb = bus->connectedBlocks();
			if (cb.size() != 2) continue;
			BlockItem * bi_f(0);
			DeviceItem * di_t(0);
			if (cb[0]->pinAtBus(bus)->text() == "write") {
				di_t = (DeviceItem *)(cb[0]);
				bi_f = (cb[1]);
			} else if (cb[1]->pinAtBus(bus)->text() == "write") {
				di_t = (DeviceItem *)(cb[1]);
				bi_f = (cb[0]);
			}
			if (!bi_f || !di_t) continue;
			QString name_from;
			int type = bi_f->propertyByName("__type").value.toInt();
			if (type == __CV_Sender) {
				SenderItem * si = ((SenderItem *)bi_f);
				si->name();
				conn->addSender(Q2PIString(si->name()), Q2PIString(di_t->path()), si->frequency());
				if (!si->data().isEmpty())
					conn->setSenderFixedData(Q2PIString(si->name()), PIByteArray::fromUserInput(Q2PIString(si->data())));
			} else {
				if (type == __CV_Device) name_from = ((DeviceItem *)bi_f)->name();
				if (type == __CV_Filter) name_from = ((FilterItem *)bi_f)->name();
				if (name_from.isEmpty()) continue;
				conn->addChannel(Q2PIString(name_from), Q2PIString(di_t->name()));
			}
		}
	}
	ui->codeEdit->setText(PI2QString(conn->makeConfig()));
}


void ConnectionEdit::on_buttonRemove_clicked() {
	ui->blockView->removeSelected();
	recreateConnection();
}


void ConnectionEdit::on_buttonClear_clicked() {
	ui->blockView->removeAll();
	recreateConnection();
}


void ConnectionEdit::on_buttonFilterAdd_clicked() {
	if (!conn) return;
	applyFilter(ui->blockView->addFilter(ui->lineFilter->text()));
}


void ConnectionEdit::on_buttonFilterModify_clicked() {
	QList<QGraphicsItem *> si = ui->blockView->scene()->selectedItems();
	if (si.isEmpty()) return;
	if (!qgraphicsitem_cast<BlockItem *>(si[0])) return;
	if (qgraphicsitem_cast<BlockItem *>(si[0])->propertyByName("__type").value.toInt() != __CV_Filter) return;
	applyFilter(qgraphicsitem_cast<FilterItem *>(si[0]));
}


void ConnectionEdit::on_buttonDeviceAdd_clicked() {
	if (!conn) return;
	QString n = ui->lineDevice->text();
	if (n.isEmpty()) {
		udevicenum++;
		n = "device" + QString::number(udevicenum);
	}
	QString p = ui->linePath->currentText();
	if (ui->linePath->findText(p) < 0) ui->linePath->addItem(p);
	qDebug() << "add:" << n;
	applyDevice(ui->blockView->addDevice(n, ui->linePath->currentText()));
}


void ConnectionEdit::on_buttonDeviceModify_clicked() {
	QList<QGraphicsItem *> si = ui->blockView->scene()->selectedItems();
	if (si.isEmpty()) return;
	if (!qgraphicsitem_cast<BlockItem *>(si[0])) return;
	if (qgraphicsitem_cast<BlockItem *>(si[0])->propertyByName("__type").value.toInt() != __CV_Device) return;
	applyDevice(qgraphicsitem_cast<DeviceItem *>(si[0]));
}


void ConnectionEdit::on_buttonSenderAdd_clicked() {
	if (!conn) return;
	applySender(ui->blockView->addSender(ui->lineSender->text()));
}


void ConnectionEdit::on_buttonSenderModify_clicked() {
	QList<QGraphicsItem *> si = ui->blockView->scene()->selectedItems();
	if (si.isEmpty()) return;
	if (!qgraphicsitem_cast<BlockItem *>(si[0])) return;
	if (qgraphicsitem_cast<BlockItem *>(si[0])->propertyByName("__type").value.toInt() != __CV_Sender) return;
	applySender(qgraphicsitem_cast<SenderItem *>(si[0]));
}


void ConnectionEdit::on_comboSplit_currentIndexChanged(int index) {
	int mode = ui->comboSplit->itemData(index).toInt();
	switch (mode) {
	case PIPacketExtractor::None:
		ui->widgetHeader->setEnabled(false);
		ui->widgetFooter->setEnabled(false);
		ui->widgetTimeout->setEnabled(false);
		ui->widgetSize->setEnabled(false);
		break;
	case PIPacketExtractor::Header:
		ui->widgetHeader->setEnabled(true);
		ui->widgetFooter->setEnabled(false);
		ui->widgetTimeout->setEnabled(false);
		ui->widgetSize->setEnabled(true);
		break;
	case PIPacketExtractor::Footer:
		ui->widgetHeader->setEnabled(false);
		ui->widgetFooter->setEnabled(true);
		ui->widgetTimeout->setEnabled(false);
		ui->widgetSize->setEnabled(true);
		break;
	case PIPacketExtractor::HeaderAndFooter:
		ui->widgetHeader->setEnabled(true);
		ui->widgetFooter->setEnabled(true);
		ui->widgetTimeout->setEnabled(false);
		ui->widgetSize->setEnabled(false);
		break;
	case PIPacketExtractor::Timeout:
		ui->widgetHeader->setEnabled(false);
		ui->widgetFooter->setEnabled(false);
		ui->widgetTimeout->setEnabled(true);
		ui->widgetSize->setEnabled(false);
		break;
	case PIPacketExtractor::Size:
		ui->widgetHeader->setEnabled(false);
		ui->widgetFooter->setEnabled(false);
		ui->widgetTimeout->setEnabled(false);
		ui->widgetSize->setEnabled(true);
		break;
	default: break;
	}
}
