//-----------------------------------------------------------------------------
//
// Desc: Matchmaking classs for Jedi Academy (ID 0x4c41000b)
// Generated by MatchSim
//
//-----------------------------------------------------------------------------
#include "match.h"




#ifdef _DEBUG
//-----------------------------------------------------------------------------
// Name: Print
// Desc: Write formatted debug output
//-----------------------------------------------------------------------------
static VOID __cdecl Print( const WCHAR* strFormat, ... )
{
    const int MAX_OUTPUT_STR = 80;
    WCHAR strBuffer[ MAX_OUTPUT_STR ];
    va_list pArglist;
    va_start( pArglist, strFormat );

    INT iChars= wvsprintfW( strBuffer, strFormat, pArglist );
    assert( iChars < MAX_OUTPUT_STR );
    (VOID) iChars; // Avoid compiler warning

    OutputDebugStringW( L"\n*** Matchmaking: " );
    OutputDebugStringW( strBuffer );
    OutputDebugStringW( L"\n\n" );

    va_end( pArglist );
}
#endif




//-----------------------------------------------------------------------------
// Name: CSession
// Desc: Constructor
//-----------------------------------------------------------------------------
CSession::CSession()
{
    m_State = STATE_IDLE;
    m_hSessionTask = NULL;
    m_bUpdate = FALSE;
    m_bListening = FALSE;
    m_bKeyRegistered = FALSE;
    SetupAttributes();
}




//-----------------------------------------------------------------------------
// Name: ~CSession
// Desc: Destructor
//-----------------------------------------------------------------------------
CSession::~CSession()
{
    Reset();
}




//-----------------------------------------------------------------------------
// Name: Reset
// Desc: Reset the Session
//-----------------------------------------------------------------------------
VOID CSession::Reset()
{
    Close();
    Listen( FALSE, NO_WAIT );
    PurgeSessionQ( TRUE );
    if( m_bKeyRegistered )
    {
        XNetUnregisterKey( &SessionID );
        m_bKeyRegistered = FALSE;
    }
}




//-----------------------------------------------------------------------------
// Name: Create
// Desc: Create a new matchmaking session
//-----------------------------------------------------------------------------
HRESULT CSession::Create()
{
    assert( m_State == STATE_IDLE );

    HRESULT hr = XOnlineMatchSessionCreate( PublicFilled, PublicOpen,
        PrivateFilled, PrivateOpen, NUM_ATTRIBUTES, m_Attributes, NULL,
        &m_hSessionTask );

    if( hr == S_OK )
    {
        m_State = STATE_CREATING;
    }
    else
    {
#ifdef _DEBUG
        Print( L"Session Creation Failed with 0x%%x", hr );

#endif
    }

    return hr;

}




//-----------------------------------------------------------------------------
// Name: Update
// Desc: Update an existing session
//-----------------------------------------------------------------------------
HRESULT CSession::Update()
{
    switch ( m_State )
    {
    case STATE_IDLE:
    case STATE_DELETING:
        return E_UNEXPECTED;
    case STATE_CREATING:
    case STATE_UPDATING:
        // If the session is still being created or is in the process of
        // updating, set m_bUpdate so that another update will be done
        // afterwards
        m_bUpdate = TRUE;
        return S_OK;
    case STATE_ACTIVE:
        {
            XOnlineTaskClose( m_hSessionTask );
            m_hSessionTask = NULL;

            HRESULT hr = XOnlineMatchSessionUpdate( SessionID,
                PublicFilled, PublicOpen, PrivateFilled,
                PrivateOpen, NUM_ATTRIBUTES, m_Attributes, NULL,
                &m_hSessionTask );

            m_bUpdate = FALSE;  // Clear update flag since we just updated

            if( SUCCEEDED( hr ) )
            {
                m_State = STATE_UPDATING;
            }
            else
            {
#ifdef _DEBUG
                Print( L"Session Update Failed with 0x%%x", hr );
#endif
                Close();
            }

            return hr;
        }
    default:
        assert(0);
        return E_UNEXPECTED;

    }
}




//-----------------------------------------------------------------------------
// Name: Delete
// Desc: Delete an existing session
//-----------------------------------------------------------------------------
HRESULT CSession::Delete()
{
    HRESULT hr = S_OK;


    switch( m_State )
    {
    case STATE_IDLE:
        break;
    case STATE_CREATING:
        Close();  // Close down create task
        break;
    case STATE_UPDATING:
        Close();  // Close down update task
        Listen( FALSE );  // Send go-aways
        break;
    case STATE_DELETING:
        break;
    case STATE_ACTIVE:
        if( m_bListening )
        {
            Listen( FALSE );
        }
        else
        {
            INT iResult = XNetUnregisterKey( &SessionID );
            assert( iResult == 0 );
            (VOID) iResult; // Avoid compiler warning
        }
        m_bKeyRegistered = FALSE;
        Close();
        hr = XOnlineMatchSessionDelete( SessionID, NULL, &m_hSessionTask );
        if(SUCCEEDED( hr ) )
        {
            m_State = STATE_DELETING;
        }
        else
        {
#ifdef _DEBUG
            Print( L"Session Delete Failed with 0x%x", hr );
#endif
        }

        break;
    }

    return hr;
}




