#include "object_editor.h"
#include <QScrollBar>
#include <QListWidget>
#include <QFileDialog>
#include <QImageReader>
#include <QToolBar>
#include <QMessageBox>
#include <QBuffer>
#include <QTimer>
#include <QDialogButtonBox>
#include "core.h"
#include "SH_plugin_widgets.h"

Q_DECLARE_METATYPE(SHS::Command);

ObjectEditor::ObjectEditor(QWidget * parent): QWidget(parent), Ui::ObjectEditor() {
	setupUi(this);
	qRegisterMetaType<SHS::Command>();
	connect(SHCORE, SIGNAL(changedPlugins()), this, SLOT(changedPlugins()));
	connect(&RUNTIME->plugins_manager->localInterface(), SIGNAL(aboutUnload()), this, SLOT(deleteEditors()));
	connect(&RUNTIME->plugins_manager->localInterface(), &SHPluginsLoader::aboutUnloadPlugin, this, [this](SHPluginBase * p){
		piDeleteSafety(packet_editors[p->name()]);
		packet_editors.remove(p->name());
	});
	flags_delegate = new FlagsDelegate();
	flags_delegate->setSingle(true);
	pins_delegate = new PacketPinDelegate();
	reply_by_delegate = new PacketReplyByDelegate();
	reply_by_delegate->packets = treeCommands;
	flags_delegate->setEnum(getMetaEnum<SHS>("PacketFlag"));
	treeCommands->setItemDelegateForColumn(2, flags_delegate);
	treeCommands->setItemDelegateForColumn(3, pins_delegate);
	treeCommands->setItemDelegateForColumn(4, reply_by_delegate);
	treeCommands->header()->resizeSection(1, 250);
	treeCommands->header()->resizeSection(2, 200);
	command_dialog.setLayout(new QBoxLayout(QBoxLayout::TopToBottom));
	command_dialog.setWindowTitle(tr("Command"));
	command_dialog.setWindowIcon(QIcon(":/icons/SH_designer.png"));
	QDialogButtonBox * bb = new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Cancel);
	connect(bb, SIGNAL(accepted()), &command_dialog, SLOT(accept()));
	connect(bb, SIGNAL(rejected()), &command_dialog, SLOT(reject()));
	command_dialog.layout()->addItem(new QSpacerItem(1, 1, QSizePolicy::Preferred, QSizePolicy::Expanding));
	command_dialog.layout()->addWidget(bb);
}


ObjectEditor::~ObjectEditor() {
}


void ObjectEditor::setObject(const SHS::Object & o) {
	treeObject->clear();
	object_ = o;
	createObjectTree(object_, treeObject->invisibleRootItem());
	treeObject->setCurrentItem(0);
	on_treeObject_currentItemChanged(0, 0);
}


void ObjectEditor::on_buttonPacketAdd_clicked() {
	QStringList vl;
	QStringList lpins;
	QStringList el;
	for (int i = 0; i < treeCommands->topLevelItemCount(); ++i)
		el << treeCommands->topLevelItem(i)->text(0);
	vl << uniqueName("command", el) << "" << QString::number(SHS::Input);
	for (int i = 0; i < pins_delegate->pins->topLevelItemCount(); ++i) {
		QTreeWidgetItem * ti = pins_delegate->pins->topLevelItem(i);
		for (int j = 0; j < ti->childCount(); ++j) {
			QString ni = ti->child(j)->text(0);
			lpins << ni;
		}
	}
	if (lpins.count() > 0)
		vl << lpins[0];
	QTreeWidgetItem * ti = new QTreeWidgetItem(vl);
	SHS::Command cmd;
	cmd.pin = ti->text(3);
	cmd.flags = SHS::Input;
	ti->setData(0, Qt::UserRole, QVariant::fromValue(cmd));
	ti->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
	treeCommands->addTopLevelItem(ti);
	treeCommands->setCurrentItem(ti);
	saveCurrentCommands();
	filterPackets();
}


