//
// Tools_Environment_LOS.h
//
// Created by KJFL - 2004-04-23 15:31:39
//
// Copyright (C) 2004 Rebellion, All Rights Reserved.
//

#ifdef ASURA_USE_PRAGMA_ONCE
	#pragma once
#endif

#ifndef __TOOLS_ENVIRONMENT_LOS_H__
#define __TOOLS_ENVIRONMENT_LOS_H__

#include "Misc/Tools_Large_AABB_Tree.h"
#include "Rendering/Asura_Module.h"
#include "Mesh/Asura_Mesh.h"
#include "Streaming/Tools_Streaming_System.h"

#define TOOLS_LOS_DEFAULT_FLAGS_TO_IGNORE (AsuraPolygonFlag_DontRender)

class Tools_EnvironmentLOSZone
{
public :
	Asura_Hash_ID m_uZoneHash;
	Asura_Module_Handler* m_pxModuleHandler;
	u_int m_uModuleIndexOffset;
};


class Tools_Environment_LOS_Data : public Tools_Large_AABB_Tree_RayData
{
	public:
		Tools_Environment_LOS_Data( const Asura_Vector_3& xRayOrigin, const Asura_Vector_3& xRayDirection)
		: Tools_Large_AABB_Tree_RayData( xRayOrigin, xRayDirection )
		{
			Initialise();
		}


		Tools_Environment_LOS_Data()
		: Tools_Large_AABB_Tree_RayData( Asura_Maths::ZeroVector3, Asura_Maths::ZeroVector3 )
		{
			Initialise();
		}

		void Initialise()
		{
			m_uPolyFlagsToIgnore = TOOLS_LOS_DEFAULT_FLAGS_TO_IGNORE;
			m_uPolyFlagsToRequire = 0;
			m_bReturnOnFirstHit = false;
			m_bIgnoreTransparent = false;
			m_uCurrentGlobalModuleIndex = 0;
			m_uCurrentLocalModuleIndex = 0;
			m_pxCurrentZone = 0;
			m_uZoneHash = 0;
			m_uModuleIndex = 0;
			m_uPolyIndex = 0;
			m_bFacingTowards = false;
			m_pfnShouldTestModule = 0;
			m_pShouldTestModuleUserData = 0;
		}


		/////////////////////////////////////
		//Setting input options :
		//(Also see SetRayOrigin() and SetRayDirection() in parent)

		//Ignore any polygons with any of these flags
		void SetPolyFlagsToIgnore(u_int uPolyFlagsToIgnore) {m_uPolyFlagsToIgnore = uPolyFlagsToIgnore;}

		//Ignore any polygons that don't have all these flags
		void SetPolyFlagsToRequire(u_int uPolyFlagsToRequire) {m_uPolyFlagsToRequire = uPolyFlagsToRequire;}

		//Ignore any polygons with transparent materials
		void SetIgnoreTransparent(bool bIgnoreTransparent) {m_bIgnoreTransparent = bIgnoreTransparent;}

		//Stop doing tests once any hit has been found		
		void SetReturnOnFirstHit(bool bReturnOnFirstHit) {m_bReturnOnFirstHit = bReturnOnFirstHit;}

		//Sets callback for determining whether a given zone/module should be tested for LOS
		void SetShouldTestModuleCallback(bool (*pfnShouldTestModule)(Asura_Hash_ID uZoneHash, u_int uModuleIndex, const void* pUserData, const Tools_Environment_LOS_Data& xLOSData), const void* pUserData)
		{
			m_pfnShouldTestModule = pfnShouldTestModule;
			m_pShouldTestModuleUserData = pUserData;
		}


		/////////////////////////////////////
		//Getting output :
		//(Also see GetDistance() in parent)

		Asura_Hash_ID GetZoneHash( void ) const { return m_uZoneHash; }
		u_int GetModuleIndex( void ) const { return m_uModuleIndex; }
		u_int GetPolyIndex( void ) const { return m_uPolyIndex; }
		bool GetFacingTowards( void ) const { return m_bFacingTowards; }


		/////////////////////////////////////
		//Mainly internal stuff	:


		// input data
		bool GetReturnOnFirstHit( void ) const { return m_bReturnOnFirstHit; }
		u_int GetPolyFlagsToIgnore( void ) const { return m_uPolyFlagsToIgnore; }
		u_int GetPolyFlagsToRequire( void ) const {return m_uPolyFlagsToRequire; }
		bool GetIgnoreTransparent( void ) const { return m_bIgnoreTransparent; }