//-----------------------------------------------------------------------------
// Name: ProcessStateCreateSession
// Desc: Continue servicing the creation task
//-----------------------------------------------------------------------------
HRESULT CSession::ProcessStateCreateSession()
{
    HRESULT hr  = XOnlineTaskContinue( m_hSessionTask );
    if( hr != XONLINETASK_S_RUNNING )
    {
        if( FAILED( hr ) )
        {
            Close();
#ifdef _DEBUG
            Print( L"Session Creation Failed with 0x%x", hr );
#endif
        }
        else
        {
 
            // Extract the new session ID and Key-Exchange Key
            HRESULT hrGet = XOnlineMatchSessionGetInfo(
                m_hSessionTask,  &SessionID, &KeyExchangeKey );
            assert( SUCCEEDED( hrGet ) );
            (VOID)hrGet; // Avoid compiler warning
            m_State = STATE_ACTIVE;

            INT iKeyRegistered = XNetRegisterKey( &SessionID, 
                &KeyExchangeKey );
            if( iKeyRegistered == WSAENOMORE )
            {
#ifdef _DEBUG
                Print( L"Out of keys... Purging SessionQ and trying again" );
#endif
                // Too many keys have been registered, remove
                // the registered key on the session queue and try again
                PurgeSessionQHead();
                iKeyRegistered = XNetRegisterKey( &SessionID, 
                    &KeyExchangeKey );
            }
            assert( iKeyRegistered == NO_ERROR );
            m_bKeyRegistered = ( iKeyRegistered == NO_ERROR);

            // Start listening for Qos probes for this new session
            Listen( TRUE );

            // If a call to Update() was made while the session
            // was being created, call Update to send the new
            // information.
            if( m_bUpdate )
            {
                hr = Update();
            }
        }
    }

    return hr;
}




//-----------------------------------------------------------------------------
// Name: ProcessStateUpdateSession
// Desc: Continue servicing the session update task
//-----------------------------------------------------------------------------
HRESULT CSession::ProcessStateUpdateSession()
{
    HRESULT hr  = XOnlineTaskContinue( m_hSessionTask );
    if( hr != XONLINETASK_S_RUNNING )
    {
        if( FAILED( hr ) )
        {
            Close();
#ifdef _DEBUG
            Print( L"Session Update Failed with 0x%x", hr );
#endif
        }
        else
        {
            m_State = STATE_ACTIVE;

            // If a call to Update() was made while the session
            // was already being updated, call Update to send the new
            // information.
            if( m_bUpdate )
            {
                hr = Update();
            }
        }
    }

    return hr;
}




//-----------------------------------------------------------------------------
// Name: ProcessStateDeleteSession
// Desc: Continue servicing the session deletion task
//-----------------------------------------------------------------------------
HRESULT CSession::ProcessStateDeleteSession()
{
    HRESULT hr  = XOnlineTaskContinue( m_hSessionTask );
    if( hr != XONLINETASK_S_RUNNING )
    {
        if( FAILED( hr ) )
        {
#ifdef _DEBUG
            Print( L"Session Delete Failed with 0x%%x", hr );
#endif
        }

        Close();
    }

    return hr;
}




//-----------------------------------------------------------------------------
// Name: ProcessStateActiveSession
// Desc: Continue servicing the session task
//-----------------------------------------------------------------------------
HRESULT CSession::ProcessStateActiveSession()
{
    HRESULT hr  = XOnlineTaskContinue( m_hSessionTask );
    if( FAILED( hr ) )
    {
        Close();
#ifdef _DEBUG
        Print( L"Session Task Failed with 0x%%x", hr );
#endif
    }

    return hr;
}




//-----------------------------------------------------------------------------
// Name: Process
// Desc: Perform a unit of work (such as servicing tasks) as necessary
//-----------------------------------------------------------------------------
HRESULT CSession::Process()
{
    PurgeSessionQ();
    HRESULT hr;

    switch( m_State )
    {
    case STATE_IDLE:       hr = XONLINETASK_S_SUCCESS;       break;
    case STATE_CREATING:   hr = ProcessStateCreateSession(); break;
    case STATE_UPDATING:   hr = ProcessStateUpdateSession(); break;
    case STATE_DELETING:   hr = ProcessStateDeleteSession(); break;
    case STATE_ACTIVE:     hr = ProcessStateActiveSession(); break;
    default: assert(0);    return E_UNEXPECTED;
    }

    if( FAILED( hr ) )
    {
        Reset();
    }

    return hr;
}




//-----------------------------------------------------------------------------
// Name: Close
// Desc: Close down any session tasks
//-----------------------------------------------------------------------------
VOID CSession::Close()
{
    if( m_hSessionTask )
    {
        XOnlineTaskClose( m_hSessionTask);
        m_hSessionTask = NULL;
        m_State = STATE_IDLE;
    }

    m_bUpdate = FALSE;
}




//-----------------------------------------------------------------------------
// Name: PurgeSessionQ
// Desc: Cease Qos listening, and unregister, old SessionIDs
//-----------------------------------------------------------------------------
VOID CSession::PurgeSessionQ( BOOL fRemoveAll )
{
    // Cleanup any registered SessionIDs which are sending 
    // go-away qos responses
    const DWORD QOS_PROBE_REJECT_TIMELIMIT = 15000; // timeout in ms

    const QosQEntry *pItem;
    // Entries in the queue are in reverse chronological order, 
    // so we can stop once we encounter an entry that has not reached
    // the time limit
    while( ( pItem = m_SessionQosQ.Head() ) != NULL )
    {
        if( fRemoveAll || 
            GetTickCount() - pItem->dwStartTick >= QOS_PROBE_REJECT_TIMELIMIT )
        {
            PurgeSessionQHead();
        }
        else
            break;
    }
}




//-----------------------------------------------------------------------------
// Name: PurgeSessionQHead
// Desc: Cease Qos listening, and unregister, the first session on the queue
//-----------------------------------------------------------------------------
VOID CSession::PurgeSessionQHead()
{
    const QosQEntry *pItem;
    if ( ( pItem = m_SessionQosQ.Head() ) != NULL )
    {
#ifdef _DEBUG
        Print( L"Qos listening rejection period expired for 0x%x", pItem );
#endif
        INT iQos = XNetQosListen( &pItem->SessionID, NULL, 0, 0, XNET_QOS_LISTEN_RELEASE );
        assert( iQos == 0 );
        (VOID) iQos;
        // If this SessionID is not in use, unregister it
        if( !m_bKeyRegistered || memcmp( &pItem->SessionID, &SessionID, sizeof( XNKID ) ) != 0 )
        {
            INT iResult = XNetUnregisterKey( &pItem->SessionID );
            assert( iResult == 0 );
            (VOID) iResult;
        }
        m_SessionQosQ.Dequeue();
    }
}