void ObjectEditor::on_buttonPacketDup_clicked() {
	QTreeWidgetItem * ci = treeCommands->currentItem();
	if (!ci) return;
	QStringList el;
	for (int i = 0; i < treeCommands->topLevelItemCount(); ++i)
		el << treeCommands->topLevelItem(i)->text(0);
	QTreeWidgetItem * ti = ci->clone();
	ti->setText(0, uniqueName(ti->text(0), el));
	SHS::Command cmd = ci->data(0, Qt::UserRole).value<SHS::Command>();
	ti->setData(0, Qt::UserRole, QVariant::fromValue(cmd));
	ti->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
	treeCommands->addTopLevelItem(ti);
	treeCommands->setCurrentItem(ti);
	saveCurrentCommands();
	filterPackets();
}


void ObjectEditor::on_buttonPacketDelete_clicked() {
	qDeleteAll(treeCommands->selectedItems());
	saveCurrentCommands();
	filterPackets();
}


void ObjectEditor::on_buttonPacketClear_clicked() {
	treeCommands->clear();
	saveCurrentCommands();
}


void ObjectEditor::on_buttonObjectAdd_clicked() {
	QTreeWidgetItem * ci = treeObject->currentItem();
	if (!ci) ci = treeObject->invisibleRootItem();
	SHS::Object * co = objectByTreeItem(ci);
	if (!co) return;
	QString n = uniqueName("object", co->objects.keys());
	co->addObject(n, SHS::Object());
	QTreeWidgetItem * ni = new QTreeWidgetItem(QStringList() << n);
	ni->setFlags(ni->flags() | Qt::ItemIsEditable);
	ci->addChild(ni);
	ni->setData(0, Qt::UserRole, n);
	ci->setExpanded(true);
	on_treeObject_currentItemChanged(ci, ci);
}


void ObjectEditor::on_buttonObjectDup_clicked() {
	QTreeWidgetItem * ci = treeObject->currentItem();
	if (!ci) return;
	QTreeWidgetItem * pi = ci->parent();
	if (!pi) pi = treeObject->invisibleRootItem();
	SHS::Object * co = objectByTreeItem(ci);
	SHS::Object * po = objectByTreeItem(pi);
	if (!co || !po) return;
	QString n = uniqueName(ci->text(0), po->objects.keys());
	po->addObject(n, SHS::Object(*co));
	QTreeWidgetItem * ni = ci->clone();
	ni->setText(0, n);
	ni->setFlags(ni->flags() | Qt::ItemIsEditable);
	ni->setData(0, Qt::UserRole, n);
	pi->addChild(ni);
	pi->setExpanded(true);
	treeObject->setCurrentItem(ni);
}


void ObjectEditor::on_buttonObjectDelete_clicked() {
	QList<QTreeWidgetItem*> sil = treeObject->selectedItems();
	foreach (QTreeWidgetItem * ti, sil) {
		QStringList path = objectPath(ti);
		if (path.isEmpty()) continue;
		SHS::Object * o = &object_;
		for (int i = 0; i < path.size() - 1; ++i) {
			const QString & p(path[i]);
			if (o->objects.contains(p))
				o = &(o->objects[p]);
			else {
				o = 0;
				break;
			}
		}
		if (!o) continue;
		if (o->objects.contains(path.back()))
			o->objects.remove(path.back());
	}
	qDeleteAll(sil);
}


void ObjectEditor::on_buttonObjectClear_clicked() {
	treeObject->clear();
	object_.objects.clear();
}


void ObjectEditor::on_treeCommands_itemChanged(QTreeWidgetItem * item, int column) {
	if (!item) return;
	bool bs = treeCommands->blockSignals(true);
	SHS::Command cmd = item->data(0, Qt::UserRole).value<SHS::Command>();
	if (column == 0) {
		int r = treeCommands->indexOfTopLevelItem(item);
		if (r < 0 || r >= last_packet_names.size())
			return;
		QString pn = last_packet_names[r], nn = item->text(0);
		for (int i = 0; i < treeCommands->topLevelItemCount(); ++i) {
			QTreeWidgetItem * ti = treeCommands->topLevelItem(i);
			if (ti->text(4) == pn)
				ti->setText(4, nn);
		}
		last_packet_names[r] = nn;
	}
	if (column == 2) {
		cmd.flags = (SHS::PacketFlags)item->data(2, Qt::EditRole).toInt();
	}
	if (column == 3) {
		cmd.pin = item->text(3);
		SHPacketWidgetBase * editor = commandEditor(cmd);
		item->setText(1, editor->commandToString(cmd));
	}
	item->setData(0, Qt::UserRole, QVariant::fromValue(cmd));
	saveCurrentCommands();
	treeCommands->blockSignals(bs);
}


