/*++

Copyright (c) 1995 Microsoft Corporation

Module Name:

    billbrd.c

Abstract:

    Routines for displaying Windows that are static in nature.

Author:

    Ted Miller (tedm) 8-Jun-1995

Revision History:

--*/

#include "setupp.h"
#pragma hdrstop


//
// Define structure we pass around to describe a billboard.
//
typedef struct _BILLBOARD_PARAMS {
    UINT MessageId;
    va_list *arglist;
    HWND Owner;
    DWORD NotifyThreadId;
} BILLBOARD_PARAMS, *PBILLBOARD_PARAMS;

//
// Custom window messages
//
#define WMX_BILLBOARD_DISPLAYED     (WM_USER+243)
#define WMX_BILLBOARD_TERMINATE     (WM_USER+244)

#define ID_REBOOT_TIMER         10


//
// Import the entry point used to check whether setup is executing within an
// ASR context.
//

extern BOOL
AsrIsEnabled( VOID );


INT_PTR
BillboardDlgProc(
    IN HWND   hdlg,
    IN UINT   msg,
    IN WPARAM wParam,
    IN LPARAM lParam
    )
{
    static BOOL Initializing;
    HWND Animation = GetDlgItem(hdlg,IDA_SETUPINIT);
    static HANDLE   hBitmap;
    static HCURSOR  hCursor;


    switch(msg) {

    case WM_INITDIALOG:
        {
            PBILLBOARD_PARAMS BillParams;
            PWSTR p;
            BOOL b;


            BillParams = (PBILLBOARD_PARAMS)lParam;

            if(BillParams->MessageId == MSG_INITIALIZING) {
                Initializing = TRUE;
                b = TRUE;
                hCursor = SetCursor( LoadCursor( NULL, IDC_WAIT ) );
                ShowCursor( TRUE );
                Animate_Open(Animation,MAKEINTRESOURCE(IDA_SETUPINIT));
                hBitmap = LoadBitmap (MyModuleHandle, MAKEINTRESOURCE(IDB_INIT_WORKSTATION));
                SendDlgItemMessage(hdlg,IDC_BITMAP,STM_SETIMAGE,IMAGE_BITMAP,(LPARAM)hBitmap);
            } else {
                Initializing = FALSE;
                if(p = RetrieveAndFormatMessageV(SETUPLOG_USE_MESSAGEID,
                    BillParams->MessageId,BillParams->arglist)) {

                    b = SetDlgItemText(hdlg,IDT_STATIC_1,p);
                    MyFree(p);
                } else {
                    b = FALSE;
                }
            }


            if(b) {
                //
                // Center the billboard relative to the window that owns it.
                //
                // if we have the BB window, do the positioning on that. 
                // MainWindowHandle point to that window
                //
                if (GetBBhwnd())
                    CenterWindowRelativeToWindow(hdlg, MainWindowHandle, FALSE);
                else
                    pSetupCenterWindowRelativeToParent(hdlg);
                //
                // Post ourselves a message that we won't get until we've been
                // actually displayed on the screen. Then when we process that message,
                // we inform the thread that created us that we're up. Note that
                // once that notification has been made, the BillParams we're using
                // now will go away since they are stored in local vars (see
                // DisplayBillboard()).
                //
                PostMessage(hdlg,WMX_BILLBOARD_DISPLAYED,0,(LPARAM)BillParams->NotifyThreadId);
                //
                // Tell Windows not to process this message.
                //
                return(FALSE);
            } else {
                //
                // We won't post the message, but returning -1 will get the
                // caller of DialogBox to post it for us.
                //
                EndDialog(hdlg,-1);
            }
        }
        break;

    case WMX_BILLBOARD_DISPLAYED:

        if(Initializing) {
            Animate_Play(Animation,0,-1,-1);
        }

        PostThreadMessage(
            (DWORD)lParam,
            WMX_BILLBOARD_DISPLAYED,
            TRUE,
            (LPARAM)hdlg
            );

        break;

    case WMX_BILLBOARD_TERMINATE:

        if(Initializing) {
            SetCursor( hCursor );
            ShowCursor( FALSE );
            Animate_Stop(Animation);
            Animate_Close(Animation);
            DeleteObject(hBitmap);
        }
        EndDialog(hdlg,0);
        break;

    default:
        return(FALSE);
    }

    return(TRUE);
}


