/*
	Copyright 2006-2026 The QElectroTech Team
	This file is part of QElectroTech.

	QElectroTech is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 2 of the License, or
	(at your option) any later version.

	QElectroTech is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "linksingleelementwidget.h"
#include "contactgroupselectiondialog.h"
#include "../qetgraphicsitem/masterelement.h"
#include "../qetgraphicsitem/conductor.h"
#include "../diagram.h"
#include "../diagramposition.h"
#include "../qetgraphicsitem/element.h"
#include "../elementprovider.h"
#include "../undocommand/linkelementcommand.h"
#include "../qetinformation.h"
#include "../qetproject.h"
#include "../ui_linksingleelementwidget.h"

#include <QTreeWidgetItem>
#include <QInputDialog>


/**
	@brief LinkSingleElementWidget::LinkSingleElementWidget
	Default constructor
	@param elmt
	the edited element
	@param parent
	the parent widget
*/
LinkSingleElementWidget::LinkSingleElementWidget(Element *elmt,
						 QWidget *parent) :
	AbstractElementPropertiesEditorWidget(parent),
	ui(new Ui::LinkSingleElementWidget)
{
	ui->setupUi(this);
	
	ui->m_tree_widget->setContextMenuPolicy(Qt::CustomContextMenu);
	m_context_menu  = new QMenu(this);
	m_link_action   = new QAction(tr("Lier l'élément"), this);
	m_show_qtwi     = new QAction(tr("Montrer l'élément"), this);
	m_show_element  = new QAction(tr("Montrer l'élément esclave"), this);
	m_save_header_state = new QAction(tr("Enregistrer la disposition"), this);
	
	connect(m_show_qtwi, &QAction::triggered, this, [=]()
	{
		this->on_m_tree_widget_itemDoubleClicked(this->m_qtwi_at_context_menu, 0);
	});

	connect(m_link_action,
			&QAction::triggered,
			this,
			&LinkSingleElementWidget::linkTriggered);
	
	connect(m_show_element,  &QAction::triggered, this, [=]()
	{
		this->m_element->diagram()->showMe();
		this->m_element->setHighlighted(true);
		if(this->m_showed_element)
			m_showed_element->setHighlighted(false);
	});
	
	QHeaderView *qhv = ui->m_tree_widget->header();
	qhv->setContextMenuPolicy(Qt::CustomContextMenu);
	connect(qhv,
		&QHeaderView::customContextMenuRequested,
		this,
		&LinkSingleElementWidget::headerCustomContextMenuRequested);
	connect(m_save_header_state, &QAction::triggered, [this, qhv]()
	{
		QByteArray qba = qhv->saveState();
		QSettings settings;
		
		const auto elmt_type{this->m_element->elementData().m_type};
		if (elmt_type & ElementData::AllReport)
			settings.setValue(QStringLiteral("link-element-widget/report-state"), qba);
		else if (elmt_type == ElementData::Slave)
			settings.setValue(QStringLiteral("link-element-widget/slave-state"), qba);
	});
			
	setElement(elmt);
}

/**
	@brief LinkSingleElementWidget::~LinkSingleElementWidget
	Default destructor
*/
LinkSingleElementWidget::~LinkSingleElementWidget()
{
	if(m_showed_element)
		m_showed_element->setHighlighted(false);
	
	if(m_element)
	{
		m_element->setHighlighted(false);		
		if (!m_element->isFree())
			m_element->linkedElements().first()->setHighlighted(false);
	}
	delete ui;
}