void ObjectEditor::on_treeCommands_itemClicked(QTreeWidgetItem * item, int column) {
	if (!item) return;
	Qt::ItemFlags f = item->flags();
	f.setFlag(Qt::ItemIsEditable, column != 1);
	item->setFlags(f);
}


void ObjectEditor::on_treeCommands_currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *) {
	last_packet_names.clear();
	for (int i = 0; i < treeCommands->topLevelItemCount(); ++i)
		last_packet_names << treeCommands->topLevelItem(i)->text(0);
}


void ObjectEditor::on_treeCommands_itemDoubleClicked(QTreeWidgetItem * item, int column) {
	if (!item || column != 1) return;
	SHS::Command cmd = item->data(0, Qt::UserRole).value<SHS::Command>();
	SHPacketWidgetBase * editor = commandEditor(cmd);
	if (!editor) return;
	editor->loadCommand(cmd);
	((QBoxLayout*)command_dialog.layout())->insertWidget(0, editor);
	editor->show();
	editor->setFocus();
	if (command_dialog.exec() == QDialog::Accepted) {
		SHS::Command ncmd = editor->saveCommand();
		cmd.arguments = ncmd.arguments;
		cmd.user_arguments = ncmd.user_arguments;
		item->setData(0, Qt::UserRole, QVariant::fromValue(cmd));
		item->setText(1, editor->commandToString(cmd));
	}
	((QBoxLayout*)command_dialog.layout())->removeWidget(editor);
	editor->hide();
	editor->setParent(0);
}


void ObjectEditor::on_treeObject_itemChanged(QTreeWidgetItem * item, int column) {
	if (!item || column != 0) return;
	SHS::Object * po = objectByTreeItem(item->parent());
	if (!po) return;
	QString oldname = item->data(0, Qt::UserRole).toString();
	SHS::Object o = po->object(oldname);
	po->objects.remove(oldname);
	QString newname = uniqueName(item->text(0), po->objects.keys());
	treeObject->blockSignals(true);
	item->setText(0, newname);
	item->setData(0, Qt::UserRole, newname);
	po->objects.insert(newname, o);
	treeObject->blockSignals(false);
	on_treeObject_currentItemChanged(item, item);
}


void ObjectEditor::on_treeObject_currentItemChanged(QTreeWidgetItem * current, QTreeWidgetItem *) {
	QStringList pl = objectPath(current);
	QString path = pl.join('.');
	if (!path.isEmpty()) path.insert(0, '.');
	labelCurrent->setText(tr("Current object: <block>%1").arg(path));
	SHS::Object * co = objectByTreeItem(current);
	groupProperties->setEnabled(co);
	groupCommands->setEnabled(co);
	if (!co) return;
	propertiesObject->blockSignals(true);
	propertiesObject->setProperties(PropertyStorage(co->properties));
	propertiesObject->blockSignals(false);
	treeCommands->blockSignals(true);
	treeCommands->clear();
	QMapIterator<QString, SHS::Command> it(co->commands);
	while (it.hasNext()) {
		it.next();
		const SHS::Command & cmd(it.value());
		SHPacketWidgetBase * editor = commandEditor(cmd);
		QTreeWidgetItem * ti = new QTreeWidgetItem(QStringList() << it.key() << editor->commandToString(cmd)
																<< QString::number(cmd.flags) << cmd.pin);
		ti->setFlags(ti->flags() | Qt::ItemIsEditable);
		ti->setData(0, Qt::UserRole, QVariant::fromValue(cmd));
		treeCommands->addTopLevelItem(ti);
	}
	treeCommands->blockSignals(false);
}