DWORD
BillboardThread(
    IN PVOID ThreadParam
    )
{
    PBILLBOARD_PARAMS BillboardParams;
    INT_PTR i;

    BillboardParams = ThreadParam;

    //
    // For the "initializing" case, we use a different dialog.
    //
    if( AsrIsEnabled() ) {
        i = DialogBoxParam(
                        MyModuleHandle,
                        (BillboardParams->MessageId == MSG_INITIALIZING) ?
                        MAKEINTRESOURCE(IDD_SETUPINIT_ASR) :
                        MAKEINTRESOURCE(IDD_BILLBOARD1),
                        BillboardParams->Owner,
                        BillboardDlgProc,
                        (LPARAM)BillboardParams
                        );
    } else {
        i = DialogBoxParam(
                        MyModuleHandle,
                        (BillboardParams->MessageId == MSG_INITIALIZING) ?
                        MAKEINTRESOURCE(IDD_SETUPINIT) :
                        MAKEINTRESOURCE(IDD_BILLBOARD1),
                        BillboardParams->Owner,
                        BillboardDlgProc,
                        (LPARAM)BillboardParams
                        );
    }

    //
    // If the dialog box call failed, we have to tell the
    // main thread about it here. Otherwise the dialog proc
    // tells the main thread.
    //
    if(i == -1) {
        PostThreadMessage(
            BillboardParams->NotifyThreadId,
            WMX_BILLBOARD_DISPLAYED,
            FALSE,
            (LPARAM)NULL
            );
    }

    return(0);
}


HWND
DisplayBillboard(
    IN HWND Owner,
    IN UINT MessageId,
    ...
    )
{
    HANDLE ThreadHandle;
    DWORD ThreadId;
    BILLBOARD_PARAMS ThreadParams;
    va_list arglist;
    HWND hwnd;
    MSG msg;

    hwnd = NULL;
    // If we have a billboard, we should not need this. dialog.
    if (GetBBhwnd() == NULL)
    {
        va_start(arglist,MessageId);

        //
        // The billboard will exist in a separate thread so it will
        // always be responsive.
        //
        ThreadParams.MessageId = MessageId;
        ThreadParams.arglist = &arglist;
        ThreadParams.Owner = Owner;
        ThreadParams.NotifyThreadId = GetCurrentThreadId();

        ThreadHandle = CreateThread(
                            NULL,
                            0,
                            BillboardThread,
                            &ThreadParams,
                            0,
                            &ThreadId
                            );

        if(ThreadHandle) {
            //
            // Wait for the billboard to tell us its window handle
            // or that it failed to display the billboard dialog.
            //
            do {
                GetMessage(&msg,NULL,0,0);
                if(msg.message == WMX_BILLBOARD_DISPLAYED) {
                    if(msg.wParam) {
                        hwnd = (HWND)msg.lParam;
                        Sleep(1500);        // let the user see it even on fast machines
                    }
                } else {
                    DispatchMessage(&msg);
                }
            } while(msg.message != WMX_BILLBOARD_DISPLAYED);

            CloseHandle(ThreadHandle);
        }

        va_end(arglist);
    }
    else
    {
        // Start BB text
        StartStopBB(TRUE);
    }
    return(hwnd);
}


VOID
KillBillboard(
    IN HWND BillboardWindowHandle
    )
{
    if(BillboardWindowHandle && IsWindow(BillboardWindowHandle)) {
        PostMessage(BillboardWindowHandle,WMX_BILLBOARD_TERMINATE,0,0);
    }
}