/**
	@brief LinkSingleElementWidget::setElement
	Set element to be the edited element.
	@param element
*/
void LinkSingleElementWidget::setElement(Element *element)
{
	if (m_element == element)
		return;

		//Remove connection of previous edited element
	if (m_element)
	{
		disconnect(m_element->diagram()->project(),
			   &QETProject::diagramRemoved,
			   this,
			   &LinkSingleElementWidget::diagramWasRemovedFromProject);
		disconnect(m_element.data(),
			   &Element::linkedElementChanged,
			   this,
			   &LinkSingleElementWidget::updateUi);
		m_element->setHighlighted(false);
	}
	
	if(m_showed_element)
		m_showed_element->setHighlighted(false);
	
	m_unlink = false;
	m_showed_element = nullptr;
	m_element_to_link = nullptr;
	m_pending_qtwi = nullptr;

		//Setup the new element, connection and ui
	m_element = element;

	const auto elmt_type{m_element->elementData().m_type};
	if (elmt_type == ElementData::Slave)
		m_filter = ElementData::Master;
	else if (elmt_type & ElementData::AllReport)
		m_filter = elmt_type == ElementData::NextReport
				? ElementData::PreviousReport
				: ElementData::NextReport;
	else
		m_filter = ElementData::Simple;

	connect(m_element->diagram()->project(), &QETProject::diagramRemoved,
		this, &LinkSingleElementWidget::diagramWasRemovedFromProject);
	connect(m_element.data(), &Element::linkedElementChanged,
		this, &LinkSingleElementWidget::updateUi, Qt::QueuedConnection);

	updateUi();
}

/**
	@brief LinkSingleElementWidget::apply
	Apply the new property of the edited element by pushing
	the associated undo command to parent project undo stack
*/
void LinkSingleElementWidget::apply()
{
	QUndoCommand *undo = associatedUndo();
	if (undo)
		m_element->diagram()->undoStack().push(undo);
	
	m_unlink = false;
	m_element_to_link = nullptr;
	m_pending_qtwi = nullptr;
	m_pending_group_index = -1;
}

/**
	@brief LinkSingleElementWidget::associatedUndo
	@return the undo command associated to the current edition
	if there isn't change, return nulptr
*/
QUndoCommand *LinkSingleElementWidget::associatedUndo() const
{
	LinkElementCommand *undo = new LinkElementCommand(m_element);

	if (m_element_to_link || m_unlink)
	{
		if (m_element_to_link) {
			undo->setLink(m_element_to_link);
			if (m_pending_group_index >= 0) {
				undo->setGroupIndex(m_pending_group_index);
			}
		}
		else if (m_unlink)
			undo->unlinkAll();

		return undo;
	}

	return nullptr;
}

/**
	@brief LinkSingleElementWidget::title
	@return the title used for this editor
*/
QString LinkSingleElementWidget::title() const
{
	if (m_element->elementData().m_type & ElementData::AllReport)
		return tr("Report de folio");
	else
		return tr("Référence croisée (esclave)");
}

/**
	@brief LinkSingleElementWidget::updateUi
	Update the content of this widget
*/
void LinkSingleElementWidget::updateUi()
{
	m_unlink = false;

		//Update the behavior of link/unlink button
	if (m_element->isFree())
		hideButtons();
	else
		showButtons();
	
	buildTree();
}

