#include "database_devices.h"
#include <QScrollBar>
#include <QListWidget>
#include <QFileDialog>
#include <QImageReader>
#include <QToolBar>
#include <QMessageBox>
#include <QBuffer>
#include <QTimer>
#include "ccm_SHS_shared.h"


DatabaseDevices::DatabaseDevices(QWidget * parent): QWidget(parent), Ui::DatabaseDevices() {
	setupUi(this);
	ask_for_save = true;
	bus_delegate = new PinBusDelegate();
	pin_plugin_delegate = new PinPluginDelegate();
	widgetObject->pins_delegate->pins = treePins;
	flagsEdit->setEnum(getMetaEnum<SHS>("BlockFlag"));
	flagsEdit->setToolTip(SHS::Gateway, tr("Device can translate packets from one pin to another"));
	flagsEdit->setToolTip(SHS::AlwaysConnected, tr("No need to connect pins, device always \"connected\" to server"));
	//editDBSourceOpen->setFileSelection(false, true);
	//editDBSourceOpen->setMinimumHeight(editDBSourceOpen->sizeHint().height());
	treePins->setItemDelegateForColumn(1, bus_delegate);
	treePins->setItemDelegateForColumn(2, pin_plugin_delegate);
	//treePins->setItemDelegateForColumn(3, new PinDeviceDelegate());
	cur_id = prev_id = -1;
	menu.addAction(actionAddDevice);
	menu.addAction(actionRemoveDevice);
	menu.addAction(actionCopyDevice);
	tree->setModel(&model);
	tabWidget->setCurrentIndex(0);
	connect(tree->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(treeSelectionChanged()));
	connect(tree->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), this, SLOT(treeClicked(QModelIndex)));
	connect(tree, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(popupMenu(QPoint)));
	connect(treePins, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(updateBlock()));
	connect(treePins->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(treePinsSelectionChanged()));
	connect(SHCORE, SIGNAL(changed()), bus_delegate, SLOT(updateDB()));
	connect(SHCORE, SIGNAL(changedPlugins()), this, SLOT(reloadPlugins()));
	connect(blockView->scene(), SIGNAL(selectionChanged()), this, SLOT(selectionChanged()));
	blockitem.setFlags(QGraphicsItem::GraphicsItemFlags());
	blockitem.hide();
	blockView->addItem(&blockitem);
	blockView->viewport()->installEventFilter(this);
	treePins->viewport()->installEventFilter(this);
	DrawTools * dc = new DrawTools(blockView);
	dc->setAlignCompact(true);
	connect(dc, SIGNAL(itemCreated(QGraphicsItem*)), this, SLOT(addItem(QGraphicsItem*)));
	dc->textEditCombo()->addItems(QStringList() << "%block_id" << "%block_name" << "%block_desc"
												<< "%vendor_id" << "%vendor_name" << "%vendor_desc"
												<< "%group_id" << "%group_name" << "%group_desc");
	layoutProperties->addWidget(dc->propertyWidget());
	actionRemove_items->setEnabled(false);
	bar = new QToolBar(widgetBar);
	bar->setOrientation(Qt::Vertical);
	bar->addActions(dc->actionsForAdd());
	widgetBar->setMinimumSize(bar->sizeHint());

	bar = new QToolBar(widgetBarZ);
	bar->setOrientation(Qt::Vertical);
	bar->addActions(dc->actionsForZ());
	bar->addSeparator();
	bar->addActions(QList<QAction*>() << actionRemove_items << actionReset);
	widgetBarZ->setMinimumSize(bar->sizeHint());
}


DatabaseDevices::~DatabaseDevices() {
	/*delete pins_delegate;
	delete flags_delegate;
	delete bus_delegate;*/
}


void DatabaseDevices::resaveAll() {
	QApplication::setOverrideCursor(Qt::WaitCursor);
	QSqlDatabase db = QSqlDatabase::database();
	db.transaction();
	for (int i = 0; i < model.rowCount(); ++i) {
		treeClicked(model.index(i, 0));
		QSqlQuery q(updateQuery(cur_id));
		if (!q.exec())
			qDebug() << q.lastError();
	}
	db.commit();
	treeClicked(tree->currentIndex());
	QApplication::restoreOverrideCursor();
}


