#define INITGUID

#include <windows.h>
#include <tchar.h>
#include <shellapi.h>

#include <ole2.h>
#include <coguid.h>
#include <iadmw.h>

#include <iiscnfg.h>

#include "pwstray.h"
#include "resource.h"
#include "sink.h"

#include <pwsdata.hxx>


#define MB_TIMEOUT           5000

// not a string!
#define DW_TRAY_ICON_ID     'PWSt'

#define REGKEY_STP          _T("SOFTWARE\\Microsoft\\INetStp")
#define REGKEY_INSTALLKEY   _T("InstallPath")

//USE_MFC=1


//========================================================= globals
HINSTANCE               g_hInstance = NULL;
HWND                    g_hwnd = NULL;
HMENU                   g_hMenuMain = NULL;

UINT                    g_dwNewTaskbarMessage = 0;

DWORD                   g_dwSinkCookie = 0;
CImpIMSAdminBaseSink*   g_pEventSink = NULL;
IConnectionPoint*       g_pConnPoint = NULL;

IMSAdminBase*           g_pMBCom = NULL;

DWORD                   g_dwServerState = 0;

//========================================================= forwards
DWORD   DWGetServerState();
BOOL FUpdateTrayIcon( DWORD dwMessage );

BOOL InitializeMetabase( OLECHAR* pocMachineName );
void TerminateMetabase();
BOOL InitializeSink();
void TerminateSink();

BOOL GetAdminPath( TCHAR* pch, WORD cch );
BOOL SetServerState( DWORD dwControlCode );
BOOL LaunchAdminUI();
void RunContextMenu();

BOOL FIsW3Running();
void CheckIfServerIsRunningAgain();
void CheckIfServerUpAndDied();


// routine to see if w3svc is running
//--------------------------------------------------------------------
// the method we use to see if the service is running is different on
// windows NT from win95
BOOL   FIsW3Running()
    {
    OSVERSIONINFO info_os;
    info_os.dwOSVersionInfoSize = sizeof(info_os);

    if ( !GetVersionEx( &info_os ) )
        return FALSE;

    // if the platform is NT, query the service control manager
    if ( info_os.dwPlatformId == VER_PLATFORM_WIN32_NT )
        {
        BOOL    fRunning = FALSE;

        // open the service manager
        SC_HANDLE   sch = OpenSCManager(NULL, NULL, GENERIC_READ );
        if ( sch == NULL ) return FALSE;

        // get the service
        SC_HANDLE   schW3 = OpenService(sch, _T("W3SVC"), SERVICE_QUERY_STATUS );
        if ( sch == NULL )
            {
            CloseServiceHandle( sch );
            return FALSE;
            }

        // query the service status
        SERVICE_STATUS  status;
        ZeroMemory( &status, sizeof(status) );
        if ( QueryServiceStatus(schW3, &status) )
            {
            fRunning = (status.dwCurrentState == SERVICE_RUNNING);
            }

        CloseServiceHandle( schW3 );
        CloseServiceHandle( sch );

        // return the answer
        return fRunning;
        }

    // if the platform is Windows95, see if the object exists
    if ( info_os.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS )
        {
        HANDLE hEvent;
        BOOL fFound = FALSE;
        hEvent = CreateEvent(NULL, TRUE, FALSE, _T(PWS_SHUTDOWN_EVENT));
        if ( hEvent != NULL )
            {
            fFound = (GetLastError() == ERROR_ALREADY_EXISTS);
            CloseHandle(hEvent);
            }
        return(fFound);
        }

    return FALSE;
    }


//---------------------------------------------------------------------------
// This routine is called on a timer event. The timer events only come if we
// have received a shutdown notify callback from the metabase. So the server
// is down. We need to wait around until it comes back up, then show ourselves.
void CheckIfServerIsRunningAgain()
    {
    // see if the server is running. If it is, show the icon and stop the timer.
    if ( FIsW3Running() && g_hwnd )
        {
        // if we can't use the metabase, there is no point in this
        if ( !g_pMBCom && !InitializeMetabase(NULL) )
            {
            return;
            }

        // if we can't use the sink, there is no point in this
        if ( !g_pEventSink && !InitializeSink() )
            {
            TerminateMetabase();
            return;
            }

        // stop the life timer mechanism
        KillTimer( g_hwnd, PWS_TRAY_CHECKFORSERVERRESTART );

        // start the unexpected server death test timer
        SetTimer( g_hwnd,  PWS_TRAY_CHECKTOSEEIFINETINFODIED, TIMER_SERVERDIED, NULL );

        // show the tray icon
        g_dwServerState = DWGetServerState();
        FUpdateTrayIcon( NIM_ADD );
        }
    }