		// during test data
		u_int GetCurrentGlobalModuleIndex( void ) const { return m_uCurrentGlobalModuleIndex; }
		u_int GetCurrentLocalModuleIndex( void ) const { return m_uCurrentLocalModuleIndex; }
		Tools_EnvironmentLOSZone* GetCurrentZone( void ) const { return m_pxCurrentZone; }

		void SetModuleAndZone( u_int uGlobalModuleIndex, Tools_EnvironmentLOSZone* pxZone )
		{
			m_pxCurrentZone = pxZone;
			m_uCurrentGlobalModuleIndex = uGlobalModuleIndex;
			m_uCurrentLocalModuleIndex = uGlobalModuleIndex - m_pxCurrentZone->m_uModuleIndexOffset;
		
		}


		void SetHitData( Asura_Hash_ID uZoneHash, u_int uModuleIndex, u_int uPolyIndex, bool bFacingToward )
		{
			m_uZoneHash = uZoneHash;
			m_uModuleIndex = uModuleIndex;
			m_uPolyIndex = uPolyIndex;
			m_bFacingTowards = bFacingToward;
		}

		bool ShouldTestCurrentModule() const
		{
			if(m_pfnShouldTestModule)
			{
				return m_pfnShouldTestModule( (m_pxCurrentZone ? m_pxCurrentZone->m_uZoneHash : ASURA_HASH_ID_UNSET), m_uCurrentLocalModuleIndex, m_pShouldTestModuleUserData, *this );
			}
			return true;
		}

		

	private: friend class Tools_Environment_LOS;

		//Used by Tools_Environment_LOS to initialise tests
		void ResetWorkingData()
		{
			m_uCurrentGlobalModuleIndex = 0;
			m_uCurrentLocalModuleIndex = 0;
			m_pxCurrentZone = 0;
			SetDistance(Asura_Maths::LargeFloat);
		}


		// input data
		u_int					m_uPolyFlagsToIgnore;
		u_int					m_uPolyFlagsToRequire;
		bool					m_bReturnOnFirstHit;
		bool					m_bIgnoreTransparent;
		bool					(*m_pfnShouldTestModule)(Asura_Hash_ID uZoneHash, u_int uModuleIndex,const void* pUserData, const Tools_Environment_LOS_Data& xLOSData);
		const void*				m_pShouldTestModuleUserData;

		// during test data
		u_int						m_uCurrentGlobalModuleIndex;//index of current module in array that includes all zones
		u_int						m_uCurrentLocalModuleIndex;//index of current module in current zone's array
		Tools_EnvironmentLOSZone*	m_pxCurrentZone;

		// output data
		Asura_Hash_ID	m_uZoneHash;
		u_int			m_uModuleIndex;
		u_int			m_uPolyIndex;
		bool			m_bFacingTowards;
};


class Tools_Environment_LOS
{
	public:
		
		// Initialise() should only be called once, by Asura_Core.
		static void Initialise( void )
		{
			Asura_Assert( !s_bIsInitialised, "Initialise() called multiple times!");
			s_bIsInitialised = true;

			s_uNumberOfZones = 0;
			s_uNumberOfModules = 0;

			s_pxModulesTree = 0;
			s_ppxPolyTrees = 0;
			s_ppxVertexTrees = 0;
			s_pxZones = 0;
			s_puModuleToZoneIndex = 0;
		}

		// Call Reset() if you've changed environment data and things are no longer valid,
		// or it you want to guarantee the most up-to-date data is used.
		static void Reset(void)
		{
			Asura_Assert(s_bIsInitialised, "Reset() called but system not initialised!");

			// throw away all the data and make sure everything is nulled
			delete s_pxModulesTree;
			s_pxModulesTree = 0;

			if ( s_ppxPolyTrees )
			{
				for (u_int u=0; u<s_uNumberOfModules; u++)
				{
					delete s_ppxPolyTrees[u];
					s_ppxPolyTrees[u] = 0;
				}
			
				delete [] s_ppxPolyTrees;
				s_ppxPolyTrees = 0;
			}

			if ( s_ppxVertexTrees )
			{
				for (u_int u=0; u<s_uNumberOfModules; u++)
				{
					delete s_ppxVertexTrees[u];
					s_ppxVertexTrees[u] = 0;
				}

				delete [] s_ppxVertexTrees;
				s_ppxVertexTrees = 0;
			}

			delete [] s_pxZones;
			s_pxZones = 0;
			
			delete [] s_puModuleToZoneIndex;
			s_puModuleToZoneIndex = 0;

			s_uNumberOfZones = 0;
			s_uNumberOfModules = 0;
		}