void DatabaseDevices::reload() {
	bus_delegate->updateDB();
	int bus_pv = tree->verticalScrollBar()->value(), row = tree->currentIndex().row();
	tree->selectionModel()->blockSignals(true);
	model.setQuery("SELECT id,name FROM block_types");
	tree->selectionModel()->blockSignals(false);
	filterList();
	tree->verticalScrollBar()->setValue(bus_pv);
	widgetBlock->setEnabled(false);
	reloadTable(comboVendor, "vendors");
	reloadTable(comboGroup, "groups");
	cur_id = -1;
	if (row >= 0) {
		tree->setCurrentIndex(model.index(row, 0));
		//treeClicked(tree->currentIndex());
	}
}


void DatabaseDevices::reloadPlugins() {
	reloadPluginsByType(comboBlockPlugin, SHPluginBase::Block);
	reloadPluginsByType(comboGatewayPlugin, SHPluginBase::Gateway);
	properties->reloadPlugins();
}


void DatabaseDevices::showID(int id) {
	ask_for_save = false;
	for (int i = 0; i < model.rowCount(); ++i) {
		if (model.record(i).value("id").toInt() == id) {
			tree->setCurrentIndex(model.index(i, 0));
			break;
		}
	}
	ask_for_save = true;
}


bool DatabaseDevices::eventFilter(QObject * o, QEvent * e) {
	if (o == treePins->viewport()) {
		if (e->type() == QEvent::Drop) {
			QTimer::singleShot(0, this, SLOT(arrangePins()));
		}
	}
	if (o == blockView->viewport()) {
		if (e->type() == QEvent::Resize) {
			blockView->centerOn(&blockitem);
		}
	}
	return QWidget::eventFilter(o, e);
}


void DatabaseDevices::popupMenu(QPoint p) {
	if (!tree->selectionModel()->hasSelection()) {
		tree->setCurrentIndex(tree->indexAt(p));
	}
	treeClicked(tree->currentIndex());
	actionRemoveDevice->setEnabled(tree->selectionModel()->hasSelection());
	actionCopyDevice->setEnabled(tree->selectionModel()->hasSelection());
	menu.popup(tree->viewport()->mapToGlobal(p));
}


void DatabaseDevices::on_actionAddDevice_triggered() {
	QSqlQuery q(QString("INSERT INTO block_types (name) VALUES ('new device')"));
	int id = q.lastInsertId().toInt();
	BlockType block;
	block.id = id;
	block.name = "new device";
	block.properties = properties->defaultProperties();
	q.prepare(QString("UPDATE block_types SET data=:d WHERE id=%1").arg(id));
	q.bindValue(":d", piqSerialize(block));
	q.exec();
	reload();
	selectLastDevice();
	//properties->addDefaultProperties();
}


void DatabaseDevices::on_actionRemoveDevice_triggered() {
	QModelIndexList sl = tree->selectionModel()->selectedRows();
	QString qs("DELETE FROM block_types WHERE id IN (");
	for (int i = 0; i < sl.size(); ++i) {
		if (i > 0) qs += ",";
		qs += QString::number(sl[i].data().toInt());
	}
	qs += ")";
	QSqlQuery q(qs);
	prev_id = cur_id = -1;
	reload();
	prev_id = cur_id = -1;
}


void DatabaseDevices::on_actionCopyDevice_triggered() {
	QModelIndex ci = tree->currentIndex();
	if (!ci.isValid()) return;
	int id_src = model.index(ci.row(), 0).data().toInt();
	QSqlQuery q(QString("SELECT * FROM block_types WHERE id=%1").arg(id_src));
	if (!q.next()) return;
	BlockType block = piqDeserialize<BlockType>(q.record().value("data").toByteArray());

	if (!q.exec("INSERT INTO block_types (name) VALUES ('')")) return;
	int id = q.lastInsertId().toInt();
	block.id = id;
	q.prepare(QString("UPDATE block_types SET name=:n,data=:d WHERE id=%1").arg(id));
	q.bindValue(":n", block.name);
	q.bindValue(":d", piqSerialize(block));
	q.exec();

	reload();
	selectLastDevice();
}