/**
	@brief LinkSingleElementWidget::buildTree
	Build the content of the QTreeWidget
*/
void LinkSingleElementWidget::buildTree()
{
	clearTreeWidget();
	setUpHeaderLabels();
	QSettings settings;
	
	const auto elmt_vector{availableElements()};

	if (m_element->elementData().m_type == ElementData::Slave)
	{
		
		for(const auto &elmt : elmt_vector)
		{
			QStringList search_list;
			QStringList str_list;

			str_list << elmt->actualLabel();
			if(!str_list.last().isEmpty()) {
				search_list << str_list.last();
			}
			
			str_list << elmt->elementInformations()[QETInformation::ELMT_COMMENT].toString();
			if (!str_list.last().isEmpty())
				search_list << str_list.last();
			
			if (const auto diag = elmt->diagram())
			{
				if (settings.value(QStringLiteral("genericpanel/folio"), false).toBool())
				{
					autonum::sequentialNumbers seq;
					QString F =autonum::AssignVariables::formulaToLabel(
								diag->border_and_titleblock.folio(), seq, diag, elmt);
					str_list << F;
				}
				else
				{
					str_list << QString::number(diag->folioIndex() + 1);
				}
				str_list << diag->convertPosition(elmt->scenePos()).toString();
				str_list << diag->title();
			}
			else
			{
				qDebug() << "In method void LinkSingleElementWidget::updateUi(), provided element must be in a diagram";
			}
			
			QTreeWidgetItem *qtwi = new QTreeWidgetItem(ui->m_tree_widget, str_list);
			m_qtwi_elmt_hash.insert(qtwi, elmt);
			m_qtwi_strl_hash.insert(qtwi, search_list);
		}
		
		
		QVariant v = settings.value(QStringLiteral("link-element-widget/slave-state"));
		if(!v.isNull())
			ui->m_tree_widget->header()->restoreState(v.toByteArray());
	}
	
	else if (m_element->elementData().m_type & ElementData::AllReport)
	{	
		for(const auto &elmt : elmt_vector)
		{
			QStringList search_list;
			QStringList str_list;
			
			if (!elmt->conductors().isEmpty())
			{
				ConductorProperties cp = elmt->conductors().first()->properties();
				str_list << cp.text;
				if (!str_list.last().isEmpty())
					search_list << str_list.last();
				str_list << cp.m_function;
				if (!str_list.last().isEmpty())
					search_list << str_list.last();
				str_list << cp.m_tension_protocol;
				if (!str_list.last().isEmpty())
					search_list << str_list.last();
				str_list << cp.m_wire_color;
				if (!str_list.last().isEmpty())
					search_list << str_list.last();
				str_list << cp.m_wire_section;
				if (!str_list.last().isEmpty())
					search_list << str_list.last();
			}
			else
				str_list << "" << "" << "" << "" << "";
			
			if (Diagram *diag = elmt->diagram())
			{
				if (settings.value(QStringLiteral("genericpanel/folio"), false).toBool())
				{
					autonum::sequentialNumbers seq;
					QString F =autonum::AssignVariables::formulaToLabel(diag->border_and_titleblock.folio(), seq, diag, elmt);
					str_list << F;
				}
				else
				{
					str_list << QString::number(diag->folioIndex() + 1);
				}
				str_list << diag->convertPosition(elmt->scenePos()).toString();
				str_list << diag->title();
			}
			else
			{
				qDebug() << "In method void LinkSingleElementWidget::updateUi(), provided element must be in a diagram";
			}
			
			QTreeWidgetItem *qtwi = new QTreeWidgetItem(ui->m_tree_widget, str_list);
			m_qtwi_elmt_hash.insert(qtwi, elmt);
			m_qtwi_strl_hash.insert(qtwi, search_list);
		}
		
		QSettings settings;
		QVariant v = settings.value(QStringLiteral("link-element-widget/report-state"));
		if(!v.isNull())
			ui->m_tree_widget->header()->restoreState(v.toByteArray());
	}
	
	setUpCompleter();
}

/**
	@brief LinkSingleElementWidget::setLiveEdit
	@param live_edit
	@return
*/
bool LinkSingleElementWidget::setLiveEdit(bool live_edit)
{
	m_live_edit = live_edit;
	return true;
}

/**
	@brief LinkSingleElementWidget::availableElements
	@return A QList with all available elements
	to be linked with the edited element.
	This method takes care of the "find in diagram" combo box
*/
QVector <QPointer<Element>> LinkSingleElementWidget::availableElements()
{
	QVector <QPointer<Element>> elmt_vector;
	//if element isn't free and unlink isn't pressed, return an empty list
	if (!m_element->isFree() && !m_unlink)
		return elmt_vector;
	
	if (!m_element->diagram() || !m_element->diagram()->project()) return elmt_vector;
	
	ElementProvider ep(m_element->diagram()->project());
	if (m_filter & ElementData::AllReport)
		elmt_vector = ep.freeElement(m_filter);
	else
		elmt_vector = ep.find(m_filter);
	
	//If element is linked, remove is parent from the list
	if(!m_element->isFree()) elmt_vector.removeAll(m_element->linkedElements().first());

	// Filter out incompatible elements: PLC and non-PLC must not mix
	const bool element_is_plc = (m_element->elementData().m_type == ElementData::Slave &&
				     m_element->elementData().m_slave_type == ElementData::PLCSlave);
	for (int i = elmt_vector.size() - 1; i >= 0; --i) {
		Element *elmt = elmt_vector.at(i);

		if (elmt->linkType() == Element::Master) {
			const bool master_is_plc = (elmt->elementData().m_master_type == ElementData::PLC);

			// PLC slave can only link to PLC master, and vice versa
			if (element_is_plc != master_is_plc) {
				elmt_vector.removeAt(i);
				continue;
			}

			// If the master is full, we'll remove it from the list!
			MasterElement *master = static_cast<MasterElement*>(elmt);
			if (master->isFull()) {
				elmt_vector.removeAt(i);
			}
		}
	}
	return elmt_vector;
}