		// Shutdown() should only be called once, by Asura_Core.
		static void Shutdown(void)
		{
			Asura_Assert(s_bIsInitialised, "Shutdown() called but system not initialised!");

			Reset();

			s_bIsInitialised = false;
		}

		// Call this out of paranoia to do some tests to see if the data is up to date.
		// It will Reset() and recalc if the number of modules, zones, or polys in individual
		// modules has changed - and assert about it. If you just want to force a recalc -
		// say you've just changed environment mesh, or you just want to be safe - you should
		// call Reset().
		static void EnsureDataIsValid( void )
		{
			if (IsDataInvalid()) CreateData();
		}
		
		static bool TestWithRayUsingPreviousResults( Tools_Environment_LOS_Data& xData )
		{
			Asura_Assert(s_bIsInitialised, "System not initialised!");

			// if no data we need to Create some
			if ( !s_uNumberOfModules ) CreateData();

			// if still no data, give up
			if( NULL == s_pxModulesTree ) return( false);

			xData.ResetWorkingData();

			// test last polygon
			bool bShouldContinue = true;
			
			//find the zone from the hash
			Tools_EnvironmentLOSZone* pxZone = s_pxZones;

			for( u_int u=0; u<s_uNumberOfZones; u++, pxZone++ )
			{
				if(pxZone->m_uZoneHash == xData.GetZoneHash())
				{
					const u_int uGlobalModuleIndex = xData.GetModuleIndex() + pxZone->m_uModuleIndexOffset;
					xData.SetModuleAndZone( uGlobalModuleIndex, pxZone );
					bShouldContinue = PolygonTestCallback( xData.GetPolyIndex(), 0.0f, &xData );
					break;
				}
			}
			
			if (bShouldContinue)
			{
				s_pxModulesTree->TestTreeWithRay_ThreadSafe( &xData, ModuleTestCallback );
			}

			if (xData.GetDistance() != Asura_Maths::LargeFloat)
			{
				return true;
			}
			else
			{
				return false;
			}
		}

				
		static bool TestWithRay(Tools_Environment_LOS_Data& xData)
		{
			Asura_Assert(s_bIsInitialised, "System not initialised!");

			// if no data we need to Create some 
			if ( !s_uNumberOfModules ) CreateData();

			// if still no data, give up
			if( NULL == s_pxModulesTree ) return( false);
		
			xData.ResetWorkingData();
			
			s_pxModulesTree->TestTreeWithRay_ThreadSafe( &xData, ModuleTestCallback );

			if (xData.GetDistance() != Asura_Maths::LargeFloat)
			{
				return true;
			}
			else
			{
				return false;
			}
		}

		//Note that this function hasn't been made thread safe yet.
		static void TestWithAABB( const Asura_Bounding_Box& xTestAABB, u_int* puModuleIndex, void (*pfCallbackFunction)(u_int uIndex) )
		{	
			Asura_Assert(s_bIsInitialised, "System not initialised!");

			// if no data we need to Create some
			if ( !s_uNumberOfModules ) CreateData();

			// if still no data, give up
			if( NULL == s_pxModulesTree ) return;
		
			s_pxTestAABB = &xTestAABB;
			s_pfAABBCallback = pfCallbackFunction;
			s_puModuleIndex = puModuleIndex;

			s_pxModulesTree->TestTree( xTestAABB, ModuleAABBTestCallback );
		}

		static void FindVerticesInAABB( Tools_Large_AABB_Tree_AABBData* pxData )
		{	
			Asura_Assert(s_bIsInitialised, "System not initialised!");

			// if no data we need to Create some
			if ( !s_uNumberOfModules ) CreateData();

			// if still no data, give up
			if( NULL == s_pxModulesTree ) return;

			s_pxModulesTree->TestTreeWithAABB_ThreadSafe( pxData, FindVerticesModuleCallback );
		}

	private:
			