void DatabaseDevices::selectLastDevice() {
	tree->selectionModel()->select(model.index(model.rowCount() - 1, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows | QItemSelectionModel::Current);
	tree->setCurrentIndex(model.index(model.rowCount() - 1, 0));
	//treeClicked(model.index(model.rowCount() - 1, 0));
}


void DatabaseDevices::on_actionReset_triggered() {
	blockitem.clearDecors();
}


void DatabaseDevices::on_actionRemove_items_triggered() {
	QList<QGraphicsItem*> si = blockView->scene()->selectedItems();
	foreach (QGraphicsItem * i, si)
		blockitem.removeDecor(i);
}


void DatabaseDevices::on_buttonReset_clicked() {
	treeClicked(tree->currentIndex());
}


void DatabaseDevices::on_buttonCommit_clicked() {
	if (cur_id < 0) return;
	QSqlQuery q(updateQuery(cur_id));
	if (!q.exec())
		qDebug() << q.lastError();
	reload();
}


void DatabaseDevices::on_buttonPinAdd_clicked() {
	treePins->blockSignals(true);
	QTreeWidgetItem * ti = new QTreeWidgetItem(QStringList() << uniqueName("pin", pinsNames()) << "0");
	ti->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled);
	ti->setData(0, Qt::UserRole, qulonglong(blockitem.addPin((Qt::Alignment)Qt::AlignLeft, ti->text(1).toInt(), ti->text(0))));
	ti->setData(0, Qt::UserRole + 1, (int)Qt::AlignLeft);
	pin_tli[Qt::AlignLeft]->addChild(ti);
	treePins->setCurrentItem(ti);
	treePins->blockSignals(false);
	updateBlock();
	propertiesPins->addDefaultProperties();
	on_propertiesPins_propertiesChanged();
}


void DatabaseDevices::on_buttonPinDup_clicked() {
	QTreeWidgetItem * ci = treePins->currentItem();
	if (ci == 0) return;
	treePins->blockSignals(true);
	QTreeWidgetItem * ti = ci->clone();
	ti->setText(0, uniqueName(ti->text(0), pinsNames()));
	ti->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled);
	ti->setData(0, Qt::UserRole, qulonglong(blockitem.addPin((Qt::Alignment)ti->data(0, Qt::UserRole + 1).toInt(), ti->text(1).toInt(), ti->text(0))));
	ti->setData(0, Qt::UserRole + 1, ti->data(0, Qt::UserRole + 1).toInt());
	pin_tli[ti->data(0, Qt::UserRole + 1).toInt()]->addChild(ti);
	treePins->setCurrentItem(ti);
	treePins->blockSignals(false);
}


void DatabaseDevices::on_buttonPinDelete_clicked() {
	treePins->blockSignals(true);
	QList<QTreeWidgetItem*> si = treePins->selectedItems();
	foreach (QTreeWidgetItem * i, si) {
		if (!i->parent()) continue;
		blockitem.removePin((BlockItemPin*)(i->data(0, Qt::UserRole).toLongLong()));
		delete i;
	}
	treePins->blockSignals(false);
}


void DatabaseDevices::on_buttonPinClear_clicked() {
	treePinsClear();
	blockitem.clearPins();
}


