//========= Copyright Valve Corporation, All rights reserved. ============//
//
//	TIMESTAMP_LOG.CPP
//
//	TimeStamp Log Display.
//=====================================================================================//
#include "vxconsole.h"

#define ID_TIMESTAMPLOG_LISTVIEW 100

// column id
#define ID_TSL_TIME			0
#define ID_TSL_DELTATIME	1
#define ID_TSL_MEMORY		2
#define ID_TSL_DELTAMEMORY	3
#define ID_TSL_MESSAGE		4

typedef struct
{	const CHAR*			name;
	int					width;
	int					subItemIndex;
	CHAR				nameBuff[32];
} label_t;

struct timeStampLogNode_t
{
	int					index;
	float				time;
	float				deltaTime;
	int					memory;
	int					deltaMemory;
	char				timeBuff[32];
	char				deltaTimeBuff[32];
	char				memoryBuff[32];
	char				deltaMemoryBuff[32];
	char				*pMessage;
	timeStampLogNode_t	*pNext;
};

HWND				g_timeStampLog_hWnd;
HWND				g_timeStampLog_hWndListView;
RECT				g_timeStampLog_windowRect;
timeStampLogNode_t	*g_timeStampLog_pNodes;
int					g_timeStampLog_sortColumn;
int					g_timeStampLog_sortDescending;

label_t g_timeStampLog_Labels[] =
{
	{"Time",			100,	ID_TSL_TIME},
	{"Delta Time",		100,	ID_TSL_DELTATIME},
	{"Memory",			100,	ID_TSL_MEMORY},
	{"Delta Memory",	100,	ID_TSL_DELTAMEMORY},
	{"Message",			400,	ID_TSL_MESSAGE},
};

//-----------------------------------------------------------------------------
//	TimeStampLog_CompareFunc
// 
//-----------------------------------------------------------------------------
int CALLBACK TimeStampLog_CompareFunc( LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort )
{
	timeStampLogNode_t*	pNodeA = ( timeStampLogNode_t* )lParam1;
	timeStampLogNode_t*	pNodeB = ( timeStampLogNode_t* )lParam2;

	int sort = 0;
	switch ( g_timeStampLog_sortColumn )
	{
		case ID_TSL_TIME:
			sort = ( int )( 1000.0f*pNodeA->time - 1000.0f*pNodeB->time );
			break;

		case ID_TSL_DELTATIME:
			sort = ( int )( 1000.0f*pNodeA->deltaTime - 1000.0f*pNodeB->deltaTime );
			break;

		case ID_TSL_MEMORY:
			sort = pNodeA->memory - pNodeB->memory;
			break;

		case ID_TSL_DELTAMEMORY:
			sort = pNodeA->deltaMemory - pNodeB->deltaMemory;
			break;

		case ID_TSL_MESSAGE:
			sort = stricmp( pNodeA->pMessage, pNodeB->pMessage );
			break;
	}

	// flip the sort order
	if ( g_timeStampLog_sortDescending )
		sort *= -1;

	return ( sort );
}

//-----------------------------------------------------------------------------
//	TimeStampLog_SortItems
// 
//-----------------------------------------------------------------------------
void TimeStampLog_SortItems()
{
	LVITEM				lvitem;
	timeStampLogNode_t	*pNode;
	int					i;

	if ( !g_timeStampLog_hWnd )
	{
		// only sort if window is visible
		return;
	}

	ListView_SortItems( g_timeStampLog_hWndListView, TimeStampLog_CompareFunc, 0 );

	memset( &lvitem, 0, sizeof( lvitem ) );
	lvitem.mask = LVIF_PARAM;

	// get each item and reset its list index
	int itemCount = ListView_GetItemCount( g_timeStampLog_hWndListView );
	for ( i=0; i<itemCount; i++ )
	{
		lvitem.iItem = i;
		ListView_GetItem( g_timeStampLog_hWndListView, &lvitem );

		pNode = ( timeStampLogNode_t* )lvitem.lParam;
		pNode->index = i;
	}

	// update list view columns with sort key
	for ( i=0; i<sizeof( g_timeStampLog_Labels )/sizeof( g_timeStampLog_Labels[0] ); i++ )
	{
		char		symbol;
		LVCOLUMN	lvc; 

		if ( i == g_timeStampLog_sortColumn )
			symbol = g_timeStampLog_sortDescending ? '<' : '>';
		else
			symbol = ' ';
		sprintf( g_timeStampLog_Labels[i].nameBuff, "%s %c", g_timeStampLog_Labels[i].name, symbol );

		memset( &lvc, 0, sizeof( lvc ) );
		lvc.mask    = LVCF_TEXT; 
		lvc.pszText = ( LPSTR )g_timeStampLog_Labels[i].nameBuff;

		ListView_SetColumn( g_timeStampLog_hWndListView, i, &lvc );
	}
}