//---------------------------------------------------------------------------
// This routine is called on a timer event. The timer events only come if we know
// the server is running. To avoid wasting too many extra cycles it is called rather
// infrequently. What we are doing here is attempting to see if the server died
// without sending us proper notification. If it did, we should clean up the
// icon and start waiting for it to come back.
void CheckIfServerUpAndDied()
    {
    if ( !FIsW3Running() )
        {
        // hide the tray icon
        FUpdateTrayIcon( NIM_DELETE );
        // disconnect the sink mechanism
        TerminateSink();
        // disconnect from the metabase
        TerminateMetabase();
        // stop the death timer
        KillTimer( g_hwnd, PWS_TRAY_CHECKTOSEEIFINETINFODIED );
        // start the life timer
        SetTimer( g_hwnd,  PWS_TRAY_CHECKFORSERVERRESTART, TIMER_RESTART, NULL );
        }
    }

//---------------------------------------------------------------------------
BOOL FUpdateTrayIcon( DWORD dwMessage )
    {
    NOTIFYICONDATA  dataIcon;

    // prepare the common parts of the icon data structure
    dataIcon.cbSize = sizeof( dataIcon );
    dataIcon.hWnd = g_hwnd;
    dataIcon.uID = DW_TRAY_ICON_ID;
    dataIcon.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
    dataIcon.uCallbackMessage = WM_PWS_TRAY_SHELL_NOTIFY;

    // prepare the state-dependant icon handle
    switch( g_dwServerState )
        {
        case MD_SERVER_STATE_PAUSED:
        case MD_SERVER_STATE_PAUSING:
            dataIcon.hIcon = LoadIcon( g_hInstance, MAKEINTRESOURCE(IDI_PAUSED) );
            break;
        case MD_SERVER_STATE_STARTED:
        case MD_SERVER_STATE_STARTING:
            dataIcon.hIcon = LoadIcon( g_hInstance, MAKEINTRESOURCE(IDI_RUNNING) );
            break;
        case MD_SERVER_STATE_STOPPED:
        case MD_SERVER_STATE_STOPPING:
            dataIcon.hIcon = LoadIcon( g_hInstance, MAKEINTRESOURCE(IDI_STOPPED) );
            break;
        };

    // prepare the state-dependant tip strings
    switch( g_dwServerState )
        {
        case MD_SERVER_STATE_PAUSED:
            LoadString( g_hInstance, IDS_PAUSED, dataIcon.szTip, 63 );
            break;
        case MD_SERVER_STATE_PAUSING:
            LoadString( g_hInstance, IDS_PAUSING, dataIcon.szTip, 63 );
            break;
        case MD_SERVER_STATE_STARTED:
            LoadString( g_hInstance, IDS_STARTED, dataIcon.szTip, 63 );
            break;
        case MD_SERVER_STATE_STARTING:
            LoadString( g_hInstance, IDS_STARTING, dataIcon.szTip, 63 );
            break;
        case MD_SERVER_STATE_STOPPED:
            LoadString( g_hInstance, IDS_STOPPED, dataIcon.szTip, 63 );
            break;
        case MD_SERVER_STATE_STOPPING:
            LoadString( g_hInstance, IDS_STOPPING, dataIcon.szTip, 63 );
            break;
        };
    DWORD err = GetLastError();

    // make the shell call
    return Shell_NotifyIcon( dwMessage, &dataIcon );
    }

