#include "private.h"
#include <shlguid.h>

#define TF_THISMODULE TF_WEBCHECKCORE

DWORD   g_idSchedThread = 0;

// global containing pointer to instance of CWebcheck.  Needed to control
// externals loading on demand.
CWebCheck *g_pwc = NULL;

//////////////////////////////////////////////////////////////////////////
//
// CWebCheck implementation
//
//////////////////////////////////////////////////////////////////////////

CWebCheck::CWebCheck()
{
    // Maintain global object count
    DllAddRef();

    // Initialize object
    m_cRef = 1;

    // save our instance
    g_pwc = this;
}

CWebCheck::~CWebCheck()
{
    // Maintain global object count
    DllRelease();

    // no longer available
    g_pwc = NULL;
}

//
// IUnknown members
//

STDMETHODIMP_(ULONG) CWebCheck::AddRef(void)
{
//  TraceMsg(TF_THISMODULE, "CWebCheck::AddRef m_cRef=%d", m_cRef+1);

    return ++m_cRef;
}

STDMETHODIMP_(ULONG) CWebCheck::Release(void)
{
//  TraceMsg(TF_THISMODULE, "CWebCheck::Release m_cRef=%d", m_cRef-1);

    if( 0L != --m_cRef )
        return m_cRef;

    delete this;
    return 0L;
}

STDMETHODIMP CWebCheck::QueryInterface(REFIID riid, void ** ppv)
{
    *ppv=NULL;

    // Validate requested interface
    if (IsEqualIID(riid, IID_IUnknown))
        *ppv = (IUnknown *)this;
    else if (IsEqualIID(riid, IID_IOleCommandTarget))
        *ppv = (IOleCommandTarget *)this;
    else
        return E_NOINTERFACE;

    // Addref through the interface
    ((LPUNKNOWN)*ppv)->AddRef();
    return S_OK;
}

//
// IOleCommandTarget members
// The shell will send notifications to us through this interface.
//

STDMETHODIMP CWebCheck::QueryStatus(const GUID *pguidCmdGroup, ULONG cCmds,
                                    OLECMD prgCmds[], OLECMDTEXT *pCmdText)
{
    if (IsEqualGUID(*pguidCmdGroup, CGID_ShellServiceObject))
    {
        // We like Shell Service Object notifications...
        return S_OK;
    }

    return(OLECMDERR_E_UNKNOWNGROUP);
}

STDMETHODIMP CWebCheck::Exec(const GUID *pguidCmdGroup, DWORD nCmdID,
                             DWORD nCmdexecopt, VARIANTARG *pvaIn,
                             VARIANTARG *pvaOut)
{
    if (pguidCmdGroup && IsEqualGUID(*pguidCmdGroup, CGID_ShellServiceObject))
    {
        // Handle Shell Service Object notifications here.
        switch (nCmdID)
        {
            case SSOCMDID_OPEN:
                StartService(FALSE);
                break;

            case SSOCMDID_CLOSE:
                StopService();
                break;
        }
        return S_OK;
    }

    return(E_NOTIMPL);
}


//
// IWebCheck members
//

// Starts the webcheck service in a process
STDMETHODIMP CWebCheck::StartService(BOOL fForceExternals)
{
    DBG("CWebCheck::StartService entered");

    // reset offline mode for all platforms except NT5
    if(FALSE == g_fIsWinNT5)
    {
        HMODULE hWininet = GetModuleHandle(TEXT("WININET.DLL"));
        if(hWininet)
        {
            // wininet is loaded - tell it to go online
            INTERNET_CONNECTED_INFO ci;
            memset(&ci, 0, sizeof(ci));
            ci.dwConnectedState = INTERNET_STATE_CONNECTED;
            InternetSetOption(NULL, INTERNET_OPTION_CONNECTED_STATE, &ci, sizeof(ci));
        }
        else
        {
            // wininet not loaded - blow away offline reg key so we'll
            // be online when it does load
            DWORD dwOffline = 0;        // FALSE => not offline
            WriteRegValue(HKEY_CURRENT_USER, c_szRegPathInternetSettings,
                TEXT("GlobalUserOffline"), &dwOffline, sizeof(DWORD), REG_DWORD);
        }
    }

    // create dialmon window
    DialmonInit();

    // Fire up LCE and sens if necessary
    if(fForceExternals || ShouldLoadExternals())
        LoadExternals();

    //
    // Process the Infodelivery Admin Policies on user login.  (User login coincides
    // with webcheck's StartService() call.)
    //
    ProcessInfodeliveryPolicies();

    DBG("CWebCheck::StartService exiting");
    return S_OK;
}