/**
	@brief LinkSingleElementWidget::setUpCompleter
	Setup the completer of search_field
*/
void LinkSingleElementWidget::setUpCompleter()
{
	ui->m_search_field->clear();
	if(ui->m_search_field->completer())
		delete ui->m_search_field->completer();
	
	QStringList search;
	for(const auto &str_list : m_qtwi_strl_hash.values())
		search.append(str_list);
	
	QCompleter *c = new QCompleter(search, ui->m_search_field);
	c->setCaseSensitivity(Qt::CaseInsensitive);
	ui->m_search_field->setCompleter(c);
}

/**
	@brief LinkSingleElementWidget::clearTreeWidget
	Clear the tree widget.
	Delete all QTreeWidget (in the tree widget and in the hash).
	Clear the hash.
*/
void LinkSingleElementWidget::clearTreeWidget()
{
	while(ui->m_tree_widget->topLevelItemCount())
	{
		QTreeWidgetItem *qtwi = ui->m_tree_widget->takeTopLevelItem(0);
		if (!m_qtwi_elmt_hash.contains(qtwi))
			delete qtwi;
	}
	
	foreach(QTreeWidgetItem *qtwi, m_qtwi_elmt_hash.keys())
		delete qtwi;
	
	m_qtwi_elmt_hash.clear();
	m_qtwi_strl_hash.clear();
}

void LinkSingleElementWidget::setUpHeaderLabels()
{
	QStringList list;
	const QSettings settings;
	
	const auto elmt_type{m_element->elementData().m_type};
	if (elmt_type == ElementData::Slave)
	{
		if (settings.value(QStringLiteral("genericpanel/folio"), false).toBool())
		{
			list << tr("Label")
			     << tr("Commentaire")
			     << tr("Label de folio")
			     << tr("Position")
			     << tr("Titre de folio");
		}
		else
		{
			list << tr("Label")
			     << tr("Commentaire")
			     << tr("N° de folio")
			     << tr("Position")
			     << tr("Titre de folio");
		}
	}
	
	if (elmt_type & ElementData::AllReport)
	{
		if (settings.value(QStringLiteral("genericpanel/folio"), false).toBool())
		{
			list << tr("N° de fil")
			     << tr("Fonction")
			     << tr("Tension / Protocole")
			     << tr("Couleur du conducteur")
			     << tr("Section du conducteur")
			     << tr("Label de folio")
			     << tr("Position")
			     << tr("Titre de folio");
		}
		else
		{
			list << tr("N° de fil")
			     << tr("Fonction")
			     << tr("Tension / Protocole")
			     << tr("Couleur du conducteur")
			     << tr("Section du conducteur")
			     << tr("N° de folio")
			     << tr("Position")
			     << tr("Titre de folio");
		}
	}
	
	ui->m_tree_widget->setHeaderLabels(list);
}

/**
	@brief LinkSingleElementWidget::diagramWasRemovedFromProject
	This slot is called when a diagram
	is removed from the parent project of edited element
	to update the content of this widget
*/
void LinkSingleElementWidget::diagramWasRemovedFromProject()
{
	// We use a timer because if the removed diagram
	// contains the master element linked to the edited element
	// we must wait for this elements to be unlinked,
	// or else the list of available master isn't up to date
	QTimer::singleShot(10, this, SLOT(updateUi()));
}