//---------------------------------------------------------------------------
DWORD   DWGetServerState()
    {
    HRESULT         hErr;
    METADATA_HANDLE hMeta;
    METADATA_RECORD mdRecord;
    DWORD           state = 1000;
    DWORD           dwRequiredLen;

    // open the w3svc key so we can get its state
    // since this is a pws thing, we only care about the first
    // server instance
    hErr = g_pMBCom->OpenKey(
            METADATA_MASTER_ROOT_HANDLE,
            L"/LM/W3SVC/1/",
            METADATA_PERMISSION_READ,
            MB_TIMEOUT,
            &hMeta);
    if ( FAILED(hErr) )
        return state;

    // prepare the metadata record
    mdRecord.dwMDIdentifier  = MD_SERVER_STATE;
    mdRecord.dwMDAttributes  = METADATA_INHERIT;
    mdRecord.dwMDUserType    = IIS_MD_UT_SERVER;
    mdRecord.dwMDDataType    = DWORD_METADATA;
    mdRecord.dwMDDataLen     = sizeof(DWORD);
    mdRecord.pbMDData        = (PBYTE)&state;

    // get the data
    hErr = g_pMBCom->GetData(
        hMeta,
        L"",
        &mdRecord,
        &dwRequiredLen );

    // close the key
    hErr = g_pMBCom->CloseKey( hMeta );

    // return the answer
    return state;
    }

//---------------------------------------------------------------------------
// deal with mouse messages that occur on the tray icon
void On_WM_PWS_TRAY_SHELL_NOTIFY(UINT uID, UINT uMouseMsg)
    {
    if ( uID != DW_TRAY_ICON_ID )
        return;

    // act on the mouse message
    switch( uMouseMsg )
        {
        case WM_LBUTTONDBLCLK:
            LaunchAdminUI();
            break;
        case WM_RBUTTONDOWN:
            RunContextMenu();
            break;
        }
    }


