/** \file
 * \brief Declaration of ogdf::SpringEmbedderKK.
 *
 * \author Karsten Klein
 *
 * \par License:
 * This file is part of the Open Graph Drawing Framework (OGDF).
 *
 * \par
 * Copyright (C)<br>
 * See README.md in the OGDF root directory for details.
 *
 * \par
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * Version 2 or 3 as published by the Free Software Foundation;
 * see the file LICENSE.txt included in the packaging of this file
 * for details.
 *
 * \par
 * This program 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.
 *
 * \par
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, see
 * http://www.gnu.org/copyleft/gpl.html
 */

#pragma once

#include <ogdf/basic/Graph.h>
#include <ogdf/basic/LayoutModule.h>
#include <ogdf/basic/basic.h>
#include <ogdf/basic/tuples.h>

#include <limits>

namespace ogdf {
class GraphAttributes;

//! The spring-embedder layout algorithm by Kamada and Kawai.
/**
 * @ingroup gd-energy
 *
 * The implementation used in SpringEmbedderKK is based on
 * the following publication:
 *
 * Tomihisa Kamada, Satoru Kawai: <i>%An Algorithm for Drawing
 * General Undirected Graphs</i>. Information Processing Letters 31, pp. 7-15, 1989.
 *
 * Precondition: The input graph has to be connected.
 * <H3>Optional parameters</H3>
 * There are some parameters that can be tuned to optimize the
 * algorithm's behavior regarding runtime and layout quality.
 * First of all note that the algorithm uses all pairs shortest path
 * to compute the graph theoretic distance. This can be done either
 * with BFS (ignoring node sizes) in quadratic time or by using
 * e.g. Floyd's algorithm in cubic time with given edge lengths
 * that may reflect the node sizes.  Also m_computeMaxIt decides
 * if the computation is stopped after a fixed maximum number of
 * iterations. The desirable edge length can either be set or computed
 * from the graph and the given layout.
 * Kamada-Kawai layout provides the following optional parameters.
 *
 * <table>
 *   <tr>
 *     <th><i>Option</i><th><i>Type</i><th><i>Default</i><th><i>Description</i>
 *   </tr><tr>
 *     <td><i>tolerance</i><td>int<td>0.0001
 *     <td>Tolerance for the energy level (below which the main loop stops).
 *   </tr>
 * </table>
 */
class OGDF_EXPORT SpringEmbedderKK : public LayoutModule {
public:
	using dpair = Tuple2<double, double>;

	//! Constructor: Constructs instance of Kamada Kawai Layout
	SpringEmbedderKK()
		: m_tolerance(0.001)
		, m_ltolerance(0.0001)
		, m_computeMaxIt(true)
		, m_K(5.0)
		, m_prevEnergy(startVal)
		, m_prevLEnergy(startVal)
		, m_zeroLength(-1.0)
		, m_desLength(0.0)
		, m_distFactor(2.0)
		, m_useLayout(true)
		, m_gItBaseVal(50)
		, m_gItFactor(16) {
		m_maxLocalIt = m_maxGlobalIt = maxVal;
	}

	//! Calls the layout algorithm for graph attributes \p GA.
	//! Currently, GA.doubleWeight is NOT used to allow simple
	//! distinction of BFS/APSS. Precondition: Graph is connected.
	virtual void call(GraphAttributes& GA) override;

	//! Calls the layout algorithm for graph attributes \p GA
	//! using values in eLength for distance computation.
	//! Precondition: Graph is connected.
	void call(GraphAttributes& GA, const EdgeArray<double>& eLength);

	//! Sets the value for the stop tolerance, below which the
	//! system is regarded stable (balanced) and the optimization stopped
	void setStopTolerance(double s) { m_tolerance = s; }

	//! If set to true, the given layout is used for the initial positions
	void setUseLayout(bool b) { m_useLayout = b; }

	bool useLayout() { return m_useLayout; }

	//! If set != 0, value zerolength is used to determine the
	//! desirable edge length by L = zerolength / max distance_ij.
	//! Otherwise, zerolength is determined using the node number and sizes.
	void setZeroLength(double d) { m_zeroLength = d; }

	double zeroLength() { return m_zeroLength; }

	//! Sets desirable edge length directly
	void setDesLength(double d) { m_desLength = d; }

	//! It is possible to limit the number of iterations to a fixed value
	//! Returns the current setting of iterations.
	//! These values are only used if m_computeMaxIt is set to true.
	int maxLocalIterations() const { return m_maxLocalIt; }

	void setGlobalIterationFactor(int i) {
		if (i > 0) {
			m_gItFactor = i;
		}
	}

	int maxGlobalIterations() const { return m_maxGlobalIt; }

	//! Sets the number of global iterations to \p i.
	void setMaxGlobalIterations(int i) {
		if (i > 0) {
			m_maxGlobalIt = i;
		}
	}

	//! Sets the number of local iterations to \p i.
	void setMaxLocalIterations(int i) {
		if (i > 0) {
			m_maxLocalIt = i;
		}
	}

	//! If set to true, number of iterations is computed depending on G
	void computeMaxIterations(bool b) { m_computeMaxIt = b; }
#if 0
	//We could add some noise to the computation

	//! Returns the current setting of nodes.
	bool noise() const {
		return m_noise;
	}

	//! Sets the parameter noise to \p on.
	void noise(bool on) {
		m_noise = on;
	}
#endif

protected:
	//! Does the actual call
	void doCall(GraphAttributes& GA, const EdgeArray<double>& eLength, bool simpleBFS);