//-----------------------------------------------------------------------------
//	TimeStampLog_AddViewItem
// 
//-----------------------------------------------------------------------------
void TimeStampLog_AddViewItem( timeStampLogNode_t* pNode )
{
	LVITEM	lvi;

	if ( !g_timeStampLog_hWnd )
	{
		// only valid if log window is visible
		return;
	}

	// update the text callback buffers
	sprintf( pNode->timeBuff, "%2.2d:%2.2d:%2.2d:%3.3d", ( int )pNode->time/3600, ( ( int )pNode->time/60 )%60, ( int )pNode->time%60, ( int )( 1000*( pNode->time-( int )pNode->time ) )%1000 );
	sprintf( pNode->deltaTimeBuff, "%.3f", pNode->deltaTime );
	sprintf( pNode->memoryBuff, "%.2f MB", pNode->memory/( 1024.0f*1024.0f ) );
	sprintf( pNode->deltaMemoryBuff, "%d", pNode->deltaMemory );

	int itemCount = ListView_GetItemCount( g_timeStampLog_hWndListView );

	// setup and insert at end of list
	memset( &lvi, 0, sizeof( lvi ) );
	lvi.mask      = LVIF_TEXT | LVIF_PARAM | LVIF_STATE; 
	lvi.iItem     = itemCount;
	lvi.iSubItem  = 0;
	lvi.state     = 0; 
	lvi.stateMask = 0;
	lvi.pszText   = LPSTR_TEXTCALLBACK;  
	lvi.lParam    = ( LPARAM )pNode;
								 
	// insert
	pNode->index = ListView_InsertItem( g_timeStampLog_hWndListView, &lvi );
}

//-----------------------------------------------------------------------------
//	TimeStampLog_AddItem
// 
//-----------------------------------------------------------------------------
void TimeStampLog_AddItem( float time, float deltaTime, int memory, int deltaMemory, const char* pMessage )
{
	timeStampLogNode_t*	pNode;

	// create and init
	pNode = new timeStampLogNode_t;
	memset( pNode, 0, sizeof( timeStampLogNode_t ) );

	pNode->time        = time;
	pNode->deltaTime   = deltaTime;
	pNode->memory      = memory;
	pNode->deltaMemory = deltaMemory;
	pNode->pMessage    = Sys_CopyString( pMessage ? pMessage : "" );

	// link in
	pNode->pNext = g_timeStampLog_pNodes;
	g_timeStampLog_pNodes = pNode;

	TimeStampLog_AddViewItem( pNode );
}

//-----------------------------------------------------------------------------
//	TimeStampLog_Clear
// 
//-----------------------------------------------------------------------------
void TimeStampLog_Clear()
{
	timeStampLogNode_t*	pNode;
	timeStampLogNode_t*	pNextNode;

	// delete all the list view entries
	if ( g_timeStampLog_hWnd )
		ListView_DeleteAllItems( g_timeStampLog_hWndListView );

	// delete nodes in chain
	pNode = g_timeStampLog_pNodes;
	while ( pNode )
	{
		pNextNode = pNode->pNext;

		Sys_Free( pNode->pMessage );
		delete pNode;	
		
		pNode = pNextNode;
	}
	g_timeStampLog_pNodes = NULL;
}

//-----------------------------------------------------------------------------
//	TimeStampLog_SaveConfig
// 
//-----------------------------------------------------------------------------
void TimeStampLog_SaveConfig()
{
	char	buff[256];

	Sys_SetRegistryInteger( "timeStampLogSortColumn", g_timeStampLog_sortColumn );
	Sys_SetRegistryInteger( "timeStampLogSortDescending", g_timeStampLog_sortDescending );

	WINDOWPLACEMENT wp;
	memset( &wp, 0, sizeof( wp ) );
	wp.length = sizeof( WINDOWPLACEMENT );
	GetWindowPlacement( g_timeStampLog_hWnd, &wp );
	g_timeStampLog_windowRect = wp.rcNormalPosition;

	sprintf( buff, "%d %d %d %d", g_timeStampLog_windowRect.left, g_timeStampLog_windowRect.top, g_timeStampLog_windowRect.right, g_timeStampLog_windowRect.bottom );
	Sys_SetRegistryString( "timeStampLogWindowRect", buff );
}