//---------------------------------------------------------------------------
// the window proc callback procedure
LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
    switch( message )
        {
        case WM_CREATE:
            // get the registered windows message signifying that there is a new
            // taskbar. When this message is triggered, we need to re-insert the icon
            // into the taskbar. This is usually done when the shell restarts.
            // see http://www.microsoft.com/msdn/sdk/inetsdk/help/itt/shell/taskbar.htm
            // also see the default case for processing of the message
            g_dwNewTaskbarMessage = RegisterWindowMessage(TEXT("TaskbarCreated"));
            break;

        case WM_PWS_TRAY_SHUTDOWN_NOTIFY:
            // hide the tray icon
            FUpdateTrayIcon( NIM_DELETE );
            // disconnect the sink mechanism
            TerminateSink();
            // disconnect from the metabase
            TerminateMetabase();
            // start the time mechanism
            SetTimer( g_hwnd,  PWS_TRAY_CHECKFORSERVERRESTART, TIMER_RESTART, NULL );
            break;

        case WM_TIMER:
            switch ( wParam )
                {
                case PWS_TRAY_CHECKFORSERVERRESTART:
                    CheckIfServerIsRunningAgain();
                    break;
                case PWS_TRAY_CHECKTOSEEIFINETINFODIED:
                    CheckIfServerUpAndDied();
                    break;
                };
            break;

        case WM_PWS_TRAY_SHELL_NOTIFY:
            On_WM_PWS_TRAY_SHELL_NOTIFY( (UINT)wParam, (UINT)lParam );
            break;
        case WM_PWS_TRAY_UPDATE_STATE:
            g_dwServerState = DWGetServerState();
            FUpdateTrayIcon( NIM_MODIFY );
            break;
        case WM_DESTROY:
            FUpdateTrayIcon( NIM_DELETE );
            PostQuitMessage(0);
            break;

        case WM_COMMAND:
            switch( LOWORD(wParam) )
                {
                case ID_START:
                    SetServerState( MD_SERVER_COMMAND_START );
                    break;
                case ID_STOP:
                    SetServerState( MD_SERVER_COMMAND_STOP );
                    break;
                case ID_PAUSE:
                    SetServerState( MD_SERVER_COMMAND_PAUSE );
                    break;
                case ID_CONTINUE:
                    SetServerState( MD_SERVER_COMMAND_CONTINUE );
                    break;
                case ID_PROPERTIES:
                    LaunchAdminUI();
                    break;
                };
            break;

        default:
            // cannot case directly on g_dwNewTaskbarMessage because it is not a constant
            if ( message == g_dwNewTaskbarMessage )
                {
                // Just go straight into waitin' for the server mode. If the server is
                // running it will catch the first time throught the timer. If it is not,
                // then we just sit around and wait for it
                // start the time mechanism
                SetTimer( g_hwnd,  PWS_TRAY_CHECKFORSERVERRESTART, TIMER_RESTART, NULL );
                }
            break;

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

//---------------------------------------------------------------------------
// main routine
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow )
    {
    WNDCLASS    wndclass;
    MSG         msg;

    // record the instance handle
    g_hInstance = hInstance;

    // do nothing if this app is already running
    if ( hPrevInstance )
        return 0;

    // one more test of previous instances
    if ( FindWindow(PWS_TRAY_WINDOW_CLASS,NULL) )
        return 0;

    // start ole
    if ( FAILED(CoInitialize( NULL )) )
        return 0;

    // get the menu handle
    g_hMenuMain = LoadMenu( g_hInstance, MAKEINTRESOURCE(IDR_POPUP) );

    // prepare and register the window class
    wndclass.style  =   0;
    wndclass.lpfnWndProc = WndProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance  = hInstance;
    wndclass.hIcon      = NULL;
    wndclass.hCursor    = NULL;
    wndclass.hbrBackground = NULL;
    wndclass.lpszMenuName = NULL;
    wndclass.lpszClassName = PWS_TRAY_WINDOW_CLASS;
    RegisterClass( &wndclass );

    // create the window
    g_hwnd = CreateWindow(
        PWS_TRAY_WINDOW_CLASS,  // pointer to registered class name
        _T(""),                 // pointer to window name
        0,   // window style
        0,                     // horizontal position of window
        0,                     // vertical position of window
        0,                    // window width
        0,                    // window height
        NULL,                   // handle to parent or owner window
        NULL,                   // handle to menu or child-window identifier
        hInstance,              // handle to application instance
        NULL                    // pointer to window-creation data
       );

    // Just go straight into waitin' for the server mode. If the server is
    // running it will catch the first time throught the timer. If it is not,
    // then we just sit around and wait for it
    // start the time mechanism
    SetTimer( g_hwnd,  PWS_TRAY_CHECKFORSERVERRESTART, TIMER_RESTART, NULL );

    // run the message loop
    while (GetMessage(&msg, NULL, 0, 0))
        {
        TranslateMessage(&msg);
        DispatchMessage( &msg);
        }

    // clean up the sink and the metabase
    DestroyMenu( g_hMenuMain );
    TerminateSink();
    TerminateMetabase();
    CoUninitialize();

    return((int)msg.wParam);
    }

//------------------------------------------------------------------
BOOL InitializeSink()
    {
    IConnectionPointContainer * pConnPointContainer = NULL;
    HRESULT                     hRes;
    BOOL                        fSinkConnected = FALSE;

    // g_pMBCom is defined in wrapmb
    IUnknown*                   pmb = (IUnknown*)g_pMBCom;

    g_pEventSink = new CImpIMSAdminBaseSink();

    if ( !g_pEventSink )
        {
        return FALSE;
        }

    //
    // First query the object for its Connection Point Container. This
    // essentially asks the object in the server if it is connectable.
    //
    hRes = pmb->QueryInterface( IID_IConnectionPointContainer,
    (PVOID *)&pConnPointContainer);
    if SUCCEEDED(hRes)
        {
        // Find the requested Connection Point. This AddRef's the
        // returned pointer.

        hRes = pConnPointContainer->FindConnectionPoint( IID_IMSAdminBaseSink,
        &g_pConnPoint);

        if (SUCCEEDED(hRes))
            {
            hRes = g_pConnPoint->Advise( (IUnknown *)g_pEventSink,
            &g_dwSinkCookie);

            if (SUCCEEDED(hRes))
                {
                fSinkConnected = TRUE;
                }
            }

        if ( pConnPointContainer )
            {
            pConnPointContainer->Release();
            pConnPointContainer = NULL;
            }
        }

    if ( !fSinkConnected )
        {
        delete g_pEventSink;
        g_pEventSink = NULL;
        }

    return fSinkConnected;
    }

