// =====================================================================================================================
//  fennec, a free and open source game engine
//  Copyright © 2025 - 2026  Medusa Slockbower
//
//  This program 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 3 of the License, or
//  (at your option) any later version.
//
//  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.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
// =====================================================================================================================

///
/// \file fennec/containers/graph.h
/// \brief A header containing the definition for a graph of vertices connected by edges
///
///
/// \details
/// \author Medusa Slockbower
///
/// \copyright Copyright © 2025 - 2026 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html))
///
///

#ifndef FENNEC_CONTAINERS_GRAPH_H
#define FENNEC_CONTAINERS_GRAPH_H

#include <fennec/containers/dynarray.h>
#include <fennec/containers/list.h>
#include <fennec/containers/map.h>
#include <fennec/containers/object_pool.h>

/*
 * With the directed tree we were able to cheat a little, the structure has more rules to it which allows
 * tighter constraints. A graph is basically no rules whatsoever. Some variants, such as weighted graphs, assign
 * properties or rules to edges which can simply be an extension to this graph.
 *
 * The most effective way to do this is to have a dynarray of lists, however this results in double the
 * memory being used. This can also result in two edge objects being created.
 *
 * There is no nice way to avoid the problem of mapping vertices to edges
 */

namespace fennec
{

///
/// \brief Graph Data Structure, describes sets of arbitrarily connected vertices
///
/// \details
/// | Property    | Value           |
/// |:-----------:|:---------------:|
/// | stable      |        ⛔       |
/// | dynamic     |        ✅       |
/// | homogeneous |        ✅       |
/// | distinct    |        ⛔       |
/// | ordered     |        ⛔       |
/// | linear      |        ✅       |
/// | space       | \emph{O(N + M)} |
/// | access      |   \emph{O(1)}   |
/// | find        |   \emph{O(1)}   |
/// | insertion   |   \emph{O(1)}   |
/// | deletion    |   \emph{O(M)}   |
///
/// Graphs contain vertices and edges. Graphs are either directed
/// or undirected. This structure allows the creation of both directed and undirected edges. As
/// far as what that means; a directed graph means that edges have direction, where there are edges
/// that are "to" and "from," rather than "between" which is used in undirected graphs.
///
/// An undirected graph is connected if there is a path between every pair of vertices in the graph.
///
/// A directed graph is weakly connected if replacing all of its directed edges with undirected edges would
/// produce a connected graph. We will call this "disjointed"
///
/// A directed graph is semi-connected if there is a directed path p for \emph{u} &rarr; \emph{v} *or* \emph{v} &rarr; \emph{u} for every
/// pair of vertices \emph{[u, v]}. We will call this "unilateral"
///
/// A directed graph is strongly-connected if there is a directed path p for \emph{u} &rarr; \emph{v} *and* \emph{v} &rarr; \emph{u} for every pair
/// of vertices \emph{[u, v]}. We will call this "connected"
///
/// \tparam VertexT The type associated with each vertex
/// \tparam EdgeT The type associated with each edge
template<typename VertexT, typename EdgeT = empty_t>
struct graph {
public:
// Definitions =========================================================================================================

	/// \name Definitions
	/// @{

	using edge_t        = EdgeT;                         //!< Alias for the edge type
	using vertex_t      = VertexT;                       //!< Alias for the vertex type
	using vertex_pool_t = object_pool<vertex_t>;         //!< Alias for a pool of vertices
	using edge_map_t    = dynarray<map<size_t, size_t>>; //!< Alias for edge mapping
	using edge_pool_t   = object_pool<edge_t>;           //!< Alias for a pool of edges

	/// @}


	/// \name Constants
	/// @{

	static constexpr size_t npos = -1; //!< Constant for a non-existent vertex

	/// @}


// Constructors ========================================================================================================

	/// \name Constructors & Destructor
	/// @{

	///
	/// \brief Default Constructor, initializes empty graph
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr graph() = default;

	///
	/// \brief Destructor
	///
	/// \par Complexity
	/// \math{\textbf{O(N} + \textbf{M)}}
	///
	constexpr ~graph() = default;

	/// @}


	/// \name Assignment Operators
	/// @{