//-----------------------------------------------------------------------------
//	TimeStampLog_LoadConfig	
// 
//-----------------------------------------------------------------------------
void TimeStampLog_LoadConfig()
{
	int		numArgs;
	char	buff[256];

	Sys_GetRegistryInteger( "timeStampLogSortColumn", ID_TSL_TIME, g_timeStampLog_sortColumn );
	Sys_GetRegistryInteger( "timeStampLogSortDescending", false, g_timeStampLog_sortDescending );

	Sys_GetRegistryString( "timeStampLogWindowRect", buff, "", sizeof( buff ) );
	numArgs = sscanf( buff, "%d %d %d %d", &g_timeStampLog_windowRect.left, &g_timeStampLog_windowRect.top, &g_timeStampLog_windowRect.right, &g_timeStampLog_windowRect.bottom );
	if ( numArgs != 4 || g_timeStampLog_windowRect.left < 0 || g_timeStampLog_windowRect.top < 0 || g_timeStampLog_windowRect.right < 0 || g_timeStampLog_windowRect.bottom < 0 )
		memset( &g_timeStampLog_windowRect, 0, sizeof( g_timeStampLog_windowRect ) );
}

//-----------------------------------------------------------------------------
//	TimeStampLog_SizeWindow
// 
//-----------------------------------------------------------------------------
void TimeStampLog_SizeWindow( HWND hwnd, int width, int height )
{
    if ( width==0 || height==0 )
    {
        RECT rcClient;
        GetClientRect( hwnd, &rcClient );
        width  = rcClient.right;
        height = rcClient.bottom;
    }

	// position the ListView
	SetWindowPos( g_timeStampLog_hWndListView, NULL, 0, 0, width, height, SWP_NOZORDER );
}

//-----------------------------------------------------------------------------
//	TimeStampLog_Export
// 
//-----------------------------------------------------------------------------
void TimeStampLog_Export()
{
	OPENFILENAME		ofn;
	char				logFilename[MAX_PATH]; 
	int					retval;
	FILE*				fp;
	int					i;
	int					count;

	memset( &ofn, 0, sizeof( ofn ) );
	ofn.lStructSize     = sizeof( ofn );
	ofn.hwndOwner       = g_timeStampLog_hWnd;
	ofn.lpstrFile       = logFilename;
	ofn.lpstrFile[0]    = '\0';
	ofn.nMaxFile        = sizeof( logFilename );
	ofn.lpstrFilter     = "Excel CSV\0*.CSV\0All Files\0*.*\0";
	ofn.nFilterIndex    = 1;
	ofn.lpstrFileTitle  = NULL;
	ofn.nMaxFileTitle   = 0;
	ofn.lpstrInitialDir = "c:\\";
	ofn.Flags           = OFN_PATHMUSTEXIST;

	// display the open dialog box 
	retval = GetOpenFileName( &ofn ); 
	if ( !retval )
		return;

	Sys_AddExtension( ".csv", logFilename, sizeof( logFilename ) );

	fp = fopen( logFilename, "wt+" );
	if ( !fp )
		return;

	// labels
	count = ARRAYSIZE( g_timeStampLog_Labels );
	for ( i=0; i<count; i++ )
	{
		fprintf( fp, "\"%s\"", g_timeStampLog_Labels[i].name );
		if ( i != count-1 )
			fprintf( fp, "," );
	}
	fprintf( fp, "\n" );

	// build a list for easy reverse traversal
	CUtlVector< timeStampLogNode_t* > nodeList;
	timeStampLogNode_t	*pNode;
	pNode = g_timeStampLog_pNodes;
	while ( pNode )
	{
		nodeList.AddToTail( pNode );
		pNode = pNode->pNext;
	}

	// dump to the log
	for ( int i=nodeList.Count()-1; i>=0; i-- )
	{
		pNode = nodeList[i];

		fprintf( fp, "\"%s\"", pNode->timeBuff );
		fprintf( fp, ",\"%s\"", pNode->deltaTimeBuff );
		fprintf( fp, ",\"%s\"", pNode->memoryBuff );
		fprintf( fp, ",\"%s\"", pNode->deltaMemoryBuff );
		fprintf( fp, ",\"%s\"", pNode->pMessage );
		fprintf( fp, "\n" );
	}

	fclose( fp );
}

