/* Copyright (C) 2010 Wildfire Games.
 * This file is part of 0 A.D.
 *
 * 0 A.D. 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 2 of the License, or
 * (at your option) any later version.
 *
 * 0 A.D. 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 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef INCLUDED_SPATIAL
#define INCLUDED_SPATIAL

#include "simulation2/serialization/SerializeTemplates.h"

/**
 * A very basic subdivision scheme for finding items in ranges.
 * Items are stored in lists in fixed-size divisions.
 * Items have a size (min/max values of their axis-aligned bounding box)
 * and are stored in all divisions overlapping that area.
 *
 * It is the caller's responsibility to ensure items are only added
 * once, aren't removed unless they've been added, etc, and that
 * Move/Remove are called with the same coordinates originally passed
 * to Add (since this class doesn't remember which divisions an item
 * occupies).
 *
 * (TODO: maybe an adaptive quadtree would be better than fixed sizes?)
 */
template<typename T>
class SpatialSubdivision
{
public:
	SpatialSubdivision() :
		m_DivisionsW(0), m_DivisionsH(0)
	{
	}

	/**
	 * Equivalence test (ignoring order of items within each subdivision)
	 */
	bool operator==(const SpatialSubdivision& rhs)
	{
		if (m_DivisionSize != rhs.m_DivisionSize || m_DivisionsW != rhs.m_DivisionsW || m_DivisionsH != rhs.m_DivisionsH)
			return false;
		
		for (u32 j = 0; j < m_DivisionsH; ++j)
		{
			for (u32 i = 0; i < m_DivisionsW; ++i)
			{
				std::vector<T> div1 = m_Divisions.at(i + j*m_DivisionsW);
				std::vector<T> div2 = rhs.m_Divisions.at(i + j*m_DivisionsW);
				std::sort(div1.begin(), div1.end());
				std::sort(div2.begin(), div2.end());
				if (div1 != div2)
					return false;
			}
		}

		return true;
	}

	bool operator!=(const SpatialSubdivision& rhs)
	{
		return !(*this == rhs);
	}

	void Reset(entity_pos_t maxX, entity_pos_t maxZ, entity_pos_t divisionSize)
	{
		m_DivisionSize = divisionSize;
		m_DivisionsW = (maxX / m_DivisionSize).ToInt_RoundToInfinity();
		m_DivisionsH = (maxZ / m_DivisionSize).ToInt_RoundToInfinity();
		m_Divisions.clear();
		m_Divisions.resize(m_DivisionsW * m_DivisionsH);
	}

	/**
	 * Add an item with the given 'to' size.
	 * The item must not already be present.
	 */
	void Add(T item, CFixedVector2D toMin, CFixedVector2D toMax)
	{
		ENSURE(toMin.X <= toMax.X && toMin.Y <= toMax.Y);

		u32 i0 = GetI0(toMin.X);
		u32 j0 = GetJ0(toMin.Y);
		u32 i1 = GetI1(toMax.X);
		u32 j1 = GetJ1(toMax.Y);
		for (u32 j = j0; j <= j1; ++j)
		{
			for (u32 i = i0; i <= i1; ++i)
			{
				std::vector<T>& div = m_Divisions.at(i + j*m_DivisionsW);
				div.push_back(item);
			}
		}
	}

	/**
	 * Remove an item with the given 'from' size.
	 * The item should already be present.
	 * The size must match the size that was last used when adding the item.
	 */
	void Remove(T item, CFixedVector2D fromMin, CFixedVector2D fromMax)
	{
		ENSURE(fromMin.X <= fromMax.X && fromMin.Y <= fromMax.Y);

		u32 i0 = GetI0(fromMin.X);
		u32 j0 = GetJ0(fromMin.Y);
		u32 i1 = GetI1(fromMax.X);
		u32 j1 = GetJ1(fromMax.Y);
		for (u32 j = j0; j <= j1; ++j)
		{
			for (u32 i = i0; i <= i1; ++i)
			{
				std::vector<T>& div = m_Divisions.at(i + j*m_DivisionsW);

				for (u32 n = 0; n < div.size(); ++n)
				{
					if (div[n] == item)
					{
						// Delete by swapping with the last element then popping
						div[n] = div.back();
						div.pop_back();
						break;
					}
				}
			}
		}
	}

	/**
	 * Equivalent to Remove() then Add(), but potentially faster.
	 */
	void Move(T item, CFixedVector2D fromMin, CFixedVector2D fromMax, CFixedVector2D toMin, CFixedVector2D toMax)
	{
		// Skip the work if we're staying in the same divisions
		if (GetIndex0(fromMin) == GetIndex0(toMin) && GetIndex1(fromMax) == GetIndex1(toMax))
			return;

		Remove(item, fromMin, fromMax);
		Add(item, toMin, toMax);
	}

