//
// Created by Maddie on 7/11/2024.
//

#ifndef DIRECTEDGRAPH_H
#define DIRECTEDGRAPH_H

#include <deque>
#include <vector>

template<typename T>
class DirectedGraph
{
public:
	// Typedefs ========================================================================================================

	using DataType = T;
	using Node = uint32_t;
	using NodeQueue = std::deque<Node>;

private:
	// Data Structures =================================================================================================

	struct Director
	{
		enum Flags
		{
			VALID = 0b00000000000000000000000000000001
		};

		Node Parent, Child, Sibling;
		uint32_t Flags;

		Director() : Parent(0), Child(0), Sibling(0), Flags(VALID) { }
	};

	using Hierarchy = std::vector<Director>;
	using Storage = std::vector<DataType>;

public:
	// Functions =======================================================================================================

	DirectedGraph() : Graph{ Director() }, Data{ DataType() }, Freed{ } { }

	[[nodiscard]] Node Parent(Node node) const { return Graph[node].Parent; }
	[[nodiscard]] Node FirstChild(Node node) const { return Graph[node].Child; }
	[[nodiscard]] Node NextSibling(Node node) const { return Graph[node].Sibling; }

	[[nodiscard]] Node LeftMost(Node node) const
	{
		Node current = node;
		while(node = FirstChild(current)) current = node;
		return current;
	}

	[[nodiscard]] uint32_t Depth(Node node) const
	{
		uint32_t depth = 0;
		while (node)
		{
			node = Parent(node);
			++depth;
		}
		return depth;
	}

	Node Insert(const DataType& data, Node parent)
	{
		if(Freed.empty())
		{
			Freed.push_back(static_cast<Node>(Graph.size()));
			Graph.push_back(Director()); Data.push_back(DataType());
		}

		Node next = Freed.front(); Freed.pop_front();
		Director& pnode = Graph[parent];
		Director& node = Graph[next];

		// Setup Node
		node.Parent = parent;
		node.Sibling = pnode.Child;
		node.Child = 0;
		node.Flags = Director::VALID;

		// Set parent's child
		pnode.Child = next;

		Data[next] = data;

		return next;
	}

	void Erase(Node node)
	{
		if(node == 0) return;

		Director& erased = Graph[node];
		erased.Flags &= ~Director::VALID;
		Freed.push_back(node);

		Graph[erased.Parent].Child = erased.Sibling;

		NodeQueue stack{ erased.Child };

		while(stack.empty() == false)
		{
			Node next = stack.front(); stack.pop_front();
			Director& child = Graph[next];
			child.Flags &= ~Director::VALID;
			Freed.push_back(next);

			if(child.Sibling) stack.push_front(child.Sibling);
			if(child.Child) stack.push_front(child.Child);
		}
	}

	DataType& operator[](Node node) { return Data[node]; }
	[[nodiscard]] const DataType& operator[](Node node) const { return Data[node]; }

	template<typename V, typename O>
	void Traverse(V& visitor)
	{
		Traverser<V, O> traverser(*this, visitor);
		traverser();
	}

private:
	// Variables =======================================================================================================

	Hierarchy Graph;
	Storage   Data;
	NodeQueue Freed;

public:
	// Navigation ======================================================================================================

	friend class BreadthFirst;
	class BreadthFirst
	{
	public:
		BreadthFirst(DirectedGraph& graph) : Graph(graph), VisitQueue(0) { }

		Node operator()(Node node)
		{
			node = VisitQueue.back(); VisitQueue.pop_back();
			Director& current = Graph.Graph[node];

			if(current.Sibling) VisitQueue.push_back(current.Sibling);
			if(current.Child) VisitQueue.push_front(current.Child);

			if(VisitQueue.empty()) return 0;
			return node;
		}

	private:
		DirectedGraph& Graph;
		NodeQueue      VisitQueue;
	};

	friend class PreOrder;
	class PreOrder
	{
	public:
		PreOrder(DirectedGraph& graph) : Graph(graph) { }

		Node operator()(Node node)
		{
			Director& current = Graph.Graph[node];

			if(current.Sibling) VisitQueue.push_front(current.Sibling);
			if(current.Child) VisitQueue.push_front(current.Child);

			if(VisitQueue.empty()) return 0;
			Node next = VisitQueue.front(); VisitQueue.pop_front();
			return next;
		}

	private:
		DirectedGraph& Graph;
		NodeQueue      VisitQueue;
	};

	friend class InOrder;
	class InOrder
	{
	public:
		InOrder(DirectedGraph& graph) : Graph(graph) { }

		Node operator()(Node node)
		{
			if(node == 0) VisitQueue.push_back(Graph.LeftMost(node));

			node = VisitQueue.front(); VisitQueue.pop_front();
			Director& current = Graph.Graph[node];

			if(current.Sibling)
			{
				if(Graph.NextSibling(current.Sibling)) VisitQueue.push_back(current.Parent);
				VisitQueue.push_back(Graph.LeftMost(current.Sibling));
			}

			return node;
		}

	private:
		DirectedGraph& Graph;
		NodeQueue      VisitQueue;
	};

	friend class PostOrder;
	class PostOrder
	{
	public:
		PostOrder(DirectedGraph& graph) : Graph(graph) { }

		Node operator()(Node node)
		{
			if(VisitQueue.empty()) VisitQueue.push_back(Graph.LeftMost(node));

			node = VisitQueue.front(); VisitQueue.pop_front();
			if(node == 0) return node;
			Director& current = Graph.Graph[node];

			VisitQueue.push_back(current.Sibling ? Graph.LeftMost(current.Sibling) : Graph.Parent(node));

			return node;
		}

	private:
		DirectedGraph& Graph;
		NodeQueue      VisitQueue;
	};

	template<typename V, typename O>
	class Traverser
	{
	public:
		using VisitorType = V;
		using OrderType = O;

		Traverser(DirectedGraph& graph, VisitorType& visitor) : Graph(graph), Visitor(visitor), Order(graph) { }

		void operator()()
		{
			Node node = 0;
			while(node = Order(node))
			{
				if(Visitor(Graph[node], node)) break;
			}
		}

	private:
		DirectedGraph& Graph;
		VisitorType&   Visitor;
		OrderType      Order;
	};
};

#endif //DIRECTEDGRAPH_H
