/*-------------------------------------------------------------------------
 * fedsrv\AdminSession.CPP
 * 
 * This houses the implementation of CAdminSession Class.  This class
 * exposes log-on methods allowing an Admin Session (using COM) with AllSrv.
 * 
 * Owner: , 
 * 
 * Copyright 1986-1998 Microsoft Corporation, All Rights Reserved
 *-----------------------------------------------------------------------*/



#include "pch.h"
#include "AdminSessionEventSink.h"
#include "zreg.h"


/////////////////////////////////////////////////////////////////////////////
// CAdminSession

TC_OBJECT_EXTERN_IMPL(CAdminSession)


/////////////////////////////////////////////////////////////////////////////
// Static Initialization

/*static*/ TCAutoCriticalSection    CAdminSession::s_cs;
/*static*/ CAdminSession::XSessions CAdminSession::s_vecSessions;

// this is temporary until launcher obj gets done
/*static*/ CAdminSession*           CAdminSession::s_pWhoStartedServer = NULL; // This is NULL unless a session started the server


/////////////////////////////////////////////////////////////////////////////
// Construction / Destruction

/*-------------------------------------------------------------------------
 * CAdminSession()
 *-------------------------------------------------------------------------
 */
CAdminSession::CAdminSession() :
  m_pEventSink(NULL)
{
  XLockStatic lock(&s_cs);
  if (_Module.WasCOMStarted() && !s_pWhoStartedServer)
    s_pWhoStartedServer = this;

  // Add ourself to the static collection
  s_vecSessions.push_back(this);
}


/*-------------------------------------------------------------------------
 * ~CAdminSession()
 *-------------------------------------------------------------------------
 */
CAdminSession::~CAdminSession()
{
  XLockStatic lock(&s_cs);
  if (this == s_pWhoStartedServer) 
    s_pWhoStartedServer = NULL;

  // Remove ourself from the static collection
  XSessionsIt it = std::find(s_vecSessions.begin(), s_vecSessions.end(), this);
  if (it != s_vecSessions.end())
    s_vecSessions.erase(it);
  lock.Unlock();

  #if defined(ALLSRV_STANDALONE)
    // Possibly shutdown the standalone server if no more sessions
    if (0 == GetSessionCount())
    {
      // Get the Server property
      IAdminServerPtr spServer;
      if (SUCCEEDED(get_Server(&spServer)))
      {
        IAdminGamesPtr spGames;
        if (SUCCEEDED(spServer->get_Games(&spGames)))
        {
          // Shutdown the server if no games exist
          long cGames = 0;
          spGames->get_Count(&cGames);
		  // KGJV #114 - if lobbied then dont shutdown if create game allowed on this server
		  bool bSupposedToConnectToLobby = !(FEDSRV_GUID != g.fm.GetHostApplicationGuid());
		  if ((0 == cGames) && (bSupposedToConnectToLobby ? (g.cStaticCoreInfo==0) : true))
            PostThreadMessage(g.idReceiveThread, WM_QUIT, 0, 0);
        }
      }
    }
  #endif // defined(ALLSRV_STANDALONE)
}


HRESULT CAdminSession::FinalConstruct()
{
  // #define CAdminSession_TRACE_CONSTRUCTION
  #ifdef CAdminSession_TRACE_CONSTRUCTION
    _TRACE_BEGIN
      DWORD id = GetCurrentThreadId();
      _TRACE_PART2("CAdminSession::FinalConstruct(): ThreadId = %d (0x%X)\n", id, id);
      _TRACE_PART1("\tRaw pointer = 0x%08X\n", this);
    _TRACE_END
  #endif // CAdminSession_TRACE_CONSTRUCTION

  // Create the event sink object
  CComObject<CAdminSessionEventSink>* pEventSink = NULL;
  RETURN_FAILED(pEventSink->CreateInstance(&pEventSink));
  pEventSink->AddRef();
  pEventSink->Init(this);
  m_pEventSink = pEventSink;

  // Indicate success
  return S_OK;
}

