//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=============================================================================//

#include "mathlib/vector.h"
#include "DownloadListGenerator.h"
#include "filesystem.h"
#include "filesystem_engine.h"
#include "sys.h"
#include "cmd.h"
#include "common.h"
#include "quakedef.h"
#include "vengineserver_impl.h"
#include "tier1/strtools.h"
#include "tier0/icommandline.h"
#include "checksum_engine.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <tier0/dbg.h>

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

CDownloadListGenerator g_DownloadListGenerator;
CDownloadListGenerator &DownloadListGenerator()
{
	return g_DownloadListGenerator;
}

ConVar	sv_logdownloadlist( "sv_logdownloadlist", IsX360() ? "0" : "1" );

extern int GetSvPureMode();

//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CDownloadListGenerator::CDownloadListGenerator()
	: m_AlreadyWrittenFileNames( 0, 0, true )
{
	m_hReslistFile = FILESYSTEM_INVALID_HANDLE;
	m_pStringTable = NULL;
	m_mapName[0] = 0;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CDownloadListGenerator::SetStringTable( INetworkStringTable *pStringTable )
{
	if ( IsX360() )
	{
		// not supporting
		return;
	}

	m_pStringTable = pStringTable;

	// reset the duplication list
	m_AlreadyWrittenFileNames.RemoveAll();

	// add in the bsp file to the list, and its node graph and nav mesh
	char path[_MAX_PATH];
	Q_snprintf(path, sizeof(path), "maps\\%s.bsp", m_mapName);
	OnResourcePrecached(path);

	bool useNodeGraph = true;
	KeyValues *modinfo = new KeyValues("ModInfo");
	if ( modinfo->LoadFromFile( g_pFileSystem, "gameinfo.txt" ) )
	{
		useNodeGraph = modinfo->GetInt( "nodegraph", 1 ) != 0;
	}
	modinfo->deleteThis();

	if ( useNodeGraph )
	{
		Q_snprintf(path, sizeof(path), "maps\\graphs\\%s.ain", m_mapName);
		OnResourcePrecached(path);
	}

	Q_snprintf(path, sizeof(path), "maps\\%s.nav", m_mapName);
	OnResourcePrecached(path);

	char resfilename[MAX_OSPATH];
	KeyValues *resfilekeys = new KeyValues( "resourefiles" );

	Q_snprintf( resfilename, sizeof( resfilename), "maps/%s.res", m_mapName );

	if ( resfilekeys->LoadFromFile( g_pFileSystem, resfilename, "GAME" ) )
	{
		KeyValues *entry = resfilekeys->GetFirstSubKey();
		while ( entry )
		{
			OnResourcePrecached( entry->GetName() );
			entry = entry->GetNextKey();
		}
	}
	resfilekeys->deleteThis();
}

//-----------------------------------------------------------------------------
// Purpose: call to mark level load/end
//-----------------------------------------------------------------------------
void CDownloadListGenerator::OnLevelLoadStart(const char *levelName)
{
	if ( IsX360() )
	{
		// not supporting
		return;
	}

	// close the previous level reslist, if any
	if (m_hReslistFile != FILESYSTEM_INVALID_HANDLE)
	{
		g_pFileSystem->Close(m_hReslistFile);
		m_hReslistFile = FILESYSTEM_INVALID_HANDLE;
	}

	// reset the duplication list
	m_AlreadyWrittenFileNames.RemoveAll();

	if ( sv_logdownloadlist.GetBool() )
	{
		// open the new level reslist
		char path[MAX_OSPATH];
		g_pFileSystem->CreateDirHierarchy( "DownloadLists", "MOD" );
		Q_snprintf(path, sizeof(path), "DownloadLists/%s.lst", levelName);
		m_hReslistFile = g_pFileSystem->Open(path, "wt", "GAME");
	}

	// add a slash to the end of com_gamedir, so we can only deal with files for this mod
	Q_snprintf( m_gameDir, sizeof(m_gameDir), "%s/", com_gamedir );
	Q_FixSlashes( m_gameDir );

	// save off the map name
	Q_snprintf( m_mapName, sizeof( m_mapName ), "%s", levelName );
}

//-----------------------------------------------------------------------------
// Purpose: call to mark level load/end
//-----------------------------------------------------------------------------
void CDownloadListGenerator::OnLevelLoadEnd()
{
	if ( IsX360() )
	{
		// not supporting
		return;
	}

	if ( m_hReslistFile != FILESYSTEM_INVALID_HANDLE )
	{
		g_pFileSystem->Close(m_hReslistFile);
		m_hReslistFile = FILESYSTEM_INVALID_HANDLE;
	}
	m_pStringTable = NULL;
}

//-----------------------------------------------------------------------------
// Purpose: logs and handles mdl files being precaches
//-----------------------------------------------------------------------------
void CDownloadListGenerator::OnModelPrecached(const char *relativePathFileName)
{
	if ( IsX360() )
	{
		// not supporting
		return;
	}

	if (Q_strstr(relativePathFileName, ".vmt"))
	{
		// it's a materials file, make sure that it starts in the materials directory, and we get the .vtf
		char file[_MAX_PATH];

		if (!Q_strnicmp(relativePathFileName, "materials", strlen("materials")))
		{
			Q_strncpy(file, relativePathFileName, sizeof(file));
		}
		else
		{
			// prepend the materials directory
			Q_snprintf(file, sizeof(file), "materials\\%s", relativePathFileName);
		}

		OnResourcePrecached(file);

		// get the matching vtf file
		char *ext = Q_strstr(file, ".vmt");
		if (ext)
		{
			Q_strncpy(ext, ".vtf", 5);
			OnResourcePrecached(file);
		}
	}
	else
	{
		OnResourcePrecached(relativePathFileName);
	}
}

//-----------------------------------------------------------------------------
// Purpose: logs sound file access
//-----------------------------------------------------------------------------
void CDownloadListGenerator::OnSoundPrecached(const char *relativePathFileName)
{
	if ( IsX360() )
	{
		// not supporting
		return;
	}

	// skip any special characters
	if (!V_isalnum(relativePathFileName[0]))
	{
		++relativePathFileName;
	}

	// prepend the sound/ directory if necessary
	char file[_MAX_PATH];
	if (!Q_strnicmp(relativePathFileName, "sound", strlen("sound")))
	{
		Q_strncpy(file, relativePathFileName, sizeof(file));
	}
	else
	{
		// prepend the materials directory
		Q_snprintf(file, sizeof(file), "sound\\%s", relativePathFileName);
	}

	OnResourcePrecached(file);
}

//-----------------------------------------------------------------------------
// Purpose: logs the precache as a file access
//-----------------------------------------------------------------------------
void CDownloadListGenerator::OnResourcePrecached(const char *relativePathFileName)
{
	if ( IsX360() )
	{
		// not supporting
		return;
	}

	// ignore empty string
	if (relativePathFileName[0] == 0)
	{
		return;
	}

	// ignore files that start with '*' since they signify special models
	if (relativePathFileName[0] == '*')
	{
		return;
	}

	char fullPath[_MAX_PATH];
	if (g_pFileSystem->GetLocalPath(relativePathFileName, fullPath, sizeof(fullPath)))
	{
		OnResourcePrecachedFullPath( fullPath, relativePathFileName);
	}
}

//-----------------------------------------------------------------------------
// Purpose: marks a precached file as needing a specific CRC on the client
//-----------------------------------------------------------------------------
void CDownloadListGenerator::ForceSimpleMaterial( const char *relativePathFileName )
{
	if ( IsX360() )
	{
		// not supporting
		return;
	}

	if ( !m_pStringTable )
		return;

	if ( !Q_stristr(relativePathFileName, ".vmt") && !Q_stristr(relativePathFileName, ".vtf"))
	{
		DevMsg( "Tried to enforce simple material on %s\n", relativePathFileName );
		return;
	}

	// it's a materials file, make sure that it starts in the materials directory, and we get the .vtf
	char szFixedFilename[_MAX_PATH];
	if (!Q_strnicmp(relativePathFileName, "materials", strlen("materials")))
	{
		V_strcpy_safe( szFixedFilename, relativePathFileName );
	}
	else
	{
		// prepend the materials directory
		V_sprintf_safe( szFixedFilename, "materials\\%s", relativePathFileName );
	}
	V_FixSlashes( szFixedFilename );
	if ( !g_pFullFileSystem->FileExists( szFixedFilename, "game" ) )
	{
		DevMsg( "Cannot force simple material on %s; file not found\n", szFixedFilename );
		return;
	}

	ExactFileUserData userData;
	userData.consistencyType = CONSISTENCY_SIMPLE_MATERIAL;
	userData.crc = 0;

	// Only set consistency data if pure, otherwise just create entry in download list
	if ( GetSvPureMode() < 0 )
	{
		m_pStringTable->AddString( true, szFixedFilename );
	}
	else
	{
		int index = m_pStringTable->FindStringIndex( szFixedFilename );
		if ( index != INVALID_STRING_INDEX )
		{
			m_pStringTable->SetStringUserData( index, sizeof( ExactFileUserData ), &userData );
		}
		else
		{
			m_pStringTable->AddString( true, szFixedFilename, sizeof( ExactFileUserData ), &userData );
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: marks a precached model as having a maximum size on the client
//-----------------------------------------------------------------------------
void CDownloadListGenerator::ForceModelBounds( const char *relativePathFileName, const Vector &mins, const Vector &maxs )
{
	if ( IsX360() )
	{
		// not supporting
		return;
	}

	if ( !m_pStringTable )
		return;

	if ( !relativePathFileName )
		relativePathFileName = "";

	if (!Q_stristr(relativePathFileName, ".mdl"))
	{
		DevMsg( "Warning - trying to enforce model bounds on %s\n", relativePathFileName );
		return;
	}

	char relativeFileName[_MAX_PATH];
	Q_strncpy( relativeFileName, relativePathFileName, sizeof( relativeFileName ) );
	Q_FixSlashes( relativeFileName );

	// Only set consistency data if pure, otherwise just create entry in download list
	if ( GetSvPureMode() < 0 )
	{
		m_pStringTable->AddString( true, relativePathFileName );
	}
	else
	{
		ModelBoundsUserData userData;
		userData.consistencyType = CONSISTENCY_BOUNDS;
		userData.mins = mins;
		userData.maxs = maxs;

		int index = m_pStringTable->FindStringIndex( relativeFileName );
		if ( index != INVALID_STRING_INDEX )
		{
			m_pStringTable->SetStringUserData( index, sizeof( ModelBoundsUserData ), &userData );
		}
		else
		{
			m_pStringTable->AddString( true, relativeFileName, sizeof( ModelBoundsUserData ), &userData );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Logs out file access to a file
//-----------------------------------------------------------------------------
void CDownloadListGenerator::OnResourcePrecachedFullPath( char *fullPathFileName, const char *relativeFileName )
{
	if ( IsX360() )
	{
		// not supporting
		return;
	}

	Q_FixSlashes( fullPathFileName );

	if ( !g_pFileSystem->FileExists( fullPathFileName ) )
	{
		return;	// don't allow files for download the server doesn't have
	}

	if ( Q_strncasecmp( m_gameDir, fullPathFileName, Q_strlen( m_gameDir ) ) )
	{
		return;	// the game dir must be part of the full name
	}

	// make sure the filename hasn't already been written
	UtlSymId_t filename = m_AlreadyWrittenFileNames.Find( fullPathFileName );
	if ( filename != UTL_INVAL_SYMBOL )
	{
		return;
	}

	// record in list, so we don't write it again
	m_AlreadyWrittenFileNames.AddString( fullPathFileName );

	// add extras for mdl's
	if (Q_strstr(relativeFileName, ".mdl"))
	{
		// it's a model, get it's other files as well
		char file[_MAX_PATH];
		Q_strncpy(file, relativeFileName, sizeof(file) - 10);
		char *ext = Q_strstr(file, ".mdl");

		Q_strncpy(ext, ".vvd", 10);
		OnResourcePrecached(file);

		Q_strncpy(ext, ".ani", 10);
		OnResourcePrecached(file);

		Q_strncpy(ext, ".dx80.vtx", 10);
		OnResourcePrecached(file);

		Q_strncpy(ext, ".dx90.vtx", 10);
		OnResourcePrecached(file);

		Q_strncpy(ext, ".sw.vtx", 10);
		OnResourcePrecached(file);

		Q_strncpy(ext, ".phy", 10);
		OnResourcePrecached(file);

		Q_strncpy(ext, ".jpg", 10);
		OnResourcePrecached(file);
	}

	FileHandle_t handle = m_hReslistFile;
	if ( handle != FILESYSTEM_INVALID_HANDLE )
	{
		g_pFileSystem->Write("\"", 1, handle);
		g_pFileSystem->Write( relativeFileName, Q_strlen(relativeFileName), handle );
		g_pFileSystem->Write("\"\n", 2, handle);
	}
	if ( m_pStringTable )
	{
		m_pStringTable->AddString( true, relativeFileName );
	}
}