//
// Asura_Env.h
//
// Created by Kevin Lea - 12/06/2001 11:15:19
// 
// Copyright (C) 2001 Rebellion, All Rights Reserved.
//

#ifdef ASURA_USE_PRAGMA_ONCE
    #pragma once
#endif

#ifndef __ASURA_ENV_H__
#define __ASURA_ENV_H__

#include "Core/Asura_Maths.h"
#include "Core/Asura_String.h"
#include "Debugging/Asura_Assert.h"
#include "Entity/Spawners/Asura_ClientEntity_StartPoint.h"
#include "Entity/Spawners/Asura_ServerEntity_StartPoint.h"
#include "Network/Asura_Network.h"
#include <limits.h>

#ifdef ASURA_TOOLS
#include "Tools_System.h"
#include "Lighting/Tools_LightSets.h"
#endif

enum ASURA_MODULE_FLAG
{
#ifdef ASURA_TOOLS
    ASURA_MODULE_FLAG_IGNORE_FOR_REGIONING = 1,
#endif
    ASURA_MODULE_FLAG_SCENERY = 2,
	ASURA_MODULE_FLAG_EXTERIOR = 4, //Used for large outside modules that probably completely surround other modules.
									//Things are only considered to be in the exterior module for module linking etc , if
									//they aren't in any non-exterior modules. RAR 10/04/2007
	ASURA_MODULE_FLAG_NEVERCAMERAMODULE = 8, //Camera is never considered to be in this module for determining which module(s)
											 //to take module links from , but objects within this module will be linked in with
											 //the module as normal.
};


// NB: Various bits of code assume this is 32 because then we can
// conveniently use unsigned int bitfields for working with them.
static const u_int ASURA_ENV_MAX_REGIONS = 32;

class Asura_Environment_Module
{
	public:
		Asura_Environment_Module()
        {
            m_uRegion      = 0;
            m_uReqdRegions = 0;
            m_uModuleFlags = 0;
			#ifdef ASURA_TOOLS
			m_uLightSets = 0xffffffff;
			#endif
        }

		~Asura_Environment_Module()
		{
		}
		
		u_int m_uRegion;

        // Bit r is on iff region r needs to be loaded when the player/camera
        // is in this module.
        u_int m_uReqdRegions;

        // Bitwise ORing of values from ASURA_MODULE_FLAG enum above.
		u_int m_uModuleFlags;

		#ifdef ASURA_TOOLS
		// Bitfield of light sets that this module should be included in
		// In practice this may end up being just one bit if a project is
		// using this ,or all bits if not.
		u_int m_uLightSets;
		#endif

};

class Asura_Environment_StartPosition
{
	public:
		
		class Asura_Vector_3 Position;
		class Asura_Matrix_3x3 Orientation;

		// SNH - 18/03/2003 11:23:11 - int to hold team number. Can be ignored in single player.
		int TeamNumber;	
};

class Asura_Environment
{
	friend class Asura_Environment_System;
	friend class Tools_System;

	public:
		Asura_Environment( void )
		{
			Valid = false;
			m_iNumberOfModules = 0;
			Modules = 0;
			NumberOfStartPositions = 0;
			StartPositions = 0;
			m_uReqdRegionsThisFrame = 0;
			m_uVisRegionsThisFrame = 0;
		}

		~Asura_Environment()
		{
			delete [] Modules;
			delete [] StartPositions;
		}

		Asura_Vector_3& GetStartPosition(int index=0) const
		{
			// first check start point entities
			if( Asura_Network::IsServer() )
			{
				int iCurrentSP = 0;
				Asura_Hashed_List_It<Asura_ServerEntity_StartPoint> xIt( Asura_ServerEntity_StartPoint::GetList() );

				while(!xIt.Done())
				{
					Asura_ServerEntity_StartPoint* pxStartPoint = static_cast<Asura_ServerEntity_StartPoint*>(xIt.GetCurrent());
					if(pxStartPoint && pxStartPoint->IsEntityActive())
					{
						if(iCurrentSP == index)
						{
							return pxStartPoint->GetPosition();
						}
						iCurrentSP++;						
					}
					xIt.Next();
				}
			}
			else
			{
				int iCurrentSP = 0;
				Asura_Hashed_List_It<Asura_ClientEntity_StartPoint> xIt( Asura_ClientEntity_StartPoint::GetList() );

				while(!xIt.Done())
				{
					Asura_ClientEntity_StartPoint* pxStartPoint = static_cast<Asura_ClientEntity_StartPoint*>(xIt.GetCurrent());
					if(pxStartPoint && pxStartPoint->IsEntityActive())
					{
						if(iCurrentSP == index)
						{
							return pxStartPoint->GetPosition();
						}
						iCurrentSP++;						
					}
					xIt.Next();
				}
			}

			// couldn't find a start point entity. Try the environment start points.
			if(NumberOfStartPositions)
			{
				Asura_Assert(index>=0 && index<NumberOfStartPositions,"");
				return StartPositions[index].Position;
			}
			else
			{
				//provide a default start position , since none has been created
				static Asura_Vector_3 DefaultStartPosition;
				DefaultStartPosition.Zero();
				DefaultStartPosition.y -= 0.2f;
				return DefaultStartPosition;
			}
		}