void CAdminSession::FinalRelease()
{
  // Terminate the event sink object
  if (m_pEventSink)
  {
    m_pEventSink->Term();
    m_pEventSink->Release();
    m_pEventSink = NULL;
  }
}


/////////////////////////////////////////////////////////////////////////////
// Overrides

IUnknown* CAdminSession::OnGetUnknown()
{
  return NULL;
}


/*-------------------------------------------------------------------------
 * DestroyAllSessions()
 *-------------------------------------------------------------------------
 * Purpose:
 *    Kill all active (and hosed) sessions
 * 
 */
/* static*/void  CAdminSession::DestroyAllSessions()
{
  int cSessions;
  while (cSessions = GetSessionCount()) // Intentional assignment
  {
    // Keep a reference while disconnecting object
    CAdminSession* pSession = GetLastSession();
    IUnknownPtr spUnk(pSession);

    // Disconnect all external references
    if (NULL != spUnk)
      ::CoDisconnectObject(spUnk, 0);
    
    // Release the IUnknown
    spUnk = NULL;

    // That /should/ have forced a deletion. If not, remove from list
    XLockStatic lock(&s_cs);
    if (GetSessionCount() == cSessions)
    {
      XSessionsIt it = std::find(s_vecSessions.begin(), s_vecSessions.end(), pSession);
      assert(it != s_vecSessions.end());
      s_vecSessions.erase(it);
    }
  }
}




/*-------------------------------------------------------------------------
 * put_SessionInfo()
 *-------------------------------------------------------------------------
 * Purpose:
 *    
 * 
 */
STDMETHODIMP CAdminSession::put_SessionInfo(ITCSessionInfo* pSessionInfo)
{
//  GetAGCGlobal()->TriggerEvent(ppListeners, EventID_NewSession, 0);
  return S_OK;
}


/*-------------------------------------------------------------------------
 * get_SessionInfo()
 *-------------------------------------------------------------------------
 * Purpose:
 *    
 * 
 */
STDMETHODIMP CAdminSession::get_SessionInfo(ITCSessionInfo** ppSessionInfo)
{
  return S_OK;
}

/*-------------------------------------------------------------------------
 * get_SessionInfos()
 *-------------------------------------------------------------------------
 * Purpose:
 *    
 * 
 */
STDMETHODIMP CAdminSession::get_SessionInfos(ITCSessionInfos** ppSessionInfos)
{

  return S_OK;
}

/*-------------------------------------------------------------------------
 * get_SessionInfos()
 *-------------------------------------------------------------------------
 * Purpose:
 *   Use this to find out of an event of a certain type is firing 
 *
 *   A uniqueID of AGC_Any_Objects causes the check to be done for global event firing,
 *   otherwise this id is used to check for specialized event firing for the AGC
 *   object with this id.
 * 
 */
STDMETHODIMP CAdminSession::get_IsEventActivated(AGCEventID event, AGCUniqueID uniqueID, BOOL *pVal)
{
  if (uniqueID == -1)
    uniqueID = AGC_Any_Objects;

  assert(m_pEventSink);
  *pVal = GetAGCGlobal()->IsRegistered(event, uniqueID,
    reinterpret_cast<IAGCEventSink*>(m_pEventSink)); 
  return S_OK;
}



/*-------------------------------------------------------------------------
 * get_Server()
 *-------------------------------------------------------------------------
 * Purpose:
 *    
 * 
 */
STDMETHODIMP CAdminSession::get_Server(IAdminServer** ppAdminServer)
{
  // Get the CAdminServer instance from the GIT
  HRESULT hr;
  assert(g.dwServerGITCookie);
  ZSucceeded(hr = GetAGCGlobal()->GetInterfaceFromGlobal(
    g.dwServerGITCookie, IID_IAdminServer, (void**)ppAdminServer));
  return hr;
}


