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

#include "mathlib/mathlib.h"
#include "bsplib.h"
#include "tier0/icommandline.h"
#include "iscratchpad3d.h"
#include "filesystem_tools.h"
#include "tier2/fileutils.h"
#include "gamebspfile.h"
#include "tier1/utlstringmap.h"
#include "tools_minidump.h"
#include "cmdlib.h"

bool g_bTreeInfo = false;
bool g_bDrawTree = false;


float g_nOptimumDepth;
int g_nMinTreeDepth;
int g_nMaxTreeDepth;
int g_TotalTreeDepth;
float g_TotalVariance;

float g_ySpacing = -1; // (set by code)
double g_xSpacing = 1.0;


void CalculateTreeInfo_R( int iNode, int depth )
{
	dnode_t *pNode = &dnodes[iNode];
	if ( iNode < 0 ) // (is this a leaf)
	{
		g_nMinTreeDepth = min( g_nMinTreeDepth, depth );
		g_nMaxTreeDepth = max( g_nMaxTreeDepth, depth );
		g_TotalTreeDepth += depth;
		g_TotalVariance += fabs( depth - g_nOptimumDepth );
	}
	else
	{
		CalculateTreeInfo_R( pNode->children[0], depth+1 );
		CalculateTreeInfo_R( pNode->children[1], depth+1 );
	}
}


void DrawTreeToScratchPad_R( 
	IScratchPad3D *pPad,
	int iNode,					// Which node we're drawing.
	int iLevel,					// (used to get Y coordinate)
	float flXMin,
	float flXMax,
	const Vector *pParentPos	// Parent node position to draw connecting line (if there is a parent).
	)
{
	float flMyX = (flXMin + flXMax) * 0.5f;

	Vector vMyPos;
	vMyPos.x = 0;
	vMyPos.y = flMyX;
	vMyPos.z = -iLevel * g_ySpacing;

	// Draw the connecting line.
	if ( pParentPos )
	{
		pPad->DrawLine( CSPVert( *pParentPos, Vector(1,1,1) ), CSPVert( vMyPos, Vector(1,0,0) ) );
	}

	dnode_t *pNode = &dnodes[iNode];
	if ( iNode < 0 )
	{
		// This is a leaf.
		pPad->DrawPoint( CSPVert( vMyPos, Vector(1,0,0) ), 6 );
	}
	else
	{
		pPad->DrawPoint( CSPVert( vMyPos, Vector(1,1,1) ), 2 );
		
		DrawTreeToScratchPad_R( 
			pPad,
			pNode->children[0],
			iLevel+1,
			flXMin,
			flMyX,
			&vMyPos );
		
		DrawTreeToScratchPad_R( 
			pPad,
			pNode->children[1],
			iLevel+1,
			flMyX,
			flXMax,
			&vMyPos );
	}
}


void CalcTreeDepth_R( int iNode, int iLevel, int &iMaxDepth )
{
	iMaxDepth = max( iLevel, iMaxDepth );
	if ( iNode < 0 )
		return;

	CalcTreeDepth_R( dnodes[iNode].children[0], iLevel+1, iMaxDepth );
	CalcTreeDepth_R( dnodes[iNode].children[1], iLevel+1, iMaxDepth );
}


void DrawTreeToScratchPad()
{
	IScratchPad3D *pPad = ScratchPad3D_Create();
	pPad->SetAutoFlush( false );

	int maxDepth = 0;
	CalcTreeDepth_R( dmodels[0].headnode, 0, maxDepth );
	float flXSpace = (1 << min( maxDepth, 14 )) * g_xSpacing;
	g_ySpacing = (flXSpace / maxDepth) / 4;

	DrawTreeToScratchPad_R(
		pPad,
		dmodels[0].headnode,
		0,	// start on level 0
		-flXSpace/2,
		flXSpace/2,
		NULL );
	
	pPad->Release();
}

struct WorldTextureStats_t
{
	int texdataID;
	int refCount;
};

int WorldTextureCompareFunc( const void *t1, const void *t2 )
{
	WorldTextureStats_t *pStat1 = ( WorldTextureStats_t * )t1;
	WorldTextureStats_t *pStat2 = ( WorldTextureStats_t * )t2;

	if( pStat1->refCount < pStat2->refCount )
	{
		return 1;
	}
	if( pStat1->refCount > pStat2->refCount )
	{
		return -1;
	}
	return 0;
}