//-----------------------------------------------------------------------------
// Name: Listen
// Desc: Control Qos listening
// bEnable     dwBitsPerSec
// -------     ---------
// TRUE        Bandwidth setting (zero for default)
// FALSE       Bandwidth setting (zero is default, NO_WAIT means shut down
//             immediately)
//-----------------------------------------------------------------------------
VOID CSession::Listen( BOOL bEnable, DWORD dwBitsPerSec )
{
    if( bEnable )
    {
        m_SessionQosQ.Remove( SessionID );
        INT iQos = XNetQosListen( &SessionID, (BYTE *) m_QosResponse.Data,
            m_QosResponse.Length, dwBitsPerSec, 
            XNET_QOS_LISTEN_ENABLE | XNET_QOS_LISTEN_SET_DATA |
            XNET_QOS_LISTEN_SET_BITSPERSEC );
        assert( iQos == 0 );
        (VOID)iQos; // Avoid compiler warning
        m_bListening = TRUE;
    }
    else
    {
        if( m_bListening )
        {
            INT iQos;
            m_bListening = FALSE;
            if( dwBitsPerSec == NO_WAIT ) // Stop listening, and release Qos resources
            {
#ifdef _DEBUG
                Print( L"Qos listening stopped" );
#endif
                iQos = XNetQosListen( &SessionID, NULL, 0, 0, XNET_QOS_LISTEN_RELEASE );
                assert( iQos == 0 );
            }
            else
            {
                // Start rejecting probes for a period of time by sending a "go away"
                // response.  Then, after a certain period of time, actually stop
                // listening by releasing Qos resources
                m_SessionQosQ.Add( SessionID, GetTickCount() );
                iQos = XNetQosListen( &SessionID, NULL, 0, 
                    dwBitsPerSec, 
                    XNET_QOS_LISTEN_DISABLE | XNET_QOS_LISTEN_SET_BITSPERSEC );
                assert( iQos == 0 );
#ifdef _DEBUG
                Print( L"Qos probe rejection period started for 0x%x", m_SessionQosQ.Tail() );
#endif
            }
            (VOID)iQos; // Avoid compiler warning
        }
    }
}




//-----------------------------------------------------------------------------
// Name: GetQosResponse
// Desc: Return the value of the Qos Response
//-----------------------------------------------------------------------------
CBlob CSession::GetQosResponse()
{
    return m_QosResponse;
}




//-----------------------------------------------------------------------------
// Name: SetQosResponse
// Desc: Set data to be sent in response to Qos probes
//-----------------------------------------------------------------------------
VOID  CSession::SetQosResponse( CBlob Value )
{
    m_QosResponse = Value;
    if( m_bListening )
    {
        // Call XNetQosListen to set the new value
        INT iQos = XNetQosListen(&SessionID, (BYTE *) m_QosResponse.Data,
            m_QosResponse.Length, 0, XNET_QOS_LISTEN_SET_DATA  );
        assert( iQos == 0 );
        (VOID)iQos; // Avoid compiler warning
    }
}




//-----------------------------------------------------------------------------
// Name: SetupAttributes
// Desc: Initialize the m_Attributes array and related string/blob buffers
//-----------------------------------------------------------------------------
VOID CSession::SetupAttributes()
{
    ZeroMemory( &m_Attributes, sizeof( m_Attributes ) );
    m_Attributes[GAME_TYPE_INDEX].dwAttributeID = XATTRIB_GAME_TYPE;
    m_Attributes[CURRENT_MAP_INDEX].dwAttributeID = XATTRIB_CURRENT_MAP;
    m_Attributes[SESSION_NAME_INDEX].dwAttributeID = XATTRIB_SESSION_NAME;
    m_Attributes[SESSION_NAME_INDEX].info.string.lpValue = m_strSessionName;
    m_strSessionName[0] = L'\0';
    m_Attributes[FRIENDLY_FIRE_INDEX].dwAttributeID = XATTRIB_FRIENDLY_FIRE;
    m_Attributes[JEDI_MASTERY_INDEX].dwAttributeID = XATTRIB_JEDI_MASTERY;
    m_Attributes[TOTAL_PLAYERS_INDEX].dwAttributeID = XATTRIB_TOTAL_PLAYERS;
    m_Attributes[SABER_ONLY_INDEX].dwAttributeID = XATTRIB_SABER_ONLY;
    m_Attributes[DEDICATED_INDEX].dwAttributeID = XATTRIB_DEDICATED;
}



//-----------------------------------------------------------------------------
// Name: GetGameType
// Desc: Return the value of the 'GameType' attribute
//-----------------------------------------------------------------------------
ULONGLONG CSession::GetGameType()
{
    return m_Attributes[GAME_TYPE_INDEX].info.integer.qwValue;
}




//-----------------------------------------------------------------------------
// Name: SetGameType
// Desc: Set the 'GameType' attribute
//-----------------------------------------------------------------------------
VOID  CSession::SetGameType( ULONGLONG Value )
{
    m_Attributes[GAME_TYPE_INDEX].info.integer.qwValue = Value;
    m_Attributes[GAME_TYPE_INDEX].fChanged = TRUE;
}




//-----------------------------------------------------------------------------
// Name: GetCurrentMap
// Desc: Return the value of the 'CurrentMap' attribute
//-----------------------------------------------------------------------------
ULONGLONG CSession::GetCurrentMap()
{
    return m_Attributes[CURRENT_MAP_INDEX].info.integer.qwValue;
}




