//////////////////////////////////////////////////////////////////////////////
//
// MAIN.C / ChkDskW
//
//  Microsoft Confidential
//  Copyright (c) Microsoft Corporation 1998
//  All rights reserved
//
//  8/98 - Jason Cohen (JCOHEN)
//
//////////////////////////////////////////////////////////////////////////////


// Include file(s).
//
#include "main.h"
#include "fmifs.h"

// Internal global variable(s).
//
static BOOL     g_bSuccess;
static HWND     g_hProgressDlg;
static HICON    g_hIconScan[3];


// External global variable(s).
//
extern HINSTANCE    g_hInstance;
extern DWORD        g_dwFlags;


// Internal function prototype(s).
//
static DWORD WINAPI     ThreadChkdsk(LPVOID);
static BOOL CALLBACK    FmifsCallback(FMIFS_PACKET_TYPE, ULONG, PVOID);
static VOID             UpdateStatus(LPSTR);
static VOID             SummaryDialog(HWND, LPTSTR, BOOL);
static INT_PTR CALLBACK SummaryProc(HWND, UINT, WPARAM, LPARAM);
static BOOL             Summary_OnInitDialog(HWND, HWND, LPARAM);
static FARPROC          LoadDllFunction(LPTSTR, LPCSTR, HINSTANCE *);


HANDLE SpawnChkdsk(HWND hDlg, DWORD dwDrives)
{
    DWORD   dwThreadId;

    // Set that we are scanning a drive currently.
    //
    g_dwFlags |= SCANDISK_SCANNING;
    g_dwFlags &= ~SCANDISK_CANCELSCAN;

    // Set up the global variables needed.
    //
    g_bSuccess = FALSE;
    g_hProgressDlg = hDlg;

    // Load the icons for the scanning animation.
    //
    g_hIconScan[0] = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_SCAN1));
    g_hIconScan[1] = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_SCAN2));
    g_hIconScan[2] = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_SCAN3));

    // Create the thread that will run chkdsk.
    //
    return CreateThread(NULL, 0, ThreadChkdsk, (LPVOID)(ULONG_PTR)dwDrives, 0, &dwThreadId);
}