	///
	/// \brief Copy Assignment Operator
	/// \param g The graph to copy
	/// \returns A reference to this after assigning g
	///
	/// \par Complexity
	/// \math{\textbf{O(N} + \textbf{M)}}
	///
	constexpr graph& operator=(const graph& g) = default;

	///
	/// \brief Move Assignment Operator
	/// \param g The graph to copy
	/// \returns A reference to this after assigning g
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr graph& operator=(graph&& g) = default;

	/// @}


// Properties ==========================================================================================================

	/// \name Properties
	/// @{

	///
	/// \returns The number of vertices in the graph
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr size_t num_vertices() const {
		return _vertex_pool.size();
	}

	///
	/// \returns The number of edges in the graph
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr size_t num_edges() const {
		return _edge_pool.size();
	}

	///
	/// \returns The capacity of the vertex pool
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr size_t capacity() const {
		return _vertex_pool.capacity();
	}

	///
	/// \returns \emph{true} when there are no vertices in the graph, \emph{false} otherwise
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr bool is_empty() const {
		return num_vertices() == 0;
	}

	///
	/// \brief Checks if there exists an edge \math{e} that starts from \emph{a} and ends at \emph{b}
	/// \param a The first vertex
	/// \param b The second vertex
	/// \returns \emph{true} if the edge exists, \emph{false} otherwise
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr bool exists(size_t a, size_t b) const {
		return _edge_map[a][b] != nullptr;
	}

	///
	/// \brief Checks if there exists an edge \math{e_0} that starts from \emph{a} and ends at \emph{b} and \math{e_1} that starts from \emph{b}
	///        and ends at \emph{a}
	/// \param a The first vertex
	/// \param b The second vertex
	/// \returns \emph{true} if both edges exist, \emph{false} otherwise
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr bool is_symmetric(size_t a, size_t b) const {
		return exists(a, b) and exists(b, a);
	}

	///
	/// \brief Checks if there exists an edge \math{e} between \emph{a} and \emph{b}
	/// \param a The first vertex
	/// \param b The second vertex
	/// \returns \emph{true} if both edges exist, \emph{false} otherwise
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr bool is_undirected(size_t a, size_t b) const {
		const auto* e0 = _edge_map[a][b];
		const auto* e1 = _edge_map[b][a];
		if (not (e0 != nullptr && e1 != nullptr)) {
			return false;
		}
		return *e0 == *e1;
	}

	// TODO: connected, disjoint, unilateral, get_component

	/// @}


// Access ==============================================================================================================

	/// \name Access
	/// @{

	///
	/// \brief vertex Access Operator
	/// \param vertex The id of the vertex
	/// \returns A reference to the value stored in the vertex
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr vertex_t& operator[](size_t vertex) {
		return _vertex_pool[vertex];
	}

	///
	/// \brief vertex Const Access Operator
	/// \param vertex The id of the vertex
	/// \returns A reference to the value stored in the vertex
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr const vertex_t& operator[](size_t vertex) const {
		return _vertex_pool[vertex];
	}

	///
	/// \brief edge Access Operator
	/// \param a The id of the first vertex
	/// \param b The id of the second vertex
	/// \returns A pointer to the value stored in the edge, \emph{nullptr} if not found
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr edge_t* operator[](size_t a, size_t b) {
		if (is_empty()) {
			return nullptr;
		}
		edge_t* it = _edge_map[a][b];
		if (it) {
			return _edge_pool[*it];
		}
		return nullptr;
	}

	///
	/// \brief edge Const Access Operator
	/// \param a The id of the first vertex
	/// \param b The id of the second vertex
	/// \returns A const-qualified pointer to the value stored in the edge, \emph{nullptr} if not found
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr const edge_t* operator[](size_t a, size_t b) const {
		if (is_empty()) {
			return nullptr;
		}
		const edge_t* it = _edge_map[a][b];
		if (it) {
			return _edge_pool[*it];
		}
		return nullptr;
	}