//-----------------------------------------------------------------------------
// Name: SetCurrentMap
// Desc: Set the 'CurrentMap' attribute
//-----------------------------------------------------------------------------
VOID  CSession::SetCurrentMap( ULONGLONG Value )
{
    m_Attributes[CURRENT_MAP_INDEX].info.integer.qwValue = Value;
    m_Attributes[CURRENT_MAP_INDEX].fChanged = TRUE;
}




//-----------------------------------------------------------------------------
// Name: GetSessionName
// Desc: Return the value of the 'SessionName' attribute
//-----------------------------------------------------------------------------
const WCHAR * CSession::GetSessionName()
{
    return m_strSessionName;
}




//-----------------------------------------------------------------------------
// Name: SetSessionName
// Desc: Set the 'SessionName' attribute
//-----------------------------------------------------------------------------
VOID  CSession::SetSessionName( const WCHAR * Value )
{
    wcscpy( m_strSessionName, Value );
    m_Attributes[SESSION_NAME_INDEX].fChanged = TRUE;
}




//-----------------------------------------------------------------------------
// Name: GetFriendlyFire
// Desc: Return the value of the 'FriendlyFire' attribute
//-----------------------------------------------------------------------------
ULONGLONG CSession::GetFriendlyFire()
{
    return m_Attributes[FRIENDLY_FIRE_INDEX].info.integer.qwValue;
}




//-----------------------------------------------------------------------------
// Name: SetFriendlyFire
// Desc: Set the 'FriendlyFire' attribute
//-----------------------------------------------------------------------------
VOID  CSession::SetFriendlyFire( ULONGLONG Value )
{
    m_Attributes[FRIENDLY_FIRE_INDEX].info.integer.qwValue = Value;
    m_Attributes[FRIENDLY_FIRE_INDEX].fChanged = TRUE;
}




//-----------------------------------------------------------------------------
// Name: GetJediMastery
// Desc: Return the value of the 'JediMastery' attribute
//-----------------------------------------------------------------------------
ULONGLONG CSession::GetJediMastery()
{
    return m_Attributes[JEDI_MASTERY_INDEX].info.integer.qwValue;
}




//-----------------------------------------------------------------------------
// Name: SetJediMastery
// Desc: Set the 'JediMastery' attribute
//-----------------------------------------------------------------------------
VOID  CSession::SetJediMastery( ULONGLONG Value )
{
    m_Attributes[JEDI_MASTERY_INDEX].info.integer.qwValue = Value;
    m_Attributes[JEDI_MASTERY_INDEX].fChanged = TRUE;
}




//-----------------------------------------------------------------------------
// Name: GetTotalPlayers
// Desc: Return the value of the 'TotalPlayers' attribute
//-----------------------------------------------------------------------------
ULONGLONG CSession::GetTotalPlayers()
{
    return m_Attributes[TOTAL_PLAYERS_INDEX].info.integer.qwValue;
}




//-----------------------------------------------------------------------------
// Name: SetTotalPlayers
// Desc: Set the 'TotalPlayers' attribute
//-----------------------------------------------------------------------------
VOID  CSession::SetTotalPlayers( ULONGLONG Value )
{
    m_Attributes[TOTAL_PLAYERS_INDEX].info.integer.qwValue = Value;
    m_Attributes[TOTAL_PLAYERS_INDEX].fChanged = TRUE;
}




//-----------------------------------------------------------------------------
// Name: GetSaberOnly
// Desc: Return the value of the 'SaberOnly' attribute
//-----------------------------------------------------------------------------
ULONGLONG CSession::GetSaberOnly()
{
    return m_Attributes[SABER_ONLY_INDEX].info.integer.qwValue;
}




//-----------------------------------------------------------------------------
// Name: SetSaberOnly
// Desc: Set the 'SaberOnly' attribute
//-----------------------------------------------------------------------------
VOID  CSession::SetSaberOnly( ULONGLONG Value )
{
    m_Attributes[SABER_ONLY_INDEX].info.integer.qwValue = Value;
    m_Attributes[SABER_ONLY_INDEX].fChanged = TRUE;
}




//-----------------------------------------------------------------------------
// Name: GetDedicated
// Desc: Return the value of the 'Dedicated' attribute
//-----------------------------------------------------------------------------
ULONGLONG CSession::GetDedicated()
{
    return m_Attributes[DEDICATED_INDEX].info.integer.qwValue;
}




//-----------------------------------------------------------------------------
// Name: SetDedicated
// Desc: Set the 'Dedicated' attribute
//-----------------------------------------------------------------------------
VOID  CSession::SetDedicated( ULONGLONG Value )
{
    m_Attributes[DEDICATED_INDEX].info.integer.qwValue = Value;
    m_Attributes[DEDICATED_INDEX].fChanged = TRUE;
}




//-----------------------------------------------------------------------------
// Name: CSessionQosQ
// Desc: Constructor
//-----------------------------------------------------------------------------
CSession::CSessionQosQ::CSessionQosQ()
{
    m_pHead = m_pTail = NULL;
}




//-----------------------------------------------------------------------------
// Name: Add
// Desc: Add a session id to the qos go-away response queue
//-----------------------------------------------------------------------------

VOID CSession::CSessionQosQ::Add( XNKID & SessionID, DWORD dwStartTick )
{
    assert( m_pHead && m_pTail || !m_pHead && !m_pTail );
    QosQEntry *pItem = new QosQEntry;
    pItem->SessionID = SessionID;
    pItem->dwStartTick = dwStartTick;
    pItem->pNext = NULL;
    if( m_pTail )
        m_pTail->pNext = pItem;
    m_pTail = pItem;
    if( !m_pHead )
        m_pHead = pItem;
}




