//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
//=============================================================================//
#include "incremental.h"
#include "lightmap.h"



static bool g_bFileError = false;


// -------------------------------------------------------------------------------- //
// Static helpers.
// -------------------------------------------------------------------------------- //

static bool CompareLights( dworldlight_t *a, dworldlight_t *b )
{
	static float flEpsilon = 1e-7;

	bool a1  = VectorsAreEqual( a->origin,    b->origin,    flEpsilon );
	bool a2  = VectorsAreEqual( a->intensity, b->intensity, 1.1f ); // intensities are huge numbers
	bool a3  = VectorsAreEqual( a->normal,    b->normal,    flEpsilon );
	bool a4  = fabs( a->constant_attn - b->constant_attn ) < flEpsilon;
	bool a5  = fabs( a->linear_attn - b->linear_attn ) < flEpsilon;
	bool a6  = fabs( a->quadratic_attn - b->quadratic_attn ) < flEpsilon;
	bool a7  = fabs( float( a->flags - b->flags ) ) < flEpsilon;
	bool a8  = fabs( a->stopdot - b->stopdot ) < flEpsilon;
	bool a9  = fabs( a->stopdot2 - b->stopdot2 ) < flEpsilon;
	bool a10 = fabs( a->exponent - b->exponent ) < flEpsilon;
	bool a11 = fabs( a->radius - b->radius ) < flEpsilon;

	return a1 && a2 && a3 && a4 && a5 && a6 && a7 && a8 && a9 && a10 && a11;
}


long FileOpen( char const *pFilename, bool bRead )
{
	g_bFileError = false;
	return (long)g_pFileSystem->Open( pFilename, bRead ? "rb" : "wb" );
}


void FileClose( long fp )
{
	if( fp )
		g_pFileSystem->Close( (FILE*)fp );
}


// Returns true if there was an error reading from the file.
bool FileError()
{
	return g_bFileError;
}

static inline void FileRead( long fp, void *pOut, int size )
{
	if( g_bFileError || g_pFileSystem->Read( pOut, size, (FileHandle_t)fp ) != size )
	{
		g_bFileError = true;
		memset( pOut, 0, size );
	}
}


template<class T>
static inline void FileRead( long fp, T &out )
{
	FileRead( fp, &out, sizeof(out) );
}


static inline void FileWrite( long fp, void const *pData, int size )
{
	if( g_bFileError || g_pFileSystem->Write( pData, size, (FileHandle_t)fp ) != size )
	{
		g_bFileError = true;
	}
}


template<class T>
static inline void FileWrite( long fp, T out )
{
	FileWrite( fp, &out, sizeof(out) );
}


IIncremental* GetIncremental()
{
	static CIncremental inc;
	return &inc;
}


// -------------------------------------------------------------------------------- //
// CIncremental.
// -------------------------------------------------------------------------------- //

CIncremental::CIncremental()
{
	m_TotalMemory = 0;
	m_pIncrementalFilename = NULL;
	m_pBSPFilename = NULL;
	m_bSuccessfulRun = false;
}


CIncremental::~CIncremental()
{
}


bool CIncremental::Init( char const *pBSPFilename, char const *pIncrementalFilename )
{
	m_pBSPFilename = pBSPFilename;
	m_pIncrementalFilename = pIncrementalFilename;
	return true;
}


