/*
 * Copyright (C) 1998  Transarc Corporation.
 * All rights reserved.
 *
 */


/*
 * INCLUDES ___________________________________________________________________
 *
 */
#include "afscfg.h"		// Main header for this application
#include "resource.h"
#include "salvage_results_dlg.h"
extern "C" {
#include "afs_bosAdmin.h"
}


/*
 * DEFINITIONS _________________________________________________________________
 *
 */
static const int     UPDATE_INTERVAL        = 5000;             // milleseconds
static const int     LOG_BUF_SIZE           = 25000;            // bytes
static const int     EDIT_CONTROL_MAX_CHARS = 64000;            // Max chars an edit control can hold
static const char   *SALVAGE_LOG_FILE_NAME  = "SalvageLog.txt";

static rwWindowData arwDialog[] = {
	{ IDC_LOG,				raSizeX | raSizeY, MAKELONG(350, 150) },
	{ IDC_CLOSE,			raMoveX | raMoveY },
	{ 9,					raMoveX | raMoveY },
	{ idENDLIST,			0 }
};


/*
 * Variables _________________________________________________________________
 *
 */
static HWND hDlg = 0;						// HWND for this page's dialog
static HWND hLogo;
static afs_status_t nStatus;
static void *hServer;
static int nResult; 
static BOOL bSalvageComplete;


/*
 * PROTOTYPES _________________________________________________________________
 *
 */
static void OnInitDialog(HWND hwndDlg);
static void OnClose();
static void SetMessages(int nStatusMsgID, int nLogMsgID);
static DWORD WINAPI ShowResults(LPVOID param);
static void SaveLogToDisk(char *pszLogBuf, char *pszFileName);
static char *AddCarriageReturnsToLog(char *pszInBuf, char *&pszOutBuf);
static char *GetMaxPartOfLogWeCanShow(char *pszLogBuf);
static BOOL AllocMemory(char *&pszLogBuf, int nBufSize);

BOOL CALLBACK SalvageResultsDlgProc(HWND hRHS, UINT msg, WPARAM wp, LPARAM lp);


/*
 * EXPORTED FUNCTIONS _________________________________________________________
 *
 */
BOOL ShowSalvageResults(HWND hParent)
{	
	int nResult = ModalDialog(IDD_SALVAGE_RESULTS, hParent, (DLGPROC)SalvageResultsDlgProc);

	return (nResult == IDOK);
}


/*
 * Dialog Proc _________________________________________________________________
 *
 */
BOOL CALLBACK SalvageResultsDlgProc(HWND hwndDlg, UINT msg, WPARAM wp, LPARAM lp)
{
	if (AfsAppLib_HandleHelp(IDD_SALVAGE_RESULTS, hwndDlg, msg, wp, lp))
		return TRUE;

	switch (msg) {
		case WM_INITDIALOG:
			OnInitDialog(hwndDlg);
			break;

		case WM_COMMAND:
			switch (LOWORD(wp)) {
				case IDC_CLOSE:
                    OnClose();
					break;

                case IDCANCEL:
                    if (bSalvageComplete)
                        OnClose();
			}
		    break;

		case WM_SIZE:	
			if (lp != 0)
				ResizeWindow(hwndDlg, arwDialog, rwaFixupGuts);
			break;
	}

    return FALSE;
}


/*
 * STATIC FUNCTIONS _________________________________________________________________
 *
 */

/*
 * Event Handler Functions __________________________________________________________
 *
 */
static void OnInitDialog(HWND hwndDlg)
{
	hDlg = hwndDlg;

    bSalvageComplete = FALSE;

    ResizeWindow(hDlg, arwDialog, rwaFixupGuts);

    hLogo = GetDlgItem(hDlg, IDC_LOGO);

    AfsAppLib_StartAnimation(hLogo, 8);

    SetMessages(IDS_SALVAGING, IDS_CURRENT_SALVAGE_LOG);

	nResult = bos_ServerOpen(g_hCell, GetHostnameA(), &hServer, &nStatus);
	if (!nResult) {
		ShowError(hDlg, nStatus, IDS_BOS_OPEN_FAILED);
		return;
	}

    // Remove the start menu - we do this so the user can't close
    // the dialog while salvage is being performed.
    DWORD dw;
    dw = GetWindowLong(hDlg, GWL_STYLE);
    dw &= ~WS_SYSMENU;
    SetWindowLong(hDlg, GWL_STYLE, dw);

	// Create a thread to keep the view of the log up to date
	DWORD dwThreadID;
	HANDLE hThread = CreateThread(0, 0, ShowResults, 0, 0, &dwThreadID);
	CloseHandle(hThread);
}

static void OnClose()
{
	bos_ServerClose(hServer, &nStatus);

    EndDialog(hDlg, IDOK);
}


/*
 * Utility Functions _________________________________________________________________
 *
 */
static void SetMessages(int nStatusMsgID, int nLogMsgID)
{
    TCHAR szMsgBuf[cchRESOURCE];

    SetDlgItemText(hDlg, IDC_SALVAGE_STATUS, GetResString(nStatusMsgID, szMsgBuf));
    SetDlgItemText(hDlg, IDC_LOG_TITLE, GetResString(nLogMsgID, szMsgBuf));
}

