#pragma once

#include "ElementRenderer.hpp"
#include "ListElement.hpp"

//#########################################################################
//#########################  GridElement  ###############################
//#########################################################################

namespace ElementRenderer {

	class GroupElement;

	class GridElement:
		public ListElement
	{
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Initialization
	//
	public:
		static void InitializeClass();
		static void TerminateClass();
		static ClassData *DefaultData;

		typedef ListElement BaseClass;

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Constructors/Destructors
	//
	protected:
		GridElement(
			ClassData *class_data,
			Stuff::MemoryStream *stream,
			int version,
			ShapeHolder shapes
		);

	public:
		GridElement(
			BYTE row_size,
			BYTE column_size,
			Stuff::Scalar row_offset,
			Stuff::Scalar column_offset,
			Stuff::Scalar total_row_size,
			Stuff::Scalar total_column_size,
			ClassData *class_data=DefaultData
		);

		~GridElement();

		static GridElement* Make(
			Stuff::MemoryStream *stream,
			int version,
			ShapeHolder shapes=NULL
		);

		void Save(Stuff::MemoryStream *stream);

		static HGOSHEAP s_Heap;

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Testing
	//
	public:
		void TestInstance();

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Hierarchy
	//
	public:
		void AttachIndexedChild(
			BYTE row,
			BYTE column,
			Element *element
		);
		Element* GetIndexedElement(
			BYTE row,
			BYTE column
		)
			{Check_Object(this); return m_list[row*m_columnCellCount+column];} 
		Element* GetIndexedElement(WORD index)
			{Check_Object(this); return m_list[index];} 

		WORD FindElementIndex(
			Stuff::Scalar row,
			Stuff::Scalar column
		);

		void FindRowAndColumn(
			WORD index,
			BYTE *row,
			BYTE *column
		)
			{
				Check_Object(this);
				if (index != 0)
					*row = static_cast<BYTE>(index/m_columnCellCount);
				else
					*row = 0;
				*column = static_cast<BYTE>(index-(*row*m_columnCellCount));
			}

		void SetSize(WORD total_cells);

		void SetSize(
			BYTE row_size,
			BYTE column_size
		);

		void GetSize(
			BYTE* row_size,
			BYTE* column_size
		)
			{
				Check_Object(this); Check_Pointer(row_size); Check_Pointer(column_size);
				*row_size = m_rowCellCount; *column_size = m_columnCellCount;
			} 

		void GetDimensions(
			Stuff::Scalar *row_offset,
			Stuff::Scalar *column_offset,
			Stuff::Scalar *row_cell_size,
			Stuff::Scalar *column_cell_size
		);

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Sync support
	//
	protected:
		void SetSyncState();
		static SyncMethod SyncMethods[4];

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Culling
	//
	protected:
		virtual void SetCullState();

		static CullMethod CullMethods;

		int GridCullMethod(CameraElement *camera);
		bool GridRayCullMethod(CollisionQuery *query);
		int GridSphereTestMethod(SphereTest *test);

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Collision
	//
	public:
		typedef void* (*CellTest)(
			GridElement *grid,
			WORD cell,
			CollisionQuery *query,
			Stuff::Scalar enter,
			Stuff::Scalar leave
		);
		void* IterateLine(
			CollisionQuery *query,
			CellTest test_function
		);

	protected:
		bool CastCulledRay(CollisionQuery *query);
		Element* FindSmallestElementContainingCulled(SphereTest *test);

		static void* TestCell(
			GridElement *grid,
			WORD cell,
			CollisionQuery *query,
			Stuff::Scalar enter,
			Stuff::Scalar leave
		);

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Drawing
	//
	protected:
		void SetDrawState();
		static DrawMethod DrawMethods[DrawStateCount];

		void InheritDrawMethod(
			CameraElement *camera,
			const StateChange *inherited_state,
			int clipping_state
		);
		void OverrideDrawMethod(
			CameraElement *camera,
			const StateChange *inherited_state,
			int clipping_state
		);

		BYTE m_rowCellCount, m_columnCellCount;
		Stuff::Scalar m_rowCellSize, m_columnCellSize;
		Stuff::Scalar m_rowOffset, m_columnOffset;
	};

}