bool CIncremental::PrepareForLighting()
{
	if( !m_pBSPFilename )
		return false;

	// Clear the touched faces list.
	m_FacesTouched.SetSize( numfaces );
	memset( m_FacesTouched.Base(), 0, numfaces );

	// If we haven't done a complete successful run yet, then we either haven't
	// loaded the lights, or a run was aborted and our lights are half-done so we
	// should reload them.
	if( !m_bSuccessfulRun )
		LoadIncrementalFile();

	// unmatched = a list of the lights we have
	CUtlLinkedList<int,int> unmatched;
	for( int i=m_Lights.Head(); i != m_Lights.InvalidIndex(); i = m_Lights.Next(i) )
		unmatched.AddToTail( i );

	// Match the light lists and get rid of lights that we already have all the data for.
	directlight_t *pNext;
	directlight_t **pPrev = &activelights;
	for( directlight_t *dl=activelights; dl != NULL; dl = pNext )
	{
		pNext = dl->next;

		//float flClosest = 3000000000;
		//CIncLight *pClosest = 0;

		// Look for this light in our light list.
		int iNextUnmatched, iUnmatched;
		for( iUnmatched=unmatched.Head(); iUnmatched != unmatched.InvalidIndex(); iUnmatched = iNextUnmatched )
		{
			iNextUnmatched = unmatched.Next( iUnmatched );

			CIncLight *pLight = m_Lights[ unmatched[iUnmatched] ];

			//float flTest = (pLight->m_Light.origin - dl->light.origin).Length();
			//if( flTest < flClosest )
			//{
			//	flClosest = flTest;
			//	pClosest = pLight;
			//}

			if( CompareLights( &dl->light, &pLight->m_Light ) )
			{
				unmatched.Remove( iUnmatched );

				// Ok, we have this light's data already, yay!
				// Get rid of it from the active light list.
				*pPrev = dl->next;
				free( dl );
				dl = 0;
				break;
			}
		}

		//bool bTest=false;
		//if(bTest)
		//	CompareLights( &dl->light, &pClosest->m_Light );

		if( iUnmatched == unmatched.InvalidIndex() )
			pPrev = &dl->next;
	}

	// Remove any of our lights that were unmatched.
	for( int iUnmatched=unmatched.Head(); iUnmatched != unmatched.InvalidIndex(); iUnmatched = unmatched.Next( iUnmatched ) )
	{
		CIncLight *pLight = m_Lights[ unmatched[iUnmatched] ];
		
		// First tag faces that it touched so they get recomposited.
		for( unsigned short iFace=pLight->m_LightFaces.Head(); iFace != pLight->m_LightFaces.InvalidIndex(); iFace = pLight->m_LightFaces.Next( iFace ) )
		{
			m_FacesTouched[ pLight->m_LightFaces[iFace]->m_FaceIndex ] = 1;
		}
		
		delete pLight;
		m_Lights.Remove( unmatched[iUnmatched] );
	}

	// Now add a light structure for each new light.
	AddLightsForActiveLights();
	
	return true;
}


bool CIncremental::ReadIncrementalHeader( long fp, CIncrementalHeader *pHeader )
{
	int version;
	FileRead( fp, version );
	if( version != INCREMENTALFILE_VERSION )
		return false;

	int nFaces;
	FileRead( fp, nFaces );

	pHeader->m_FaceLightmapSizes.SetSize( nFaces );
	FileRead( fp, pHeader->m_FaceLightmapSizes.Base(), sizeof(CIncrementalHeader::CLMSize) * nFaces );

	return !FileError();
}


bool CIncremental::WriteIncrementalHeader( long fp )
{
	int version = INCREMENTALFILE_VERSION;
	FileWrite( fp, version );

	int nFaces = numfaces;
	FileWrite( fp, nFaces );

	CIncrementalHeader hdr;
	hdr.m_FaceLightmapSizes.SetSize( nFaces );

	for( int i=0; i < nFaces; i++ )
	{
		hdr.m_FaceLightmapSizes[i].m_Width = g_pFaces[i].m_LightmapTextureSizeInLuxels[0];
		hdr.m_FaceLightmapSizes[i].m_Height = g_pFaces[i].m_LightmapTextureSizeInLuxels[1];
	}

	FileWrite( fp, hdr.m_FaceLightmapSizes.Base(), sizeof(CIncrementalHeader::CLMSize) * nFaces );
	
	return !FileError();
}


bool CIncremental::IsIncrementalFileValid()
{
	long fp = FileOpen( m_pIncrementalFilename, true );
	if( !fp )
		return false;

	bool bValid = false;
	CIncrementalHeader hdr;
	if( ReadIncrementalHeader( fp, &hdr ) )
	{
		// If the number of faces is the same and their lightmap sizes are the same,
		// then this file is considered a legitimate incremental file.
		if( hdr.m_FaceLightmapSizes.Count() == numfaces )
		{
			int i;
			for( i=0; i < numfaces; i++ )
			{
				if( hdr.m_FaceLightmapSizes[i].m_Width  != g_pFaces[i].m_LightmapTextureSizeInLuxels[0] ||							
					hdr.m_FaceLightmapSizes[i].m_Height != g_pFaces[i].m_LightmapTextureSizeInLuxels[1] )
				{
					break;
				}
			}

			// Were all faces valid?
			if( i == numfaces )
				bValid = true;
		}
	}

	FileClose( fp );
	return bValid && !FileError();
}