//------------------------------------------------------------------
void TerminateSink()
    {
    HRESULT hRes;
    if ( g_dwSinkCookie && g_pConnPoint )
        hRes = g_pConnPoint->Unadvise( g_dwSinkCookie );
    }

//------------------------------------------------------------------
BOOL InitializeMetabase( OLECHAR* pocMachineName )
    {
    IClassFactory*  pcsfFactory = NULL;
    COSERVERINFO        csiMachineName;
    COSERVERINFO*       pcsiParam = NULL;

    HRESULT                 hresError;

    //release previous interface if needed
    if( g_pMBCom != NULL )
        {
        g_pMBCom->Release();
        g_pMBCom = NULL;
        }

    //fill the structure for CoGetClassObject
    ZeroMemory( &csiMachineName, sizeof(csiMachineName) );
    // csiMachineName.pAuthInfo = NULL;
    // csiMachineName.dwFlags = 0;
    // csiMachineName.pServerInfoExt = NULL;
    csiMachineName.pwszName = pocMachineName;
    pcsiParam = &csiMachineName;

    hresError = CoGetClassObject(GETAdminBaseCLSID(TRUE), CLSCTX_SERVER, pcsiParam,
            IID_IClassFactory, (void**) &pcsfFactory);
    if (FAILED(hresError))
        return FALSE;

    // create the instance of the interface
    hresError = pcsfFactory->CreateInstance(NULL, IID_IMSAdminBase, (void **)&g_pMBCom);
    if (FAILED(hresError))
        {
        g_pMBCom = FALSE;
        return FALSE;
        }

    // release the factory
    pcsfFactory->Release();

    // success
    return TRUE;
    }

//------------------------------------------------------------------
void TerminateMetabase()
    {
    if ( g_pMBCom )
        {
        g_pMBCom->Release();
        g_pMBCom = NULL;
        }
    }


//======================================== control actions
//------------------------------------------------------------------------
// get the inetinfo path
BOOL LaunchAdminUI()
    {
    TCHAR   chPath[MAX_PATH+1];

    // get the path to the admin UI
    if ( !GetAdminPath( chPath, MAX_PATH ) )
        {
        LoadString( g_hInstance, IDS_ADMINUI_ERR, chPath, MAX_PATH );
        MessageBox( g_hwnd, chPath, NULL, MB_OK|MB_ICONERROR );
        return FALSE;
        }

    // do it
    ShellExecute(
        NULL,   // handle to parent window
        NULL,   // pointer to string that specifies operation to perform
        chPath, // pointer to filename or folder name string
        NULL,   // pointer to string that specifies executable-file parameters
        NULL,   // pointer to string that specifies default directory
        SW_SHOW     // whether file is shown when opened
       );
    return TRUE;
    }

//------------------------------------------------------------------------
// get the inetinfo path
BOOL GetAdminPath( TCHAR* pch, WORD cch )
    {
        HKEY        hKey;
        DWORD       err, type;
        DWORD       cbBuff = cch * sizeof(TCHAR);

    // get the server install path from the registry
    // open the registry key, if it exists
    err = RegOpenKeyEx(
            HKEY_LOCAL_MACHINE, // handle of open key
            REGKEY_STP,         // address of name of subkey to open
            0,                  // reserved
            KEY_READ,           // security access mask
            &hKey               // address of handle of open key
           );

    // if we did not open the key for any reason (say... it doesn't exist)
    // then leave right away
    if ( err != ERROR_SUCCESS )
        return FALSE;

    type = REG_SZ;
    err = RegQueryValueEx(
            hKey,               // handle of key to query
            REGKEY_INSTALLKEY,  // address of name of value to query
            NULL,               // reserved
            &type,              // address of buffer for value type
            (PUCHAR)pch,        // address of data buffer
            &cbBuff             // address of data buffer size
           );

    // close the key
    RegCloseKey( hKey );

    // if we did get the key for any reason (say... it doesn't exist)
    // then leave right away
    if ( err != ERROR_SUCCESS )
        return FALSE;

    // tack on the file name itself
    TCHAR   chFile[64];

    if ( LoadString( g_hInstance, IDS_ADMIN_UI, chFile, 63 ) == 0 )
        return FALSE;

    _tcscat( pch, chFile );

    // success
    return TRUE;
    }