void DatabaseDevices::treeClicked(const QModelIndex & index) {
	properties->setDefaultProperties(SHCORE->default_block_properties);
	propertiesPins->setDefaultProperties(SHCORE->default_pin_properties);
	cur_id = model.record(index.row()).value("id").toInt();
	if (cur_id != prev_id && prev_id > 0 && ask_for_save) {
		QSqlQuery cur_query = updateQuery(prev_id);
		if ((prev_query.lastQuery() != cur_query.lastQuery() ||
			prev_query.boundValues() != cur_query.boundValues()) &&
			!prev_query.lastQuery().isEmpty()) {
			int mb = QMessageBox::question(this, "SH Designer Database", tr("Save changes?"), QMessageBox::Yes | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Yes);
			if (mb == QMessageBox::Cancel) {
				tree->selectionModel()->blockSignals(true);
				for (int i = 0; i < model.rowCount(); ++i)
					if (model.record(i).value("id").toInt() == prev_id) {
						tree->setCurrentIndex(model.index(i, 0));
						break;
					}
				tree->selectionModel()->blockSignals(false);
				return;
			}
			if (mb == QMessageBox::Yes)
				cur_query.exec();
		}
	}
	prev_id = cur_id;
	QSqlQuery q(QString("SELECT * FROM block_types WHERE id=%1").arg(cur_id));
	QSqlRecord r;
	if (q.next()) {
		blockitem.show();
		r = q.record();
	} else {
		widgetBlock->setEnabled(false);
		blockitem.hide();
		return;
	}
	widgetBlock->setEnabled(true);
	BlockType block = piqDeserialize<BlockType>(r.value("data").toByteArray());
	lineName->setText(block.name);
	lineDescription->setText(block.description);
	int ind = comboVendor->findData(block.vendor_id);
	if (ind >= 0) comboVendor->setCurrentIndex(ind);
	ind = comboGroup->findData(block.group_id);
	if (ind >= 0) comboGroup->setCurrentIndex(ind);
	spinWidth->setValue(block.width);
	spinHeight->setValue(block.height);
	spinPinSpacing->setValue(block.pins_spacing);
	colorButton->setColor(block.color);
	flagsEdit->setFlags(block.flags);
	setPluginByName(comboBlockPlugin, block.block_plugin);
	setPluginByName(comboGatewayPlugin, block.gateway_plugin);
	blockitem.loadModel(block.model);
	SHDatabaseStorage::loadBlockPinsProperties(&blockitem, block.pins_properties);
	treePinsClear();
	treePins->blockSignals(true);
	QVector<BlockItemPin * > pins = blockitem.pins();
	foreach (BlockItemPin * p, pins) {
		QTreeWidgetItem * ti = new QTreeWidgetItem(QStringList() << p->text() << QString::number(p->busType())
												   << p->propertyValueByName(SH_Pin_Plugin_name).toString());
		//ti->setData(3, Qt::DisplayRole, devices2variant(p->devices()));
		ti->setData(0, Qt::UserRole, qulonglong(p));
		ti->setData(0, Qt::UserRole + 1, (int)p->alignment());
		ti->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled);
		if (pin_tli.value(p->alignment(), 0))
			pin_tli[p->alignment()]->addChild(ti);
	}
	foreach (QGraphicsItem * i, blockitem.decors()) {
		i->setData(bvidMoveParent, false);
		i->setData(bvidCorrectMove, true);
	}
	treePins->blockSignals(false);
	properties->setProperties(SHDatabaseStorage::loadBlockProperties(&blockitem, block.properties, comboBlockPlugin->currentText()));
	widgetObject->setObject(block.object);
	prev_query = updateQuery(cur_id);
}


void DatabaseDevices::on_treePins_currentItemChanged(QTreeWidgetItem * current, QTreeWidgetItem * previous) {
	groupPinProperties->setEnabled(current != 0);
	if (!current) return;
	BlockItemPin * pin = blockitem.pinByText(current->text(0));
	if (!pin) return;
	propertiesPins->blockSignals(true);
	pin->properties() = SHDatabaseStorage::loadPinProperties(pin, pin->propertyStorage(), SHCORE->databaseStorage().bus_types.value(pin->busType()).plugin).properties();
	propertiesPins->setProperties(pin->propertyStorage());
	propertiesPins->blockSignals(false);
}