// Stops Webcheck if running.
STDMETHODIMP CWebCheck::StopService(void)
{
    DBG("CWebCheck::StopService entered");

    // kill dialmon window
    DialmonShutdown();

    // shut down the external bits
    if(FALSE == g_fIsWinNT)
        UnloadExternals();

    DBG("CWebCheck::StopService exiting");
    return S_OK;
}

//
// load behavior: (win9x)
//
// "auto"   Load if on a laptop
// "yes"    Load always
// "no"     Load never
//
static const WCHAR s_szAuto[] = TEXT("auto");
static const WCHAR s_szYes[] = TEXT("yes");
static const WCHAR s_szNo[] = TEXT("no");

BOOL CWebCheck::ShouldLoadExternals(void)
{
    WCHAR   szSens[16], szLce[16];
    DWORD   cbData;

    //
    // don't load on NT
    //
    if(g_fIsWinNT)
    {
        DBG("CWebCheck::ShouldLoadExternals -> NO (NT)");
        return FALSE;
    }

    //
    // read sens/lce user settings - no setting means auto
    //
    cbData = sizeof(szLce);
    if(ERROR_SUCCESS != SHGetValueW(HKEY_LOCAL_MACHINE, c_szRegKey, L"LoadLCE", NULL, szLce, &cbData))
    {
        StrCpyW(szLce, s_szAuto);
    }

    cbData = sizeof(szSens);
    if(ERROR_SUCCESS != SHGetValueW(HKEY_LOCAL_MACHINE, c_szRegKey, L"LoadSens", NULL, szSens, &cbData))
    {
        StrCpyW(szSens, s_szAuto);
    }

    //
    // if either is yes, load
    //
    if(0 == StrCmpIW(szLce, s_szYes) || 0 == StrCmpIW(szSens, s_szYes))
    {
        DBG("CWebCheck::ShouldLoadExternals -> YES (reg = yes)");
        return TRUE;
    }

    //
    // if either is auto, check for laptop
    //
    if(0 == StrCmpIW(szLce, s_szAuto) || 0 == StrCmpIW(szSens, s_szAuto))
    {
        if(SHGetMachineInfo(GMI_LAPTOP))
        {
            // Is a laptop - load
            DBG("CWebCheck::ShouldLoadExternals -> YES (reg = auto, laptop)");
            return TRUE;
        }
    }

    // don't load
    DBG("CWebCheck::ShouldLoadExternals -> NO");
    return FALSE;
}

BOOL CWebCheck::AreExternalsLoaded(void)
{
    return (_hThread != NULL);
}

void CWebCheck::LoadExternals(void)
{
    DWORD dwThreadId;

    DBG("CWebCheck::LoadExternals");

    if(_hThread)
    {
        DBG("CWebCheck::LoadExternals - already loaded");
        return;
    }

    // fire up a thread to do the work
    _hThread = CreateThread(NULL, 4096, ExternalsThread, this, 0, &dwThreadId);
    if(NULL == _hThread) {
        DBG("LoadExternals failed to create externals thread!");
        return;
    }

    // create initializion and termination events

    //
    // [darrenmi 2/7/00] Wininet now tries to find this named mutex instead of querying
    // dialmon.  It's the A version because wininet isn't unicode and OpenEventA can't
    // find events created with CreateEventW.
    //
    // See GetSensLanState in inet\wininet\dll\autodial.cxx.
    //
    _hTerminateEvent = CreateEventA(NULL, TRUE, FALSE, "MS_WebcheckExternalsTerminateEvent");
    if(NULL == _hTerminateEvent) {
        DBG("LoadExternals failed to create termination event");
        return;
    }

    DBG("CWebCheck::LoadExternals exiting");
    return;
}

