#pragma once
#include <QString>
#include <QMap>
#include <QStringList>

template <char Tseparator>
class SeparatorPrefixTree
{
public:
	SeparatorPrefixTree(QStringList paths)
	{
		insert(paths);
	}

	SeparatorPrefixTree(bool contained = false)
	{
		m_contained = contained;
	}

	void insert(QStringList paths)
	{
		for(auto &path: paths)
		{
			insert(path);
		}
	}

	/// insert an exact path into the tree
	SeparatorPrefixTree & insert(QString path)
	{
		auto sepIndex = path.indexOf(Tseparator);
		if(sepIndex == -1)
		{
			children[path] = SeparatorPrefixTree(true);
			return children[path];
		}
		else
		{
			auto prefix = path.left(sepIndex);
			if(!children.contains(prefix))
			{
				children[prefix] = SeparatorPrefixTree(false);
			}
			return children[prefix].insert(path.mid(sepIndex + 1));
		}
	}

	/// is the path fully contained in the tree?
	bool contains(QString path) const
	{
		auto node = find(path);
		return node != nullptr;
	}

	/// does the tree cover a path? That means the prefix of the path is contained in the tree
	bool covers(QString path) const
	{
		// if we found some valid node, it's good enough. the tree covers the path
		if(m_contained)
		{
			return true;
		}
		auto sepIndex = path.indexOf(Tseparator);
		if(sepIndex == -1)
		{
			auto found = children.find(path);
			if(found == children.end())
			{
				return false;
			}
			return (*found).covers(QString());
		}
		else
		{
			auto prefix = path.left(sepIndex);
			auto found = children.find(prefix);
			if(found == children.end())
			{
				return false;
			}
			return (*found).covers(path.mid(sepIndex + 1));
		}
	}

	/// return the contained path that covers the path specified
	QString cover(QString path) const
	{
		// if we found some valid node, it's good enough. the tree covers the path
		if(m_contained)
		{
			return QString("");
		}
		auto sepIndex = path.indexOf(Tseparator);
		if(sepIndex == -1)
		{
			auto found = children.find(path);
			if(found == children.end())
			{
				return QString();
			}
			auto nested = (*found).cover(QString());
			if(nested.isNull())
			{
				return nested;
			}
			if(nested.isEmpty())
				return path;
			return path + Tseparator + nested;
		}
		else
		{
			auto prefix = path.left(sepIndex);
			auto found = children.find(prefix);
			if(found == children.end())
			{
				return QString();
			}
			auto nested = (*found).cover(path.mid(sepIndex + 1));
			if(nested.isNull())
			{
				return nested;
			}
			if(nested.isEmpty())
				return prefix;
			return prefix + Tseparator + nested;
		}
	}

	/// Does the path-specified node exist in the tree? It does not have to be contained.
	bool exists(QString path) const
	{
		auto sepIndex = path.indexOf(Tseparator);
		if(sepIndex == -1)
		{
			auto found = children.find(path);
			if(found == children.end())
			{
				return false;
			}
			return true;
		}
		else
		{
			auto prefix = path.left(sepIndex);
			auto found = children.find(prefix);
			if(found == children.end())
			{
				return false;
			}
			return (*found).exists(path.mid(sepIndex + 1));
		}
	}

	/// find a node in the tree by name
	const SeparatorPrefixTree * find(QString path) const
	{
		auto sepIndex = path.indexOf(Tseparator);
		if(sepIndex == -1)
		{
			auto found = children.find(path);
			if(found == children.end())
			{
				return nullptr;
			}
			return &(*found);
		}
		else
		{
			auto prefix = path.left(sepIndex);
			auto found = children.find(prefix);
			if(found == children.end())
			{
				return nullptr;
			}
			return (*found).find(path.mid(sepIndex + 1));
		}
	}

	/// is this a leaf node?
	bool leaf() const
	{
		return children.isEmpty();
	}

	/// is this node actually contained in the tree, or is it purely structural?
	bool contained() const
	{
		return m_contained;
	}

	/// Remove a path from the tree
	bool remove(QString path)
	{
		return removeInternal(path) != Failed;
	}

	/// Clear all children of this node tree node
	void clear()
	{
		children.clear();
	}

	QStringList toStringList() const
	{
		QStringList collected;
		// collecting these is more expensive.
		auto iter = children.begin();
		while(iter != children.end())
		{
			QStringList list = iter.value().toStringList();
			for(int i = 0; i < list.size(); i++)
			{
				list[i] = iter.key() + Tseparator + list[i];
			}
			collected.append(list);
			if((*iter).m_contained)
			{
				collected.append(iter.key());
			}
			iter++;
		}
		return collected;
	}
private:
	enum Removal
	{
		Failed,
		Succeeded,
		HasChildren
	};
	Removal removeInternal(QString path = QString())
	{
		if(path.isEmpty())
		{
			if(!m_contained)
			{
				// remove all children - we are removing a prefix
				clear();
				return Succeeded;
			}
			m_contained = false;
			if(children.size())
			{
				return HasChildren;
			}
			return Succeeded;
		}
		Removal remStatus = Failed;
		QString childToRemove;
		auto sepIndex = path.indexOf(Tseparator);
		if(sepIndex == -1)
		{
			childToRemove = path;
			auto found = children.find(childToRemove);
			if(found == children.end())
			{
				return Failed;
			}
			remStatus = (*found).removeInternal();
		}
		else
		{
			childToRemove = path.left(sepIndex);
			auto found = children.find(childToRemove);
			if(found == children.end())
			{
				return Failed;
			}
			remStatus = (*found).removeInternal(path.mid(sepIndex + 1));
		}
		switch (remStatus)
		{
			case Failed:
			case HasChildren:
			{
				return remStatus;
			}
			case Succeeded:
			{
				children.remove(childToRemove);
				if(m_contained)
				{
					return HasChildren;
				}
				if(children.size())
				{
					return HasChildren;
				}
				return Succeeded;
			}
		}
		return Failed;
	}

private:
	QMap<QString,SeparatorPrefixTree<Tseparator>> children;
	bool m_contained = false;
};