static DWORD WINAPI ThreadChkdsk(LPVOID dwDrives)
{
    HINSTANCE   hFmifsDll;
    FARPROC     Chkdsk;
    WCHAR       szwFileSystem[16],
                szwDrive[] = L"A:\\";
    TCHAR       szBuffer[256];
    INT         nCount,
                nIndex;
    LPINT       lpnSelected,
                lpnIndex;
    LPTSTR      lpBuffer,
                lpCaption,
                lpCapPre;
    DWORD_PTR   dwMask;
    BOOL        bContinue,
                bSurface,
                bFix,
                bAllHappy = FALSE;

    // Load and run the Chkdsk function.
    //
    if ( (Chkdsk = LoadDllFunction(_T("FMIFS.DLL"), "Chkdsk", &hFmifsDll)) == NULL )
        return 0;

    // Get the number of selected items and allocate a buffer to hold all the indexs.
    //
    if ( ( (nCount = (INT)SendDlgItemMessage(g_hProgressDlg, IDC_DRIVES, LB_GETSELCOUNT, 0, 0L)) > 0 ) &&
         ( lpnSelected = (LPINT) MALLOC(nCount * sizeof(INT)) ) )
    {
        // Now get the list of selected items.
        //
        if ( (nCount = (INT)SendDlgItemMessage(g_hProgressDlg, IDC_DRIVES, LB_GETSELITEMS, nCount, (LPARAM) lpnSelected)) > 0 )
        {
            // Disable the controls.
            //
            EnableWindow(GetDlgItem(g_hProgressDlg, IDOK), FALSE);
            EnableWindow(GetDlgItem(g_hProgressDlg, IDC_DRIVESTEXT), FALSE);
            EnableWindow(GetDlgItem(g_hProgressDlg, IDC_DRIVES), FALSE);
            EnableWindow(GetDlgItem(g_hProgressDlg, IDC_SURFACE), FALSE);
            EnableWindow(GetDlgItem(g_hProgressDlg, IDC_AUTOFIX), FALSE);

            // Change the text of the IDCANCEL button to Cancel (from Close).
            //
            if ( lpBuffer = AllocateString(NULL, IDS_CANCEL) )
            {
                SetDlgItemText(g_hProgressDlg, IDCANCEL, lpBuffer);
                FREE(lpBuffer);
            }

            // Get the scan options.
            //
            bSurface = IsDlgButtonChecked(g_hProgressDlg, IDC_SURFACE);
            bFix = IsDlgButtonChecked(g_hProgressDlg, IDC_AUTOFIX);

            // Get the caption prefix.
            //
            lpCapPre = AllocateString(NULL, IDS_RESULTS);

            // Loop through all the drives in the list box to see if they
            // are selected.
            //
            lpnIndex = lpnSelected;
            nIndex = 0;
            bAllHappy = TRUE;
            for (dwMask = 1; ((DWORD_PTR) dwDrives & ~(dwMask - 1)) && ( !(g_dwFlags & SCANDISK_CANCELSCAN) ); dwMask <<= 1)
            {
                // Is this drive in the list box.
                //
                if ( (DWORD_PTR) dwDrives & dwMask )
                {
                    // Test to see if this item is the
                    // next selected one.
                    //
                    if ( *lpnIndex == nIndex )
                    {
                        //
                        // Ok, try and run chkdsk on this drive.
                        //

                        // Get the file system type.
                        //
                        bContinue = TRUE;
                        while ( bContinue && !GetVolumeInformationW(szwDrive, NULL, 0, NULL, NULL, NULL, szwFileSystem, sizeof(szwFileSystem)) )
                        {
                            bContinue = FALSE;
                            if ( ( GetLastError() == ERROR_NOT_READY ) &&
                                 ( lpBuffer = AllocateString(NULL, IDS_NOTREADY) ) )
                            {
                                if ( MessageBox(g_hProgressDlg, lpBuffer, NULL, MB_RETRYCANCEL | MB_ICONERROR) == IDRETRY )
                                    bContinue = TRUE;
                                FREE(lpBuffer);
                            }
                        }

                        if ( bContinue )
                        {                       
                            // Now finally launch Chkdsk.
                            //
                            Chkdsk(szwDrive, szwFileSystem, FALSE, FALSE, FALSE, bSurface, NULL, FALSE, FmifsCallback);

                            // Make sure the user didn't cancel.
                            //
                            if ( !(g_dwFlags & SCANDISK_CANCELSCAN) )
                            {
                                // Check to see if the scan on this drive returned with errors or not.
                                //
                                if ( g_bSuccess )
                                {
                                    // Get the text to use as the caption for the summary box.
                                    //
                                    if (lpCapPre)
                                        lstrcpy(szBuffer, lpCapPre);
                                    else
                                        szBuffer[0] = _T('\0');
                                    if ( SendDlgItemMessage(g_hProgressDlg, IDC_DRIVES, LB_GETTEXT, *lpnIndex, (LPARAM) szBuffer + (lstrlen(szBuffer) * sizeof(TCHAR))) > 0 )
                                        lpCaption = szBuffer;
                                    else
                                        lpCaption = NULL;

                                    // Display the summary message for this drive.
                                    //
                                    SummaryDialog(g_hProgressDlg, NULL, g_bSuccess);

                                }
                                else
                                {
                                    bAllHappy = FALSE;

                                    // If there were errors on this drive and the user
                                    // wants to automatically fix them, we need to run
                                    // the check disk function again with the fix error
                                    // flag set (/F).  We don't do this first because if
                                    // the drive can't be locked it won't even scan the
                                    // drive to see if there is an error before asking to
                                    // check on reboot.
                                    //
                                    if ( bFix )
                                        Chkdsk(szwDrive, szwFileSystem, TRUE, FALSE, FALSE, bSurface, NULL, FALSE, FmifsCallback);
                                }
                            }

                            // Make sure the memory for the summary dialog is freed.
                            //
                            SummaryDialog(NULL, NULL, FALSE);

                            // Reset the progress control.
                            //
                            SendDlgItemMessage(g_hProgressDlg, IDC_PROGRESS, PBM_SETPOS, 0, 0L);
                            SetDlgItemText(g_hProgressDlg, IDC_STATUS, NULLSTR);
                        }
                        lpnIndex++;
                    }
                    // Keep an index of what list box
                    // item this should be.
                    //
                    nIndex++;
                }

                // Go look at the next drive
                //
                szwDrive[0]++;
            }

            // Free the caption prefix.
            //
            FREE(lpCapPre);

            // Renable the controls.
            //
            EnableWindow(GetDlgItem(g_hProgressDlg, IDOK), TRUE);
            EnableWindow(GetDlgItem(g_hProgressDlg, IDC_DRIVES), TRUE);
            EnableWindow(GetDlgItem(g_hProgressDlg, IDC_DRIVESTEXT), TRUE);
            EnableWindow(GetDlgItem(g_hProgressDlg, IDC_SURFACE), TRUE);
            EnableWindow(GetDlgItem(g_hProgressDlg, IDC_AUTOFIX), TRUE);

            // Change the text of the IDCANCEL button back to Close.
            //
            if ( lpBuffer = AllocateString(NULL, IDS_CLOSE) )
            {
                SetDlgItemText(g_hProgressDlg, IDCANCEL, lpBuffer);
                FREE(lpBuffer);
            }
        }

        FREE(lpnSelected);
    }

    // Free the DLL library.
    //
    FreeLibrary(hFmifsDll);

    // Reset the scaning bit.
    //
    g_dwFlags &= ~SCANDISK_SCANNING;

    // If we are in SAGERUN mode, end the dialog now that
    // we are finished scanning.
    //
    if ( g_dwFlags & SCANDISK_SAGERUN )
        EndDialog(g_hProgressDlg, 0);

    // Return the success or failure.
    //
    return (DWORD) bAllHappy;
}