//-----------------------------------------------------------------------------
// Name: Remove
// Desc: Remove a session from the qos go-away response queue
//-----------------------------------------------------------------------------
VOID CSession::CSessionQosQ::Remove( XNKID &SessionID )
{
    assert( m_pHead && m_pTail || !m_pHead && !m_pTail );
    QosQEntry *pPrev = NULL;

    for( QosQEntry *pItem = m_pHead; pItem != NULL; pItem = pItem->pNext )
    {
        if( memcmp(&pItem->SessionID, &SessionID, sizeof( XNKID ) ) == 0 )
        {
            if( pPrev )
            {
                pPrev->pNext = pItem->pNext;
            }
            else
            {
                m_pHead = pItem->pNext;
            }
            if( pItem == m_pTail )
            {
                m_pTail = pPrev;
            }
            delete pItem;
            break;
        }
        pPrev = pItem;
    }
}




//-----------------------------------------------------------------------------
// Name: Dequeue
// Desc: Remove the head of the qos go-away response queue
//-----------------------------------------------------------------------------
VOID CSession::CSessionQosQ::Dequeue()
{
    assert( m_pHead && m_pTail || !m_pHead && !m_pTail );
    QosQEntry *pItem = m_pHead;
    if( pItem )
    {
        m_pHead = pItem->pNext;
        delete pItem;
        if( !m_pHead )
            m_pTail = NULL;
    }
}



// Attribute layout for OptiMatch return attributes.
// This must match the order of both the query return attributes
// and the fields specified in the COptiMatchResult class.
static const
XONLINE_ATTRIBUTE_SPEC OptiMatchAttributeSpec[]=
{
    { X_ATTRIBUTE_DATATYPE_INTEGER, sizeof( ULONGLONG ) }, // GameType
    { X_ATTRIBUTE_DATATYPE_INTEGER, sizeof( ULONGLONG ) }, // CurrentMap
    { X_ATTRIBUTE_DATATYPE_STRING, ( XATTRIB_SESSION_NAME_MAX_LEN + 1 ) * sizeof( WCHAR ) }, // SessionName
    { X_ATTRIBUTE_DATATYPE_INTEGER, sizeof( ULONGLONG ) }, // FriendlyFire
    { X_ATTRIBUTE_DATATYPE_INTEGER, sizeof( ULONGLONG ) }, // JediMastery
    { X_ATTRIBUTE_DATATYPE_INTEGER, sizeof( ULONGLONG ) }, // SaberOnly
    { X_ATTRIBUTE_DATATYPE_INTEGER, sizeof( ULONGLONG ) }, // Dedicated
};




//-----------------------------------------------------------------------------
// Name: COptiMatchQuery
// Desc: Constructor
//-----------------------------------------------------------------------------
COptiMatchQuery::COptiMatchQuery()
{
    m_State = STATE_IDLE;
    m_hrQuery = S_FALSE;
    m_hSearchTask = NULL;
    m_pXnQos = NULL;
}



//-----------------------------------------------------------------------------
// Name: COptiMatchQuery()
// Desc: Destructor
//-----------------------------------------------------------------------------
COptiMatchQuery::~COptiMatchQuery()
{
    Cancel();
}



//-----------------------------------------------------------------------------
// Name: Cancel
// Desc: Cancel a OptiMatch query
//-----------------------------------------------------------------------------
void COptiMatchQuery::Cancel()
{
    switch( m_State )
    {
    case STATE_IDLE:
       break;
    case STATE_RUNNING:
    case STATE_PROBING_BANDWIDTH:
    case STATE_PROBING_CONNECTIVITY:
    case STATE_DONE:
        if( m_hSearchTask )
        {
            XOnlineTaskClose( m_hSearchTask );
            m_hSearchTask = NULL;
        }
        Clear();
        m_State = STATE_IDLE;
        m_hrQuery = S_FALSE;
        break;
    default:
        assert(0);
    }
}



//-----------------------------------------------------------------------------
// Name: Clear
// Desc: Release resources
//-----------------------------------------------------------------------------
void COptiMatchQuery::Clear()
{
    if( m_pXnQos )
    {
        XNetQosRelease( m_pXnQos );
        m_pXnQos = NULL;
    }
    Results.Clear();
}