	///
	/// \brief Getter for a list of vertices \emph{X} that \emph{vertex} has an edge to \emph{X...}
	/// \param vertex The id of the vertex
	/// \returns A list containing all vertices \emph{X} with edges from \emph{vertex} to \emph{X...}
	///
	/// \par Complexity
	/// \emph{O(M)}
	///
	list<size_t> outgoing(size_t vertex) {
		list<size_t> res;
		if (is_empty() || vertex >= _edge_map.size()) {
			return res;
		}
		for (const auto& it : _edge_map[vertex]) {
			res.push_back(it.first);
		}
		return res;
	}

	///
	/// \brief Getter for a list of vertices \emph{X} that \emph{vertex} has an edge from \emph{X...}
	/// \param vertex The id of the vertex
	/// \returns A list containing all vertices \emph{X} with edges from \emph{X...} to \emph{vertex}
	///
	/// \par Complexity
	/// \emph{O(M)}
	///
	list<size_t> incoming(size_t vertex) {
		list<size_t> res;
		if (is_empty() || vertex >= _edge_map.size()) {
			return res;
		}
		for (size_t n = 0; n < _edge_map.size(); ++n) {
			if (_edge_map[n][vertex]) {
				res.push_back(n);
			}
		}
		return res;
	}

	///
	/// \brief Getter for a list of vertices \emph{X} that \emph{vertex} has an edge to and from \emph{X...}
	/// \param vertex The id of the vertex
	/// \returns A list containing all vertices \emph{X} that have symmetric edges with \emph{vertex}
	///
	/// \par Complexity
	/// \emph{O(M)}
	///
	list<size_t> symmetric(size_t vertex) {
		list<size_t> res;
		if (is_empty() || vertex >= _edge_map.size()) {
			return res;
		}
		for (const auto& it : _edge_map[vertex]) {
			if (_edge_map[it.first][vertex]) {
				res.push_back(it.first);
			}
		}
		return res;
	}

	///
	/// \brief Getter for a list of vertices \emph{X} that \emph{vertex} has an edge to and from \emph{X...} and share the same value
	/// \details
	/// "Joined" edges may also be referred to as "undirected." A joined, or undirected, edge may be
	/// turned into a directed edge by changing the weight object associated with the edge, or by
	/// removing one of the sub-edges.
	/// \param vertex The id of the vertex
	/// \returns A list containing all vertices \emph{X} that have symmetric edges with \emph{vertex}
	///
	/// \par Complexity
	/// \emph{O(M)}
	///
	list<size_t> undirected(size_t vertex) {
		list<size_t> res;
		if (is_empty() || vertex >= _edge_map.size()) {
			return res;
		}
		for (const auto& it : _edge_map[vertex]) {
			const auto* at = _edge_map[it.first][vertex];
			if (at != nullptr && *at == it.second) {
				res.push_back(it.first);
			}
		}
		return res;
	}

	///
	/// \brief Getter for the internal storage of mapped edges from this vertex.
	///        Use this when you want to iterate over edges that start from this vertex.
	/// \param vertex The id of the vertex
	/// \returns A pointer to a map containing edges mapped from this vertex
	///
	/// \par Complexity
	/// \emph{O(M)}
	///
	const auto* edges(size_t vertex) {
		if (is_empty() || vertex >= _edge_map.size()) {
			return nullptr;
		}
		return &_edge_map[vertex];
	}

	/// @}


// Modifiers ===========================================================================================================

	/// \name Modifiers
	/// @{

	///
	/// \brief Move a new vertex into the graph
	/// \param vertex The vertex to move into the graph
	/// \returns The id of the new vertex
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr size_t insert(vertex_t&& vertex) {
		return this->_insert(fennec::forward<vertex_t>(vertex));
	}

	///
	/// \brief Copy a new vertex into the graph
	/// \param vertex The vertex to copy into the graph
	/// \returns The id of the new vertex
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr size_t insert(const vertex_t& vertex) {
		return this->_insert(vertex);
	}

	///
	/// \brief Construct a new vertex in the graph
	/// \tparam ArgsT The types of the arguments
	/// \param args The arguments to construct the vertex with
	/// \returns The id of the new vertex
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	template<typename...ArgsT>
	constexpr size_t emplace(ArgsT&&...args) {
		return this->_insert(fennec::forward<ArgsT>(args)...);
	}