//-----------------------------------------------------------------------------
//	TimeStampLog_WndProc
// 
//-----------------------------------------------------------------------------
LRESULT CALLBACK TimeStampLog_WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
{
    WORD				wID = LOWORD( wParam );
	timeStampLogNode_t	*pNode;

	switch ( message )
	{
		case WM_CREATE:
			return 0L;

		case WM_DESTROY:
			TimeStampLog_SaveConfig();
			g_timeStampLog_hWnd = NULL;
			return 0L;
	
		case WM_SIZE:
			TimeStampLog_SizeWindow( hwnd, LOWORD( lParam ), HIWORD( lParam ) );
			return 0L;

		case WM_NOTIFY:
			switch ( ( ( LPNMHDR )lParam )->code )
			{
				case LVN_COLUMNCLICK:
					NMLISTVIEW*	pnmlv;
					pnmlv = ( NMLISTVIEW* )lParam; 
					if ( g_timeStampLog_sortColumn == pnmlv->iSubItem )
					{
						// user has clicked on same column - flip the sort
						g_timeStampLog_sortDescending ^= 1;
					}
					else
					{
						// sort by new column
						g_timeStampLog_sortColumn = pnmlv->iSubItem;
					}
					TimeStampLog_SortItems();
					return 0L;

				case LVN_GETDISPINFO:
					NMLVDISPINFO* plvdi;
					plvdi = ( NMLVDISPINFO* )lParam;
					pNode = ( timeStampLogNode_t * )plvdi->item.lParam;
					switch ( plvdi->item.iSubItem )
					{
						case ID_TSL_TIME:
							plvdi->item.pszText = pNode->timeBuff;
							return 0L;
	  					
						case ID_TSL_DELTATIME:
							plvdi->item.pszText = pNode->deltaTimeBuff;
							return 0L;

						case ID_TSL_MEMORY:
							plvdi->item.pszText = pNode->memoryBuff;
							return 0L;

						case ID_TSL_DELTAMEMORY:
							plvdi->item.pszText = pNode->deltaMemoryBuff;
							return 0L;

						case ID_TSL_MESSAGE:
							plvdi->item.pszText = pNode->pMessage;
							return 0L;

						default:
							break;
					}
					break;
			}
			break;

        case WM_COMMAND:
            switch ( wID )
            {
				case IDM_OPTIONS_CLEAR:
					TimeStampLog_Clear();
					return 0L;

				case IDM_OPTIONS_EXPORT:
					TimeStampLog_Export();
					return 0L;
			}
			break;
	}	

	return ( DefWindowProc( hwnd, message, wParam, lParam ) );
}

//-----------------------------------------------------------------------------
//	TimeStampLog_Init
// 
//-----------------------------------------------------------------------------
bool TimeStampLog_Init()
{
	// set up our window class
	WNDCLASS wndclass;

	memset( &wndclass, 0, sizeof( wndclass ) );
	wndclass.style         = 0;
	wndclass.lpfnWndProc   = TimeStampLog_WndProc;
	wndclass.cbClsExtra    = 0;
	wndclass.cbWndExtra    = 0;
	wndclass.hInstance     = g_hInstance;
	wndclass.hIcon         = g_hIcons[ICON_APPLICATION];
	wndclass.hCursor       = LoadCursor( NULL, IDC_ARROW );
	wndclass.hbrBackground = g_hBackgroundBrush;
	wndclass.lpszMenuName  = MAKEINTRESOURCE( MENU_TIMESTAMPLOG );
	wndclass.lpszClassName = "TIMESTAMPLOGCLASS";
	if ( !RegisterClass( &wndclass ) )
		return false;

	TimeStampLog_LoadConfig();

	return true;
}

