/*
	Copyright 2006-2023 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 "elementsmover.h"

#include "conductorautonumerotation.h"
#include "diagram.h"
#include "qetgraphicsitem/conductor.h"
#include "diagramcommands.h"
#include "qetgraphicsitem/conductortextitem.h"
#include "qetgraphicsitem/diagramimageitem.h"
#include "qetgraphicsitem/dynamicelementtextitem.h"
#include "qetgraphicsitem/element.h"
#include "qetgraphicsitem/elementtextitemgroup.h"
#include "qetgraphicsitem/independenttextitem.h"
#include "undocommand/addgraphicsobjectcommand.h"
#include "qetapp.h"
#include "qetdiagrameditor.h"

/**
	@brief ElementsMover::ElementsMover Constructor
*/
ElementsMover::ElementsMover(){}

/**
	@brief ElementsMover::~ElementsMover Destructor
*/
ElementsMover::~ElementsMover(){}

/**
	@brief ElementsMover::isReady
	@return True if this element mover is ready to be used.
	A element mover is ready when the previous managed movement is finish.
*/
bool ElementsMover::isReady() const
{
	return(!m_movement_running);
}

/**
	@brief ElementsMover::beginMovement
	Start a new movement
	@param diagram diagram where the movement is applied
	@param driver_item item moved by mouse and don't be moved by Element mover
	@return the numbers of items to be moved or -1 if movement can't be init.
*/
int ElementsMover::beginMovement(Diagram *diagram, QGraphicsItem *driver_item)
{
		// They must be no movement in progress
	if (m_movement_running) return(-1);

		// Be sure we have diagram to work
	if (!diagram) return(-1);
	m_diagram = diagram;

	if (!diagram->views().isEmpty()) {
		const auto qde = QETApp::diagramEditorAncestorOf(diagram->views().at(0));
		if (qde) {
			m_status_bar = qde->statusBar();
		}
	} else {
		m_status_bar.clear();
	}

		// Take count of driver item
	m_movement_driver = driver_item;

		// At the beginning of movement, move is NULL
	m_current_movement -= m_current_movement;

	m_moved_content = DiagramContent(diagram);
	m_moved_content.removeNonMovableItems();

        //Remove element text and text group, if the parent element is selected.
    const auto element_text{m_moved_content.m_element_texts};
    for(const auto &deti : element_text) {
		if(m_moved_content.m_elements.contains(deti->parentElement())) {
			m_moved_content.m_element_texts.remove(deti);
		}
    }
    const auto element_text_group{m_moved_content.m_texts_groups};
    for(const auto &etig : element_text_group) {
		if (m_moved_content.m_elements.contains(etig->parentElement())) {
			m_moved_content.m_texts_groups.remove(etig);
		}
	}

	if (!m_moved_content.count()) return(-1);

	/* At this point, we've got all info to manage movement.
	 * There is now a move in progress */
	m_movement_running = true;

	return(m_moved_content.count());
}