static char *AddCarriageReturnsToLog(char *pszInBuf, char *& pszOutBuf)
{
    ASSERT(pszInBuf);

    char *pInBuf = pszInBuf;

    // How man new lines are there?
    int nNumNLs = 0;

    while (*pInBuf) {
        if (*pInBuf == '\n')
            nNumNLs++;
		pInBuf++;
    }

    // Allocate enough memory for the log buffer plus CRs plus a NULL
    if (!AllocMemory(pszOutBuf, strlen(pszInBuf) + nNumNLs + 1))
        return 0;

    // Copy over the whole buffer, adding \r's as we go.  The edit control
    // seems to need \r before \n, so we set it up that way.
    pInBuf = pszInBuf;
    char *pOutBuf = pszOutBuf;

    while (*pInBuf) {
        if (*pInBuf == '\n') {
            *pOutBuf++ = '\r';
            *pOutBuf++ = '\n';
        } else
            *pOutBuf++ = *pInBuf;
		
		pInBuf++;
    }

    *pOutBuf = 0;

    return pszOutBuf;
}

static char *GetMaxPartOfLogWeCanShow(char *pszLogBuf)
{
    ASSERT(pszLogBuf);

    // Find out how much of the log buffer is in use
    int nLen = strlen(pszLogBuf);

    if (nLen < EDIT_CONTROL_MAX_CHARS)
        return pszLogBuf;

    // Buffer is bigger than maximum, so find the last full line
    // under the limit and return it.
    char *psz = pszLogBuf + nLen - EDIT_CONTROL_MAX_CHARS;
    
    // Make sure we return the beginning of a line
    while (*psz && (*psz != '\n'))
        psz++;

	if (*psz == '\n')
		psz++;

    return psz;
}    

static BOOL AllocMemory(char *&pszLogBuf, int nBufSize)
{
    if (pszLogBuf)
        delete pszLogBuf;

    pszLogBuf = new char[nBufSize];

    if (!pszLogBuf) {
        g_LogFile.Write("Failed to allocate memory (%d bytes) for the salvage log buffer.\r\n", nBufSize);
        return FALSE;
    }

    return TRUE;
}

static void SaveLogToDisk(char *pszLogBuf, char *pszFileName)
{
    // If no log file was specified by the user, then do nothing
    if (!pszFileName[0])
        return;

	FILE *fp = fopen(pszFileName, "w");
	if (!fp) {
		ShowError(hDlg, 0, IDS_ERROR_SAVING_SALVAGE_LOG_TO_DISK);
		return;
	}

	fprintf(fp, "%s", pszLogBuf);

	fclose(fp);
}

static DWORD WINAPI ShowResults(LPVOID param)
{
    ULONG nBufSize = LOG_BUF_SIZE;
    char *pszLogBuf = 0;
    char *pszLogBufWithCRs = 0;
    DWORD dwWait = WAIT_TIMEOUT;
    DWORD rc = 1;
    ULONG nLogSize = LOG_BUF_SIZE;

    if (!AllocMemory(pszLogBuf, nBufSize))
        return 0;
    
    while (dwWait != WAIT_OBJECT_0) {
        // Wait for either the salvage thread to terminate or our update
        // interval to pass.
        dwWait = WaitForSingleObject(g_CfgData.hSalvageThread, UPDATE_INTERVAL);

        // In either case, update the log display for the user

        // Get the salvage log as it currently exists
getlog: nResult = bos_LogGet(hServer, "SalvageLog", &nLogSize, pszLogBuf, &nStatus);
        if (!nResult) {
            if (nStatus == ADMMOREDATA) {
                // If salvage isn't done, then get a bigger buffer than we need to
                // prevent future ADMMOREDATA errors.
                nBufSize = nLogSize;
                if (dwWait != WAIT_OBJECT_0) {
                    nBufSize += LOG_BUF_SIZE;
                    nLogSize = nBufSize;
                }
                if (AllocMemory(pszLogBuf, nBufSize))
                    goto getlog;
            }

            rc = 0;
            break;            
        }

        // NULL-terminate the log
        pszLogBuf[nLogSize] = 0;

        // The edit control that we use to show the log to the user needs \r\n at the end
        // of each line, but the salvage log only has \n.  We must replace the \n with
        // \r\n before showing the log.
        if (!AddCarriageReturnsToLog(pszLogBuf, pszLogBufWithCRs)) {
            rc = 0;
            break;
        }
        
        // The edit control has a limit to the number of characters it can hold.  If the log
        // is bigger than that limit, then we will show the maximum portion of the end of the
        // log that we can fit in the edit control.
        char *pszLogToShow = GetMaxPartOfLogWeCanShow(pszLogBufWithCRs);

        // Put it into the edit control so the user can see it
        SetDlgItemText(hDlg, IDC_LOG, A2S(pszLogToShow));
    }

    AfsAppLib_StopAnimation(hLogo);

    if (rc) {
        SetMessages(IDS_SALVAGE_COMPLETE, IDS_FINAL_SALVAGE_LOG);
		SaveLogToDisk(pszLogBufWithCRs, GetSalvageLogFileNameA());
        MsgBox(hDlg, IDS_SALVAGE_COMPLETE, GetAppTitleID(), MB_OK);
    } else {
        SetMessages(IDS_SALVAGING, IDS_CANT_GET_SALVAGE_LOG);
        ShowError(hDlg, nStatus, IDS_CANT_GET_SALVAGE_LOG);
    }

    CloseHandle(g_CfgData.hSalvageThread);

    if (pszLogBuf)
        delete pszLogBuf;

    if (pszLogBufWithCRs)
        delete pszLogBufWithCRs;

    SetEnable(hDlg, IDC_CLOSE);

    bSalvageComplete = TRUE;

    return rc;
}

