#pragma once

#include <map>
#include <stack>

#include "BinTrees.h"

namespace bTree
{

	// Takes vector of triangles and creates vector of vertices and vector of indices
	// It is like input array is for glDrawArray(), output is for glDrawElements()
	inline void TriangleArrayToTriangleList(const std::vector<Triangle> &in_triangles, std::vector<Vector3> &out_vertices, std::vector<unsigned int> &out_indices)
	{
		std::map<Vector3, int> vertices_map;		// Maps vertex to index
		for (auto iter = in_triangles.begin(); iter != in_triangles.end(); iter++)
		{
			std::map<Vector3, int>::iterator vertices_map_find;
			Vector3 v;
			// First vertex
			v = iter->v1;
			vertices_map_find = vertices_map.find(v);
			if (vertices_map_find == vertices_map.end())
			{
				vertices_map_find = vertices_map.insert(std::pair<Vector3, int>(v, (int)out_vertices.size())).first;
				out_vertices.push_back(v);
			}
			out_indices.push_back(vertices_map_find->second);
			// Second vertex
			v = iter->v2;
			vertices_map_find = vertices_map.find(v);
			if (vertices_map_find == vertices_map.end())
			{
				vertices_map_find = vertices_map.insert(std::pair<Vector3, int>(v, (int)out_vertices.size())).first;
				out_vertices.push_back(v);
			}
			out_indices.push_back(vertices_map_find->second);
			// Third vertex
			v = iter->v3;
			vertices_map_find = vertices_map.find(v);
			if (vertices_map_find == vertices_map.end())
			{
				vertices_map_find = vertices_map.insert(std::pair<Vector3, int>(v, (int)out_vertices.size())).first;
				out_vertices.push_back(v);
			}
			out_indices.push_back(vertices_map_find->second);
		}
		out_vertices.shrink_to_fit();
	}