/**
	@brief ElementsMover::continueMovement
	Add a move to the current movement.
	@param movement movement to applied
*/
void ElementsMover::continueMovement(const QPointF &movement)
{
	if (!m_movement_running || movement.isNull()) return;

	m_current_movement += movement;

		//Move every movable item, except conductor
	typedef DiagramContent dc;
	for (auto &qgi : m_moved_content.items(dc::Elements
										   | dc::TextFields
										   | dc::Images
										   | dc::Shapes
										   | dc::ElementTextFields
										   | dc::TextGroup
										   | dc::ConductorsToMove))
	{
		if (qgi == m_movement_driver)
			continue;
		qgi->setPos(qgi->pos() + movement);
	}

	// Move some conductors
	for (auto &conductor : m_moved_content.m_conductors_to_update)
	{
#if TODO_LIST
#pragma message("@TODO fix this problem correctly, probably we must see conductor class.")
#endif
			//Due to a weird behavior, we must ensure that the position of the conductor is (0,0).
			//If not, in some unknown case the function QGraphicsScene::itemsBoundingRect() return a rectangle
			//that take into account the pos() of the conductor, even if the bounding rect returned by the conductor is not in the pos().
			//For the user this situation appears when the top right of the folio is not at the top right of the graphicsview,
			//but displaced to the right and/or bottom.

	//@TODO fix this problem correctly, probably we must see conductor class.
//		if (c->pos() != QPointF(0,0)) { //<- they work, but the conductor text return to its original pos when the pos is set by user and not auto
//			c->setPos(0,0);				// because set the pos to 0,0 so text move to, and after call updatePath but because text pos is user defined
//		}								// we don't move it.
		conductor->updatePath();
	}

    if (m_status_bar && m_movement_driver)
	{
		const auto point_{m_movement_driver->scenePos()};
		m_status_bar->showMessage(QString("x %1 : y %2").arg(QString::number(point_.x()), QString::number(point_.y())));
	}
}

/**
	@brief ElementsMover::endMovement
	Ended the current movement by creating an undo added to the undostack of the diagram.
	If there is only one element moved, we try to auto-connect new conductor from this element
	and other possible element.
*/
void ElementsMover::endMovement()
{
		// A movement must be inited
	if (!m_movement_running) return;

		//empty command to be used has parent of commands below
	QUndoCommand *undo_object{new QUndoCommand()};

		//Create undo move if there is a movement
	if (!m_current_movement.isNull()) {
		QUndoCommand *quc{new MoveElementsCommand(m_diagram, m_moved_content, m_current_movement, undo_object)};
		undo_object->setText(quc->text());
	}

		//There is only one element moved, and project authorize auto conductor,
		//we try auto connection of conductor;
	typedef DiagramContent dc;
	if (m_moved_content.items(dc::TextFields
							  | dc::Images
							  | dc::Shapes).isEmpty()
		&& m_moved_content.items(dc::Elements).size() == 1
		&& m_diagram->project()->autoConductor())
	{
		const Element *elmt{m_moved_content.m_elements.first()};
		const auto aligned_free_terminals{elmt->AlignedFreeTerminals()};

		if (const int acc = aligned_free_terminals.size())
		{
			for (const auto &pair : aligned_free_terminals)
			{
				Conductor *conductor{new Conductor(pair.first, pair.second)};

					//Create an undo object for each new auto conductor, with undo_object for parent
				new AddGraphicsObjectCommand(conductor, m_diagram, QPointF(), undo_object);
				if (undo_object->text().isEmpty())
					undo_object->setText(QObject::tr("Ajouter %n conducteur(s)", "add a numbers of conductor one or more", acc));

					//Get all conductors at the same potential of conductor
				const auto conductors_list{conductor->relatedPotentialConductors()};

					//Compare the properties of every conductors stored in conductors_list,
					//if every conductors properties is equal, we use this properties for conductor.
				ConductorProperties others_properties;
				bool use_properties = false;
				if (!conductors_list.isEmpty())
				{
					use_properties = true;
					others_properties = (*conductors_list.cbegin())->properties();
					for (const auto &cond :  conductors_list)
						if (cond->properties() != others_properties)
							use_properties = false;
				}

				if (use_properties)
					conductor->setProperties(others_properties);
				else
				{
					conductor -> setProperties(m_diagram -> defaultConductorProperties);
					//Autonum the new conductor, the undo command associated for this, have for parent undo_object
					ConductorAutoNumerotation can  (conductor, m_diagram, undo_object);
					can.numerate();
				}
			}
		}
	}

		//Add undo_object if have child
	if (undo_object->childCount())
		m_diagram->undoStack().push(undo_object);
	else
		delete undo_object;

		// There is no movement in progress now
	m_movement_running = false;
	m_moved_content.clear();

	if (m_status_bar) {
		m_status_bar->clearMessage();
	}
}