void PrintWorldTextureStats( FILE *fp )
{
	static WorldTextureStats_t stats[MAX_MAP_TEXDATA];
	int i;
	for( i = 0; i < numtexdata; i++ )
	{
		stats[i].texdataID = i;
		stats[i].refCount = 0;
	}

	for( i = 0; i < numfaces; i++ )
	{
		dface_t *pFace = &dfaces[i];
		int texinfoID = pFace->texinfo;
		Assert( texinfoID >= 0 && texinfoID < texinfo.Count() );
		int texdataID = texinfo[texinfoID].texdata;
		Assert( texdataID >= 0 && texdataID < numtexdata );
		stats[texdataID].refCount++;
	}

	qsort( stats, numtexdata, sizeof( WorldTextureStats_t ), WorldTextureCompareFunc );
	for( i = 0; i < numtexdata; i++ )
	{
		const char *pTextureName = TexDataStringTable_GetString( dtexdata[stats[i].texdataID].nameStringTableID );
		fprintf( fp, "%5d surface(s) use material \"%s\"\n", stats[i].refCount, pTextureName );
	}
}

void PrintModelStats( FILE *fp )
{
	CUtlStringMap<int> modelMap;

	// -------------------------------------------------------
	// Deal with static props
	// -------------------------------------------------------
	GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle( GAMELUMP_STATIC_PROPS );
//	int nLumpSize = g_GameLumps.GameLumpSize( handle );
	void *pStaticPropLump = g_GameLumps.GetGameLump( handle );
	unsigned char *pScan = ( unsigned char * )pStaticPropLump;
	//	fprintf( fp, "nLumpSize: %d\n", nLumpSize );

	// read dictionary
	int nDictCount = ( ( int * )pScan )[0];
	pScan += sizeof( int );
	StaticPropDictLump_t *pDictLump = ( StaticPropDictLump_t * )pScan;
	pScan += nDictCount * sizeof( StaticPropDictLump_t );

	// read leaves
	int nLeafCount = ( ( int * )pScan )[0];
	pScan += sizeof( int );
//	StaticPropLeafLump_t *pLeafLump = ( StaticPropLeafLump_t * )pScan;
	pScan += nLeafCount * sizeof( StaticPropLeafLump_t );

	// read objects
	int nObjCount = ( ( int * )pScan )[0];
	pScan += sizeof( int );
	StaticPropLump_t *pStaticPropLumpData = ( StaticPropLump_t * )pScan;
	pScan += nObjCount * sizeof( StaticPropLump_t );

	int i;
	for( i = 0; i < nObjCount; i++ )
	{
		StaticPropLump_t &pData = pStaticPropLumpData[i];
		const char *pName = pDictLump[pData.m_PropType].m_Name;
		if( modelMap.Defined( pName ) )
		{
			modelMap[pName]++;
		}
		else
		{
			modelMap[pName] = 1;
		}
	}

	extern int num_entities;
	extern entity_t	entities[MAX_MAP_ENTITIES];

	ParseEntities();

	for( i = 0; i < num_entities; i++ )
	{
		const entity_t *pEnt = &entities[i];
		const epair_t *pEPair = pEnt->epairs;
		const char *pClassName = NULL;
		const char *pModelName = NULL;
		for( ; pEPair; pEPair = pEPair->next )
		{
			if ( Q_stricmp( pEPair->key, "classname" ) == 0 )
			{
				pClassName = pEPair->value;
			}
			else if( Q_stricmp( pEPair->key, "model" ) == 0 )
			{
				if( StringHasPrefix( pEPair->value, "models" ) )
				{
					pModelName = pEPair->value;
				}
			}
		}
		if( pClassName && pModelName )
		{
			if( modelMap.Defined( pModelName ) )
			{
				modelMap[pModelName]++;
			}
			else
			{
				modelMap[pModelName] = 1;
			}
		}
	}
	for( i = 0; i < modelMap.GetNumStrings(); i++ )
	{
		printf( "%s,%d\n", modelMap.String( i ), modelMap[modelMap.String( i )] );
	}
}