void LinkSingleElementWidget::showedElementWasDeleted()
{
	m_showed_element = nullptr;
}

/**
	@brief LinkSingleElementWidget::linkTriggered
	Action linkis triggered
*/
void LinkSingleElementWidget::linkTriggered()
{
	if(!m_qtwi_at_context_menu)
		return;

	m_element_to_link = m_qtwi_elmt_hash.value(m_qtwi_at_context_menu);
	m_pending_group_index = -1;

	//If linking a slave to a master with contact groups, show group selection dialog
	if (m_element->linkType() == Element::Slave
		&& m_element_to_link
		&& m_element_to_link->linkType() == Element::Master)
	{
		// Check if this is a PLC master
		if (m_element_to_link->elementData().m_master_type == ElementData::PLC)
		{
			// Show PLC IO selection dialog
			const auto &plc_data = m_element_to_link->elementData().plcMasterData();
			if (!plc_data.ios.isEmpty())
			{
				// Collect already-used IO indices from the master
				QSet<int> used_indices;
				for (Element *linked : m_element_to_link->linkedElements()) {
					int idx = m_element_to_link->groupIndexForElement(linked);
					if (idx >= 0) {
						used_indices.insert(idx);
					}
				}

				// Build selection dialog
				QStringList items;
				for (int i = 0; i < plc_data.ios.size(); ++i) {
					const auto &io = plc_data.ios.at(i);
					QString label = QString("[%1] %2 - %3")
						.arg(i + 1)
						.arg(io.address)
						.arg(io.functionText);
					if (used_indices.contains(i))
						label += tr(" (déjà utilisé)");
					items << label;
				}

				bool ok = false;
				int selected = QInputDialog::getInt(
					this,
					tr("Sélectionner un IO PLC"),
					tr("IO disponible:"),
					0, 0, plc_data.ios.size() - 1, 1, &ok);

				if (ok && selected >= 0) {
					m_pending_group_index = selected;
				} else {
					m_element_to_link = nullptr;
					return;
				}
			}
		}
		else
		{
			// Normal contact group selection for non-PLC masters
			const auto &groups = m_element_to_link->elementData().m_slave_contact_groups;
			if (!groups.isEmpty())
			{
				// Collect already-used group indices from the master
				QSet<int> used_indices;
				for (Element *linked : m_element_to_link->linkedElements()) {
					int idx = m_element_to_link->groupIndexForElement(linked);
					if (idx >= 0) {
						used_indices.insert(idx);
					}
				}

				ContactGroupSelectionDialog dlg(groups, used_indices,
					m_element->elementData(), this);
				if (dlg.exec() == QDialog::Accepted && dlg.selectedIndex() >= 0) {
					m_pending_group_index = dlg.selectedIndex();
				} else {
					m_element_to_link = nullptr;
					return;
				}
			}
		}
	}

	if(m_live_edit)
	{
		apply();
		updateUi();
	}
	else
	{
		//In no live edit mode, we set the background of the qtwi green,
		// to inform the user
		// which element will be linked when he press the apply button
		if (m_pending_qtwi)
		{
			for(int i=0 ; i<6 ; i++)
			{
				m_pending_qtwi->setBackground(i,
							      QBrush(
								      Qt::white,
								      Qt::NoBrush));
			}
		}

		for (int i=0 ; i<6 ; i++)
		{
			m_qtwi_at_context_menu->setBackground(i,
							      QBrush(Qt::green,
								     Qt::SolidPattern));
		}
		m_pending_qtwi = m_qtwi_at_context_menu;
	}

}

/**
	@brief LinkSingleElementWidget::hideButtons
	Hide the button displayed when element is already linked
*/
void LinkSingleElementWidget::hideButtons()
{
	ui->m_label->hide();
	ui->m_unlink_pb->hide();
	ui->m_show_linked_pb->hide();
	ui->m_show_this_pb->hide();
	ui->m_search_field->show();
}