/*-------------------------------------------------------------------------
 * ActivateEvents()
 *-------------------------------------------------------------------------
 * Purpose:
 *   Use this to activate event firing for events of a certain type 
 *
 * Parameters:
 *    event: id of the event to be deactivated
 *    uniqueID: if not AGC_Any_Objects, then this is AGC unqiue object id telling who the event
 *              should be fired for.  AGC_Any_Objects means for all applicable objects
 */
STDMETHODIMP CAdminSession::ActivateEvents(AGCEventID event, AGCUniqueID uniqueID)
{
  if (-1 == uniqueID)
    uniqueID = AGC_Any_Objects;

  assert(m_pEventSink);
  GetAGCGlobal()->RegisterEvent(event, uniqueID,
    reinterpret_cast<IAGCEventSink*>(m_pEventSink));
  return S_OK;
}


/*-------------------------------------------------------------------------
 * DeactivateEvents()
 *-------------------------------------------------------------------------
 * Purpose:
 *    Use this to activate event firing for events of a certain type
 *
 * Parameters:
 *    event: id of the event to be deactivated
 *    uniqueID: if not AGC_Any_Objects, then this is AGC unqiue object id telling who the event
 *              should be fired for.  AGC_Any_Objects means for all applicable objects
 *
 * Remarks:
 *    There should be a DeactivateEvents() call for each ActivateEvents() call.
 *    A single DeactivateEvents() call with AGC_Any_Objects as the unique ID does not turn off 
 *    all event firing for the AGCEventID event.
 */
STDMETHODIMP CAdminSession::DeactivateEvents(AGCEventID event, AGCUniqueID uniqueID)
{
  if (-1 == uniqueID)
    uniqueID = AGC_Any_Objects;

  assert(m_pEventSink);
  GetAGCGlobal()->RevokeEvent(event, uniqueID,
    reinterpret_cast<IAGCEventSink*>(m_pEventSink));
  return S_OK;
}


/*-------------------------------------------------------------------------
 * ActivateAllEvents()
 *-------------------------------------------------------------------------
 * Purpose:
 *   Use this to activate event firing all events for this session
 * 
 */
STDMETHODIMP CAdminSession::ActivateAllEvents()
{
  assert(m_pEventSink);
  GetAGCGlobal()->RegisterAllEvents(
    reinterpret_cast<IAGCEventSink*>(m_pEventSink));
  return S_OK;
}



/*-------------------------------------------------------------------------
 * DeactivateAllEvents()
 *-------------------------------------------------------------------------
 * Purpose:
 *   Use this to deactivate event firing all events for this session
 * 
 */
STDMETHODIMP CAdminSession::DeactivateAllEvents()
{
  assert(m_pEventSink);
  GetAGCGlobal()->RevokeAllEvents(
    reinterpret_cast<IAGCEventSink*>(m_pEventSink));
  return S_OK;
}


/*-------------------------------------------------------------------------
 * get_ProcessID()
 *-------------------------------------------------------------------------
 * Purpose:
 *    Determine the process identifier of this AllSrv process.
 * 
 */
STDMETHODIMP CAdminSession::get_ProcessID(long* pdwProcessID)
{
  CLEAROUT(pdwProcessID, long(GetCurrentProcessId()));
  return S_OK;
}


/*-------------------------------------------------------------------------
 * get_ProductVersion()
 *-------------------------------------------------------------------------
 * Purpose:
 *    Determine the version number of AllSrv
 * 
 */
STDMETHODIMP CAdminSession::get_Version(IAGCVersionInfo** ppVersion)
{
  // Initialize the [out] parameter
  CLEAROUT(ppVersion, (IAGCVersionInfo*)NULL);

  // Create an instance of the version object
  IAGCVersionInfoPtr spVersion;
  RETURN_FAILED(spVersion.CreateInstance(CLSID_AGCVersionInfo));

  // Initialize the version object
  TCHAR szModule[_MAX_PATH];
  GetModuleFileName(_Module.GetModuleInstance(), szModule, sizeofArray(szModule));
  RETURN_FAILED(spVersion->put_FileName(CComBSTR(szModule)));

  // Detach the object to the [out] parameter
  *ppVersion = spVersion.Detach();

  // Indicate success
  return S_OK;
}