//-----------------------------------------------------------------------------
// Name: Query
// Desc: Execute the OptiMatch query (id 0x1)
//-----------------------------------------------------------------------------
HRESULT COptiMatchQuery::Query(
                  ULONGLONG GameType, // Optional: X_MATCH_NULL_INTEGER to omit
                  ULONGLONG CurrentMap, // Optional: X_MATCH_NULL_INTEGER to omit
                  ULONGLONG MinimumPlayers,
                  ULONGLONG MaximumPlayers,
                  ULONGLONG FriendlyFire, // Optional: X_MATCH_NULL_INTEGER to omit
                  ULONGLONG JediMastery, // Optional: X_MATCH_NULL_INTEGER to omit
                  ULONGLONG SaberOnly, // Optional: X_MATCH_NULL_INTEGER to omit
                  ULONGLONG Dedicated // Optional: X_MATCH_NULL_INTEGER to omit
                  )
{
    const DWORD SEARCH_PROC_ID = 0x1;

    if( m_State == STATE_DONE ) // Clear existing results
    {
        Clear();
        m_State = STATE_IDLE;
    }
    assert( m_State == STATE_IDLE );
    if (m_State != STATE_IDLE)
        return E_UNEXPECTED;

    XONLINE_ATTRIBUTE QueryParameters[8] = { 0 };
    QueryParameters[0].dwAttributeID = ( GameType == X_MATCH_NULL_INTEGER ) ? X_ATTRIBUTE_DATATYPE_NULL : X_ATTRIBUTE_DATATYPE_INTEGER;
    QueryParameters[0].info.integer.qwValue = GameType;
    QueryParameters[1].dwAttributeID = ( CurrentMap == X_MATCH_NULL_INTEGER ) ? X_ATTRIBUTE_DATATYPE_NULL : X_ATTRIBUTE_DATATYPE_INTEGER;
    QueryParameters[1].info.integer.qwValue = CurrentMap;
    QueryParameters[2].dwAttributeID = X_ATTRIBUTE_DATATYPE_INTEGER;
    QueryParameters[2].info.integer.qwValue = MinimumPlayers;
    QueryParameters[3].dwAttributeID = X_ATTRIBUTE_DATATYPE_INTEGER;
    QueryParameters[3].info.integer.qwValue = MaximumPlayers;
    QueryParameters[4].dwAttributeID = ( FriendlyFire == X_MATCH_NULL_INTEGER ) ? X_ATTRIBUTE_DATATYPE_NULL : X_ATTRIBUTE_DATATYPE_INTEGER;
    QueryParameters[4].info.integer.qwValue = FriendlyFire;
    QueryParameters[5].dwAttributeID = ( JediMastery == X_MATCH_NULL_INTEGER ) ? X_ATTRIBUTE_DATATYPE_NULL : X_ATTRIBUTE_DATATYPE_INTEGER;
    QueryParameters[5].info.integer.qwValue = JediMastery;
    QueryParameters[6].dwAttributeID = ( SaberOnly == X_MATCH_NULL_INTEGER ) ? X_ATTRIBUTE_DATATYPE_NULL : X_ATTRIBUTE_DATATYPE_INTEGER;
    QueryParameters[6].info.integer.qwValue = SaberOnly;
    QueryParameters[7].dwAttributeID = ( Dedicated == X_MATCH_NULL_INTEGER ) ? X_ATTRIBUTE_DATATYPE_NULL : X_ATTRIBUTE_DATATYPE_INTEGER;
    QueryParameters[7].info.integer.qwValue = Dedicated;

    // Calculate maximum space required to hold results
    DWORD dwResultsLen = XOnlineMatchSearchResultsLen( MAX_OPTI_MATCH_RESULTS, sizeof( OptiMatchAttributeSpec ) / sizeof( OptiMatchAttributeSpec[0] ), OptiMatchAttributeSpec );

    HRESULT hr = XOnlineMatchSearch( SEARCH_PROC_ID, MAX_OPTI_MATCH_RESULTS,
                                    8, QueryParameters, dwResultsLen, NULL, &m_hSearchTask );
    if( SUCCEEDED( hr ) )
        m_State = STATE_RUNNING;

    return hr;

}



//-----------------------------------------------------------------------------
// Name: Probe
// Desc: Initiate bandwidth probing
//-----------------------------------------------------------------------------
HRESULT COptiMatchQuery::Probe()
{
    assert( m_State == STATE_DONE );
    if (m_State != STATE_DONE)
       return E_UNEXPECTED;

    // Clean up any earlier probes
    if( m_pXnQos )
    {
        XNetQosRelease( m_pXnQos );
        m_pXnQos = NULL;
    }

    DWORD dwNumSessions = Results.Size();
    if( dwNumSessions )
    {
        for( DWORD i = 0; i < dwNumSessions; ++i )
        {
            Results[i].pQosInfo = NULL;
            m_rgpXnAddr[i] = &Results[i].HostAddress;
            m_rgpXnKid[i]  = &Results[i].SessionID;
            m_rgpXnKey[i]  = &Results[i].KeyExchangeKey;
        }
        INT iQos = XNetQosLookup( dwNumSessions, m_rgpXnAddr,
            m_rgpXnKid, m_rgpXnKey, 0, NULL, NULL,
            NUM_QOS_PROBES, QOS_BITS_PER_SEC, 0, NULL,
            &m_pXnQos );
        assert( iQos == 0 );
        if( iQos == 0 )
            m_State = STATE_PROBING_BANDWIDTH;
        else
            return E_FAIL;
    }

    return S_OK;

}