/**
	@brief LinkSingleElementWidget::showButtons
	Show the button displayed when element is already linked
*/
void LinkSingleElementWidget::showButtons()
{
	ui->m_label->show();
	ui->m_unlink_pb->show();
	ui->m_show_linked_pb->show();
	ui->m_show_this_pb->show();
	ui->m_search_field->hide();
}

void LinkSingleElementWidget::headerCustomContextMenuRequested(
		const QPoint &pos)
{
	m_context_menu->clear();
	m_context_menu->addAction(m_save_header_state);
	m_context_menu->popup(ui->m_tree_widget->header()->mapToGlobal(pos));
}

void LinkSingleElementWidget::on_m_unlink_pb_clicked()
{
	m_unlink = true;
	
	if(m_live_edit)
	{
		apply();
		updateUi();
	}
	else
		buildTree();
}

/**
	@brief LinkSingleElementWidget::on_m_tree_widget_itemDoubleClicked
	Highlight the element represented by item
	@param item
	@param column
*/
void LinkSingleElementWidget::on_m_tree_widget_itemDoubleClicked(
		QTreeWidgetItem *item,
		int column)
{
	Q_UNUSED(column);
	
	if (m_showed_element)
	{
		disconnect(m_showed_element, SIGNAL(destroyed()),
			   this, SLOT(showedElementWasDeleted()));
		m_showed_element->setHighlighted(false);
	}
	
	Element *elmt = m_qtwi_elmt_hash.value(item);
	elmt->diagram()->showMe();
	elmt->setHighlighted(true);
	m_showed_element = elmt;
	connect(m_showed_element, SIGNAL(destroyed()),
		this, SLOT(showedElementWasDeleted()));
	
}

void LinkSingleElementWidget::on_m_tree_widget_customContextMenuRequested(
		const QPoint &pos)
{
	//add the size of the header to display the topleft of the QMenu
	//at the position of the mouse.
	//See doc about QWidget::customContextMenuRequested
	//section related to QAbstractScrollArea
	QPoint point = pos;
	point.ry()+=ui->m_tree_widget->header()->height();
	point = ui->m_tree_widget->mapToGlobal(point);
	
	m_context_menu->clear();
	
	if (ui->m_tree_widget->currentItem())
	{
		m_qtwi_at_context_menu = ui->m_tree_widget->currentItem();
		m_context_menu->addAction(m_link_action);
		m_context_menu->addAction(m_show_qtwi);
	}
	
	m_context_menu->addAction(m_show_element);
	m_context_menu->popup(point);
}

void LinkSingleElementWidget::on_m_show_linked_pb_clicked()
{
	if (!m_element->isFree())
	{
		Element *elmt = m_element->linkedElements().first();
		elmt->diagram()->showMe();
		elmt->setHighlighted(true);
	}
}

void LinkSingleElementWidget::on_m_show_this_pb_clicked()
{
	m_show_element->trigger();
}

/**
	@brief LinkSingleElementWidget::on_m_search_field_textEdited
	Search all items which match with arg1 and shows it,
	other items is hidden.
	If arg1 is empty, show all items.
	@param arg1
*/
void LinkSingleElementWidget::on_m_search_field_textEdited(const QString &arg1)
{
	//Show all items if arg1 is empty, if not hide all items
	foreach(QTreeWidgetItem *qtwi, m_qtwi_elmt_hash.keys())
		qtwi->setHidden(!arg1.isEmpty());
	
	QList <QTreeWidgetItem *> qtwi_list;
	
	foreach(QTreeWidgetItem *qtwi, m_qtwi_strl_hash.keys())
	{
		foreach(QString str, m_qtwi_strl_hash.value(qtwi))
		{
			if(str.contains(arg1, Qt::CaseInsensitive))
			{
				qtwi_list << qtwi;
				continue;
			}
		}
	}
	
	//Show items which match with arg1
	foreach(QTreeWidgetItem *qtwi, qtwi_list)
		qtwi->setHidden(false);
}