void CIncremental::AddLightToFace( 
	IncrementalLightID lightID, 
	int iFace, 
	int iSample,
	int lmSize,
	float dot,
	int iThread )
{
	// If we're not being used, don't do anything.
	if( !m_pIncrementalFilename )
		return;

	CIncLight *pLight = m_Lights[lightID];

	// Check for the 99.99% case in which the face already exists.
	CLightFace *pFace;
	if( pLight->m_pCachedFaces[iThread] && 
		pLight->m_pCachedFaces[iThread]->m_FaceIndex == iFace )
	{
		pFace = pLight->m_pCachedFaces[iThread];
	}
	else
	{
		bool bNew;
		
		EnterCriticalSection( &pLight->m_CS );
			pFace = pLight->FindOrCreateLightFace( iFace, lmSize, &bNew );
		LeaveCriticalSection( &pLight->m_CS );

		pLight->m_pCachedFaces[iThread] = pFace;

		if( bNew )
			m_TotalMemory += pFace->m_LightValues.Count() * sizeof( pFace->m_LightValues[0] );
	}

	// Add this into the light's data.
	pFace->m_LightValues[iSample].m_Dot = dot;
}


unsigned short DecodeCharOrShort( CUtlBuffer *pIn )
{
	unsigned short val = pIn->GetUnsignedChar();
	if( val & 0x80 )
	{
		val = ((val & 0x7F) << 8) | pIn->GetUnsignedChar();
	}

	return val;
}


void EncodeCharOrShort( CUtlBuffer *pBuf, unsigned short val )
{
	if( (val & 0xFF80) == 0 )
	{
		pBuf->PutUnsignedChar( (unsigned char)val );
	}
	else
	{
		if( val > 32767 )
			val = 32767;

		pBuf->PutUnsignedChar( (val >> 8) | 0x80 );
		pBuf->PutUnsignedChar( val & 0xFF );
	}
}


void DecompressLightData( CUtlBuffer *pIn, CUtlVector<CLightValue> *pOut )
{
	int iOut = 0;
	while( pIn->TellGet() < pIn->TellPut() )
	{
		unsigned char runLength = pIn->GetUnsignedChar();
		unsigned short usVal = DecodeCharOrShort( pIn );

		while( runLength > 0 )
		{
			--runLength;

			pOut->Element(iOut).m_Dot = usVal;
			++iOut;
		}
	}
}

#ifdef _WIN32
#pragma warning (disable:4701)
#endif

void CompressLightData( 
	CLightValue const *pValues, 
	int nValues, 
	CUtlBuffer *pBuf )
{
	unsigned char runLength=0;
	unsigned short flLastValue;

	for( int i=0; i < nValues; i++ )
	{
		unsigned short flCurValue = (unsigned short)pValues[i].m_Dot;

		if( i == 0 )
		{
			flLastValue = flCurValue;
			runLength = 1;
		}
		else if( flCurValue == flLastValue && runLength < 255 )
		{
			++runLength;
		}
		else
		{
			pBuf->PutUnsignedChar( runLength );
			EncodeCharOrShort( pBuf, flLastValue );

			flLastValue = flCurValue;
			runLength = 1;
		}
	}

	// Write the end..
	if( runLength )
	{
		pBuf->PutUnsignedChar( runLength );
		EncodeCharOrShort( pBuf, flLastValue );
	}
}

#ifdef _WIN32
#pragma warning (default:4701)
#endif

void MultiplyValues( CUtlVector<CLightValue> &values, float scale )
{
	for( int i=0; i < values.Count(); i++ )
		values[i].m_Dot *= scale;
}


void CIncremental::FinishFace(
	IncrementalLightID lightID,
	int iFace,
	int iThread )
{
	CIncLight *pLight = m_Lights[lightID];

	// Check for the 99.99% case in which the face already exists.
	CLightFace *pFace;
	if( pLight->m_pCachedFaces[iThread] && pLight->m_pCachedFaces[iThread]->m_FaceIndex == iFace )
	{
		pFace = pLight->m_pCachedFaces[iThread];

		// Compress the data.
		MultiplyValues( pFace->m_LightValues, pLight->m_flMaxIntensity );
		
		pFace->m_CompressedData.SeekPut( CUtlBuffer::SEEK_HEAD, 0 );
		CompressLightData( 
			pFace->m_LightValues.Base(), 
			pFace->m_LightValues.Count(), 
			&pFace->m_CompressedData );

#if 0
	// test decompression
	CUtlVector<CLightValue> test;
	test.SetSize( 2048 );
	pFace->m_CompressedData.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
	DecompressLightData( &pFace->m_CompressedData, &test );
#endif

		if( pFace->m_CompressedData.TellPut() == 0 )
		{
			// No contribution.. delete this face from the light.
			EnterCriticalSection( &pLight->m_CS );
				pLight->m_LightFaces.Remove( pFace->m_LightFacesIndex );
				delete pFace;
			LeaveCriticalSection( &pLight->m_CS );
		}
		else
		{
			// Discard the uncompressed data.
			pFace->m_LightValues.Purge();
			m_FacesTouched[ pFace->m_FaceIndex ] = 1;
		}
	}
}