//-----------------------------------------------------------------------------
// Name: Process
// Desc: Continue servicing the query task
//-----------------------------------------------------------------------------
HRESULT COptiMatchQuery::Process()
{

    if( m_State == STATE_IDLE )
        return S_OK;

    if( m_State == STATE_DONE )
        return m_hrQuery;

    if( m_State == STATE_PROBING_BANDWIDTH )
    {
        // Update any completed Qos info for sessions
        // as they finish
        for( DWORD iResult = 0; iResult < Results.Size(); ++iResult )
        {
            if( !Results[iResult].pQosInfo && 
               ( m_pXnQos->axnqosinfo[iResult].bFlags & XNET_XNQOSINFO_COMPLETE ) )
            {
                Results[iResult].pQosInfo = &m_pXnQos->axnqosinfo[iResult];
            }
        }

        // Check if all probing is complete
        if( m_pXnQos->cxnqosPending == 0 )
        {
            m_State = STATE_DONE;
            return m_hrQuery;
        }

        return XONLINETASK_S_RUNNING;
    }

    if (m_State == STATE_PROBING_CONNECTIVITY ) 
    {
        // Check if probing is complete
        if( m_pXnQos->cxnqosPending == 0 )
        {
            // Copy QoS info into individual result objects, removing
            // any entries which are not reachable
            DWORD dwNumQosResults = Results.Size();
            DWORD iResult = 0;
            for( DWORD iQos = 0; iQos < dwNumQosResults; ++iQos )
            {
                if( ( m_pXnQos->axnqosinfo[iQos].bFlags & XNET_XNQOSINFO_TARGET_CONTACTED ) &&
                    !( m_pXnQos->axnqosinfo[iQos].bFlags & XNET_XNQOSINFO_TARGET_DISABLED ) )
                {
                    Results[iResult].pQosInfo = &m_pXnQos->axnqosinfo[iQos];
                    iResult++;
                }
                else
                {
#ifdef _DEBUG
                    Print( L"Removing Matching Session %lu (host not reachable or disabled)\n", iResult );
#endif
                    // Target not contacted, or is disabled, so remove result
                    Results.Remove( iResult );
                }
            }
            m_State = STATE_DONE;
            return m_hrQuery;
        }

        return XONLINETASK_S_RUNNING;
    }

    HRESULT hr = XOnlineTaskContinue( m_hSearchTask );
    if( hr != XONLINETASK_S_RUNNING )
    {
        m_hrQuery = hr;
        m_State = STATE_DONE;
        if( SUCCEEDED( hr ) )
        {
            // Fetch results
            XONLINE_MATCH_SEARCHRESULT** ppSearchResults;
            DWORD dwNumSessions;
            hr= XOnlineMatchSearchGetResults( m_hSearchTask,
                &ppSearchResults, &dwNumSessions );
            if( SUCCEEDED( hr ) )
            {
                Results.SetSize( dwNumSessions );
                for( DWORD i=0; i < dwNumSessions; ++i )
                {
                    XONLINE_MATCH_SEARCHRESULT* pxms = ppSearchResults[i];

                    Results.v[i].SessionID = pxms->SessionID;
                    Results.v[i].KeyExchangeKey = pxms->KeyExchangeKey;
                    Results.v[i].HostAddress = pxms->HostAddress;
                    Results.v[i].PublicOpen = pxms->dwPublicOpen;
                    Results.v[i].PrivateOpen = pxms->dwPrivateOpen;
                    Results.v[i].PublicFilled = pxms->dwPublicFilled;
                    Results.v[i].PrivateFilled = pxms->dwPrivateFilled;
                    hr = XOnlineMatchSearchParse( ppSearchResults[i],
                        sizeof( OptiMatchAttributeSpec ) / sizeof( OptiMatchAttributeSpec[0] ),
                        OptiMatchAttributeSpec, &Results.v[i] );
                    assert(SUCCEEDED(hr));
                   // Save data for Qos probing
                    Results.v[i].pQosInfo = NULL;
                    m_rgpXnAddr[i] = &Results.v[i].HostAddress;
                    m_rgpXnKid[i]  = &Results.v[i].SessionID;
                    m_rgpXnKey[i]  = &Results.v[i].KeyExchangeKey;
                }
                if( dwNumSessions )
                {
                    INT iQos = XNetQosLookup( dwNumSessions, m_rgpXnAddr,
                        m_rgpXnKid, m_rgpXnKey, 0, NULL, NULL,
                        0, QOS_BITS_PER_SEC, 0, NULL,
                        &m_pXnQos );
                    assert( iQos == 0 );
                    if( iQos == 0 )
                        m_State = STATE_PROBING_CONNECTIVITY;
                }
            }
        }

        XOnlineTaskClose( m_hSearchTask );
        m_hSearchTask = NULL;
    }

    return hr;
}



//-----------------------------------------------------------------------------
// Name: CJoinSessionByIDQuery
// Desc: Constructor
//-----------------------------------------------------------------------------
CJoinSessionByIDQuery::CJoinSessionByIDQuery()
{
    m_State = STATE_IDLE;
    m_hrQuery = S_FALSE;
    m_hSearchTask = NULL;
    m_pXnQos = NULL;
}



//-----------------------------------------------------------------------------
// Name: CJoinSessionByIDQuery()
// Desc: Destructor
//-----------------------------------------------------------------------------
CJoinSessionByIDQuery::~CJoinSessionByIDQuery()
{
    Cancel();
}



//-----------------------------------------------------------------------------
// Name: Cancel
// Desc: Cancel a JoinSessionByID query
//-----------------------------------------------------------------------------
void CJoinSessionByIDQuery::Cancel()
{
    switch( m_State )
    {
    case STATE_IDLE:
       break;
    case STATE_RUNNING:
    case STATE_PROBING_BANDWIDTH:
    case STATE_PROBING_CONNECTIVITY:
    case STATE_DONE:
        if( m_hSearchTask )
        {
            XOnlineTaskClose( m_hSearchTask );
            m_hSearchTask = NULL;
        }
        Clear();
        m_State = STATE_IDLE;
        m_hrQuery = S_FALSE;
        break;
    default:
        assert(0);
    }
}



//-----------------------------------------------------------------------------
// Name: Clear
// Desc: Release resources
//-----------------------------------------------------------------------------
void CJoinSessionByIDQuery::Clear()
{
    if( m_pXnQos )
    {
        XNetQosRelease( m_pXnQos );
        m_pXnQos = NULL;
    }
    Results.Clear();
}



//-----------------------------------------------------------------------------
// Name: Query
// Desc: Execute the JoinSessionByID query (id 0x2)
//-----------------------------------------------------------------------------
HRESULT CJoinSessionByIDQuery::Query(
                  ULONGLONG SessionID
                  )
{
    const DWORD SEARCH_PROC_ID = 0x2;

    if( m_State == STATE_DONE ) // Clear existing results
    {
        Clear();
        m_State = STATE_IDLE;
    }
    assert( m_State == STATE_IDLE );
    if (m_State != STATE_IDLE)
        return E_UNEXPECTED;

    XONLINE_ATTRIBUTE QueryParameters[1] = { 0 };
    QueryParameters[0].dwAttributeID = X_ATTRIBUTE_DATATYPE_INTEGER;
    QueryParameters[0].info.integer.qwValue = SessionID;

    // Calculate maximum space required to hold results
    DWORD dwResultsLen = XOnlineMatchSearchResultsLen( MAX_JOIN_SESSION_BY_ID_RESULTS, 0, NULL );

    HRESULT hr = XOnlineMatchSearch( SEARCH_PROC_ID, MAX_JOIN_SESSION_BY_ID_RESULTS,
                                    1, QueryParameters, dwResultsLen, NULL, &m_hSearchTask );
    if( SUCCEEDED( hr ) )
        m_State = STATE_RUNNING;

    return hr;

}