void ObjectEditor::on_treeObject_itemSelectionChanged() {
	if (treeObject->selectedItems().isEmpty())
		treeObject->setCurrentItem(0);
}


QStringList ObjectEditor::objectPath(QTreeWidgetItem * ti) {
	QStringList ret;
	if (!ti) return ret;
	while (ti) {
		ret.insert(0, ti->text(0));
		ti = ti->parent();
	}
	return ret;
}


SHS::Object * ObjectEditor::objectByTreeItem(QTreeWidgetItem * ti) {
	QStringList path = objectPath(ti);
	SHS::Object * co = &object_;
	foreach (const QString & p, path) {
		if (!co) break;
		if (co->objects.contains(p))
			co = &(co->objects[p]);
	}
	return co;
}


void ObjectEditor::createObjectTree(const SHS::Object & o, QTreeWidgetItem * ti) {
	QMapIterator<QString, SHS::Object> it(o.objects);
	while (it.hasNext()) {
		it.next();
		QTreeWidgetItem * ni = new QTreeWidgetItem(QStringList() << it.key());
		ni->setFlags(ni->flags() | Qt::ItemIsEditable);
		ti->addChild(ni);
		createObjectTree(it.value(), ni);
		ni->setExpanded(true);
	}
}


SHPacketWidgetBase * ObjectEditor::commandEditor(const SHS::Command & cmd) {
	SHPacketWidgetBase * ret = packet_editors.value(SHCORE->defaultPacketPlugin()->name());
	if (cmd.pin.isEmpty()) return ret;
	QString plugname;
	for (int i = 0; i < pins_delegate->pins->topLevelItemCount(); ++i) {
		QTreeWidgetItem * ti = pins_delegate->pins->topLevelItem(i);
		for (int j = 0; j < ti->childCount(); ++j) {
			QString ni = ti->child(j)->text(0);
			if (ni == cmd.pin) {
				plugname = ti->child(j)->text(2);
				break;
			}
		}
	}
	SHPacketWidgetBase * ple = packet_editors.value(plugname, 0);
	if (ple) ret = ple;
	//qDebug() << cmd.pin << plugname << ret;
	return ret;
}


void ObjectEditor::saveCurrentCommands() {
	//qDebug() << "scc";
	SHS::Object * co = objectByTreeItem(treeObject->currentItem());
	if (!co) return;
	co->commands.clear();
	for (int i = 0; i < treeCommands->topLevelItemCount(); ++i) {
		QString pn = treeCommands->topLevelItem(i)->text(0);
		co->commands[pn] = treeCommands->topLevelItem(i)->data(0, Qt::UserRole).value<SHS::Command>();
	}
}


void ObjectEditor::deleteEditors() {
	qDeleteAll(packet_editors.values());
	packet_editors.clear();
}


void ObjectEditor::changedPlugins() {
	//qDeleteAll(packet_editors.values());
	packet_editors.clear();
	auto pil = RUNTIME->plugins_manager->localInterface().getPlugins(SHPluginBase::Packet);
	for (const auto & i: pil) {
		QString pn = i.plugin->name();
		SHPacketWidgetBase * editor = ((SHPluginBasePacket*)(i.plugin))->createEditor();
		if (!editor) continue;
		packet_editors[pn] = editor;
	}
}


void ObjectEditor::filterPackets() {
	QString f = linePacketFilter->text();
	for (int i = 0; i < treeCommands->topLevelItemCount(); ++i) {
		QTreeWidgetItem * ti = treeCommands->topLevelItem(i);
		ti->setHidden(!(ti->text(0).contains(f, Qt::CaseInsensitive) ||
						ti->text(1).contains(f, Qt::CaseInsensitive) ||
						flags_delegate->displayText(ti->text(2), locale()).contains(f, Qt::CaseInsensitive) ||
						ti->text(3).contains(f, Qt::CaseInsensitive)));
	}
}


void ObjectEditor::on_propertiesObject_propertiesChanged() {
	SHS::Object * co = objectByTreeItem(treeObject->currentItem());
	if (!co) return;
	co->properties = propertiesObject->properties().toVariantMap();
}
