#pragma once

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

namespace MidLevelRenderer {
	class MLRClipper;
	class MLRSorter;
}

namespace ElementRenderer {

	class ListElement;

	//#########################################################################
	//########################  CameraElement  ################################
	//#########################################################################

	class CameraElement:
		public GroupElement
	{
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Initialization
	//
	public:
		static void InitializeClass(Stuff::NotationFile *startup_ini);
		static void TerminateClass(Stuff::NotationFile *startup_ini);
		static ClassData *DefaultData;

		typedef GroupElement BaseClass;

		static bool s_HideSky;

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

	public:
		CameraElement(StateChange *properties);
		~CameraElement();

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

		void Save(Stuff::MemoryStream *stream);

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

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Matrix support
	//
	protected:
		void SetSyncState();
		static SyncMethod SyncMethods[2];

		void CleanCameraSyncMethod();
		void DirtyCameraSyncMethod();

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Culling
	//
	public:
		void SetPerspective(
			Stuff::Scalar near_clip,
			Stuff::Scalar far_clip,
			Stuff::Scalar left_clip,
			Stuff::Scalar right_clip,
			Stuff::Scalar top_clip,
			Stuff::Scalar bottom_clip
		);
		void SetPerspective(
			Stuff::Scalar near_clip,
			Stuff::Scalar far_clip,
			const Stuff::Radian &horizontal_fov,
			Stuff::Scalar height_to_width = 0.75f
		);

		void GetPerspective(
			Stuff::Scalar &near_clip,
			Stuff::Scalar &far_clip,
			Stuff::Scalar &horizontal_fov,
			Stuff::Scalar &height_to_width
		);
		

		enum {
			CullingPlaneCount = 6
		};

		const Stuff::Plane& GetLocalCullingPlane(int index)
			{
				Check_Object(this);
				Verify(static_cast<unsigned>(index)<CullingPlaneCount);
				return m_localCullingPlanes[index];
			}
		const Stuff::Plane& GetWorldCullingPlane(int index)
			{
				Check_Object(this);
				Verify(static_cast<unsigned>(index)<CullingPlaneCount);
				return m_worldCullingPlanes[index];
			}

		void GetNearPlaneBorders(
			Stuff::Scalar *left,
			Stuff::Scalar *right,
			Stuff::Scalar *top,
			Stuff::Scalar *bottom,
			Stuff::Scalar *n,
			Stuff::Scalar *f
		)
			{
				Check_Object(this); Check_Pointer(left); Check_Pointer(right);
				Check_Pointer(top); Check_Pointer(bottom); Check_Pointer(n);
				Check_Pointer(f);
				*left = m_nearLeft; *right = m_nearRight; *top = m_nearTop;
				*bottom = m_nearBottom; *n = m_nearClip; *f = m_farClip;
			}

		const Stuff::Point3D* GetCullPoints()
			{ Check_Object(this); return m_cullFrustrumXZ; }
		Stuff::Radian GetFOVAngle()
			{Check_Object(this); return m_fovAngle;}

		Stuff::Matrix4D& GetCameraToClip()
			{Check_Object(this); return m_cameraToClip;}

	protected:
		void SetCullState();
		int CameraOnlyCullMethod(CameraElement *camera);
		static CullMethod CullMethodObject;

		Stuff::Plane
			m_localCullingPlanes[CullingPlaneCount],
			m_worldCullingPlanes[CullingPlaneCount];
		Stuff::Scalar
			m_nearLeft,
			m_nearRight,
			m_nearTop,
			m_nearBottom,
			m_nearClip,
			m_farClip;
		
		Stuff::Radian m_fovAngle;

		Stuff::Point3D m_cullFrustrumXZ[5];

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

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Drawing
	//
	public:
		void DrawElement(
			Element *element,
			const StateChange *inherited_state
		)
			{
				Check_Object(this); Check_Object(element);
#if 0
				if (m_acceptingElement)
				{
					element->DrawTo(this, inherited_state, 0);
				}
				else
				{
					int culling_state = element->Cull(this);
					if (culling_state != -1)
						element->DrawTo(this, inherited_state, culling_state);
					m_acceptingElement = NULL;
				}
#else
				int culling_state = element->Cull(this);
				if (culling_state != -1)
					element->DrawTo(this, inherited_state, culling_state);
#endif
			}

		void 
			DrawScene(bool gosClearedScreen=false);
		void CastShadow(
			const Stuff::LinearMatrix4D &shadow_to_world,
			const Stuff::UnitVector3D &sun_in_world,
			Stuff::Scalar radius
		);

		void MakeFootStep(
			MidLevelRenderer::MLRShape *shape,
			const Stuff::LinearMatrix4D &foot,
			Stuff::Scalar radius,
			MidLevelRenderer::MLRTexture *tex
		);

		void SetScene(Element *scene);
		Element* GetScene()
			{Check_Object(this); return m_scene.GetCurrent();}

		void AdoptSkyElement(Element *sky);

		Element* GetSky()
			{Check_Object(this); return m_sky.GetCurrent();}

		StateChange* GetViewingStateChange()
			{Check_Object(this); return m_viewingStateChange;}

		MidLevelRenderer::MLRClipper* GetClipper()
			{Check_Object(this); return m_clipper;}

		void ClearZBeforeDraw(bool flag)
			{Check_Object(this); m_clearZBufferOnDraw = flag;}
		void ClearBackBufferBeforeDraw(bool flag)
			{Check_Object(this); m_clearBackBufferOnDraw = flag;}

		void DrawSky(bool flag)
			{Check_Object(this); m_drawSky = flag;}

		void SetSecondaryCamera()
			{Check_Object(this); m_clearZBufferOnDraw = false; m_clearBackBufferOnDraw = false;}

		void DeleteSky();
		void SetTintColor(Stuff::RGBAColor *tint_color)
			{Check_Object(this); m_tintColor = *tint_color; m_useTintColor = true;}
		void ClearTintColor(Stuff::RGBAColor *tint_color)
			{Check_Object(this); m_tintColor = Stuff::RGBAColor::White; m_useTintColor = false;}

		void SetViewport(
			Stuff::Scalar x_position,
			Stuff::Scalar y_position,
			Stuff::Scalar width,
			Stuff::Scalar height
		)
			{
				Check_Object(this);
				m_viewPortLeft = x_position;
				m_viewPortTop = y_position;
				m_viewPortRight = width;
				m_viewPortBottom = height;
			}
	
		void GetViewport (Stuff::Scalar& left,Stuff::Scalar& top,Stuff::Scalar& right,Stuff::Scalar& bottom)
			{
				left = m_viewPortLeft;
				top = m_viewPortTop;
				right = m_viewPortRight;
				bottom = m_viewPortBottom;
			}
	
		Element *m_acceptingElement;

	protected:
		bool
			m_clearZBufferOnDraw,
			m_clearBackBufferOnDraw,
			m_drawSky;
		MidLevelRenderer::MLRClipper *m_clipper;
		MidLevelRenderer::MLRSorter *m_sorter;
		Stuff::SlotOf<Element *> m_sky;
		Stuff::SlotOf<Element*> m_scene;
		Stuff::Matrix4D m_cameraToClip;
		StateChange *m_viewingStateChange;
		Stuff::Scalar
			m_viewPortLeft,
			m_viewPortTop,
			m_viewPortRight,
			m_viewPortBottom;
		Stuff::RGBAColor m_tintColor;
		bool m_useTintColor;
	//sanghoon begin
	public:
		bool m_hsh_special_flag;
		void SetHSHSpecialFlag(bool new_flag){
			m_hsh_special_flag=new_flag;
		}
	//sanghoon end
	};

}