//-----------------------------------------------------------------------------
// Name: Probe
// Desc: Initiate bandwidth probing
//-----------------------------------------------------------------------------
HRESULT CJoinSessionByIDQuery::Probe()
{
    assert( m_State == STATE_DONE );
    if (m_State != STATE_DONE)
       return E_UNEXPECTED;

    // Clean up any earlier probes
    if( m_pXnQos )
    {
        XNetQosRelease( m_pXnQos );
        m_pXnQos = NULL;
    }

    DWORD dwNumSessions = Results.Size();
    if( dwNumSessions )
    {
        for( DWORD i = 0; i < dwNumSessions; ++i )
        {
            Results[i].pQosInfo = NULL;
            m_rgpXnAddr[i] = &Results[i].HostAddress;
            m_rgpXnKid[i]  = &Results[i].SessionID;
            m_rgpXnKey[i]  = &Results[i].KeyExchangeKey;
        }
        INT iQos = XNetQosLookup( dwNumSessions, m_rgpXnAddr,
            m_rgpXnKid, m_rgpXnKey, 0, NULL, NULL,
            NUM_QOS_PROBES, QOS_BITS_PER_SEC, 0, NULL,
            &m_pXnQos );
        assert( iQos == 0 );
        if( iQos == 0 )
            m_State = STATE_PROBING_BANDWIDTH;
        else
            return E_FAIL;
    }

    return S_OK;

}



//-----------------------------------------------------------------------------
// Name: Process
// Desc: Continue servicing the query task
//-----------------------------------------------------------------------------
HRESULT CJoinSessionByIDQuery::Process()
{

    if( m_State == STATE_IDLE )
        return S_OK;

    if( m_State == STATE_DONE )
        return m_hrQuery;

    if( m_State == STATE_PROBING_BANDWIDTH )
    {
        // Update any completed Qos info for sessions
        // as they finish
        for( DWORD iResult = 0; iResult < Results.Size(); ++iResult )
        {
            if( !Results[iResult].pQosInfo && 
               ( m_pXnQos->axnqosinfo[iResult].bFlags & XNET_XNQOSINFO_COMPLETE ) )
            {
                Results[iResult].pQosInfo = &m_pXnQos->axnqosinfo[iResult];
            }
        }

        // Check if all probing is complete
        if( m_pXnQos->cxnqosPending == 0 )
        {
            m_State = STATE_DONE;
            return m_hrQuery;
        }

        return XONLINETASK_S_RUNNING;
    }

    if (m_State == STATE_PROBING_CONNECTIVITY ) 
    {
        // Check if probing is complete
        if( m_pXnQos->cxnqosPending == 0 )
        {
            // Copy QoS info into individual result objects, removing
            // any entries which are not reachable
            DWORD dwNumQosResults = Results.Size();
            DWORD iResult = 0;
            for( DWORD iQos = 0; iQos < dwNumQosResults; ++iQos )
            {
                if( ( m_pXnQos->axnqosinfo[iQos].bFlags & XNET_XNQOSINFO_TARGET_CONTACTED ) &&
                    !( m_pXnQos->axnqosinfo[iQos].bFlags & XNET_XNQOSINFO_TARGET_DISABLED ) )
                {
                    Results[iResult].pQosInfo = &m_pXnQos->axnqosinfo[iQos];
                    iResult++;
                }
                else
                {
#ifdef _DEBUG
                    Print( L"Removing Matching Session %lu (host not reachable or disabled)\n", iResult );
#endif
                    // Target not contacted, or is disabled, so remove result
                    Results.Remove( iResult );
                }
            }
            m_State = STATE_DONE;
            return m_hrQuery;
        }

        return XONLINETASK_S_RUNNING;
    }

    HRESULT hr = XOnlineTaskContinue( m_hSearchTask );
    if( hr != XONLINETASK_S_RUNNING )
    {
        m_hrQuery = hr;
        m_State = STATE_DONE;
        if( SUCCEEDED( hr ) )
        {
            // Fetch results
            XONLINE_MATCH_SEARCHRESULT** ppSearchResults;
            DWORD dwNumSessions;
            hr= XOnlineMatchSearchGetResults( m_hSearchTask,
                &ppSearchResults, &dwNumSessions );
            if( SUCCEEDED( hr ) )
            {
                Results.SetSize( dwNumSessions );
                for( DWORD i=0; i < dwNumSessions; ++i )
                {
                    XONLINE_MATCH_SEARCHRESULT* pxms = ppSearchResults[i];

                    Results.v[i].SessionID = pxms->SessionID;
                    Results.v[i].KeyExchangeKey = pxms->KeyExchangeKey;
                    Results.v[i].HostAddress = pxms->HostAddress;
                    Results.v[i].PublicOpen = pxms->dwPublicOpen;
                    Results.v[i].PrivateOpen = pxms->dwPrivateOpen;
                    Results.v[i].PublicFilled = pxms->dwPublicFilled;
                    Results.v[i].PrivateFilled = pxms->dwPrivateFilled;
                   // Save data for Qos probing
                    Results.v[i].pQosInfo = NULL;
                    m_rgpXnAddr[i] = &Results.v[i].HostAddress;
                    m_rgpXnKid[i]  = &Results.v[i].SessionID;
                    m_rgpXnKey[i]  = &Results.v[i].KeyExchangeKey;
                }
                if( dwNumSessions )
                {
                    INT iQos = XNetQosLookup( dwNumSessions, m_rgpXnAddr,
                        m_rgpXnKid, m_rgpXnKey, 0, NULL, NULL,
                        0, QOS_BITS_PER_SEC, 0, NULL,
                        &m_pXnQos );
                    assert( iQos == 0 );
                    if( iQos == 0 )
                        m_State = STATE_PROBING_CONNECTIVITY;
                }
            }
        }

        XOnlineTaskClose( m_hSearchTask );
        m_hSearchTask = NULL;
    }

    return hr;
}