void PrintListStaticProps( FILE *fp )
{
	// -------------------------------------------------------
	// Deal with static props
	// -------------------------------------------------------
	GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle( GAMELUMP_STATIC_PROPS );
	//	int nLumpSize = g_GameLumps.GameLumpSize( handle );
	void *pStaticPropLump = g_GameLumps.GetGameLump( handle );
	unsigned char *pScan = ( unsigned char * )pStaticPropLump;
	//	fprintf( fp, "nLumpSize: %d\n", nLumpSize );

	// read dictionary
	int nDictCount = ( ( int * )pScan )[0];
	pScan += sizeof( int );
	StaticPropDictLump_t *pDictLump = ( StaticPropDictLump_t * )pScan;
	pScan += nDictCount * sizeof( StaticPropDictLump_t );

	// read leaves
	int nLeafCount = ( ( int * )pScan )[0];
	pScan += sizeof( int );
	//	StaticPropLeafLump_t *pLeafLump = ( StaticPropLeafLump_t * )pScan;
	pScan += nLeafCount * sizeof( StaticPropLeafLump_t );

	// read objects
	int nObjCount = ( ( int * )pScan )[0];
	pScan += sizeof( int );
	StaticPropLump_t *pStaticPropLumpData = ( StaticPropLump_t * )pScan;
	pScan += nObjCount * sizeof( StaticPropLump_t );

	int i;
	for( i = 0; i < nObjCount; i++ )
	{
		StaticPropLump_t &pData = pStaticPropLumpData[i];
		const char *pName = pDictLump[pData.m_PropType].m_Name;

		printf( "%03d  %s\n", i, pName );
	}
}
void PrintCommandLine( int argc, char **argv )
{
	Warning( "Command line: " );
	for ( int z=0; z < argc; z++ )
	{
		Warning( "\"%s\" ", argv[z] );
	}
	Warning( "\n\n" );
}