static BOOL CALLBACK FmifsCallback(FMIFS_PACKET_TYPE PacketType, ULONG PacketLength, PVOID PacketData)
{

#ifdef _DEBUG
    DWORD   dwBytes;
    TCHAR   szDebug[1024] = NULLSTR;
    HANDLE  hFile = CreateFile(_T("C:\\SCANDISK.LOG"), GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

    switch (PacketType)
    {
        case FmIfsPercentCompleted:
            wsprintf(szDebug, _T("DEBUG: FmifsCallback() - FmIfsPercentCompleted (%d%%)\r\n"), ((PFMIFS_PERCENT_COMPLETE_INFORMATION) PacketData)->PercentCompleted);
            break;
        case FmIfsFormatReport:
            wsprintf(szDebug, _T("DEBUG: FmifsCallback() - FmIfsFormatReport\r\n"));
            break;
        case FmIfsInsertDisk:
            wsprintf(szDebug, _T("DEBUG: FmifsCallback() - FmIfsInsertDisk\r\n"));
            break;
        case FmIfsIncompatibleFileSystem:
            wsprintf(szDebug, _T("DEBUG: FmifsCallback() - FmIfsIncompatibleFileSystem\r\n"));
            break;
        case FmIfsFormattingDestination:
            wsprintf(szDebug, _T("DEBUG: FmifsCallback() - FmIfsFormattingDestination\r\n"));
            break;
        case FmIfsIncompatibleMedia:
            wsprintf(szDebug, _T("DEBUG: FmifsCallback() - FmIfsIncompatibleMedia\r\n"));
            break;
        case FmIfsAccessDenied:
            wsprintf(szDebug, _T("DEBUG: FmifsCallback() - FmIfsAccessDenied\r\n"));
            break;
        case FmIfsMediaWriteProtected:
            wsprintf(szDebug, _T("DEBUG: FmifsCallback() - FmIfsMediaWriteProtected\r\n"));
            break;
        case FmIfsCantLock:
            wsprintf(szDebug, _T("DEBUG: FmifsCallback() - FmIfsCantLock\r\n"));
            break;
        case FmIfsCantQuickFormat:
            wsprintf(szDebug, _T("DEBUG: FmifsCallback() - FmIfsCantQuickFormat\r\n"));
            break;
        case FmIfsIoError:
            wsprintf(szDebug, _T("DEBUG: FmifsCallback() - FmIfsIoError\r\n"));
            break;
        case FmIfsFinished:
            wsprintf(szDebug, _T("DEBUG: FmifsCallback() - FmIfsFinished (%s)\r\n"), ((PFMIFS_FINISHED_INFORMATION) PacketData)->Success ? _T("TRUE") : _T("FALSE"));
            break;
        case FmIfsBadLabel:
            wsprintf(szDebug, _T("DEBUG: FmifsCallback() - FmIfsBadLabel\r\n"));
            break;
        case FmIfsCheckOnReboot:
            wsprintf(szDebug, _T("DEBUG: FmifsCallback() - FmIfsCheckOnReboot\r\n"));
            break;
        case FmIfsTextMessage:
            break;
        case FmIfsHiddenStatus:
            wsprintf(szDebug, _T("DEBUG: FmifsCallback() - FmIfsHiddenStatus\r\n"));
            break;
        case FmIfsClusterSizeTooSmall:
            wsprintf(szDebug, _T("DEBUG: FmifsCallback() - FmIfsClusterSizeTooSmall\r\n"));
            break;
        case FmIfsClusterSizeTooBig:
            wsprintf(szDebug, _T("DEBUG: FmifsCallback() - FmIfsClusterSizeTooBig\r\n"));
            break;
        case FmIfsVolumeTooSmall:
            wsprintf(szDebug, _T("DEBUG: FmifsCallback() - FmIfsVolumeTooSmall\r\n"));
            break;
        case FmIfsVolumeTooBig:
            wsprintf(szDebug, _T("DEBUG: FmifsCallback() - FmIfsVolumeTooBig\r\n"));
            break;
        case FmIfsNoMediaInDevice:
            wsprintf(szDebug, _T("DEBUG: FmifsCallback() - FmIfsNoMediaInDevice\r\n"));
            break;
    }

    if ( hFile != INVALID_HANDLE_VALUE )
    {
        if ( szDebug[0] )
        {
            SetFilePointer(hFile, 0, 0, FILE_END);
            WriteFile(hFile, szDebug, lstrlen(szDebug) * sizeof(TCHAR), &dwBytes, NULL);
        }
        CloseHandle(hFile);
    }
#endif // _DEBUG

    switch (PacketType)
    {
        case FmIfsPercentCompleted:
            // Advance the current position of the progress bar
            // to the percent returned.
            //
            SendDlgItemMessage(g_hProgressDlg, IDC_PROGRESS, PBM_SETPOS, ((PFMIFS_PERCENT_COMPLETE_INFORMATION) PacketData)->PercentCompleted, 0L);
            SendDlgItemMessage(g_hProgressDlg, IDC_SCANDISK, STM_SETIMAGE, IMAGE_ICON, (LPARAM) g_hIconScan[((PFMIFS_PERCENT_COMPLETE_INFORMATION) PacketData)->PercentCompleted % 3]);
            break;

        case FmIfsFinished:
            g_bSuccess = !((PFMIFS_FINISHED_INFORMATION) PacketData)->Success;
            break;

        case FmIfsTextMessage:
            UpdateStatus(((PFMIFS_TEXT_MESSAGE) PacketData)->Message);
            break;

        case FmIfsCheckOnReboot:
            ((PFMIFS_CHECKONREBOOT_INFORMATION) PacketData)->QueryResult =
                            (MessageBox(g_hProgressDlg, _T("This drive contains errors and must be checked on startup.\n\n")
                                                        _T("Do you want this drive to be checked next time you restart you computer?"), _T("Scandisk"), MB_YESNO | MB_ICONERROR) == IDYES);
            break;
    }

    return ( !(g_dwFlags & SCANDISK_CANCELSCAN) );
}


static VOID UpdateStatus(LPSTR lpText)
{
    TCHAR   szTextOut[256];
    LPTSTR  lpChkDsk    = NULL,
            lpScanDisk  = NULL,
            lpSearch,
            lpCopy,
            lpNewText;
    DWORD   dwLen;
    HANDLE  hFile = INVALID_HANDLE_VALUE;
#ifdef _UNICODE
    TCHAR   wcHeader = 0x0000;
    TCHAR   szNewText[256];
#endif // _UNICODE

#ifdef _UNICODE
    if ( MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, lpText, -1, szNewText, sizeof(szNewText)) )
    {
        // If this is unicode, make sure the poitner passed in points to a unicode string.
        //
        lpNewText = szNewText;

        // See if we need to write the header bit so that notepad will
        // see the file as unicode.
        //
        if ( (hFile = CreateFile(_T("C:\\SCANDISK.LOG"), GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE )
            wcHeader = 0xFEFF;
#else // _UNICODE
        lpNewText = lpText;
#endif // _UNICODE

        // Write to the log file.
        //
        if ( ( hFile != INVALID_HANDLE_VALUE ) ||
             ( (hFile = CreateFile(_T("C:\\SCANDISK.LOG"), GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)) != INVALID_HANDLE_VALUE ) )
        {
#ifdef _UNICODE
            if ( wcHeader )
                WriteFile(hFile, &wcHeader, sizeof(WCHAR), &dwLen, NULL);
#endif // _UNICODE
            SetFilePointer(hFile, 0, 0, FILE_END);
            WriteFile(hFile, (LPTSTR) lpNewText, lstrlen((LPTSTR) lpNewText) * sizeof(TCHAR), &dwLen, NULL);
            CloseHandle(hFile);
        }

        // Remove proceeding characters we don't want (space, \r, \n, \t).
        //
        for (lpSearch = (LPTSTR) lpNewText; (*lpSearch == _T(' ')) || (*lpSearch == _T('\r')) || (*lpSearch == _T('\n')) || (*lpSearch == _T('\t')); lpSearch++);

        if ( ISNUM(*lpSearch) )
        {
            // We want this info for the summary page.
            //
            SummaryDialog(g_hProgressDlg, lpSearch, FALSE);
        }
        else
        {
            if ( ( lpChkDsk = AllocateString(NULL, IDS_CHKDSK) ) &&
                 ( lpScanDisk = AllocateString(NULL, IDS_SCANDISK) ) )
            {
                dwLen = lstrlen(lpChkDsk);
                lpCopy = szTextOut;
                while ( (*lpSearch != _T('\0')) && (*lpSearch != _T('\r')) )
                {
                    if ( _tcsncmp(lpSearch, lpChkDsk, dwLen) == 0 )
                    {
                        lstrcpy(lpCopy, lpScanDisk);
                        lpSearch += dwLen;
                        lpCopy += lstrlen(lpScanDisk);
                    }
                    else
                        *lpCopy++ = *lpSearch++;
                }
                *lpCopy = _T('\0');
                SetDlgItemText(g_hProgressDlg, IDC_STATUS, szTextOut);
            }           
            FREE(lpScanDisk);
            FREE(lpChkDsk);
        }
#ifdef _UNICODE
    }
#endif // _UNICODE
}


static VOID SummaryDialog(HWND hWndParent, LPTSTR lpText, BOOL bSuccess)
{
    static LPTSTR   lpSumText[16];
    static DWORD    dwIndex = 0;

    LPTSTR          lpSearch;

    // First check to make sure lpText is a valid pointer.  If it is NULL
    // then we must be showing the summary dialog and/or freeing the memory.
    //
    if ( lpText )
    {
        // Make sure we don't already have 16 strings in our buffer.
        //
        if ( dwIndex < 16 )
        {
            //
            // lpText should already point to the first digit of the number
            // part of the summary.
            //

            // We need a pointer to the text after the number.  We will search
            // for the first space, which should divide the number and the text.
            //
            for (lpSearch = lpText; (*lpSearch) && (*lpSearch != _T(' ')); lpSearch++);

            // Now that we know where the number ends, we can allocate a buffer for it
            // and copy the number into it.
            //
            if ( lpSumText[dwIndex++] = (LPTSTR) MALLOC((size_t)((lpSearch - lpText + 1) * sizeof(TCHAR))) )
                lstrcpyn(lpSumText[dwIndex - 1], lpText, (size_t)(lpSearch - lpText + 1));

            // We should advance lpSearch to point to the text description, because now
            // it should point to a space (unless we hit the end of the string before the space).
            //
            if ( *lpSearch )
                lpSearch++;

            // Now we need to know where the text description ends.  We just want to search
            // for the first new line or line feed character.
            //
            for (lpText = lpSearch; (*lpSearch) && (*lpSearch != _T('\r')) && (*lpSearch != _T('\n')); lpSearch++);

            // Now that we know where the text ends, we can allocate a buffer for it
            // also and copy the text into it.
            //
            if ( lpSumText[dwIndex++] = (LPTSTR) MALLOC((size_t)((lpSearch - lpText + 1) * sizeof(TCHAR))) )
                lstrcpyn(lpSumText[dwIndex - 1], lpText, (size_t)(lpSearch - lpText + 1));
        }
    }
    else
    {
        // We check to make sure dwIndex still isn't zero, because if it is,
        // there is no text to display in the summary dialog or to free.
        //
        if ( dwIndex > 0 )
        {
            // If the hwnd is valid and the text is null, then we are
            // going to display the summary box.
            //
            if ( hWndParent )
                DialogBoxParam(g_hInstance, MAKEINTRESOURCE(IDD_SUMMARY), hWndParent, SummaryProc, (LPARAM) lpSumText);

            //
            // Now free the memory because eaither a NULL was passed in for the hwnd
            // or we already displayed the dialog and now the memory needs to be freed.
            //
            
            // Loop through all the strings that may have
            // been allocated by going back from where the index
            // is now.
            //
            // Note that some of the pointers may contain NULL
            // if a malloc failed, but the FREE() macro will check
            // with that before freeing the memory.
            //
            while ( dwIndex-- > 0 )
                FREE(lpSumText[dwIndex]);

            // Reset the index so that it doesn't get messed up
            // the next time we display a summary.
            //
            dwIndex = 0;
        }
    }
}


static INT_PTR CALLBACK SummaryProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        HANDLE_MSG(hDlg, WM_INITDIALOG, Summary_OnInitDialog);
        case WM_COMMAND:
            if ( (INT) LOWORD(wParam) != IDOK )
                return TRUE;
        case WM_CLOSE:
            EndDialog(hDlg, 0);
            return 0;
    }
    return FALSE;
}