	/**
	 * Convenience function for Add() of individual points.
	 * (Note that points on a boundary may occupy multiple divisions.)
	 */
	void Add(T item, CFixedVector2D to)
	{
		Add(item, to, to);
	}

	/**
	 * Convenience function for Remove() of individual points.
	 */
	void Remove(T item, CFixedVector2D from)
	{
		Remove(item, from, from);
	}

	/**
	 * Convenience function for Move() of individual points.
	 */
	void Move(T item, CFixedVector2D from, CFixedVector2D to)
	{
		Move(item, from, from, to, to);
	}

	/**
	 * Returns a sorted list of unique items that includes all items
	 * within the given axis-aligned square range.
	 */
	std::vector<T> GetInRange(CFixedVector2D posMin, CFixedVector2D posMax)
	{
		std::vector<T> ret;

		ENSURE(posMin.X <= posMax.X && posMin.Y <= posMax.Y);

		u32 i0 = GetI0(posMin.X);
		u32 j0 = GetJ0(posMin.Y);
		u32 i1 = GetI1(posMax.X);
		u32 j1 = GetJ1(posMax.Y);
		for (u32 j = j0; j <= j1; ++j)
		{
			for (u32 i = i0; i <= i1; ++i)
			{
				std::vector<T>& div = m_Divisions.at(i + j*m_DivisionsW);
				ret.insert(ret.end(), div.begin(), div.end());
			}
		}

		// Remove duplicates
		std::sort(ret.begin(), ret.end());
		ret.erase(std::unique(ret.begin(), ret.end()), ret.end());

		return ret;
	}

	/**
	 * Returns a sorted list of unique items that includes all items
	 * within the given circular distance of the given point.
	 */
	std::vector<T> GetNear(CFixedVector2D pos, entity_pos_t range)
	{
		// TODO: be cleverer and return a circular pattern of divisions,
		// not this square over-approximation

		return GetInRange(pos - CFixedVector2D(range, range), pos + CFixedVector2D(range, range));
	}

private:
	// Helper functions for translating coordinates into division indexes
	// (avoiding out-of-bounds accesses, and rounding correctly so that
	// points precisely between divisions are counted in both):

	u32 GetI0(entity_pos_t x)
	{
		return Clamp((x / m_DivisionSize).ToInt_RoundToInfinity()-1, 0, (int)m_DivisionsW-1);
	}

	u32 GetJ0(entity_pos_t z)
	{
		return Clamp((z / m_DivisionSize).ToInt_RoundToInfinity()-1, 0, (int)m_DivisionsH-1);
	}

	u32 GetI1(entity_pos_t x)
	{
		return Clamp((x / m_DivisionSize).ToInt_RoundToNegInfinity(), 0, (int)m_DivisionsW-1);
	}

	u32 GetJ1(entity_pos_t z)
	{
		return Clamp((z / m_DivisionSize).ToInt_RoundToNegInfinity(), 0, (int)m_DivisionsH-1);
	}

	u32 GetIndex0(CFixedVector2D pos)
	{
		return GetI0(pos.X) + GetJ0(pos.Y)*m_DivisionsW;
	}

	u32 GetIndex1(CFixedVector2D pos)
	{
		return GetI1(pos.X) + GetJ1(pos.Y)*m_DivisionsW;
	}

	entity_pos_t m_DivisionSize;
	std::vector<std::vector<T> > m_Divisions;
	u32 m_DivisionsW;
	u32 m_DivisionsH;

	template<typename ELEM> friend struct SerializeSpatialSubdivision;
};

/**
 * Serialization helper template for SpatialSubdivision
 */
template<typename ELEM>
struct SerializeSpatialSubdivision
{
	template<typename T>
	void operator()(ISerializer& serialize, const char* UNUSED(name), SpatialSubdivision<T>& value)
	{
		serialize.NumberFixed_Unbounded("div size", value.m_DivisionSize);
		SerializeVector<SerializeVector<ELEM> >()(serialize, "divs", value.m_Divisions);
		serialize.NumberU32_Unbounded("divs w", value.m_DivisionsW);
		serialize.NumberU32_Unbounded("divs h", value.m_DivisionsH);
	}

	template<typename T>
	void operator()(IDeserializer& serialize, const char* UNUSED(name), SpatialSubdivision<T>& value)
	{
		serialize.NumberFixed_Unbounded("div size", value.m_DivisionSize);
		SerializeVector<SerializeVector<ELEM> >()(serialize, "divs", value.m_Divisions);

		u32 w, h;
		serialize.NumberU32_Unbounded("divs w", w);
		serialize.NumberU32_Unbounded("divs h", h);
		value.m_DivisionsW = w;
		value.m_DivisionsH = h;
	}
};

#endif // INCLUDED_SPATIAL
