//----------------------------------------------------------------------------
// File: mazeapp.cpp
//
// Desc: see main.cpp
//
// Copyright (c) 1999-2001 Microsoft Corp. All rights reserved.
//-----------------------------------------------------------------------------
#define STRICT
#define D3D_OVERLOADS
#include <windows.h>
#include <d3dx.h>
#include <stdio.h>
#include <tchar.h>
#include <math.h>
#include <process.h>
#include <dxerr9.h>
#include <dplay8.h>
#include "DXUtil.h"
#include "SyncObjects.h"
#include "DummyConnector.h"
#include "DPlay8Client.h"
#include "MazeClient.h"
#include "IMazeGraphics.h"
#include "MazeApp.h"


static CMazeApp* s_pMazeApp = NULL;


//-----------------------------------------------------------------------------
// Name: CMazeApp
// Desc: Constructor
//-----------------------------------------------------------------------------
CMazeApp::CMazeApp()
{
    s_pMazeApp                  = this;
    m_hOutputMsgThread          = NULL;
    m_bQuitThread               = NULL;
    m_hOutputMsgEvent           = NULL;
    m_dwNextOutputMsg           = 0;
    m_bLocalLoopback            = TRUE;
    m_bAllowConnect             = FALSE;
    m_bConnectNow               = TRUE;
    m_bSaveSettings             = TRUE;
    m_lQueueSize                = 0;
    m_dwNextFreeOutputMsg       = 0;
    m_hLogFile                  = NULL;

    m_bLocalLoopbackInitDone    = FALSE;
    m_bInitDone                 = FALSE;

    // Create an event object to flag pending output messages
    m_hOutputMsgEvent = CreateEvent( NULL, FALSE, FALSE, NULL );

    //Initialize our Config structure to 0.
    ZeroMemory(&m_Config, sizeof(m_Config));

    //Must init buffer size to -1, since 0 is valid.
    m_Config.dwSPBufferSize = 0xffffffff;


}




//-----------------------------------------------------------------------------
// Name: ~CMazeApp
// Desc: Destructor
//-----------------------------------------------------------------------------
CMazeApp::~CMazeApp()
{
    CloseHandle( m_hOutputMsgEvent );
}