	///
	/// \brief Erase a vertex from the graph
	/// \param vertex The id of the vertex to erase
	///
	/// \par Complexity
	/// \emph{O(M)}
	///
	constexpr void erase(size_t vertex) {
		cut(vertex);
		_vertex_pool.erase(vertex);
	}

	///
	/// \brief Form an edge from vertex \emph{a} to vertex \emph{b}
	/// \tparam ArgsT The argument types
	/// \param a The first vertex id
	/// \param b The second vertex id
	/// \param args The arguments to construct the edge with
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	template<typename...ArgsT>
	constexpr void make_edge(size_t a, size_t b, ArgsT&&...args) {
		if (a == b) {
			return;
		}

		if (_edge_map.size() < _vertex_pool.capacity()) {
			_edge_map.resize(_vertex_pool.capacity());
		}

		const auto it = _edge_map[a][b];
		size_t conn;
		if (it != nullptr) {
			conn = *it;
			_edge_pool[conn] = vertex_t(fennec::forward<ArgsT>(args)...);
		} else {
			conn = _edge_pool.emplace(fennec::forward<ArgsT>(args)...);
		}
		_edge_map[a].emplace(b, conn);
	}

	///
	/// \brief Form an undirected edge between vertex \emph{a} and vertex \emph{b}
	/// \tparam ArgsT The argument types
	/// \param a The first vertex id
	/// \param b The second vertex id
	/// \param args The arguments to construct the edge with
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	template<typename...ArgsT>
	constexpr void make_edge2(size_t a, size_t b, ArgsT&&...args) {
		if (a == b) {
			return;
		}

		if (_edge_map.size() < _vertex_pool.capacity()) {
			_edge_map.resize(_vertex_pool.capacity());
		}

		const auto it = _edge_map[a][b];
		size_t conn;
		if (it != nullptr) {
			conn = *it;
			_edge_pool[conn] = vertex_t(fennec::forward<ArgsT>(args)...);
		} else {
			conn = _edge_pool.emplace(fennec::forward<ArgsT>(args)...);
		}

		_edge_map[a].emplace(b, conn);
		_edge_map[b].emplace(a, conn);
	}

	///
	/// \brief Disconnect an edge from vertex \emph{a} to vertex \emph{b}
	/// \param a The first vertex id
	/// \param b The second vertex id
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr void cut_edge(size_t a, size_t b) {

		// Find the edge object
		const auto* it = _edge_map[a][b];
		if (not it) {
			return;
		}
		size_t    c = *it;

		// Check if undirected
		const auto* at = _edge_map[b][a];
		if (not at || *at != c) {
			_edge_pool.erase(c);
		}

		// Erase the edge mapping
		_edge_map[a].erase(b);
	}

	///
	/// \brief Disconnect both directed edges between vertices \emph{a} and \emph{b}
	/// \param a The first vertex id
	/// \param b The second vertex id
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr void cut_edge2(size_t a, size_t b) {
		const auto* ita = _edge_map[a][b];
		const auto* itb = _edge_map[a][b];
		if (not (ita || itb)) {
			return;
		}
		if (ita) _edge_pool.erase(*ita);
		if (itb) _edge_pool.erase(*itb);
		_edge_map[a].erase(b);
		_edge_map[b].erase(a);
	}

	///
	/// \brief Break *all* edges connected to \emph{n}
	/// \param n The vertex id
	///
	/// \par Complexity
	/// \emph{O(M)}
	///
	void cut(size_t n) {
		for (const auto it : outgoing(n)) {
			cut_edge(n, it);
		}
		for (const auto it : incoming(n)) {
			cut_edge(it, n);
		}
	}

	///
	/// \brief Clear the graph, destructing all vertices and edges.
	///
	/// \par Complexity
	/// \emph{O(N + M)}
	///
	void clear() {
		_vertex_pool.clear();
		_edge_pool.clear();
		_edge_map.clear();
	}

	/// @}


// edges =========================================================================================================


private:
	vertex_pool_t _vertex_pool;
	edge_pool_t   _edge_pool;
	edge_map_t    _edge_map;

	template<typename...ArgsT>
	size_t _insert(ArgsT&&...args) {
		return _vertex_pool.emplace(fennec::forward<ArgsT>(args)...);
	}

};

}

#endif // FENNEC_CONTAINERS_GRAPH_H