		static void CreateData(void)
		{
			Reset();

			//Determine number of (streaming) zones and modules
			s_uNumberOfZones = 0;
			s_uNumberOfModules = 0;
			for(Tools_Streaming_Iterator xIt;!xIt.Done();xIt.Next())
			{
				s_uNumberOfZones++;
				s_uNumberOfModules += Asura_Module_System::GetNumberOfModules();
			}

			//Initialise zones array , and module to zone conversion array
			s_pxZones = new Tools_EnvironmentLOSZone[s_uNumberOfZones];
			s_puModuleToZoneIndex = new u_int[s_uNumberOfModules];
			u_int uZoneIndex = 0;
			u_int uModuleIndex = 0;
			for(Tools_Streaming_Iterator xIt;!xIt.Done();xIt.Next(),uZoneIndex++)
			{
				s_pxZones[uZoneIndex].m_uZoneHash = xIt.GetHash();
				s_pxZones[uZoneIndex].m_pxModuleHandler = &Asura_Module_System::GetCurrentHandler();
				s_pxZones[uZoneIndex].m_uModuleIndexOffset = uModuleIndex;

				u_int uModulesInZone = Asura_Module_System::GetNumberOfModules();
				for(u_int u=0;u<uModulesInZone;u++,uModuleIndex++)
				{
					s_puModuleToZoneIndex[uModuleIndex] = uZoneIndex;
				}

			}

		
			Asura_Bounding_Box* pxModuleBBs = new Asura_Bounding_Box[s_uNumberOfModules];


			uZoneIndex = 0;
			uModuleIndex = 0;
			for(Tools_Streaming_Iterator xIt;!xIt.Done();xIt.Next(),uZoneIndex++)
			{
				u_int uModulesInZone = Asura_Module_System::GetNumberOfModules();
				for (u_int u=0; u<uModulesInZone; u++,uModuleIndex++)
				{
					pxModuleBBs[uModuleIndex] = Asura_Module_System::GetMeshForModule(u)->BoundingBox;
					pxModuleBBs[uModuleIndex].Displace(Asura_Module_System::GetPositionOfModule(u));
				}
			}
			

			s_pxModulesTree = Tools_Large_AABB_Tree_Creator::Create(s_uNumberOfModules,pxModuleBBs);

			delete [] pxModuleBBs;

			s_ppxPolyTrees = new Tools_Large_AABB_Tree*[s_uNumberOfModules];
			s_ppxVertexTrees = new Tools_Large_AABB_Tree*[s_uNumberOfModules];

			uZoneIndex = 0;
			uModuleIndex = 0;
			for(Tools_Streaming_Iterator xIt;!xIt.Done();xIt.Next(),uZoneIndex++)
			{
				u_int uModulesInZone = Asura_Module_System::GetNumberOfModules();
				for (u_int u=0; u<uModulesInZone; u++,uModuleIndex++)
				{
					const Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule( u );
					const Asura_Vector_3& xPos = Asura_Module_System::GetPositionOfModule( u );

					// first make the polygon tree
					{
						const u_int uNumPolys = pxMesh->NumberOfPolygons;

						Asura_Bounding_Box* pxBBs = new Asura_Bounding_Box[uNumPolys];

						for( u_int uPoly = 0; uPoly < uNumPolys; ++uPoly )
						{
							pxBBs[uPoly].Reset();
						
							Asura_Mesh_Polygon& xPolygon =  pxMesh->PolygonDataPtr[uPoly];
								
							const int iNumVerts = xPolygon.NumberOfVertices();

							for( int i = 0; i < iNumVerts; ++i )
							{
								const Asura_Vector_3 xVertPos = pxMesh->VertexDataPtr[xPolygon.VertexID[i]].Position + xPos;
								pxBBs[uPoly].Set( xVertPos );
							}
				
						}

						s_ppxPolyTrees[uModuleIndex] = Tools_Large_AABB_Tree_Creator::Create(uNumPolys, pxBBs);
						
						delete [] pxBBs;
					}
					// and then the vertex tree
					{
						const u_int uNumVerts = pxMesh->NumberOfVertices;

						Asura_Bounding_Box* pxBBs = new Asura_Bounding_Box[uNumVerts];

						for( u_int uVert = 0; uVert < uNumVerts; ++uVert )
						{
							pxBBs[uVert].SetToPoint( pxMesh->VertexDataPtr[uVert].Position + xPos );
						}

						s_ppxVertexTrees[uModuleIndex] = Tools_Large_AABB_Tree_Creator::Create(uNumVerts, pxBBs);

						delete [] pxBBs;
					}
				}
			}
			
		}
			