//-----------------------------------------------------------------------------
// Name: Create
// Desc: Plug in and initialize the given MazeGraphics object (console or d3d)
//-----------------------------------------------------------------------------
HRESULT CMazeApp::Create( IMazeGraphics* pMazeGraphics )
{
    m_pMazeGraphics = pMazeGraphics;
    if( m_pMazeGraphics == NULL )
        return E_FAIL;

    m_pMazeGraphics->Init( this, &m_DP8Client, &m_MazeClient );

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: Run
// Desc: Launch the application
//-----------------------------------------------------------------------------
int CMazeApp::Run( HINSTANCE hInst )
{
    if( NULL == m_pMazeGraphics )
        return 0;

    // Start the timer and init the random seed
    DXUtil_Timer( TIMER_START );
    DWORD dwSRand = (DWORD) (DXUtil_Timer( TIMER_GETABSOLUTETIME ) * UINT_MAX * (DWORD)GetCurrentThreadId() );
    srand( dwSRand );

    // Tell OS's that have power management to not 
    // sleep, since this app will be using the 
    // network connection and need very little user input
    SuspendPowerManagement();

    // Initialize COM
    CoInitializeEx( NULL, COINIT_MULTITHREADED );

    // Extract configuration settings from the registry
    ReadConfig();

    if( m_Config.bFileLogging )
        CreateTempLogFile();

    if( SUCCEEDED( m_pMazeGraphics->Create( hInst ) ) )
    {
        ConsolePrintf( LINE_LOG, TEXT("DirectPlayMaze client started.") );

        // Spin up a thread to record/display the output
        UINT dwOutputMsgThreadID;
        m_hOutputMsgThread = (HANDLE)_beginthreadex( NULL, 0, StaticOutputMsgThread, 
                                                  NULL, 0, &dwOutputMsgThreadID );

        // Initialize maze client object - basically just build the maze
        m_MazeClient.Init( this, m_pMazeGraphics );

        m_pMazeGraphics->Run();

        // Wait for threads to shutdown
        DXUtil_Trace( TEXT("Quiting\n") );
        m_bQuitThread = TRUE;
        SetEvent( m_hOutputMsgEvent );
        WaitForSingleObject( m_hOutputMsgThread, INFINITE );
        CloseHandle( m_hOutputMsgThread );

        // Write configuration settings to registry
        WriteConfig();

        m_pMazeGraphics->Shutdown();
    }

    // Clean up
    DXUtil_Trace( TEXT("Shutting down client\n") );
    m_MazeClient.Shutdown();
    DXUtil_Trace( TEXT("Shutting down dp8\n") );
    m_DP8Client.Shutdown();
    CoUninitialize();

    return 0;
}
    



//-----------------------------------------------------------------------------
// Name: SuspendPowerManagement
// Desc: Turn off power management, which would interfere with the application
//-----------------------------------------------------------------------------
void CMazeApp::SuspendPowerManagement()
{
    TCHAR szPath[MAX_PATH];
    HINSTANCE hInstKernel32 = NULL;
    typedef EXECUTION_STATE (WINAPI* LPSETTHREADEXECUTIONSTATE)( EXECUTION_STATE esFlags );
    LPSETTHREADEXECUTIONSTATE pSetThreadExecutionState = NULL;

    GetSystemDirectory(szPath, MAX_PATH);

    // SetThreadExecutionState() isn't availible on some old OS's, 
    // so do a LoadLibrary to get to it.
    lstrcat(szPath, TEXT("\\kernel32.dll"));
    hInstKernel32 = LoadLibrary(szPath);

    if (hInstKernel32 != NULL)
    {
        pSetThreadExecutionState = (LPSETTHREADEXECUTIONSTATE)GetProcAddress(hInstKernel32, "SetThreadExecutionState");
        if( pSetThreadExecutionState != NULL )
        {
            // Tell OS's that have power management to not 
            // sleep, since this app will be using the 
            // network connection and need very little user input
            pSetThreadExecutionState( ES_SYSTEM_REQUIRED | ES_CONTINUOUS );
        }

        FreeLibrary(hInstKernel32);
    }
}




//-----------------------------------------------------------------------------
// Name: FrameMove
// Desc: Handle a single frame increment
//-----------------------------------------------------------------------------
HRESULT CMazeApp::FrameMove( FLOAT fElapsedTime )
{
    HRESULT hr;
    FLOAT fCurTime = DXUtil_Timer( TIMER_GETAPPTIME );

    static FLOAT s_fLastConnect     = INT_MIN;
    static FLOAT s_fStartEnumTime   = INT_MIN;
    static FLOAT s_fStopEnumTime    = INT_MIN;
    static FLOAT s_fEnumStarted     = FALSE;

    if( m_DP8Client.IsSessionLost() ) 
    {

        if( FALSE == m_bLocalLoopbackInitDone )
        {
            if( m_bInitDone )
            {
                ConsolePrintf( LINE_LOG, TEXT("Disconnected from server") );

                if( m_DP8Client.GetSessionLostReason() == DISCONNNECT_REASON_CLIENT_OUT_OF_DATE )
                {
                    ConsolePrintf( LINE_LOG, TEXT("Disconnected because MazeClient is out of date.") );
                    ConsolePrintf( LINE_LOG, TEXT("Please get updated version") );
                    ConsolePrintf( LINE_LOG, TEXT("from http://msdn.microsoft.com/directx/") );
                    m_bOutOfDateClient = TRUE;
                }

                // Disconnected, so retry in 10 seconds
                s_fStopEnumTime = fCurTime - m_Config.dwNetworkRetryDelay * 60.0f + 10.0f;     
            }
            else
            {
                // If just starting up, then retry immediately
                m_bInitDone = TRUE;
            }

            m_MazeClient.LockWorld();
            m_MazeClient.Reset();
            m_MazeClient.UnlockWorld();

            // Now that the session is lost we need to 
            // restart DirectPlay by calling Close() 
            // and Init() on m_pDPlay
            m_DP8Client.Shutdown();

            //Pass in the our structure in order to get out configuration data.
            m_DP8Client.Init(GetConfig());

            if( m_bAllowLoopback )
                InitServerForLoopback();
       
            m_bLocalLoopbackInitDone = TRUE;
        }

        if( ( !s_fEnumStarted && fCurTime - s_fStopEnumTime > m_Config.dwNetworkRetryDelay * 60.0f || m_bConnectNow )
            && m_bAllowConnect && !m_bOutOfDateClient )
        {
            m_bConnectNow = FALSE;

            if( SUCCEEDED( hr = StartSessionEnum() ) )
            {
                // DirectPlay host enumeration started
                ConsolePrintf( LINE_LOG, TEXT("Starting DirectPlay host enumeration") );
                s_fStartEnumTime = fCurTime;
                s_fEnumStarted = TRUE;
            }
            else
            {
                //If we are in the stress connect, try the next connection. 
                //When we get back to 1, then we have gone through the list, so let's wait.
                if(m_Config.dwStressConnect > 1) 
                {
                    //We are going through the list of machines to attempt connection. So lets try the next right away.
                    ConsolePrintf( LINE_LOG, TEXT("Host not found. Trying local subnet.") );
                    m_bConnectNow = TRUE;  //Try the enum right away with the next item in the list.
                }
                else
                {
                    ConsolePrintf( LINE_LOG, TEXT("DirectPlay host enumeration failed to start.") );
                    ConsolePrintf( LINE_LOG, TEXT("Will try again in %d minutes."), m_Config.dwNetworkRetryDelay );
                }
                
                // DirectPlay host enumeration failed to start
                // Will try again in m_Config.dwNetworkRetryDelay minutes
                s_fStopEnumTime = fCurTime;
                s_fEnumStarted = FALSE;
            }
        }

        if( s_fEnumStarted && fCurTime - s_fStartEnumTime > 5.0f * 60.0f )
        {
            //If we are in the stress connect, try the next connection. 
            //When we get back to 1, then we have gone through the list, so let's start over.
            if(m_Config.dwStressConnect > 1) 
            {
                //We are going through the list of machines to attempt connection. So lets try the next right away.
                ConsolePrintf( LINE_LOG, TEXT("Host not found. Trying local subnet.") );
                m_bConnectNow = TRUE;  //Try the enum right away with the next item in the list.
            }
            else
            {
                ConsolePrintf( LINE_LOG, TEXT("No host found. Stopping DirectPlay host enumeration") );
                ConsolePrintf( LINE_LOG, TEXT("Will try again in %d minutes."), m_Config.dwNetworkRetryDelay );
            }
            // Stop enumeration
            m_DP8Client.StopSessionEnum();
            s_fStopEnumTime = fCurTime;
            s_fEnumStarted = FALSE;
        }

        if( s_fEnumStarted && fCurTime - s_fLastConnect > 0.5f )
        {
            if( TRUE == TryToConnect() )
            {
                // Connect successful 
                ConsolePrintf( LINE_LOG, TEXT("Connected to server.  Host enumeration stopped.") );
                m_bLocalLoopback    = FALSE;
                s_fEnumStarted      = FALSE;
                m_bLocalLoopbackInitDone = FALSE;
                
            }

            s_fLastConnect = fCurTime;
        }

        m_bDisconnectNow = FALSE;
    }
    else
    {
        m_bLocalLoopbackInitDone = FALSE;

        if( m_Config.dwLogLevel > 1 )
        {
            // Display position every so often
            static float fLastPosUpdate = fCurTime;
            if( fCurTime - fLastPosUpdate > 10.0f )
            {
                D3DXVECTOR3 vPos = m_MazeClient.GetCameraPos(); 
                DWORD dwNumPlayers, dwNumNearbyPlayers;
                m_MazeClient.GetPlayerStats( &dwNumPlayers, &dwNumNearbyPlayers );
                ConsolePrintf( LINE_LOG, TEXT("Position: (%5.1f,%5.1f), Players: %d, Nearby Players: %d"), 
                              vPos.x, vPos.z, dwNumPlayers, dwNumNearbyPlayers );
                fLastPosUpdate = fCurTime;
            }
        }

        // Display connection info every so often
        static float fLastLogUpdate = fCurTime;
        if( m_Config.dwAutoPrintStats > 0 && 
            fCurTime - fLastLogUpdate > m_Config.dwAutoPrintStats * 60.0f )
        {
            D3DXVECTOR3 vPos = m_MazeClient.GetCameraPos(); 
            DWORD dwNumPlayers, dwNumNearbyPlayers;
            m_MazeClient.GetPlayerStats( &dwNumPlayers, &dwNumNearbyPlayers );
            ConsolePrintf( LINE_LOG, TEXT("Position: (%5.1f,%5.1f), Players: %d, Nearby Players: %d"), 
                          vPos.x, vPos.z, dwNumPlayers, dwNumNearbyPlayers );

            TCHAR strInfo[5000];
            TCHAR* strEndOfLine;
            TCHAR* strStartOfLine;

            // Query the IOutboudNet for info about the connection to this user
            m_DP8Client.GetConnectionInfo( strInfo, 5000 );

            ConsolePrintf( LINE_LOG, TEXT("Displaying connection info for 0x%0.8x"), m_MazeClient.GetLocalClientID() );
            ConsolePrintf( LINE_LOG, TEXT("(Key: G=Guaranteed NG=Non-Guaranteed B=Bytes P=Packets)") );

            // Display each line seperately
            strStartOfLine = strInfo;
            for( ; ; )
            {
                strEndOfLine = _tcschr( strStartOfLine, '\n' );
                if( strEndOfLine == NULL )
                    break;

                *strEndOfLine = 0;
                ConsolePrintf( LINE_LOG, strStartOfLine );
                strStartOfLine = strEndOfLine + 1;
            }

            fLastLogUpdate = fCurTime;
        }

        // If we are testing connect/disconnect, break after so many iterations.
        if( m_Config.bAutoDisconnnect )  
        {
            // Disconnect between 5-25seconds 
            static float fDisconnectCountdown = 10.0f;

            fDisconnectCountdown -= fElapsedTime;
            if( fDisconnectCountdown < 0.0f )  
            {
                fDisconnectCountdown = (float)(rand() % 20000 + 5000 ) / 1000.0f;
                ConsolePrintf( LINE_LOG, TEXT("Intentional disconnect.  Connecting again for %0.0f seconds..."), fDisconnectCountdown );
                m_DP8Client.Shutdown();
                m_MazeClient.Shutdown();
            }
        }

        if( m_bDisconnectNow )
        {
            m_bDisconnectNow = FALSE;
            ConsolePrintf( LINE_LOG, TEXT("Intentional disconnect.") );
            m_DP8Client.Shutdown();
            m_MazeClient.Shutdown();

            if( m_bAllowLoopback )
                InitServerForLoopback();
        }
    }

    // Update state of client
    m_MazeClient.Update( fElapsedTime );

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: StartSessionEnum
// Desc: Start session enumeration to locate active servers
//-----------------------------------------------------------------------------
HRESULT CMazeApp::StartSessionEnum()
{
    HRESULT hr = S_OK;

    // If we're not the preview, then enum sessions
    if( m_pMazeGraphics->IsPreview() )
        return S_OK;

    // Start enumerating available sessions at specified IP address.

    if( m_Config.bConnectToMicrosoftSite )
    {
        ConsolePrintf( LINE_LOG, TEXT("Connecting to DPMaze.directtaps.net") );
        hr = m_DP8Client.StartSessionEnum( MICROSOFT_SERVER );
    }
    else if( m_Config.bConnectToLocalServer )
    {
        ConsolePrintf( LINE_LOG, TEXT("Connecting to local server (searches the local subnet)") );
        hr = m_DP8Client.StartSessionEnum( TEXT("") );
    }
    else if( m_Config.bConnectToRemoteServer )
    {
        ConsolePrintf( LINE_LOG, TEXT("Connecting to remote server at '%s'"), m_Config.szIPAddress );
        hr = m_DP8Client.StartSessionEnum( m_Config.szIPAddress );
    }
    // If users wants Stress Connect, go through the sequence they have requested.
    else if( m_Config.dwStressConnect )
    {
        if( m_Config.dwStressConnect == 1 )
        {
            // Point to local subnet for next enum.
            m_Config.dwStressConnect = 2;

            ConsolePrintf( LINE_LOG, TEXT("Connecting to remote server at '%s'"), m_Config.szIPAddress );
            hr = m_DP8Client.StartSessionEnum( m_Config.szIPAddress );
            
        }
        else
        {
            // Point back at remote server for next enum.
            m_Config.dwStressConnect = 1;

            // Must equal 2 or something higher. Try the local subnet.
            ConsolePrintf( LINE_LOG, TEXT("Connecting to local server (searches the local subnet)") );
            hr = m_DP8Client.StartSessionEnum( TEXT("") );
            
        }
    }

    return hr;
}




//-----------------------------------------------------------------------------
// Name: TryToConnect
// Desc: Attempt to establish a connection with any of the detected sessions
//-----------------------------------------------------------------------------
BOOL CMazeApp::TryToConnect()
{
    if( m_DP8Client.GetNumSessions() > 0 )
    {
        m_MazeClient.Reset();
        m_MazeClient.SetOutboundClient( m_DP8Client.GetOutboundClient() );
        m_DP8Client.SetClient( &m_MazeClient );

        // Loop through the available sessions and attempt to connect
        for( DWORD i = 0; i < m_DP8Client.GetNumSessions(); i++ )
        {
            if( SUCCEEDED(m_DP8Client.JoinSession( i ) ) )
            {
                return TRUE;
            }
        }
    }

    return FALSE;
}




//-----------------------------------------------------------------------------
// Name: InitServerForLoopback
// Desc: Initialize server for loopback
//-----------------------------------------------------------------------------
HRESULT CMazeApp::InitServerForLoopback()
{
    HRESULT hr;

    m_bLocalLoopback = TRUE;

    #define LOOPBACK_MAZE_WIDTH  16
    #define LOOPBACK_MAZE_HEIGHT 16

    // Initalize maze and server objects for loopback mode
    if( FAILED( hr = m_MazeClient.m_Maze.Init( LOOPBACK_MAZE_WIDTH, 
                                               LOOPBACK_MAZE_HEIGHT, 
                                               DEFAULT_SEED ) ) )
    {
        return DXTRACE_ERR_MSGBOX( TEXT("m_Maze.Init"), hr );
    }

    // Initialize maze server object - hook up to the maze object in the client
    m_MazeServer.Init( m_bLocalLoopback, &m_MazeClient.m_Maze );

    m_DummyClientConnection.SetTarget( &m_MazeServer );
    m_MazeClient.SetOutboundClient( &m_DummyClientConnection );
    m_DummyServerConnection.SetTarget( &m_MazeClient );
    m_MazeServer.SetOutboundServer( &m_DummyServerConnection );

    m_DummyClientConnection.Connect( 2 );
    m_MazeClient.EngageAutopilot( TRUE );

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: StaticOutputMsgThread
// Desc: Helper for OutputMsgThread
//-----------------------------------------------------------------------------
UINT WINAPI CMazeApp::StaticOutputMsgThread( LPVOID pParam )
{
    return s_pMazeApp->OutputMsgThread( pParam );
}




//-----------------------------------------------------------------------------
// Name: OutputMsgThread
// Desc: Thread body for message output
//-----------------------------------------------------------------------------
UINT WINAPI CMazeApp::OutputMsgThread( LPVOID pParam )
{
#define MAX_LOG_LINES 100

    TCHAR szLogBuffer[MAX_PATH];
    DWORD dwNumProcessed = 0;

    for( ; ; )
    {
        // Wait for output to be added to the queue or the quit flag to be set
        WaitForSingleObject( m_hOutputMsgEvent, INFINITE );
        if( m_bQuitThread )
            break;

        // Update the time stamp
        UpdateTimeStamp();

        // Lock output queue
        m_OutputMsgQueueLock.Enter();

        dwNumProcessed = 0;

        // While we have there are messages to print and we
        // have display'ed less than 5 messages 
        while ( m_lQueueSize > 0 && dwNumProcessed < 5 )
        {
            switch( m_EnumLineType[m_dwNextOutputMsg] )
            {
                case LINE_LOG:
                {
                    // Add m_szOutputMsgBuffer[m_dwNextOutputMsg] to szLogBuffer array,
                    // and redisplay the array on the top half of the screen
                    _sntprintf( szLogBuffer, MAX_PATH-1, TEXT("%s %s"), 
                               m_strTimeStamp, m_szOutputMsgBuffer[m_dwNextOutputMsg] );
                    szLogBuffer[ MAX_PATH-1 ] = 0;

#ifdef _DEBUG
                    OutputDebugString( szLogBuffer );
                    OutputDebugString( TEXT("\n") );
#endif
                    if( m_hLogFile )
                    {
                        DWORD dwWritten;
                        WriteFile( m_hLogFile, szLogBuffer, 
                                   lstrlen( szLogBuffer ), &dwWritten, NULL );
                        TCHAR strEOL = TEXT('\r');
                        WriteFile( m_hLogFile, &strEOL, 
                                   sizeof(TCHAR), &dwWritten, NULL );
                        strEOL = TEXT('\n');
                        WriteFile( m_hLogFile, &strEOL, 
                                   sizeof(TCHAR), &dwWritten, NULL );

                        static float s_fLastFlushTime = DXUtil_Timer( TIMER_GETAPPTIME );
                        float fCurTime = DXUtil_Timer( TIMER_GETAPPTIME );
                        if( fCurTime - s_fLastFlushTime > 0.2f )
                        {
                            FlushFileBuffers( m_hLogFile );
                            s_fLastFlushTime = fCurTime;
                        }
                    }
                    break;
                }

                default:
                    _tcscpy( szLogBuffer, m_szOutputMsgBuffer[m_dwNextOutputMsg] );
                    break;
            }

            m_pMazeGraphics->HandleOutputMsg( m_EnumLineType[m_dwNextOutputMsg], szLogBuffer );

            m_dwNextOutputMsg++;
            if( m_dwNextOutputMsg == MAX_OUTPUT_QUEUE )
                m_dwNextOutputMsg = 0;

            m_lQueueSize--;
            dwNumProcessed++;
        }

        // Unlock output queue
        m_OutputMsgQueueLock.Leave();

        if( m_hLogFile )
            FlushFileBuffers( m_hLogFile );

        // Yield time to other threads
        Sleep( 10 );

        // If there are still messages left, then signal the event
        if( m_lQueueSize > 0 )
            SetEvent( m_hOutputMsgEvent );
    }

    if( m_hLogFile )
    {
        CloseHandle( m_hLogFile );
        m_hLogFile = NULL;
    }

    return 0;
}




//-----------------------------------------------------------------------------
// Name: ConsolePrintf
// Desc: Print the given string to the console using the format flags
//-----------------------------------------------------------------------------
void CMazeApp::ConsolePrintf( EnumLineType enumLineType, const TCHAR* fmt, ... )
{
    // Format the message into a buffer
    const int BUF_LENGTH = 512;
    
    TCHAR buffer[ BUF_LENGTH ];
    _vsntprintf( buffer, BUF_LENGTH-1, fmt, (CHAR*) ((&fmt)+1) );
    buffer[ BUF_LENGTH-1 ] = 0;

    // Lock the output queue
    m_OutputMsgQueueLock.Enter();

    // Find free spot
    if( m_lQueueSize != MAX_OUTPUT_QUEUE )
    {
        // Format message into the buffer
        _vsntprintf( m_szOutputMsgBuffer[m_dwNextFreeOutputMsg], 255, fmt, (CHAR*)((&fmt)+1) );
        m_szOutputMsgBuffer[ m_dwNextFreeOutputMsg ][ 255 ] = 0;

        m_EnumLineType[m_dwNextFreeOutputMsg] = enumLineType;

        // Increment output pointer and wrap around
        m_dwNextFreeOutputMsg++;
        if( m_dwNextFreeOutputMsg == MAX_OUTPUT_QUEUE )
            m_dwNextFreeOutputMsg = 0;

        // Increment message count
        m_lQueueSize++;
    }

    // Unlock output queue
    m_OutputMsgQueueLock.Leave();

    // Signal event so the output thread empties the queue
    SetEvent( m_hOutputMsgEvent );
}




//-----------------------------------------------------------------------------
// Name: UpdateTimeStamp
// Desc: Update the current timestamp
//-----------------------------------------------------------------------------
void CMazeApp::UpdateTimeStamp()
{
    static float s_fTimeStampUpdateCountdown = -10.0f;
    float fCurTime = DXUtil_Timer( TIMER_GETAPPTIME );

    if( fCurTime - s_fTimeStampUpdateCountdown > 1.0f )
    {
        SYSTEMTIME sysTime;
        GetLocalTime( &sysTime );
        _stprintf( m_strTimeStamp, TEXT("[%02d-%02d-%02d %02d:%02d:%02d]"),
                   sysTime.wMonth, sysTime.wDay, sysTime.wYear % 100, 
                   sysTime.wHour, sysTime.wMinute, sysTime.wSecond );

        // Compute how many milliseconds until the next second change
        s_fTimeStampUpdateCountdown = fCurTime;
    }
}
    



//-----------------------------------------------------------------------------
// Name: CreateTempLogFile
// Desc: Create a temporary logfile
//-----------------------------------------------------------------------------
void CMazeApp::CreateTempLogFile()
{
    BOOL bSuccess;
    TCHAR strTempFileName[MAX_PATH];
    TCHAR strTime[MAX_PATH];
    DWORD dwCount;
    
    GetTempPath( MAX_PATH, m_strLogDir );
    lstrcat( m_strLogDir, TEXT("DirectPlayMaze\\") );

    // Create the directory if it doesn't exist
    if( GetFileAttributes( m_strLogDir ) == -1 )
    {
        bSuccess = CreateDirectory( m_strLogDir, NULL );
        if( !bSuccess )
        {
            ConsolePrintf( LINE_LOG, TEXT("Could not create create temp directory '%s'"), m_strLogDir );
            goto LFail;
        }
    }

    ConsolePrintf( LINE_LOG, TEXT("Log Directory: '%s'"), m_strLogDir );

    SYSTEMTIME sysTime;
    GetLocalTime( &sysTime );
    _stprintf( strTime, TEXT("client-%04d-%02d-%02d-"),
               sysTime.wYear, sysTime.wMonth, sysTime.wDay );

    dwCount = 0;

    for( ; ; )
    {
        wsprintf( m_strLogFile, TEXT("%s%05d.log"), strTime, dwCount );
        lstrcpy( strTempFileName, m_strLogDir );
        lstrcat( strTempFileName, m_strLogFile );
        DWORD dwResult = GetFileAttributes( strTempFileName );
        if( dwResult == -1 )
            break;

        dwCount++;
    }

    if( m_hLogFile )
    {
        CloseHandle( m_hLogFile );
        m_hLogFile = NULL;
    }

    m_hLogFile = CreateFile( strTempFileName, GENERIC_WRITE, FILE_SHARE_READ, NULL, 
                             CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL );
    if( m_hLogFile == INVALID_HANDLE_VALUE )
    {
        ConsolePrintf( LINE_LOG, TEXT("Could not create create temp file '%s'"), strTempFileName );
        goto LFail;
    }

    ConsolePrintf( LINE_LOG, TEXT("Logging to temp file: '%s'"), m_strLogFile );
    return;

LFail:
    ConsolePrintf( LINE_LOG, TEXT("File logging disabled") );
    m_Config.bFileLogging = FALSE;
}




//-----------------------------------------------------------------------------
// Name: CloseTempLogFile
// Desc: Close the temporary logfile
//-----------------------------------------------------------------------------
void CMazeApp::CloseTempLogFile()
{
    CloseHandle( m_hLogFile );
    m_hLogFile = NULL;
}




//-----------------------------------------------------------------------------
// Name: ReadConfig
// Desc: Read the user configuration from the registry
//-----------------------------------------------------------------------------
void CMazeApp::ReadConfig()
{
    HKEY hKey = NULL;
    RegOpenKeyEx( HKEY_CURRENT_USER, MAZE_REGKEYNAME, 0, KEY_READ, &hKey );

    DXUtil_ReadBoolRegKey( hKey, TEXT("ConnectToMicrosoftSite"), &m_Config.bConnectToMicrosoftSite, TRUE );
    DXUtil_ReadBoolRegKey( hKey, TEXT("ConnectToLocalServer"), &m_Config.bConnectToLocalServer, FALSE );
    DXUtil_ReadBoolRegKey( hKey, TEXT("ConnectToRemoteServer"), &m_Config.bConnectToRemoteServer, FALSE );
    DXUtil_ReadIntRegKey(  hKey, TEXT("NetworkRetryDelay"), &m_Config.dwNetworkRetryDelay, 30 );
    DXUtil_ReadBoolRegKey( hKey, TEXT("FileLogging"), &m_Config.bFileLogging, TRUE );
    DXUtil_ReadStringRegKeyCch( hKey, TEXT("IPAddress"), m_Config.szIPAddress, 64, TEXT("\0") );

    DXUtil_ReadBoolRegKey( hKey, TEXT("ShowFramerate"), &m_Config.bShowFramerate, TRUE );
    DXUtil_ReadBoolRegKey( hKey, TEXT("ShowIndicators"), &m_Config.bShowIndicators, TRUE );
    DXUtil_ReadBoolRegKey( hKey, TEXT("DrawMiniMap"), &m_Config.bDrawMiniMap, TRUE );
    DXUtil_ReadBoolRegKey( hKey, TEXT("FullScreen"), &m_Config.bFullScreen, TRUE );
    DXUtil_ReadBoolRegKey( hKey, TEXT("Reflections"), &m_Config.bReflections, FALSE );
    DXUtil_ReadBoolRegKey( hKey, TEXT("LatencySmoothing"), &m_Config.bSmoothLatency, TRUE );

    DXUtil_ReadBoolRegKey( hKey, TEXT("AutoDisconnnect"), &m_Config.bAutoDisconnnect, FALSE );
    DXUtil_ReadBoolRegKey( hKey, TEXT("AutoConnnect"), &m_Config.bAutoConnnect, FALSE );
    DXUtil_ReadIntRegKey( hKey, TEXT("LogLevel"), &m_Config.dwLogLevel, 2 );
    DXUtil_ReadIntRegKey( hKey, TEXT("AutoPrintStats"), &m_Config.dwAutoPrintStats, 10 );

    RegCloseKey( hKey );
}




//-----------------------------------------------------------------------------
// Name: WriteConfig
// Desc: Write the update user configuration to the registry
//-----------------------------------------------------------------------------
void CMazeApp::WriteConfig()
{
    HKEY    hKey = NULL;
    DWORD   dwDisposition;
    
    if( !m_bSaveSettings )
        return;

    RegCreateKeyEx( HKEY_CURRENT_USER, MAZE_REGKEYNAME, 0, NULL, 
                    REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, 
                    &hKey, &dwDisposition );

    DXUtil_WriteBoolRegKey( hKey, TEXT("ConnectToMicrosoftSite"), m_Config.bConnectToMicrosoftSite );
    DXUtil_WriteBoolRegKey( hKey, TEXT("ConnectToLocalServer"), m_Config.bConnectToLocalServer );
    DXUtil_WriteBoolRegKey( hKey, TEXT("ConnectToRemoteServer"), m_Config.bConnectToRemoteServer );
    DXUtil_WriteIntRegKey(  hKey, TEXT("NetworkRetryDelay"), m_Config.dwNetworkRetryDelay );
    DXUtil_WriteBoolRegKey( hKey, TEXT("FileLogging"), m_Config.bFileLogging );
    DXUtil_WriteStringRegKey( hKey, TEXT("IPAddress"), m_Config.szIPAddress );

    DXUtil_WriteBoolRegKey( hKey, TEXT("ShowFramerate"), m_Config.bShowFramerate );
    DXUtil_WriteBoolRegKey( hKey, TEXT("ShowIndicators"), m_Config.bShowIndicators );
    DXUtil_WriteBoolRegKey( hKey, TEXT("DrawMiniMap"), m_Config.bDrawMiniMap );
    DXUtil_WriteBoolRegKey( hKey, TEXT("FullScreen"), m_Config.bFullScreen );
    DXUtil_WriteBoolRegKey( hKey, TEXT("Reflections"), m_Config.bReflections );
    DXUtil_WriteBoolRegKey( hKey, TEXT("LatencySmoothing"), m_Config.bSmoothLatency );

    DXUtil_WriteBoolRegKey( hKey, TEXT("AutoDisconnnect"), m_Config.bAutoDisconnnect );
    DXUtil_WriteBoolRegKey( hKey, TEXT("AutoConnnect"), m_Config.bAutoConnnect );
    DXUtil_WriteIntRegKey( hKey, TEXT("LogLevel"), m_Config.dwLogLevel );
    DXUtil_WriteIntRegKey( hKey, TEXT("AutoPrintStats"), m_Config.dwAutoPrintStats );

    RegCloseKey( hKey );
}




//-----------------------------------------------------------------------------
// Name: ConsolePrintf
// Desc: Print the given string to the console using the format flags
//-----------------------------------------------------------------------------
void ConsolePrintf( ServerBufferType enumLineType, const TCHAR* fmt , ... )
{
    // Format the message into a buffer
    const int BUF_LENGTH = 512;

    TCHAR buffer[ BUF_LENGTH ];
    _vsntprintf( buffer, BUF_LENGTH-1, fmt, (CHAR*) ((&fmt)+1) );
    buffer[ BUF_LENGTH-1 ] = 0;

    s_pMazeApp->ConsolePrintf( LINE_LOG, buffer );
}