	//! Checks if main loop is finished because local optimum reached
	bool finished(double maxdelta) {
		if (m_prevEnergy == startVal) // first step
		{
			m_prevEnergy = maxdelta;
			return false;
		}

#if 0
		double diff = m_prevEnergy - maxdelta; // energy difference
		if (diff < 0.0) diff = -diff;
#	ifdef OGDF_DEBUG
		std::cout << "Finished(): maxdelta: " << maxdelta << " diff/prev: " << diff / m_prevEnergy << std::endl;
#	endif
#endif
		// check if we want to stop
		bool done = (maxdelta < m_tolerance); // || (diff / m_prevEnergy) < m_tolerance);

		m_prevEnergy = maxdelta; // save previous energy level
		m_prevLEnergy = startVal; // reset energy level for local node decision

		return done;
	}

	//! Checks if inner loop (current node) is finished
	bool finishedNode(double deltav) {
		if (m_prevLEnergy == startVal) {
			m_prevLEnergy = deltav;
			return deltav == 0.0; // m_ltolerance; locally stable
		}
#if 0
#	ifdef OGDF_DEBUG
		std::cout << "Local delta: " << deltav << "\n";
#	endif
#endif
		double diff = m_prevLEnergy - deltav;
		// check if we want to stop
		bool done = deltav == 0.0 || diff / m_prevLEnergy < m_ltolerance;

		m_prevLEnergy = deltav; // save previous energy level

		return done;
	}

	//! Changes given edge lengths (interpreted as weight factors)
	//! according to additional parameters like node size etc.
	void adaptLengths(const Graph& G, const GraphAttributes& GA, const EdgeArray<double>& eLengths,
			EdgeArray<double>& adaptedLengths);

	//! Adapts positions to avoid degeneracy (all nodes on a single point)
	void shufflePositions(GraphAttributes& GA);

	//! Computes contribution of node u to the first partial
	//! derivatives (dE/dx_m, dE/dy_m) (for node m) (eq. 7 and 8 in paper)
	dpair computeParDer(node m, node u, GraphAttributes& GA, NodeArray<NodeArray<double>>& ss,
			NodeArray<NodeArray<double>>& dist);

	//! Compute partial derivative for v
	dpair computeParDers(node v, GraphAttributes& GA, NodeArray<NodeArray<double>>& ss,
			NodeArray<NodeArray<double>>& dist);

	//! Does the necessary initialization work for the call functions
	void initialize(GraphAttributes& GA, NodeArray<dpair>& partialDer,
			const EdgeArray<double>& eLength, NodeArray<NodeArray<double>>& oLength,
			NodeArray<NodeArray<double>>& sstrength, bool simpleBFS);

	//! Main computation loop, nodes are moved here
	void mainStep(GraphAttributes& GA, NodeArray<dpair>& partialDer,
			NodeArray<NodeArray<double>>& oLength, NodeArray<NodeArray<double>>& sstrength);

	//! Does the scaling if no edge lengths are given but node sizes
	//! are respected
	void scale(GraphAttributes& GA);

private:
	//! The stop criterion when the forces of all strings are
	//! considered to be balanced
	double m_tolerance; //!< value for stop criterion
	double m_ltolerance; //!< value for local stop criterion
	int m_maxGlobalIt; //!< Maximum number of global iterations
	int m_maxLocalIt; //!< Maximum number of local iterations
	bool m_computeMaxIt; //!< If true, number of iterations is computed depending on number of nodes
	double m_K; //! Big K constant for strength computation
	double m_prevEnergy; //!<max energy value
	double m_prevLEnergy; //!<local energy
	double m_zeroLength; //!< Length of a side of the display area, used for edge length computation if > 0
	double m_desLength; //!< Desirable edge length, used instead if > 0
	double m_distFactor; //< introduces some distance for scaling in case BFS is used
	bool m_useLayout; //!< use positions or allow to shuffle nodes to avoid degeneration
	int m_gItBaseVal; //!< minimum number of global iterations
	int m_gItFactor; //!< factor for global iterations: m_gItBaseVal+m_gItFactor*|V|

	static const double startVal;
	static const double minVal;
	static const double desMinLength; //!< Defines minimum desired edge length. Smaller values are treated as zero
	static const int maxVal; //!< defines infinite upper bound for iteration number

	double allpairsspBFS(const Graph& G, NodeArray<NodeArray<double>>& distance);
	double allpairssp(const Graph& G, const EdgeArray<double>& eLengths,
			NodeArray<NodeArray<double>>& distance,
			const double threshold = std::numeric_limits<double>::max());
};

#if 0
	//Things that potentially could be added

	//! Returns the page ratio.
	double pageRatio() { return m_pageRatio; }

	//! Sets the page ration to \p x.
	void pageRatio(double x) { m_pageRatio = x; }

	//! Returns the current scaling method.
	Scaling scaling() const {
		return m_scaling;
	}

	//! Sets the method for scaling the inital layout to \p sc.
	void scaling(Scaling sc) {
		m_scaling = sc;
	}

	//! Returns the current scale function factor.
	double scaleFunctionFactor() const {
		return m_scaleFactor;
	}

	//! Sets the scale function factor to \p f.
	void scaleFunctionFactor(double f) {
		m_scaleFactor = f;
	}

	//! Sets the user bounding box (used if scaling method is scUserBoundingBox).
	void userBoundingBox(double xmin, double ymin, double xmax, double ymax) {
		m_bbXmin = xmin;
		m_bbYmin = ymin;
		m_bbXmax = xmax;
		m_bbYmax = ymax;
	}
#endif
}