	template <
		class TreeNodeType,
		class TreeBuilder,
		class ItemType,
		class ItemArrayType
	>
	void CommonSplitSAH(const ItemArrayType &items_in, ItemArrayType &itemsL_out, ItemArrayType &itemsR_out, TreeBuilder &builder)
	{
		typedef std::vector<ItemType> VectorOfItems;			// I need 'reserve' method

		assert(items_in.size() >= 2);			// There should be at least two items to split

		// Compute aabb of input geometry
		VRUT::AABB items_aabb = builder.GetAABBOfItems(items_in);		// VRUT::AABB TreeBuilder::GetAABBOfItems(const ItemArrayType &items);

		Vector3 center = items_aabb.Center();

		VectorOfItems item_groups[6];
		VRUT::AABB item_groups_aabb[6];
		for (size_t i = 0; i < 6; i++)
		{
			// TODO If there is a way to remove all items without releasing memory, item_groups need not to be cleared and they may be shared between calls
			item_groups[i].clear();
			item_groups[i].reserve(items_in.size() * 3 / 4);		// Hope that 3/4 of input items will be enough
		}

		for (auto iter = items_in.begin(); iter != items_in.end(); ++iter)
		{
			Vector3 item_center = builder.GetItemCenter(*iter);					// Vector3 TreeBuilder::GetItemCenter(const ItemType& item);

			if (item_center.x < center.x)
			{
				item_groups[0].push_back(*iter);
				builder.MergeAABBWithItemAABB(item_groups_aabb[0], *iter);		// void TreeBuilder::MergeAABBWithItemAABB(VRUT::AABB &in_out_aabb, const ItemType& item);
			}
			else
			{
				item_groups[1].push_back(*iter);
				builder.MergeAABBWithItemAABB(item_groups_aabb[1], *iter);
			}

			if (item_center.y < center.y)
			{
				item_groups[2].push_back(*iter);
				builder.MergeAABBWithItemAABB(item_groups_aabb[2], *iter);
			}
			else
			{
				item_groups[3].push_back(*iter);
				builder.MergeAABBWithItemAABB(item_groups_aabb[3], *iter);
			}

			if (item_center.z < center.z)
			{
				item_groups[4].push_back(*iter);
				builder.MergeAABBWithItemAABB(item_groups_aabb[4], *iter);
			}
			else
			{
				item_groups[5].push_back(*iter);
				builder.MergeAABBWithItemAABB(item_groups_aabb[5], *iter);
			}
		}
		float total_x = item_groups_aabb[0].SurfaceArea() * float(item_groups[0].size()) + item_groups_aabb[1].SurfaceArea() * float(item_groups[1].size());
		float total_y = item_groups_aabb[2].SurfaceArea() * float(item_groups[2].size()) + item_groups_aabb[3].SurfaceArea() * float(item_groups[3].size());
		float total_z = item_groups_aabb[4].SurfaceArea() * float(item_groups[4].size()) + item_groups_aabb[5].SurfaceArea() * float(item_groups[5].size());

		if (!item_groups[0].empty() && !item_groups[1].empty() && (total_x <= total_y) && (total_x <= total_z))
		{
			itemsL_out.assign(item_groups[0].begin(), item_groups[0].end());
			itemsR_out.assign(item_groups[1].begin(), item_groups[1].end());
		}
		else if (!item_groups[2].empty() && !item_groups[3].empty() && (total_y <= total_x) && (total_y <= total_z))
		{
			itemsL_out.assign(item_groups[2].begin(), item_groups[2].end());
			itemsR_out.assign(item_groups[3].begin(), item_groups[3].end());
		}
		else if (!item_groups[4].empty() && !item_groups[5].empty() && (total_z <= total_x) && (total_z <= total_y))
		{
			itemsL_out.assign(item_groups[4].begin(), item_groups[4].end());
			itemsR_out.assign(item_groups[5].begin(), item_groups[5].end());
		}
		else
		{
			// No split, divide in half

			// Find an iterator in the middle of the input array
			unsigned i;
			auto iter = items_in.begin();
			for (i = 0; i < items_in.size()/2; i++, iter++);

			// Iterator should really be in the middle
			assert((iter != items_in.begin()) && (iter != items_in.end()));

			itemsL_out.assign(items_in.begin(), iter);
			itemsR_out.assign(iter, items_in.end());
		}
	}

#pragma warning ( disable : 4503 )
	template <
		class TreeNodeType,
		class TreeBuilder,
		class ItemType,
		class ItemArrayType
	>
	void CommonTreeBuild(TreeNodeType *& out_root, TreeBuilder &builder, unsigned int max_items_in_leaves = 1)
	{
		out_root = nullptr;

		// I create nodes when it is necessary, so that items could be send into node's constructor (if builder wants)
		struct SplitNode
		{
			TreeNodeType	*Parent;				// Parent of the node (resp. subtree) to be created
			bool			ChildShouldBeLeft;		// True if the node (resp. subtree) to be created should be left child
			ItemArrayType	*ChildItems;			// Items of the node (resp. subtree) to be created
		};
		SplitNode parent_node;

		parent_node.ChildItems = new ItemArrayType;
		builder.GetItems(*parent_node.ChildItems);			// void TreeBuilder::GetItems(ItemArrayType &out_items);

		if (parent_node.ChildItems->empty())
		{
			delete parent_node.ChildItems;
			return;
		}
		
		parent_node.Parent = nullptr;

		ItemArrayType *aux_items = new ItemArrayType;			// To save some allocation and deallocation
		std::stack<SplitNode> additional_nodes_to_build;
		do
		{
			if (parent_node.ChildItems->size() > max_items_in_leaves)
			{
				// Create node as internal node
				TreeNodeType *new_node;

				if (parent_node.Parent)
				{
					new_node = parent_node.ChildShouldBeLeft ?
						builder.CreateLeftInternal(parent_node.Parent, *parent_node.ChildItems) :			// TreeNodeType *TreeBuilder::CreateLeftInternal(TreeNodeType *parent, ItemArrayType &items);
						builder.CreateRightInternal(parent_node.Parent, *parent_node.ChildItems);			// TreeNodeType *TreeBuilder::CreateRightInternal(TreeNodeType *parent, ItemArrayType &items);
				}
				else
				{
					// We create root
					assert(!out_root);			// Root wasn't assigned yet
					new_node = out_root = builder.CreateRootInternal(*parent_node.ChildItems);			// TreeNodeType *TreeBuilder::CreateRootInternal(ItemArrayType &items);
				}

				// Split items
				SplitNode left_node;
				SplitNode right_node;

				left_node.Parent = new_node;
				right_node.Parent = new_node;
				left_node.ChildShouldBeLeft = true;
				right_node.ChildShouldBeLeft = false;
				left_node.ChildItems = aux_items;
				right_node.ChildItems = new ItemArrayType;

				CommonSplitSAH<TreeNodeType, TreeBuilder, ItemType, ItemArrayType>(*parent_node.ChildItems, *left_node.ChildItems, *right_node.ChildItems, builder);

				aux_items = parent_node.ChildItems;			// Here we save the allocation and dealocation

				parent_node = left_node;
				additional_nodes_to_build.push(right_node);

				// Now we use left node to continue
			}
			else
			{
				assert(parent_node.ChildItems);		// Items must have been created, or it is a bug

				// Create node as leaf node
				if (parent_node.Parent)
				{
					parent_node.ChildShouldBeLeft ?
						builder.CreateLeftLeaf(parent_node.Parent, *parent_node.ChildItems) :		// void TreeBuilder::CreateLeftLeaf(TreeNodeType *parent, ItemArrayType &items);
						builder.CreateRightLeaf(parent_node.Parent, *parent_node.ChildItems);		// void TreeBuilder::CreateRightLeaf(TreeNodeType *parent, ItemArrayType &items);
				}
				else
				{
					// We create root
					assert(!out_root);			// Root wasn't assigned yet
					out_root = builder.CreateRootLeaf(*parent_node.ChildItems);						// TreeNodeType *TreeBuilder::CreateRootLeaf(ItemArrayType &items);
				}

				delete parent_node.ChildItems;

				// Get a node to continue
				if (!additional_nodes_to_build.empty())
				{
					parent_node = additional_nodes_to_build.top();
					additional_nodes_to_build.pop();
				}
				else
				{
					// No additional nodes
					break;
				}
			}
		}
		while (true);		// Ends with break

		delete aux_items;

		// Note that current_node.ItemsToBuild was deleted when items were assigned to the node
	}