void main (int argc, char **argv)
{
	// Install an exception handler.
	SetupDefaultToolsMinidumpHandler();

	int			i;
	char		source[1024];
	int			size;
	FILE		*f;

	bool		extractlumps[HEADER_LUMPS];
	memset( extractlumps, 0, sizeof(extractlumps) );
	bool		bHaveAnyToExtract = false;

	::SetHDRMode( false );

	CommandLine()->CreateCmdLine( argc, argv );
	InitCommandLineProgram( argc, argv );
	g_pFileSystem = g_pFullFileSystem;

	MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f, false, false, false, false );
	PrintCommandLine( argc, argv );
	if (argc == 1)
	{
		printf( "vbspinfo:  build date(" __DATE__ ")\n" );

		printf("usage: vbspinfo [parameters] bspfile [bspfiles]\n");
		printf("   -treeinfo            \n");
//		printf("   -drawtree            \n"); Remove for now until the option can be fixed
		printf("   -worldtexturestats   \n");
		printf("   -modelstats          \n");
		printf("   -liststaticprops     \n");
		printf("   -X[lump ID]          Extract BSP lump to file. i.e -X0 extracts entity lump.\n");
		printf("   -size				Show .bsp worldmodel bounds\n");
		Error("Incorrect syntax.");
	}
		
	bool bWorldTextureStats = false;
	bool bModelStats = false;
	bool bListStaticProps = false;
	bool bShowMapBounds = false;

	for (i=1 ; i<argc ; i++)
	{
		if ( stricmp( argv[i], "-treeinfo" ) == 0 )
		{
			g_bTreeInfo = true;
			continue;
		}
		else if ( stricmp( argv[i], "-drawtree" ) == 0 )
		{
			g_bDrawTree = true;
			continue;
		}
		else if( stricmp( argv[i], "-worldtexturestats" ) == 0 )
		{
			bWorldTextureStats = true;
			continue;
		}
		else if( stricmp( argv[i], "-modelstats" ) == 0 )
		{
			bModelStats = true;
			continue;
		}
		else if( stricmp( argv[i], "-liststaticprops" ) == 0 )
		{
			bListStaticProps = true;
			continue;
		}
		else if( stricmp( argv[i], "-steamlocal" ) == 0 )
		{
			continue;
		}
		else if( stricmp( argv[i], "-steam" ) == 0 )
		{
			continue;
		}
		else if( strnicmp( argv[i], "-X", 2 ) == 0 )
		{
			int iLump = atoi( argv[i]+2 );
			extractlumps[iLump] = true;
			bHaveAnyToExtract = true;
			continue;
		}
		else if ( stricmp( argv[ i ], "-size" ) == 0 )
		{
			bShowMapBounds = true;
			continue;
		}

		if( !bWorldTextureStats && !bModelStats && !bListStaticProps )
		{
			printf ("---------------------\n");
		}
		strcpy (source, argv[i]);
		Q_DefaultExtension (source, ".bsp", sizeof( source ) );
		
		strcpy( source, ExpandPath( source ) );
		f = fopen (source, "rb");
		if (f)
		{
			fseek( f, 0, SEEK_END );
			size = ftell( f );
			fclose (f);
		}
		else
			size = 0;

		if( !bWorldTextureStats && !bModelStats && !bListStaticProps )
		{
			Msg ("reading %s (%d)\n", source, size);
		}		
		

		// If we're extracting, do that and quit.
		if ( bHaveAnyToExtract )
		{
			OpenBSPFile(source);

			// If the filename doesn't have a path, prepend with the current directory
			char fullbspname[MAX_PATH];
			_fullpath( fullbspname, source, sizeof( fullbspname ) );

			for ( int extract = 0; extract < HEADER_LUMPS; extract++ )
			{
				if ( extractlumps[extract] )
				{
					printf ("Extracting lump %d.\n", extract );
					WriteLumpToFile( fullbspname, extract );
				}
			}

			CloseBSPFile();

			printf ("Finished extraction.\n" );
			return;
		}



		LoadBSPFile (source);		

		if( bWorldTextureStats )
		{
			PrintWorldTextureStats( stdout );
		}
		else if( bModelStats )
		{
			PrintModelStats( stdout );
		}
		else if( bListStaticProps )
		{
			PrintListStaticProps( stdout );
		}
		else if ( bShowMapBounds )
		{
			dmodel_t *world = &dmodels[ 0 ];
			printf( "Full     :  (%8.3f %8.3f %8.3f) - (%8.3f %8.3f %8.3f)\n",
				world->mins.x,
				world->mins.y,
				world->mins.z,
				world->maxs.x,
				world->maxs.y,
				world->maxs.z );

			if ( !num_entities )
				ParseEntities();

			for ( int e = 0; e < num_entities; ++i )
			{
				char* pEntity = ValueForKey(&entities[e], "classname");
				if ( strcmp(pEntity, "worldspawn" ) )
					continue;

				Vector wmins;
				Vector wmaxs;
				wmins.Init();
				wmaxs.Init();

				char* pchMins = ValueForKey(&entities[e], "world_mins");
				sscanf( pchMins, "%f %f %f", &wmins.x, &wmins.y, &wmins.z );
				char* pchMaxs = ValueForKey(&entities[e], "world_maxs");
				sscanf( pchMaxs, "%f %f %f", &wmaxs.x, &wmaxs.y, &wmaxs.z );


				printf( "No Skybox:  (%8.3f %8.3f %8.3f) - (%8.3f %8.3f %8.3f)\n",
					wmins.x,
					wmins.y,
					wmins.z,
					wmaxs.x,
					wmaxs.y,
					wmaxs.z );
				break;
			}
		}
		else
		{
			PrintBSPFileSizes ();
		}

		

		if ( g_bTreeInfo )
		{
			g_nOptimumDepth = (int)( log( ( float )numnodes ) / log( 2.0f ) );
			g_nMinTreeDepth = 999999;
			g_nMaxTreeDepth = -999999;
			g_TotalTreeDepth = 0;
			g_TotalVariance = 0;
			CalculateTreeInfo_R( dmodels[0].headnode, 0 );

			printf(	"\n"
					"\t-------------------\n"
					"\tTREE INFO:\n"
					"\t-------------------\n"
					"\tNumber of nodes ------------------ : %d\n"
					"\tOptimum tree depth (logN) -------- : %.3f\n"
					"\tMinimum tree depth --------------- : %d\n"
					"\tMaximum tree depth --------------- : %d\n"
					"\tAverage tree depth --------------- : %.3f\n"
					"\tAverage leaf variance from optimum : %.3f\n\n",
					numnodes,
					g_nOptimumDepth,
					g_nMinTreeDepth,
					g_nMaxTreeDepth,
					(float)g_TotalTreeDepth / numnodes,
					(float)g_TotalVariance / numnodes );
		}
		
		if ( g_bDrawTree )
		{
			DrawTreeToScratchPad();
		}
		
		if( !bWorldTextureStats && !bModelStats && !bListStaticProps )
		{
			printf ("---------------------\n");
		}
	}
}