bool CIncremental::Finalize()
{
	// If we're not being used, don't do anything.
	if( !m_pIncrementalFilename || !m_pBSPFilename )
		return false;

	CUtlVector<CFaceLightList> faceLights;
	LinkLightsToFaces( faceLights );
	
	Vector faceLight[(MAX_LIGHTMAP_DIM_WITHOUT_BORDER+2) * (MAX_LIGHTMAP_DIM_WITHOUT_BORDER+2)];
	CUtlVector<CLightValue> faceLightValues;
	faceLightValues.SetSize( (MAX_LIGHTMAP_DIM_WITHOUT_BORDER+2) * (MAX_LIGHTMAP_DIM_WITHOUT_BORDER+2) );

	// Only update the faces we've touched.
    for( int facenum = 0; facenum < numfaces; facenum++ )
    {
        if( !m_FacesTouched[facenum] || !faceLights[facenum].Count() )
			continue;

		int w = g_pFaces[facenum].m_LightmapTextureSizeInLuxels[0]+1;
		int h = g_pFaces[facenum].m_LightmapTextureSizeInLuxels[1]+1;
		int nLuxels = w * h;
		assert( nLuxels <= sizeof(faceLight) / sizeof(faceLight[0]) );

		// Clear the lighting for this face.
		memset( faceLight, 0, nLuxels * sizeof(Vector) );

		// Composite all the light contributions.
		for( int iFace=0; iFace < faceLights[facenum].Count(); iFace++ )
		{
			CLightFace *pFace = faceLights[facenum][iFace];
		
			pFace->m_CompressedData.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
			DecompressLightData( &pFace->m_CompressedData, &faceLightValues );

			for( int iSample=0; iSample < nLuxels; iSample++ )
			{
				float flDot = faceLightValues[iSample].m_Dot;
				if( flDot )
				{
					VectorMA( 
						faceLight[iSample], 
						flDot / pFace->m_pLight->m_flMaxIntensity,
						pFace->m_pLight->m_Light.intensity, 
						faceLight[iSample] );
				}
			}
		}

		// Convert to the floating-point representation in the BSP file.
		Vector *pSrc = faceLight;
		unsigned char *pDest = &(*pdlightdata)[ g_pFaces[facenum].lightofs ];

		for( int iSample=0; iSample < nLuxels; iSample++ )
		{
			VectorToColorRGBExp32( *pSrc, *( ColorRGBExp32 *)pDest );
			pDest += 4;
			pSrc++;
		}
	}
	
	m_bSuccessfulRun = true;
	return true;
}


void CIncremental::GetFacesTouched( CUtlVector<unsigned char> &touched )
{
	touched.CopyArray( m_FacesTouched.Base(), m_FacesTouched.Count() );
}


bool CIncremental::Serialize()
{
	if( !SaveIncrementalFile() )
		return false;

	WriteBSPFile( (char*)m_pBSPFilename );
	return true;
}


void CIncremental::Term()
{
	m_Lights.PurgeAndDeleteElements();
	m_TotalMemory = 0;
}


void CIncremental::AddLightsForActiveLights()
{
	// Create our lights.
	for( directlight_t *dl=activelights; dl != NULL; dl = dl->next )
	{
		CIncLight *pLight = new CIncLight;
		dl->m_IncrementalID = m_Lights.AddToTail( pLight );

		// Copy the light information.
		pLight->m_Light = dl->light;
		pLight->m_flMaxIntensity = max( dl->light.intensity[0], max( dl->light.intensity[1], dl->light.intensity[2] ) );
	}
}