		Asura_Matrix_3x3& GetStartOrientation(int index=0) const
		{
			// first check start point entities
			if( Asura_Network::IsServer() )
			{
				int iCurrentSP = 0;
				Asura_Hashed_List_It<Asura_ServerEntity_StartPoint> xIt( Asura_ServerEntity_StartPoint::GetList() );

				while(!xIt.Done())
				{
					Asura_ServerEntity_StartPoint* pxStartPoint = static_cast<Asura_ServerEntity_StartPoint*>(xIt.GetCurrent());
					if(pxStartPoint && pxStartPoint->IsEntityActive())
					{
						if(iCurrentSP == index)
						{
							return pxStartPoint->GetOrientation();
						}
						iCurrentSP++;
					}

					xIt.Next();
				}
			}
			else
			{
				int iCurrentSP = 0;
				Asura_Hashed_List_It<Asura_ClientEntity_StartPoint> xIt( Asura_ClientEntity_StartPoint::GetList() );

				while(!xIt.Done())
				{
					Asura_ClientEntity_StartPoint* pxStartPoint = static_cast<Asura_ClientEntity_StartPoint*>(xIt.GetCurrent());
					if(pxStartPoint && pxStartPoint->IsEntityActive())
					{
						if(iCurrentSP == index)
						{
							return pxStartPoint->GetOrientation();
						}
						iCurrentSP++;
					}

					xIt.Next();
				}
			}

			if(NumberOfStartPositions)
			{
				Asura_Assert(index>=0 && index<NumberOfStartPositions,"");
				return StartPositions[index].Orientation;
			}
			else
			{
				//provide a default start position , since none has been created
				static Asura_Matrix_3x3 DefaultStartOrientation;
				DefaultStartOrientation.Identity();
				return DefaultStartOrientation;
			}
		}

		int GetNumberOfStartPositions(void) const
		{
			int iNumEntities = 0;
			if( Asura_Network::IsServer() )
			{
				iNumEntities = Asura_ServerEntity_StartPoint::GetList()->GetNumEntries();
			}
			else
			{
				iNumEntities = Asura_ClientEntity_StartPoint::GetList()->GetNumEntries();
			}

			if(iNumEntities > 0)
			{
				return iNumEntities;
			}

			return NumberOfStartPositions;
		}

		int GetStartTeamNumber(int index=0) const
		{
			// team isn't applicable to entity start points (if needed, derive a project start point entity)

			if(NumberOfStartPositions)
			{
				Asura_Assert(index >=0 && index < NumberOfStartPositions, "");
				return StartPositions[index].TeamNumber;
			}
			else
			{
				// default
				return 0;	// no team
			}
		}
		
		bool DataIsValid() const
		{
			return Valid;
		}

		void CreateModules(int numberOfModules)
		{
			Asura_Assert(!Valid,"Created modules twice!");
			Valid = true;
			Asura_Assertf(numberOfModules <= SHRT_MAX,("Too many modules in environment. %d modules, maximum is %d.", numberOfModules, SHRT_MAX));
			Modules = new Asura_Environment_Module[numberOfModules];
			m_iNumberOfModules = numberOfModules;
		}

		void SetModuleRegion(int iModuleIndex,unsigned uRegion)
		{
			Asura_Assert(iModuleIndex>=0 && iModuleIndex<m_iNumberOfModules,"");
			Modules[iModuleIndex].m_uRegion = uRegion;
		}

		unsigned GetModuleRegion(int iModuleIndex) const
		{
			Asura_Assert(iModuleIndex>=0 && iModuleIndex<m_iNumberOfModules,"");
			return Modules[iModuleIndex].m_uRegion;
		}

        u_int GetRequiredRegions( int iModuleIdx ) const    
        {
			Asura_Assert( 0 <= iModuleIdx && iModuleIdx < m_iNumberOfModules, "Invalid module index." );
			return Modules[iModuleIdx].m_uReqdRegions;
        }

        void ResetReqdAndVisRgnsForThisFrame( void )
        {
            m_uReqdRegionsThisFrame = 0;
            m_uVisRegionsThisFrame  = 0;
        }