//------------------------------------------------------------------------
BOOL SetServerState( DWORD dwControlCode )
    {
    HRESULT         hErr;
    METADATA_HANDLE hMeta;
    METADATA_RECORD mdRecord;

    // open the w3svc key so we can get its state
    // since this is a pws thing, we only care about the first
    // server instance
    hErr = g_pMBCom->OpenKey(
            METADATA_MASTER_ROOT_HANDLE,
            L"/LM/W3SVC/1/",
            METADATA_PERMISSION_WRITE,
            MB_TIMEOUT,
            &hMeta);
    if ( FAILED(hErr) )
        return FALSE;

    // prepare the metadata record
    mdRecord.dwMDIdentifier  = MD_SERVER_COMMAND;
    mdRecord.dwMDAttributes  = 0;
    mdRecord.dwMDUserType    = IIS_MD_UT_SERVER;
    mdRecord.dwMDDataType    = DWORD_METADATA;
    mdRecord.dwMDDataLen     = sizeof(DWORD);
    mdRecord.pbMDData        = (PBYTE)&dwControlCode;

    // get the data
    hErr = g_pMBCom->SetData(
        hMeta,
        L"",
        &mdRecord );

    // close the key
    hErr = g_pMBCom->CloseKey( hMeta );

    // return the answer
    return TRUE;
    }

//---------------------------------------------------------------------------
void RunContextMenu()
    {
    POINT   pos;
    HMENU   hMenuSub;
    RECT    rect = {0,0,1,1};
    BOOL    f;

    static BOOL fTracking = FALSE;

    if ( fTracking ) return;
    fTracking = TRUE;

    // where is the mouse? This tells us where to put the menu
    if ( !g_hMenuMain || !GetCursorPos(&pos) )
        return;

    // get the menu handle
    hMenuSub = GetSubMenu( g_hMenuMain, 0 );

    // easiest to start by disabling all the state based items
    f = EnableMenuItem( hMenuSub, ID_START, MF_BYCOMMAND|MF_GRAYED );
    f = EnableMenuItem( hMenuSub, ID_STOP, MF_BYCOMMAND|MF_GRAYED );
    f = EnableMenuItem( hMenuSub, ID_PAUSE, MF_BYCOMMAND|MF_GRAYED );
    f = EnableMenuItem( hMenuSub, ID_CONTINUE, MF_BYCOMMAND|MF_GRAYED );

    // prepare the state based menu items
    switch( g_dwServerState )
        {
        case MD_SERVER_STATE_PAUSED:
        case MD_SERVER_STATE_PAUSING:
            EnableMenuItem( hMenuSub, ID_STOP, MF_BYCOMMAND|MF_ENABLED );
            EnableMenuItem( hMenuSub, ID_CONTINUE, MF_BYCOMMAND|MF_ENABLED );
            break;
        case MD_SERVER_STATE_STARTED:
        case MD_SERVER_STATE_STARTING:
            EnableMenuItem( hMenuSub, ID_STOP, MF_BYCOMMAND|MF_ENABLED );
            EnableMenuItem( hMenuSub, ID_PAUSE, MF_BYCOMMAND|MF_ENABLED );
            break;
        case MD_SERVER_STATE_STOPPED:
        case MD_SERVER_STATE_STOPPING:
            EnableMenuItem( hMenuSub, ID_START, MF_BYCOMMAND|MF_ENABLED );
            break;
        };

    // this is necessary, because we need to get a lose focus message for the menu
    // to go down when the user click on some other process outside the up menu
    SetForegroundWindow(g_hwnd);

    // run the menu
    TrackPopupMenu(hMenuSub, TPM_LEFTALIGN|TPM_RIGHTBUTTON, pos.x, pos.y, 0, g_hwnd, NULL);

    fTracking = FALSE;
    }