INT_PTR
DoneDlgProc(
    IN HWND   hdlg,
    IN UINT   msg,
    IN WPARAM wParam,
    IN LPARAM lParam
    )
{
    PWSTR p;
    static UINT Countdown;

    switch(msg) {

    case WM_INITDIALOG:

        // if we have the BB window, do the positioning on that. MainWindowHandle point to that window
        if (GetBBhwnd())
            CenterWindowRelativeToWindow(hdlg, MainWindowHandle, FALSE);
        else
            pSetupCenterWindowRelativeToParent(hdlg);

        SendDlgItemMessage(
            hdlg,
            IDOK,
            BM_SETIMAGE,
            0,
            (LPARAM)LoadBitmap(MyModuleHandle,MAKEINTRESOURCE(IDB_REBOOT))
            );

        if(p = RetrieveAndFormatMessage(NULL,(UINT)lParam)) {
            SetDlgItemText(hdlg,IDT_STATIC_1,p);
            MyFree(p);
        }

        Countdown = 15 * 10;
        SendDlgItemMessage(hdlg,IDC_PROGRESS1,PBM_SETRANGE,0,MAKELONG(0,Countdown));
        SendDlgItemMessage(hdlg,IDC_PROGRESS1,PBM_SETSTEP,1,0);
        SendDlgItemMessage(hdlg,IDC_PROGRESS1,PBM_SETPOS,0,0);
        SetTimer(hdlg,ID_REBOOT_TIMER,100,NULL);

        SetFocus(GetDlgItem(hdlg,IDOK));
        return(FALSE);

    case WM_TIMER:

        Countdown--;

        if(Countdown) {
            SendDlgItemMessage(hdlg,IDC_PROGRESS1,PBM_STEPIT,0,0);
        } else {
            KillTimer(hdlg,ID_REBOOT_TIMER);
            DeleteObject((HGDIOBJ)SendDlgItemMessage(hdlg,IDOK,BM_GETIMAGE,0,0));
            EndDialog(hdlg,0);
        }

        break;

    case WM_COMMAND:

        if((HIWORD(wParam) == BN_CLICKED) && (LOWORD(wParam) == IDOK)) {
            KillTimer(hdlg,ID_REBOOT_TIMER);
            DeleteObject((HGDIOBJ)SendDlgItemMessage(hdlg,IDOK,BM_GETIMAGE,0,0));
            EndDialog(hdlg,0);
        } else {
            return(FALSE);
        }
        break;

    default:
        return(FALSE);
    }

    return(TRUE);
}

typedef BOOL (CALLBACK *STOPBILLBOARD)();
typedef BOOL (CALLBACK *STARTBILLBOARD)();
typedef BOOL (WINAPI* SETTIMEESTIMATE)(LPCTSTR szText);
typedef BOOL (WINAPI* SETPROGRESSTEXT)(LPCTSTR szText);
typedef BOOL (WINAPI* SETINFOTEXT)(LPCTSTR szText);
typedef LRESULT (WINAPI* PROGRESSGAUGEMSG)(UINT msg, WPARAM wparam, LPARAM lparam);
typedef BOOL (WINAPI* SHOWPROGRESSGAUGEWINDOW)(UINT uiShow);

BOOL BB_ShowProgressGaugeWnd(UINT nCmdShow)
{
    static SHOWPROGRESSGAUGEWINDOW fpShowGauge = NULL;
    BOOL bRet = FALSE;

    if (fpShowGauge == NULL)
    {
        if (hinstBB)
        {
            fpShowGauge = (SHOWPROGRESSGAUGEWINDOW )GetProcAddress(hinstBB, "ShowProgressGaugeWindow");
        }
    }
    if (fpShowGauge != NULL)
    {
        bRet = fpShowGauge(nCmdShow);
    }
    return bRet;
}
LRESULT BB_ProgressGaugeMsg(UINT msg, WPARAM wparam, LPARAM lparam)
{
    static PROGRESSGAUGEMSG fpProgressGaugeMsg = NULL;
    LRESULT lresult = 0;

    if (fpProgressGaugeMsg == NULL)
    {
        if (hinstBB)
        {
            fpProgressGaugeMsg = (PROGRESSGAUGEMSG )GetProcAddress(hinstBB, "ProgressGaugeMsg");
        }
    }
    if (fpProgressGaugeMsg != NULL)
    {
        lresult = fpProgressGaugeMsg(msg, wparam, lparam);
    }
    return lresult;
}
void BB_SetProgressText(LPCTSTR szText)
{
    static SETPROGRESSTEXT fpSetProgressText = NULL;
    if (fpSetProgressText == NULL)
    {
        if (hinstBB)
        {
            fpSetProgressText = (SETPROGRESSTEXT )GetProcAddress(hinstBB, "SetProgressText");
        }
    }
    if (fpSetProgressText != NULL)
    {
        fpSetProgressText(szText);
    }
}
void BB_SetInfoText(LPTSTR szText)
{
    static SETINFOTEXT fpSetInfoText = NULL;
    if (fpSetInfoText == NULL)
    {
        if (hinstBB)
        {
            fpSetInfoText = (SETINFOTEXT )GetProcAddress(hinstBB, "SetInfoText");
        }
    }
    if (fpSetInfoText != NULL)
    {
        fpSetInfoText(szText);
    }
}
void BB_SetTimeEstimateText(LPTSTR szText)
{
    static SETTIMEESTIMATE fpSetTimeEstimate = NULL;
    if (fpSetTimeEstimate == NULL)
    {
        if (hinstBB)
        {
            fpSetTimeEstimate = (SETTIMEESTIMATE)GetProcAddress(hinstBB, "SetTimeEstimate");
        }
    }
    if (fpSetTimeEstimate != NULL)
    {
        fpSetTimeEstimate(szText);
    }
}