bool CIncremental::LoadIncrementalFile()
{
	Term();

	if( !IsIncrementalFileValid() )
		return false;

	long fp = FileOpen( m_pIncrementalFilename, true );
	if( !fp )
		return false;

	// Read the header.
	CIncrementalHeader hdr;
	if( !ReadIncrementalHeader( fp, &hdr ) )
	{
		FileClose( fp );
		return false;
	}


	// Read the lights.
	int nLights;
	FileRead( fp, nLights );
	for( int iLight=0; iLight < nLights; iLight++ )
	{
		CIncLight *pLight = new CIncLight;
		m_Lights.AddToTail( pLight );

		FileRead( fp, pLight->m_Light );
		pLight->m_flMaxIntensity = 
			max( pLight->m_Light.intensity.x, 
				max( pLight->m_Light.intensity.y, pLight->m_Light.intensity.z ) );

		int nFaces;
		FileRead( fp, nFaces );
		assert( nFaces < 70000 );

		for( int iFace=0; iFace < nFaces; iFace++ )
		{
			CLightFace *pFace = new CLightFace;
			pLight->m_LightFaces.AddToTail( pFace );

			pFace->m_pLight = pLight;
			FileRead( fp, pFace->m_FaceIndex );

			int dataSize;
			FileRead( fp, dataSize );

			pFace->m_CompressedData.SeekPut( CUtlBuffer::SEEK_HEAD, 0 );
			while( dataSize )
			{
				--dataSize;
				
				unsigned char ucData;
				FileRead( fp, ucData );

				pFace->m_CompressedData.PutUnsignedChar( ucData );
			}
		}
	}

	
	FileClose( fp );
	return !FileError();
}


bool CIncremental::SaveIncrementalFile()
{
	long fp = FileOpen( m_pIncrementalFilename, false );
	if( !fp )
		return false;

	if( !WriteIncrementalHeader( fp ) )
	{
		FileClose( fp );
		return false;
	}

	// Write the lights.
	int nLights = m_Lights.Count();
	FileWrite( fp, nLights );
	for( int iLight=m_Lights.Head(); iLight != m_Lights.InvalidIndex(); iLight = m_Lights.Next( iLight ) )
	{
		CIncLight *pLight = m_Lights[iLight];
		
		FileWrite( fp, pLight->m_Light );

		int nFaces = pLight->m_LightFaces.Count();
		FileWrite( fp, nFaces );
		for( int iFace=pLight->m_LightFaces.Head(); iFace != pLight->m_LightFaces.InvalidIndex(); iFace = pLight->m_LightFaces.Next( iFace ) )
		{
			CLightFace *pFace = pLight->m_LightFaces[iFace];

			FileWrite( fp, pFace->m_FaceIndex );
			
			int dataSize = pFace->m_CompressedData.TellPut();
			FileWrite( fp, dataSize );

			pFace->m_CompressedData.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
			while( dataSize )
			{
				--dataSize;
				FileWrite( fp, pFace->m_CompressedData.GetUnsignedChar() );
			}
		}
	}


	FileClose( fp );
	return !FileError();
}


void CIncremental::LinkLightsToFaces( CUtlVector<CFaceLightList> &faceLights )
{
	faceLights.SetSize( numfaces );
	
	for( int iLight=m_Lights.Head(); iLight != m_Lights.InvalidIndex(); iLight = m_Lights.Next( iLight ) )
	{
		CIncLight *pLight = m_Lights[iLight];

		for( int iFace=pLight->m_LightFaces.Head(); iFace != pLight->m_LightFaces.InvalidIndex(); iFace = pLight->m_LightFaces.Next( iFace ) )
		{
			CLightFace *pFace = pLight->m_LightFaces[iFace];

			if( m_FacesTouched[pFace->m_FaceIndex] )
				faceLights[ pFace->m_FaceIndex ].AddToTail( pFace );
		}
	}
}


// ------------------------------------------------------------------ //
// CIncLight
// ------------------------------------------------------------------ //

CIncLight::CIncLight()
{
	memset( m_pCachedFaces, 0, sizeof(m_pCachedFaces) );
	InitializeCriticalSection( &m_CS );
}


CIncLight::~CIncLight()
{
	m_LightFaces.PurgeAndDeleteElements();
	DeleteCriticalSection( &m_CS );
}


CLightFace* CIncLight::FindOrCreateLightFace( int iFace, int lmSize, bool *bNew )
{
	if( bNew )
		*bNew = false;


	// Look for it.
	for( int i=m_LightFaces.Head(); i != m_LightFaces.InvalidIndex(); i=m_LightFaces.Next(i) )
	{
		CLightFace *pFace = m_LightFaces[i];

		if( pFace->m_FaceIndex == iFace )
		{
			assert( pFace->m_LightValues.Count() == lmSize );
			return pFace;
		}
	}

	// Ok, create one.
	CLightFace *pFace = new CLightFace;
	pFace->m_LightFacesIndex = m_LightFaces.AddToTail( pFace );
	pFace->m_pLight = this;
	
	pFace->m_FaceIndex = iFace;
	pFace->m_LightValues.SetSize( lmSize );
	memset( pFace->m_LightValues.Base(), 0, sizeof( CLightValue ) * lmSize );

	if( bNew )
		*bNew = true;

	return pFace;
}