	template <
		class TreeNodeType,
		class TreeBuilder
	>
	void CommonTreeBuild(TreeNodeType *& out_root, TreeBuilder &builder, unsigned int max_items_in_leaves = 1)
	{
		CommonTreeBuild<TreeNodeType, TreeBuilder, typename TreeBuilder::ItemType, typename TreeBuilder::ItemArrayType>(out_root, builder, max_items_in_leaves);
	}

}





	//template <
	//	class TreeNodeType,
	//	class TreeBuilder,
	//	class ItemType,
	//	class ItemArrayType
	//>
	//void CommonTreeBuild(TreeNodeType *& out_root, TreeBuilder &builder, unsigned int max_items_in_leaves = 1)
	//{
	//	out_root = nullptr;

	//	struct SplitNode
	//	{
	//		TreeNodeType	*Node;
	//		ItemArrayType	*ItemsToBuild;
	//	};
	//	SplitNode current_node;

	//	current_node.ItemsToBuild = new ItemArrayType;
	//	builder.GetItems(*current_node.ItemsToBuild);			// void TreeBuilder::GetItems(ItemArrayType &out_items);

	//	if (current_node.ItemsToBuild->empty())
	//	{
	//		delete current_node.ItemsToBuild;
	//		return;
	//	}

	//	current_node.Node = out_root = builder.NewNode();		// TreeNodeType *TreeBuilder::NewNode();

	//	ItemArrayType *aux_items = new ItemArrayType;			// To save some allocation and deallocation
	//	std::stack<SplitNode> additional_nodes_to_build;
	//	do
	//	{
	//		if (current_node.ItemsToBuild->size() > max_items_in_leaves)
	//		{
	//			// Split items
	//			SplitNode left_node;
	//			SplitNode right_node;

	//			assert(current_node.Node);				// Node must have been created, or it is a bug
	//			assert(current_node.ItemsToBuild);		// Items must have been created, or it is a bug

	//			left_node.Node = current_node.Node->CreateLChild();
	//			right_node.Node = current_node.Node->CreateRChild();

	//			left_node.ItemsToBuild = aux_items;
	//			right_node.ItemsToBuild = new ItemArrayType;

	//			CommonSplitSAH<TreeNodeType, TreeBuilder, ItemType, ItemArrayType>(*current_node.ItemsToBuild, *left_node.ItemsToBuild, *right_node.ItemsToBuild, builder);

	//			aux_items = current_node.ItemsToBuild;			// Here we save the allocation and dealocation

	//			current_node = left_node;
	//			additional_nodes_to_build.push(right_node);

	//			// Now we use left node to continue
	//		}
	//		else
	//		{
	//			// Set items to node
	//			assert(current_node.Node);				// Node must have been created, or it is a bug
	//			assert(current_node.ItemsToBuild);		// Items must have been created, or it is a bug

	//			builder.AssignItemsToNode(current_node.Node, *current_node.ItemsToBuild);		// void TreeBuilder::AssignItemsToNode(TreeNodeType *node, ItemArrayType &items);

	//			delete current_node.ItemsToBuild;

	//			// Get a node to continue
	//			if (!additional_nodes_to_build.empty())
	//			{
	//				current_node = additional_nodes_to_build.top();
	//				additional_nodes_to_build.pop();
	//			}
	//			else
	//			{
	//				// No additional nodes
	//				break;
	//			}
	//		}
	//	}
	//	while (true);		// Ends with break

	//	delete aux_items;

	//	// Note that current_node.ItemsToBuild was deleted when items were assigned to the node
	//}