/*-------------------------------------------------------------------------
 * CAdminSession::Stop()
 *-------------------------------------------------------------------------
 * Purpose:
 *   Use this to kill the server.  
 * 
 */

STDMETHODIMP CAdminSession::Stop()
{
  OutputDebugString("\nSomeone is shutting down the server (possibly remotely) using the Admin Object Model.\n");

  _Module.StopAllsrv();

  return S_OK;
}


/*-------------------------------------------------------------------------
 * CAdminSession::Pause()
 *-------------------------------------------------------------------------
 * Purpose:
 *   Use this to pause the server.  When paused, no new users may join 
 *   and once everyone logs off, the server shuts down.
 *
 *   This method does nothing if the server is already paused.
 * 
 */

STDMETHODIMP CAdminSession::Pause()
{
  FedSrv_Pause();
  return S_OK;
}


/*-------------------------------------------------------------------------
 * get_WhoStartedServer()
 *-------------------------------------------------------------------------
 * Purpose:
 *    Use this to find out who started the server
 * 
 */
STDMETHODIMP CAdminSession::get_WhoStartedServer(IAdminSession** pIAdminSession)
{
  XLockStatic lock(&s_cs);
  if (s_pWhoStartedServer == NULL)
  {
    *pIAdminSession = NULL;
    return S_OK;
  }

  // TODO: NOT THREAD-SAFE!!!
  return s_pWhoStartedServer->QueryInterface(IID_IAdminSession, (void**)pIAdminSession);
}


/*-------------------------------------------------------------------------
 * CAdminSession::get_EventLog
 *-------------------------------------------------------------------------
 * Purpose:
 *    Gets the previously created event logger object.
 * 
 */
STDMETHODIMP CAdminSession::get_EventLog(IAGCEventLogger** ppEventLogger)
{
  // Delegate to the module object
  return _Module.get_EventLog(ppEventLogger);
}


/////////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP CAdminSession::get_PerfCounters(IAGCEvent** ppPerfCounters)
{
  // Create an AGCEvent object to store the named performance counters
  IAGCEventCreatePtr spEvent;
  RETURN_FAILED(spEvent.CreateInstance(CLSID_AGCEvent));
  RETURN_FAILED(spEvent->Init());

  // Populate the event with the named performance counters
  CComVariant var(0L);
  V_I4(&var) = g.pServerCounters->cPacketsIn;
  RETURN_FAILED(spEvent->AddProperty(CComBSTR(L"PacketsIn"), &var));

  V_I4(&var) = g.pServerCounters->cPlayersOnline;
  RETURN_FAILED(spEvent->AddProperty(CComBSTR(L"PlayersOnline"), &var));

  V_I4(&var) = g.pServerCounters->timeBetweenInnerLoops;
  RETURN_FAILED(spEvent->AddProperty(CComBSTR(L"TimeInnerLoop"), &var));

  // Return the new object
  return spEvent->QueryInterface(IID_IAGCEvent, (void**)ppPerfCounters);
}


/////////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP CAdminSession::SendAdminChat(BSTR bstrText, long nUserID, DATE dateOriginal)
{
  // This just triggers an event
  _AGCModule.TriggerEvent(NULL, EventID_AdminChat, "", nUserID, -1, -1, 2,
    "Message"      , VT_BSTR , bstrText,
    "OriginalTime" , VT_DATE , dateOriginal);
  
  // Indicate success
  return S_OK;
}


/*-------------------------------------------------------------------------
 * CAdminSession::Continue()
 *-------------------------------------------------------------------------
 * Purpose:
 *   Use this to continue the server from a paused state.
 *
 *   This method does nothing if the server is not paused.
 * 
 */

STDMETHODIMP CAdminSession::Continue()
{
  FedSrv_Continue();
  return S_OK;
}