void DatabaseDevices::on_treePins_itemChanged(QTreeWidgetItem * item, int column) {
	if (!item) return;
	BlockItemPin * pin = (BlockItemPin*)item->data(0, Qt::UserRole).toULongLong();
	if (!pin) return;
	switch (column) {
	case 0:
		treePins->blockSignals(true);
		item->setText(0, uniqueName(item->text(0), pinsNames(item)));
		pin->setText(item->text(0));
		treePins->blockSignals(false);
		break;
	case 1:
		pin->setBusType(item->text(1).toInt());
		break;
	case 2:
		pin->addProperty(SH_Pin_Plugin_name, item->text(2), "", SHS::Internal | SHS::Hidden);
		break;
	};
}


void DatabaseDevices::reloadPluginsByType(QComboBox * combo, SHPluginBase::PluginType type) {
	QString pv = combo->currentText();
	combo->clear();
	combo->addItem("");
	auto pl = RUNTIME->plugins_manager->localInterface().loadedPlugins();
	for (const auto & p: pl) {
		if (!p.plugin) continue;
		if (p.type != type) continue;
		combo->addItem(p.plugin->name());
		if (pv == p.plugin->name())
			combo->setCurrentIndex(combo->count() - 1);
	}
}


void DatabaseDevices::reloadTable(QComboBox * combo, QString table) {
	int prev_id = combo->currentData().toInt();
	combo->clear();
	QSqlQuery q(QString("SELECT id,name FROM %1").arg(table));
	while (q.next()) {
		combo->addItem(q.value(1).toString(), q.value(0).toInt());
		if (q.value(0).toInt() == prev_id)
			combo->setCurrentIndex(combo->count() - 1);
	}
}


void DatabaseDevices::setPluginByName(QComboBox * combo, QString plugin) {
	bool found = false;
	for (int i = 0; i < combo->count(); ++i) {
		if (combo->itemText(i) == plugin) {
			combo->setCurrentIndex(i);
			found = true;
			break;
		}
	}
	if (!found) {
		combo->addItem(QIcon(":/icons/dialog-warning.png"), plugin);
		combo->setCurrentIndex(combo->count() - 1);
	}
}


QStringList DatabaseDevices::pinsNames(QTreeWidgetItem * self) {
	QStringList ret;
	QList<QTreeWidgetItem*> tli = pin_tli.values();
	foreach (QTreeWidgetItem * ti, tli)
		for (int i = 0; i < ti->childCount(); ++i)
			if (ti->child(i) != self)
				ret << ti->child(i)->text(0);
	return ret;
}


QSqlQuery DatabaseDevices::updateQuery(int id) {
	if (id < 0) return QSqlQuery();
	BlockType block;
	block.id = id;
	block.name = lineName->text();
	block.description = lineDescription->text();
	block.vendor_id = comboVendor->currentData().toInt();
	block.group_id = comboGroup->currentData().toInt();
	block.width = spinWidth->value();
	block.height = spinHeight->value();
	block.pins_spacing = spinPinSpacing->value();
	block.color = colorButton->color();
	QVector<BlockItemPin * > pl = blockitem.pins();
	for (BlockItemPin * p: pl)
		block.pins_properties[p->text()] = p->propertyStorage();
	block.object = widgetObject->object();
	block.properties = properties->properties();
	block.model = blockitem.saveModel();
	block.block_plugin = comboBlockPlugin->currentText();
	block.gateway_plugin = comboGatewayPlugin->currentText();
	block.flags = flagsEdit->flags();
	QSqlQuery q;
	q.prepare(QString("UPDATE block_types SET name=:n,data=:d WHERE id=%1").arg(id));
	q.bindValue(":n", block.name);
	q.bindValue(":d", piqSerialize(block));
	return q;
}


void DatabaseDevices::updateBlock() {
	//qDebug() << "update";
	blockitem.setSize(spinWidth->value(), spinHeight->value());
	blockitem.setColor(colorButton->color());
	blockitem.setPinsMargin(spinPinSpacing->value());
}