void CWebCheck::UnloadExternals(void)
{
    if(NULL == _hThread)
    {
        DBG("CWebCheck::UnloadExternals - nothing to unload");
        return;
    }

    // tell externals thread to go away by setting termination event
    SetEvent(_hTerminateEvent);

    // Give thread a 10 second grace period to shut down
    // don't really care if it goes away or not... our process is going away!
    WaitForSingleObject(_hThread, 10000);

    // clean up
    CloseHandle(_hThread);
    CloseHandle(_hTerminateEvent);
    _hThread = NULL;
    _hTerminateEvent = NULL;

    return;
}

DWORD WINAPI ExternalsThread(LPVOID lpData)
{
    CWebCheck * pWebCheck = (CWebCheck *)lpData;
    HINSTANCE hLCE, hSENS = NULL;
    BOOL fLCEStarted = FALSE, fSENSStarted = FALSE;
    DWORD dwRet;
    MSG msg;

    // sleep for 10 seconds before firing off externals
    Sleep(10 * 1000);

    // fire up com
    HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
    if(FAILED(hr)) {
        DBG("LoadExternals: Failed to initialize COM");
        return 0;
    }

    // load and start LCE
    hLCE = LoadLibrary(TEXT("estier2.dll"));
    DBGASSERT(hLCE, "LoadExternals: Failed to load estier2.dll");
    if(hLCE) {
        LCESTART startfunc;
        startfunc = (LCESTART)GetProcAddress(hLCE, "LCEStartServer");
        DBGASSERT(startfunc, "LoadExternals: Failed to find LCEStartServer");
        if(startfunc) {
            hr = startfunc();
            if(SUCCEEDED(hr))
                fLCEStarted = TRUE;
            DBGASSERT(fLCEStarted, "LoadExternals: Failed to start LCE");
        }
    }

    // if LCE started sucessfully, load and start SENS
    if(fLCEStarted) {
        hSENS = LoadLibrary(TEXT("sens.dll"));
        DBGASSERT(hSENS, "LoadExternals: Failed to load sens.dll");
        if(hSENS) {
            SENSSTART startfunc;
            startfunc = (SENSSTART)GetProcAddress(hSENS, "SensInitialize");
            DBGASSERT(startfunc, "LoadExternals: Failed to find SensInitialize");
            if(startfunc) {
                if(startfunc())
                    fSENSStarted = TRUE;
                DBGASSERT(fSENSStarted, "LoadExternals: Failed to start SENS");
            }
        }
    }

    // Wait for our shutdown event but pump messages in the mean time
    do {
        dwRet = MsgWaitForMultipleObjects(1, &(pWebCheck->_hTerminateEvent),
                    FALSE, INFINITE, QS_ALLINPUT);
        if(WAIT_OBJECT_0 == dwRet) {
            // got our event, drop out of do loop
            break;
        }

        // empty the message queue...
        while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    } while(TRUE);

    // shut down SENS
    if(fSENSStarted) {
        ASSERT(hSENS);
        SENSSTOP stopfunc;
        stopfunc = (SENSSTOP)GetProcAddress(hSENS, "SensUninitialize");
        if(stopfunc) {
            stopfunc();
        }
    }

    //
    // [darrenmi] beta-1 hack: Sens may have a thread sitting in its code
    // at this point so it's not safe to unload sens.  Since we're in the
    // process of shutting down anyway, just leave it alone and let the
    // system unload it.
    //
    //if(hSENS) {
    //    FreeLibrary(hSENS);
    //}

    // shut down LCE
    if(fLCEStarted) {
        ASSERT(hLCE)
        LCESTOP stopfunc;
        stopfunc = (LCESTOP)GetProcAddress(hLCE, "LCEStopServer");
        if(stopfunc) {
            stopfunc();
        }
    }

    if(hLCE) {
        FreeLibrary(hLCE);
    }

    // clean up com goo
    CoUninitialize();

    return 0;
}