BOOL StartStopBB(BOOL bStart)
{
    static STARTBILLBOARD fpStart = NULL;
    static STOPBILLBOARD fpStop = NULL;
    BOOL bRet = FALSE;

    if ((fpStart == NULL) || (fpStop == NULL))
    {
        if (hinstBB)
        {
            fpStop = (STARTBILLBOARD )GetProcAddress(hinstBB, "StopBillBoard");
            fpStart = (STOPBILLBOARD )GetProcAddress(hinstBB, "StartBillBoard");
        }
    }
    if ((fpStart != NULL) && (fpStop != NULL))
    {
        if (bStart)
            bRet = fpStart();
        else
            bRet = fpStop();

    }
    return bRet;
}

LRESULT ProgressGaugeMsgWrapper(UINT msg, WPARAM wparam, LPARAM lparam)
{
    static DWORD MsecPerProcessTick;
    static DWORD PreviousRemainingTime = 0;
    static DWORD RemainungTimeMsecInThisPhase = 0;
    static int  iCurrentPos = 0;
    static int  iMaxPosition = 0;
    static int  iStepSize = 0;

    static UINT PreviousPhase = Phase_Unknown;
    static BOOL IgnoreSetRange = FALSE;
    static BOOL IgnoreSetPos  = FALSE;

    DWORD dwDeltaTicks = 0;
    switch (msg)
    {
        case WMX_PROGRESSTICKS:
            // If we get a WMX_PROGRESSTICKS before a PBM_SETRANGE, ignore the set range
            // This should be use if the progress bar only takes up x% of the whole bar.
            // In this case the phase sends PBM_SETRANGE and a PBM_SETPOS to setup the
            // progress values for it's part of the gauge.
            IgnoreSetRange = TRUE;
            if (PreviousPhase != CurrentPhase)
            {
                PreviousPhase = CurrentPhase;
                iCurrentPos = 0;
                iMaxPosition = (int)wparam;
                iStepSize = 10;

                MsecPerProcessTick = ((SetupPhase[CurrentPhase].Time*1000)/(iMaxPosition - iCurrentPos) )+ 1;
                RemainungTimeMsecInThisPhase = (SetupPhase[CurrentPhase].Time * 1000);
                PreviousRemainingTime = RemainungTimeMsecInThisPhase;
            }
            else
            {
                // what to do if the same phase send more then one set range.
                // don't change the remaining time, only recal the msecperprogresstick
                // 
                iCurrentPos = 0;
                iMaxPosition = (int)wparam;
                iStepSize = 10;
                MsecPerProcessTick = (RemainungTimeMsecInThisPhase /(iMaxPosition - iCurrentPos) )+ 1;
            }
            break;

        case PBM_SETPOS:
            {
                UINT uiCurrentPos;
                if (!IgnoreSetPos)
                {
                    int iDeltaPos = 0;
                    // Find out where the current position of the gasgauge is.
                    // The difference is the #ticks we use to reduce the time estimate
            
                    uiCurrentPos = (UINT)BB_ProgressGaugeMsg(PBM_GETPOS, 0, 0);
                    // See if there is a difference in the current position and the one 
                    // we think we are in.
                    // Only if the new position is greater then the current one 
                    // calc the difference and substract from remaining time.
                    if ((UINT)wparam > uiCurrentPos)
                    {
                        iDeltaPos = (UINT)wparam - uiCurrentPos;
                        iCurrentPos += iDeltaPos;
                        // Only substract if more time left
                        if ((iDeltaPos * MsecPerProcessTick) < RemainungTimeMsecInThisPhase)
                        {
                            RemainungTimeMsecInThisPhase -= (iDeltaPos * MsecPerProcessTick);
                        }
                        else
                        {
                            RemainungTimeMsecInThisPhase = 0;
                        }
                        UpdateTimeString(RemainungTimeMsecInThisPhase, &PreviousRemainingTime);
                    }
                }
                IgnoreSetPos = FALSE;
            }
            break;

        case PBM_SETRANGE:
        case PBM_SETRANGE32:
            // did the phase not send the private message above
            if (!IgnoreSetRange)
            {
                // Are we not in the same phase?
                if (PreviousPhase != CurrentPhase)
                {
                    PreviousPhase = CurrentPhase;
                    // Get the new start and max position
                    if (msg == PBM_SETRANGE32)
                    {
                        iCurrentPos = (int)wparam;
                        iMaxPosition = (int)lparam;
                    }
                    else
                    {
                        iCurrentPos = LOWORD(lparam);
                        iMaxPosition = HIWORD(lparam);
                    }
                    iStepSize = 10;

                    // Calc the msec per tick and msec in this phase
                    MsecPerProcessTick = ((SetupPhase[CurrentPhase].Time*1000)/(iMaxPosition - iCurrentPos) )+ 1;
                    RemainungTimeMsecInThisPhase = (SetupPhase[CurrentPhase].Time * 1000);
                    PreviousRemainingTime = RemainungTimeMsecInThisPhase;
                }
                else
                {
                    // the same phase send more then one set range.
                    // 1. don't change the remaining time, only recal the msecperprogresstick
                    // 2. Ignore the next PBM_SETPOS message.
                    // 
                    // Get the new start and max position
                    if (msg == PBM_SETRANGE32)
                    {
                        iCurrentPos = (int)wparam;
                        iMaxPosition = (int)lparam;
                    }
                    else
                    {
                        iCurrentPos = LOWORD(lparam);
                        iMaxPosition = HIWORD(lparam);
                    }
                    iStepSize = 10;
                    MsecPerProcessTick = (RemainungTimeMsecInThisPhase /(iMaxPosition - iCurrentPos) )+ 1;
                    IgnoreSetPos = TRUE;
                }
            }
            else
            {
                // If we ignored the setrange, also ignore the first set pos.
                IgnoreSetPos = TRUE;
            }
            IgnoreSetRange = FALSE;
            break;

        case PBM_DELTAPOS:
            {
                int iDeltaPos = 0;
                // wparam has the # of ticks to move the gas gauge
                // make sure we don't over shoot the max posistion
                if ((iCurrentPos + (int)wparam) > iMaxPosition)
                {
                    iDeltaPos = (iMaxPosition - iCurrentPos);
                }
                else
                {
                    iDeltaPos = (int)wparam;
                }

                iCurrentPos += iDeltaPos;
                if ((iDeltaPos * MsecPerProcessTick) < RemainungTimeMsecInThisPhase)
                {
                    RemainungTimeMsecInThisPhase -= (iDeltaPos * MsecPerProcessTick);
                }
                else
                {
                    RemainungTimeMsecInThisPhase = 0;
                }
                UpdateTimeString(RemainungTimeMsecInThisPhase, &PreviousRemainingTime);
            }
            break;

        case PBM_STEPIT:
            {
                int iDeltaPos = 0;
                //  make sure we don't over shoot the max posistion
                if ((iCurrentPos + iStepSize) > iMaxPosition)
                {
                    iDeltaPos = (iMaxPosition - iCurrentPos);
                }
                else
                {
                    iDeltaPos = iStepSize;
                }
                iCurrentPos += iDeltaPos;
                if ((iDeltaPos * MsecPerProcessTick) < RemainungTimeMsecInThisPhase)
                {
                    RemainungTimeMsecInThisPhase -= (iDeltaPos * MsecPerProcessTick);
                }
                else
                {
                    RemainungTimeMsecInThisPhase = 0;
                }
                UpdateTimeString(RemainungTimeMsecInThisPhase, &PreviousRemainingTime);
            }
            break;

        case PBM_SETSTEP:
            iStepSize = (int)wparam;
            break;
    }
            
    return BB_ProgressGaugeMsg(msg, wparam, lparam);
}

void UpdateTimeString(DWORD RemainungTimeMsecInThisPhase, 
                      DWORD *PreviousRemainingTime)
{
    // If the previous displayed time is 1 minute old, update the time remaining.
    if ((*PreviousRemainingTime >= 60000) && ((*PreviousRemainingTime - 60000) > RemainungTimeMsecInThisPhase))
    {
        // Substract one minute.
        RemainingTime -= 60;
        *PreviousRemainingTime = RemainungTimeMsecInThisPhase;
        SetRemainingTime(RemainingTime);
    }
}