void DatabaseDevices::arrangePins() {
	QVector<BlockItemPin * > pins = blockitem.takePins();
	QList<QTreeWidgetItem*> tli = pin_tli.values();
	foreach (QTreeWidgetItem * ti, tli) {
		for (int i = 0; i < ti->childCount(); ++i) {
			foreach (BlockItemPin * p, pins)
				if (p->text() == ti->child(i)->text(0)) {
					p->setAlignment((Qt::Alignment)ti->data(0, Qt::UserRole).toInt());
					ti->child(i)->setData(0, Qt::UserRole + 1, ti->data(0, Qt::UserRole).toInt());
					blockitem.addPin(p, false);
					break;
				}
		}
	}
	for (int i = 0; i < treePins->topLevelItemCount(); ++i) {
		QTreeWidgetItem * ti = treePins->topLevelItem(i);
		if (tli.contains(ti)) continue;
		ti = treePins->takeTopLevelItem(i);
		pin_tli[ti->data(0, Qt::UserRole + 1).toInt()]->addChild(ti);
		foreach (BlockItemPin * p, pins)
			if (p->text() == ti->text(0)) {
				p->setAlignment((Qt::Alignment)ti->data(0, Qt::UserRole + 1).toInt());
				blockitem.addPin(p, false);
				break;
			}
	}
	blockitem.arrangePins();
}


void DatabaseDevices::filterList() {
	QString f = lineFilter->text();
	for (int i = 0; i < model.rowCount(); ++i)
		tree->setRowHidden(i, QModelIndex(), !(model.index(i, 0).data().toString().contains(f, Qt::CaseInsensitive) ||
											   model.index(i, 1).data().toString().contains(f, Qt::CaseInsensitive)));
}


void DatabaseDevices::addItem(QGraphicsItem * item) {
	blockitem.addDecor(item);
	item->setData(bvidMoveParent, false);
	item->setData(bvidCorrectMove, true);
}


void DatabaseDevices::selectionChanged() {
	actionRemove_items->setEnabled(!blockView->scene()->selectedItems().isEmpty());
}


void DatabaseDevices::treeSelectionChanged() {
	if (tree->selectionModel()->selectedRows().isEmpty()) {
		blockitem.hide();
		widgetBlock->setEnabled(false);
	} else {
		blockitem.show();
		widgetBlock->setEnabled(true);
	}
}


void DatabaseDevices::treePinsSelectionChanged() {
	groupPinProperties->setEnabled(!treePins->selectionModel()->selectedRows().isEmpty());
	if (!groupPinProperties->isEnabled()) {
		propertiesPins->blockSignals(true);
		propertiesPins->clear();
		propertiesPins->blockSignals(false);
	}
}


void DatabaseDevices::treePinsClear() {
	treePins->blockSignals(true);
	treePins->clear();
	QFont bf(font()); bf.setBold(true);
	QList<int> al; al << Qt::AlignLeft << Qt::AlignRight << Qt::AlignTop << Qt::AlignBottom;
	QStringList an; an << "Left" << "Right" << "Top" << "Bottom";
	pin_tli.clear();
	for (int i = 0; i < al.size(); ++i) {
		QTreeWidgetItem * ti = new QTreeWidgetItem();
		ti->setFlags(Qt::ItemIsEnabled | Qt::ItemIsDropEnabled);
		ti->setData(0, Qt::UserRole, al[i]);
		ti->setText(0, an[i]);
		ti->setFont(0, bf);
		treePins->addTopLevelItem(ti);
		ti->setFirstColumnSpanned(true);
		ti->setExpanded(true);
		pin_tli[al[i]] = ti;
	}
	treePins->blockSignals(false);

}


void DatabaseDevices::on_propertiesPins_propertiesChanged() {
	if (!treePins->currentItem()) return;
	BlockItemPin * pin = blockitem.pinByText(treePins->currentItem()->text(0));
	if (!pin) return;
	pin->properties() = propertiesPins->properties().properties();
}