static BOOL Summary_OnInitDialog(HWND hDlg, HWND hwndFocus, LPARAM lParam)
{
    INT nSumId[] =
    {
        IDC_SUM1A, IDC_SUM1B, IDC_SUM2A, IDC_SUM2B,
        IDC_SUM3A, IDC_SUM3B, IDC_SUM4A, IDC_SUM4B,
        IDC_SUM5A, IDC_SUM5B, IDC_SUM6A, IDC_SUM6B,
        IDC_SUM7A, IDC_SUM7B, IDC_SUM8A, IDC_SUM8B
    };

    LPTSTR  *lpStrings = (LPTSTR *) lParam;
    DWORD   dwIndex;

    for (dwIndex = 0; dwIndex < 16; dwIndex++)
        SetDlgItemText(hDlg, nSumId[dwIndex], *(lpStrings++));

    return FALSE;
}


static FARPROC LoadDllFunction(LPTSTR lpDll, LPCSTR lpFunction, HINSTANCE * lphDll)
{
    FARPROC hFunc = NULL;

    if ( (*lphDll) = LoadLibrary(lpDll) )
    {
        if ( (hFunc = GetProcAddress(*lphDll, lpFunction)) == NULL )
        {
            FreeLibrary(*lphDll);
            *lphDll = NULL;
        }
    }
    else
        *lphDll = NULL;

    return hFunc;
}