//-----------------------------------------------------------------------------
//	TimeStampLog_Open	
// 
//-----------------------------------------------------------------------------
void TimeStampLog_Open()
{
	RECT				clientRect;
	HWND				hWnd;
	int					i;
	timeStampLogNode_t	*pNode;

	if ( g_timeStampLog_hWnd )
	{
		// only one file log instance
		if ( IsIconic( g_timeStampLog_hWnd ) )
			ShowWindow( g_timeStampLog_hWnd, SW_RESTORE );
		SetForegroundWindow( g_timeStampLog_hWnd );
		return;
	}

	hWnd = CreateWindowEx( 
				WS_EX_CLIENTEDGE,
				"TIMESTAMPLOGCLASS",
				"TimeStamp Log",
				WS_POPUP|WS_CAPTION|WS_SYSMENU|WS_SIZEBOX|WS_MINIMIZEBOX|WS_MAXIMIZEBOX,
				0,
				0,
				600,
				300,
				g_hDlgMain,
				NULL,
				g_hInstance,
				NULL );
	g_timeStampLog_hWnd = hWnd;

	GetClientRect( g_timeStampLog_hWnd, &clientRect ); 
	hWnd = CreateWindow( 
				WC_LISTVIEW, 
				"", 
				WS_VISIBLE|WS_CHILD|LVS_REPORT, 
				0, 
				0, 
				clientRect.right-clientRect.left,
				clientRect.bottom-clientRect.top, 
				g_timeStampLog_hWnd,
				( HMENU )ID_TIMESTAMPLOG_LISTVIEW,
				g_hInstance,
				NULL ); 
	g_timeStampLog_hWndListView = hWnd;

	// init list view columns
	for ( i=0; i<sizeof( g_timeStampLog_Labels )/sizeof( g_timeStampLog_Labels[0] ); i++ )
	{
		LVCOLUMN lvc; 
		memset( &lvc, 0, sizeof( lvc ) );

		lvc.mask     = LVCF_FMT|LVCF_WIDTH|LVCF_TEXT|LVCF_SUBITEM; 
		lvc.iSubItem = 0;
		lvc.cx       = g_timeStampLog_Labels[i].width;
		lvc.fmt      = LVCFMT_LEFT;
		lvc.pszText  = ( LPSTR )g_timeStampLog_Labels[i].name;

		ListView_InsertColumn( g_timeStampLog_hWndListView, i, &lvc );
	}
	
	ListView_SetBkColor( g_timeStampLog_hWndListView, g_backgroundColor );
	ListView_SetTextBkColor( g_timeStampLog_hWndListView, g_backgroundColor );

	DWORD style = LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES|LVS_EX_HEADERDRAGDROP;
	ListView_SetExtendedListViewStyleEx( g_timeStampLog_hWndListView, style, style );

	// populate list view
	pNode = g_timeStampLog_pNodes;
	while ( pNode )
	{
		TimeStampLog_AddViewItem( pNode );
		pNode = pNode->pNext;
	}
	TimeStampLog_SortItems();

	if ( g_timeStampLog_windowRect.right && g_timeStampLog_windowRect.bottom )
		MoveWindow( g_timeStampLog_hWnd, g_timeStampLog_windowRect.left, g_timeStampLog_windowRect.top, g_timeStampLog_windowRect.right-g_timeStampLog_windowRect.left, g_timeStampLog_windowRect.bottom-g_timeStampLog_windowRect.top, FALSE );
	ShowWindow( g_timeStampLog_hWnd, SHOW_OPENWINDOW );
}


//-----------------------------------------------------------------------------
// rc_TimeStampLog
//
// Sent from application with time stamp log
//-----------------------------------------------------------------------------
int rc_TimeStampLog( char* commandPtr )
{
	char			*cmdToken;
	int				timeStampAddr;
	int				retAddr;
	int				retVal;
	int				errCode = -1;
	xrTimeStamp_t	timeStamp;

	// get data
	cmdToken = GetToken( &commandPtr );
	if ( !cmdToken[0] )
		goto cleanUp;
	sscanf( cmdToken, "%x", &timeStampAddr );

	// get retAddr
	cmdToken = GetToken( &commandPtr );
	if ( !cmdToken[0] )
		goto cleanUp;
	sscanf( cmdToken, "%x", &retAddr );

	// get the caller's data
	DmGetMemory( ( void* )timeStampAddr, sizeof( xrTimeStamp_t ), &timeStamp, NULL );

	// swap the structure
	BigFloat( &timeStamp.time, &timeStamp.time );
	BigFloat( &timeStamp.deltaTime, &timeStamp.deltaTime );
	timeStamp.memory = BigDWord( timeStamp.memory );
	timeStamp.deltaMemory = BigDWord( timeStamp.deltaMemory );

	// add to log
	TimeStampLog_AddItem( timeStamp.time, timeStamp.deltaTime, timeStamp.memory, timeStamp.deltaMemory, timeStamp.messageString );
	TimeStampLog_SortItems();

	// return the result
	retVal = 0;
	int xboxRetVal = BigDWord( retVal );
	DmSetMemory( ( void* )retAddr, sizeof( int ), &xboxRetVal, NULL );

	DebugCommand( "0x%8.8x = TimeStampLog( 0x%8.8x )\n", retVal, timeStampAddr );

	// success
	errCode = 0;

cleanUp:	
	return ( errCode );
}