        void AddReqdRgnsForThisFrame( int iModuleIdx )
        {
            m_uReqdRegionsThisFrame |= GetRequiredRegions( iModuleIdx );
        }

        void AddVisRgnsForThisFrame( int iModuleIdx )
        {
            m_uVisRegionsThisFrame |= (1 << GetModuleRegion(iModuleIdx));
        }

        u_int GetReqdRgnsForThisFrame( void ) const { return m_uReqdRegionsThisFrame; }
        u_int GetVisRgnsForThisFrame( void ) const { return m_uVisRegionsThisFrame; }

        u_int GetModuleFlags( int iModuleIdx ) const
        {
			Asura_Assert( 0 <= iModuleIdx && iModuleIdx < m_iNumberOfModules, "Invalid module index." );
			return Modules[iModuleIdx].m_uModuleFlags;
        }

#ifdef ASURA_TOOLS
        void SetModuleFlags( int iModuleIdx, u_int uFlags )
        {
			Asura_Assert( 0 <= iModuleIdx && iModuleIdx < m_iNumberOfModules, "Invalid module index." );
			Modules[iModuleIdx].m_uModuleFlags = uFlags;
        }


        void AddModuleFlag( int iModuleIdx, ASURA_MODULE_FLAG eFlag )
        {
			Asura_Assert( 0 <= iModuleIdx && iModuleIdx < m_iNumberOfModules, "Invalid module index." );
			Modules[iModuleIdx].m_uModuleFlags |= eFlag;
        }

        void RemoveModuleFlag( int iModuleIdx, ASURA_MODULE_FLAG eFlag )
        {
			Asura_Assert( 0 <= iModuleIdx && iModuleIdx < m_iNumberOfModules, "Invalid module index." );
			Modules[iModuleIdx].m_uModuleFlags &= ~eFlag;
        }

        u_int GetLightSets( int iModuleIdx ) const
        {
			Asura_Assert( 0 <= iModuleIdx && iModuleIdx < m_iNumberOfModules, "Invalid module index." );
			return Modules[iModuleIdx].m_uLightSets;
        }
        
        void SetLightSets( int iModuleIdx, u_int uSets )
        {
			Asura_Assert( 0 <= iModuleIdx && iModuleIdx < m_iNumberOfModules, "Invalid module index." );
			Modules[iModuleIdx].m_uLightSets = uSets;
        }

		bool IsModuleInCurrentLightSet(int iModuleIdx) const
		{
			//Don't do this while in tools , to prevent interference with the option to hide/not hide
			//modules in other light sets
			if(Tools_System::IsActive()) return true;

			Asura_Assert( 0 <= iModuleIdx && iModuleIdx < m_iNumberOfModules, "Invalid module index." );

			if(Modules[iModuleIdx].m_uLightSets & Tools_LightSets::GetCurrentLightSets()) return true;

			return false;
		}
#endif

		bool IsSceneryModule(int iModuleIdx) const
		{
			Asura_Assert( 0 <= iModuleIdx && iModuleIdx < m_iNumberOfModules, "Invalid module index." );
			return (Modules[iModuleIdx].m_uModuleFlags & ASURA_MODULE_FLAG_SCENERY)!=0;
		}

		bool IsExteriorModule(int iModuleIdx) const
		{
			Asura_Assert( 0 <= iModuleIdx && iModuleIdx < m_iNumberOfModules, "Invalid module index." );
			return (Modules[iModuleIdx].m_uModuleFlags & ASURA_MODULE_FLAG_EXTERIOR)!=0;
		}

		bool IsNeverCameraModule(int iModuleIdx) const
		{
			Asura_Assert( 0 <= iModuleIdx && iModuleIdx < m_iNumberOfModules, "Invalid module index." );
			return (Modules[iModuleIdx].m_uModuleFlags & ASURA_MODULE_FLAG_NEVERCAMERAMODULE)!=0;
		}



#ifdef ASURA_TOOLS
		void DeleteModule(int iModuleIdx)
		{
			Asura_Assert( 0 <= iModuleIdx && iModuleIdx < m_iNumberOfModules, "Invalid module index." );
			Modules[iModuleIdx] = Modules[m_iNumberOfModules-1];
			m_iNumberOfModules--;
		}
#endif
        
		Asura_String Name;

		int m_iNumberOfModules;
		Asura_Environment_Module* Modules;

		int NumberOfStartPositions;
		Asura_Environment_StartPosition* StartPositions;

		bool Valid;

    private:

        u_int m_uReqdRegionsThisFrame;
        u_int m_uVisRegionsThisFrame;
        
};


#endif//__ASURA_ENV_H__