#if 0
//  TODO: need similar functionality in the new world
void SetNotificationMgrRestrictions(INotificationProcessMgr0 *pNotfMgrProcess)
{
    HRESULT hr;
    INotificationProcessMgr0 *pNotProcess = pNotfMgrProcess;

    // get NotificationMgr if it wasn't passed in
    if (!pNotfMgrProcess)
    {
        hr = CoCreateInstance(CLSID_StdNotificationMgr,
                          NULL,
                          CLSCTX_INPROC_SERVER,
                          IID_INotificationProcessMgr0,
                          (void**)&pNotProcess);
        DBGASSERT(SUCCEEDED(hr), "SetNotificationMgrRestrictions - failed to create notification mgr");
    }
    // set the restrictions
    if (pNotProcess)
    {
        const TCHAR c_szNoScheduledUpdates[] = TEXT("NoScheduledUpdates");
        THROTTLEITEM ti = {0};
        ti.NotificationType = NOTIFICATIONTYPE_AGENT_START;
        ti.nParallel = 3;

        // Has the user has disabled scheduled subscription updates?
        DWORD dwData;
        DWORD cbData = sizeof(dwData);
        if ((ERROR_SUCCESS == SHGetValue(HKEY_CURRENT_USER, c_szRegKey, c_szNoScheduledUpdates, NULL, &dwData, &cbData))
            && dwData)
        {
            ti.dwFlags |= TF_DONT_DELIVER_SCHEDULED_ITEMS;
        }

        // Has the administrator has disabled scheduled subscription updates?
        if (SHRestricted2W(REST_NoScheduledUpdates, NULL, 0))
        {
            ti.dwFlags |= TF_DONT_DELIVER_SCHEDULED_ITEMS;
        }

        // Has the administrator has excluded scheduled subscription updates
        // from this time range?
        DWORD dwBegin = SHRestricted2W(REST_UpdateExcludeBegin, NULL, 0);
        DWORD dwEnd = SHRestricted2W(REST_UpdateExcludeEnd, NULL, 0);
        if (dwBegin && dwEnd)
        {
            ti.dwFlags |= TF_APPLY_EXCLUDE_RANGE;
            ti.stBegin.wHour = (WORD)(dwBegin / 60);
            ti.stBegin.wMinute = (WORD)(dwBegin % 60);
            ti.stEnd.wHour = (WORD)(dwEnd / 60);
            ti.stEnd.wMinute = (WORD)(dwEnd %60);
        }

        // Has the admin set a minimum interval for scheduled subscription updates?
        dwData = SHRestricted2W(REST_MinUpdateInterval, NULL, 0);
        if (dwData)
        {
            ti.dwFlags |= TF_APPLY_UPDATEINTERVAL;
            ti.dwMinItemUpdateInterval = dwData;
        }

        hr = pNotProcess->RegisterThrottleNotificationType(1, &ti, 0, NULL, 0, 0);
        DBGASSERT(SUCCEEDED(hr), "SetNotificationMgrRestrictions - failed to register throttle type & restrictions");
    }
    // release NotificationMgr if it wasn't passed in
    if (!pNotfMgrProcess)
    {
        SAFERELEASE(pNotProcess);
    }
}

#endif


//
// OLE bypass code
//
// Expose a couple of APIs to call start and stop service so loadwc doesn't
// need to load up OLE at start time.
//

HRESULT
ExtStartService(
    BOOL    fForceExternals
    )
{
    HRESULT hr = E_FAIL;

    // make a webcheck object
    ASSERT(NULL == g_pwc);
    if(NULL == g_pwc)
    {
        g_pwc = new CWebCheck;
        if(g_pwc)
        {
            hr = g_pwc->StartService(fForceExternals);
        }
    }

    return hr;
}

HRESULT
ExtStopService(
    void
    )
{
    HRESULT hr = E_FAIL;

    if(g_pwc)
    {
        hr = g_pwc->StopService();
        SAFERELEASE(g_pwc);
    }

    return hr;
}