		static bool IsDataInvalid(void)
		{
			// Does it even look like we've got any data?
			// Don't assert here, as it probably just means someone has hit Reset().
			if ( s_pxModulesTree == 0 ) return true;

			// Check number of (streaming) zones and modules matches
			u_int uNumberOfZones = 0;
			u_int uNumberOfModules = 0;
			for( Tools_Streaming_Iterator xIt; !xIt.Done(); xIt.Next() )
			{
				uNumberOfZones++;
				uNumberOfModules += Asura_Module_System::GetNumberOfModules();
			}

			if ( uNumberOfModules != s_uNumberOfModules || uNumberOfZones != s_uNumberOfZones )
			{
				Asura_Assert( false, "Code bug: number of modules/zones has changed since Tools_Environment_LOS data was last made. "
									 "Whatever is changing environment data should be calling Tools_Environment_LOS::Reset(), "
									 "but the system has caught this time and will Reset() itself." );
				return true;
			}

			// Check if poly counts look right for all these modules
			u_int uModuleIndex = 0;
			for(Tools_Streaming_Iterator xIt;!xIt.Done();xIt.Next())
			{
				u_int uModulesInZone = Asura_Module_System::GetNumberOfModules();
				for (u_int u=0; u<uModulesInZone; u++,uModuleIndex++)
				{
					const Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule( u );
				
					const u_int uNumPolys = pxMesh->NumberOfPolygons;
		
					if ( s_ppxPolyTrees[uModuleIndex]->GetNumberOfObjects() != uNumPolys ) 
					{
						Asura_Assert( false, "Code bug: number of polys in a module has changed since Tools_Environment_LOS data was last made. "
											 "Whatever is changing environment data should be calling Tools_Environment_LOS::Reset(), "
											 "but the system has caught this time and will Reset() itself." );
						return true;
					}
				}
			}

			// We haven't checked that actual polys/vertices have changed - but that would 
			// effectively be doing all the work to build the tree. 
			return false;
		}

		static void ModuleAABBTestCallback( u_int uIndex )
		{
			//Ignore hits on modules outside of the main streaming zone , since
			//TestWithAABB doesn't currently have a means of returning a zone hash
			//(and the only caller at the moment doesn't want other zones to be returned)
			//RAR 20/04/2006
			if(s_puModuleToZoneIndex[uIndex] != 0) return;
				
			*s_puModuleIndex = uIndex;
			s_ppxPolyTrees[uIndex]->TestTree( *s_pxTestAABB, s_pfAABBCallback );
		}
		
		static bool ModuleTestCallback(u_int uIndex, float fBBDistance, Tools_Large_AABB_Tree_RayData* pxData )
		{
			if ( fBBDistance > pxData->GetDistance() )
			{
				Asura_Assertf( false, ( "Code bug: BB dist (%f) > current dist (%f)", fBBDistance, pxData->GetDistance() ));
			
				return true;
			}

			Tools_Environment_LOS_Data* pxInternalData = static_cast<Tools_Environment_LOS_Data*>(pxData);

			pxInternalData->SetModuleAndZone( uIndex, &s_pxZones[s_puModuleToZoneIndex[uIndex]] );

			if(pxInternalData->ShouldTestCurrentModule())
			{
				s_ppxPolyTrees[uIndex]->TestTreeWithRay_ThreadSafe( pxData, PolygonTestCallback );
			}

			return true;
		}

		static bool PolygonTestCallback(u_int uIndex, float fBBDistance, Tools_Large_AABB_Tree_RayData* pxData );
		
		static void FindVerticesModuleCallback( u_int uIndex, Tools_Large_AABB_Tree_AABBData* pxData )
		{
			pxData->SetModuleIndex( uIndex );
			s_ppxVertexTrees[uIndex]->TestTreeWithAABB_ThreadSafe( pxData, pxData->GetCallback() );
		}

		
		static bool						s_bIsInitialised;

		static u_int					s_uNumberOfZones;
		static Tools_EnvironmentLOSZone* s_pxZones;

		static u_int					s_uNumberOfModules;	//total number of modules across all streaming zones
		static Tools_Large_AABB_Tree*	s_pxModulesTree;
		static Tools_Large_AABB_Tree**	s_ppxPolyTrees;
		static Tools_Large_AABB_Tree**	s_ppxVertexTrees;
		static u_int*					s_puModuleToZoneIndex;

		static void							(*s_pfAABBCallback)(u_int uIndex);
		static const Asura_Bounding_Box*	s_pxTestAABB;
		static u_int*						s_puModuleIndex;
};

#endif//__TOOLS_ENVIRONMENT_LOS_H__
