/*
 * CiderPress
 * Copyright (C) 2007 by faddenSoft, LLC.  All Rights Reserved.
 * See the file LICENSE for distribution terms.
 */
/*
 * Support for the "view files" dialog box.
 */
#include "stdafx.h"
#include "ViewFilesDialog.h"
#include "Main.h"
#include "Print.h"
#include "../util/UtilLib.h"


/*
 * ===========================================================================
 *      ViewFilesDialog
 * ===========================================================================
 */

static const UINT gFindReplaceID = RegisterWindowMessage(FINDMSGSTRING);

BEGIN_MESSAGE_MAP(ViewFilesDialog, CDialog)
    ON_WM_CREATE()
    ON_WM_DESTROY()
    ON_WM_SIZE()
    ON_WM_GETMINMAXINFO()
    ON_REGISTERED_MESSAGE(gFindReplaceID, OnFindDialogMessage)
    ON_COMMAND(IDC_FVIEW_NEXT, OnFviewNext)
    ON_COMMAND(IDC_FVIEW_PREV, OnFviewPrev)
    ON_COMMAND(IDC_FVIEW_FONT, OnFviewFont)
    ON_COMMAND(IDC_FVIEW_PRINT, OnFviewPrint)
    ON_COMMAND(IDC_FVIEW_FIND, OnFviewFind)
    ON_BN_CLICKED(IDC_FVIEW_DATA, OnFviewData)
    ON_BN_CLICKED(IDC_FVIEW_RSRC, OnFviewRsrc)
    ON_BN_CLICKED(IDC_FVIEW_CMMT, OnFviewCmmt)
    ON_COMMAND(IDC_FVIEW_FMT_BEST, OnFviewFmtBest)
    ON_COMMAND(IDC_FVIEW_FMT_HEX, OnFviewFmtHex)
    ON_COMMAND(IDC_FVIEW_FMT_RAW, OnFviewFmtRaw)
    ON_CBN_SELCHANGE(IDC_FVIEW_FORMATSEL, OnFormatSelChange)
    ON_COMMAND(IDHELP, OnHelp)
END_MESSAGE_MAP()

BOOL ViewFilesDialog::OnInitDialog(void)
{
    LOGD("Now in VFD OnInitDialog!");

    ASSERT(fpSelSet != NULL);

    /* delete dummy control and insert our own with modded styles */
    CRichEditCtrl* pEdit = (CRichEditCtrl*)GetDlgItem(IDC_FVIEW_EDITBOX);
    ASSERT(pEdit != NULL);
    CRect rect;
    pEdit->GetWindowRect(&rect);
    pEdit->DestroyWindow();
    ScreenToClient(&rect);

    DWORD styles = ES_MULTILINE | ES_AUTOVSCROLL |  ES_READONLY | 
                    WS_BORDER | WS_VSCROLL | WS_VISIBLE | WS_TABSTOP |
                    ES_NOHIDESEL;
    if (fNoWrapText)
        styles |= ES_AUTOHSCROLL | WS_HSCROLL;
    fEditCtrl.Create(styles, rect, this, IDC_FVIEW_EDITBOX);
    fEditCtrl.SetFocus();
    /*
     * HEY: I think we can do this with pEdit->ShowScrollBar(SB_BOTH) !!
     * Could also use GetWindowLong/SetWindowLong to change the window style;
     * probably need a SetWindowPos to cause changes to flush.
     */


    /*
     * We want to adjust the size of the window to match the last size used.
     * However, if we do this after creating the edit dialog but before it
     * actually has data to display, then when we stuff data into it the
     * scroll bar goodies are all out of whack.
     */
    fFirstResize = true;
#if 0
    const Preferences* pPreferences = GET_PREFERENCES();
    long width = pPreferences->GetFileViewerWidth();
    long height = pPreferences->GetFileViewerHeight();
    CRect fullRect;
    GetWindowRect(&fullRect);
    LOGI(" VFD pre-size %dx%d", fullRect.Width(), fullRect.Height());
    fullRect.right = fullRect.left + width;
    fullRect.bottom = fullRect.top + height;
    MoveWindow(fullRect, FALSE);
#endif

    // This invokes UpdateData, which calls DoDataExchange, which leads to
    // the StreamIn call.  So don't do this until everything else is ready.
    CDialog::OnInitDialog();

    LOGD("VFD OnInitDialog done");
    return FALSE;   // don't let Windows set the focus
}

int ViewFilesDialog::OnCreate(LPCREATESTRUCT lpcs)
{
    LOGD("VFD OnCreate");

    HICON hIcon;
    hIcon = ::AfxGetApp()->LoadIcon(IDI_FILE_VIEWER);
    SetIcon(hIcon, TRUE);

    GetClientRect(&fLastWinSize);

    CRect initRect(fLastWinSize);
    initRect.left = initRect.right - ::GetSystemMetrics(SM_CXVSCROLL);
    initRect.top  = initRect.bottom - ::GetSystemMetrics(SM_CYHSCROLL);
    fGripper.Create(WS_CHILD | WS_VISIBLE |
                    SBS_SIZEBOX | SBS_SIZEBOXBOTTOMRIGHTALIGN | SBS_SIZEGRIP,
        initRect, this, AFX_IDW_SIZE_BOX);

    LOGD("VFD OnCreate done");
    return 0;
}

void ViewFilesDialog::OnDestroy(void)
{
    Preferences* pPreferences = GET_PREFERENCES_WR();
    CRect rect;
    GetWindowRect(&rect);

    pPreferences->SetPrefLong(kPrFileViewerWidth, rect.Width());
    pPreferences->SetPrefLong(kPrFileViewerHeight, rect.Height());

    CDialog::OnDestroy();
}

void ViewFilesDialog::OnOK(void)
{
    if (fBusy)
        MessageBeep(-1);
    else {
        CRect rect;

        GetWindowRect(&rect);
        LOGD(" VFD size now %dx%d", rect.Width(), rect.Height());

        CDialog::OnOK();
    }
}

void ViewFilesDialog::OnCancel(void)
{
    if (fBusy)
        MessageBeep(-1);
    else
        CDialog::OnCancel();
}

void ViewFilesDialog::OnGetMinMaxInfo(MINMAXINFO* pMMI)
{
    float scaleFactor = GetDesktopScaleFactor();

    // 664x200 was determined empirically to fit all controls.  The initial
    // window size is set over in the Preferences constructor.
    pMMI->ptMinTrackSize.x = (int)(664 * scaleFactor);
    pMMI->ptMinTrackSize.y = (int)(200 * scaleFactor);
}

void ViewFilesDialog::OnSize(UINT nType, int cx, int cy)
{
    CDialog::OnSize(nType, cx, cy);

    //LOGI("Dialog: old size %d,%d",
    //  fLastWinSize.Width(), fLastWinSize.Height());
    LOGD("Dialog: new size %d,%d", cx, cy);

    if (fLastWinSize.Width() == cx && fLastWinSize.Height() == cy) {
        LOGD("VFD OnSize: no change");
        return;
    }

    int deltaX, deltaY;
    deltaX = cx - fLastWinSize.Width();
    deltaY = cy - fLastWinSize.Height();
    //LOGI("Delta is %d,%d", deltaX, deltaY);

    ShiftControls(deltaX, deltaY);

    GetClientRect(&fLastWinSize);
}

void ViewFilesDialog::ShiftControls(int deltaX, int deltaY)
{
    HDWP hdwp;

    /*
     * Use deferred reposn so that they don't end up drawing on top of each
     * other and getting all weird.
     *
     * IMPORTANT: the DeferWindowPos stuff changes the tab order of the
     * items in the window.  The controls must be added in the reverse
     * order in which they appear in the window.
     */
    hdwp = BeginDeferWindowPos(15);
    hdwp = MoveControl(hdwp, this, AFX_IDW_SIZE_BOX, deltaX, deltaY);
    hdwp = MoveControl(hdwp, this, IDHELP, deltaX, deltaY);
    hdwp = MoveControl(hdwp, this, IDC_FVIEW_FONT, deltaX, deltaY);
    hdwp = MoveControl(hdwp, this, IDC_FVIEW_PRINT, deltaX, deltaY);
    hdwp = MoveControl(hdwp, this, IDC_FVIEW_FIND, deltaX, deltaY);
    hdwp = MoveControl(hdwp, this, IDC_FVIEW_FMT_RAW, 0, deltaY);
    hdwp = MoveControl(hdwp, this, IDC_FVIEW_FMT_HEX, 0, deltaY);
    hdwp = MoveControl(hdwp, this, IDC_FVIEW_FMT_BEST, 0, deltaY);
    hdwp = MoveControl(hdwp, this, IDC_FVIEW_PREV, 0, deltaY);
    hdwp = MoveControl(hdwp, this, IDC_FVIEW_NEXT, 0, deltaY);
    hdwp = MoveControl(hdwp, this, IDC_FVIEW_CMMT, 0, deltaY);
    hdwp = MoveControl(hdwp, this, IDC_FVIEW_RSRC, 0, deltaY);
    hdwp = MoveControl(hdwp, this, IDC_FVIEW_DATA, 0, deltaY);
    hdwp = MoveStretchControl(hdwp, this, IDC_FVIEW_FORMATSEL, 0, deltaY, deltaX, 0);
    hdwp = StretchControl(hdwp, this, IDC_FVIEW_EDITBOX, deltaX, deltaY);
    hdwp = MoveControl(hdwp, this, IDOK, deltaX, deltaY);
    if (!EndDeferWindowPos(hdwp)) {
        LOGI("EndDeferWindowPos failed");
    }

    /*
     * Work around buggy CRichEdit controls.  The inner edit area is only
     * resized when the box is shrunk, not when it's expanded, and the
     * results are inconsistent between Win98 and Win2K.
     *
     * Set the internal size equal to the size of the entire edit box.
     * This should be large enough to make everything work right, but small
     * enough to avoid funky scrolling behavior.  (If you want to set this
     * more precisely, don't forget that scroll bars are not part of the
     * edit control client area, and their sizes must be factored in.)
     */
    CRect rect;
    CRichEditCtrl* pEdit = (CRichEditCtrl*) GetDlgItem(IDC_FVIEW_EDITBOX);
    ASSERT(pEdit != NULL);
    //pEdit->GetClientRect(&rect);
    pEdit->GetWindowRect(&rect);
    //GetClientRect(&rect);
    rect.left = 2;
    rect.top = 2;
    pEdit->SetRect(&rect);
}


void ViewFilesDialog::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);

    if (pDX->m_bSaveAndValidate) {
        LOGD("COPY OUT");
    } else {
        LOGD("COPY IN");
        OnFviewNext();
    }
}

static void DumpBitmapInfo(HBITMAP hBitmap)
{
    BITMAP info;
    CBitmap* pBitmap = CBitmap::FromHandle(hBitmap);
    int gotten;

    gotten = pBitmap->GetObject(sizeof(info), &info);

    LOGD("DumpBitmapInfo: gotten=%d of %d", gotten, sizeof(info));
    LOGD("  bmType = %d", info.bmType);
    LOGD("  bmWidth=%d, bmHeight=%d", info.bmWidth, info.bmHeight);
    LOGD("  bmWidthBytes=%d", info.bmWidthBytes);
    LOGD("  bmPlanes=%d", info.bmPlanes);
    LOGD("  bmBitsPixel=%d", info.bmBitsPixel);
    LOGD("  bmPits = 0x%p", info.bmBits);
}

bool ViewFilesDialog::IsSourceEmpty(const GenericEntry* pEntry,
    ReformatHolder::ReformatPart part)
{
    switch (part) {
    case ReformatHolder::ReformatPart::kPartData:
        return pEntry->GetDataForkLen() == 0;
    case ReformatHolder::ReformatPart::kPartRsrc:
        return pEntry->GetRsrcForkLen() == 0;
    case ReformatHolder::ReformatPart::kPartCmmt:
        return !pEntry->GetHasNonEmptyComment();
    default:
        return false;
    }
}

void ViewFilesDialog::DisplayText(const WCHAR* fileName, bool zeroSourceLen)
{
    CWaitCursor wait;   // streaming of big files can take a little while
    bool errFlg;
    bool emptyFlg = false;
    bool editHadFocus = false;
    
    ASSERT(fpOutput != NULL);
    ASSERT(fileName != NULL);

    errFlg = fpOutput->GetOutputKind() == ReformatOutput::kOutputErrorMsg;

    ASSERT(fpOutput->GetOutputKind() != ReformatOutput::kOutputUnknown);

    CRichEditCtrl* pEdit = (CRichEditCtrl*) GetDlgItem(IDC_FVIEW_EDITBOX);
    ASSERT(pEdit != NULL);

    /* retain the selection even if we lose focus [can't do this in OnInit] */
    pEdit->SetOptions(ECOOP_OR, ECO_SAVESEL);

#if 0
    /*
     * Start by trashing anything that's there.  Not strictly necessary,
     * but it prevents the control from trying to maintain the old stuff
     * in an undo buffer.  (Not entirely sure if a stream-in operation is
     * undoable, but it costs very little to be certain.)
     *
     * UPDATE: I turned this off because it was dinging the speaker (?!).
     * Might be doing that because it's in read-only mode.
     */
    pEdit->SetSel(0, -1);
    pEdit->Clear();
    pEdit->EmptyUndoBuffer();
#endif

    /*
     * There's a redraw flash that goes away if you change the input
     * focus to something other than the edit ctrl.  (Move between large
     * files; it looks like you can see the text being selected and
     * hightlighted.  The control doesn't have an "always highlight" flag
     * set, so if the focus is on a different control it doesn't light up.)
     *
     * Since we're currently forcing the focus to be on the edit ctrl later
     * on, we just jam it to something else here.  If nothing has the focus,
     * as can happen if we click on "resource fork" and then Alt-N to a
     * file without a resource fork, we force the focus to return to the
     * edit window.
     *
     * NOTE: this would look a little better if we used the Prev/Next
     * buttons to hold the temporary focus, but we need to know which key
     * the user hit.  We could also create a bogus control, move it into
     * negative space where it will be invisible, and use that as a "focus
     * holder".
     *
     * TODO: on Win7 you can sometimes see a blue flash.  Not sure if it
     * relates to this or some other aspect of the redraw.
     */
    CWnd* pFocusWnd = GetFocus();
    if (pFocusWnd == NULL || pFocusWnd->m_hWnd == pEdit->m_hWnd) {
        editHadFocus = true;
        GetDlgItem(IDOK)->SetFocus();
    }

    /*
     * The text color isn't getting reset when we reload the control.  I
     * can't find a "set default text color" call, so I'm reformatting
     * part of the buffer.
     *
     * Here's the weird part: it doesn't seem to matter what color I
     * set it to under Win2K.  It reverts to black so long as I do anything
     * here.  Under Win98, it uses the new color.
     */
    //if (0)
    {
        CHARFORMAT cf;
        cf.cbSize = sizeof(CHARFORMAT);
        cf.dwMask = CFM_COLOR;
        cf.crTextColor = RGB(0, 0, 0);
        pEdit->SetSel(0, 1);    // must select at least one char
        pEdit->SetSelectionCharFormat(cf);
    }

    /*
     * Add the appropriate data.  If the "bitmap" flag is set, use the
     * MyDIBitmap pointer instead.
     */
    if (fpOutput->GetOutputKind() == ReformatOutput::kOutputBitmap) {
        CClientDC dcScreen(this);
        HBITMAP hBitmap;

        if (fpRichEditOle == NULL) {
            /* can't do this in OnInitDialog -- m_pWnd isn't initialized */
            fpRichEditOle = pEdit->GetIRichEditOle();
            ASSERT(fpRichEditOle != NULL);
        }

        //FILE* fp = fopen("C:/test/output.bmp", "wb");
        //if (fp != NULL) {
        //  pDib->WriteToFile(fp);
        //  fclose(fp);
        //}
        
        hBitmap = fpOutput->GetDIB()->ConvertToDDB(dcScreen.m_hDC);
        if (hBitmap == NULL) {
            LOGW("ConvertToDDB failed!");
            pEdit->SetWindowText(L"Internal error.");
            errFlg = true;
        } else {
            //DumpBitmapInfo(hBitmap);
            //DumpBitmapInfo(pDib->GetHandle());

            LOGD("Inserting bitmap");
            pEdit->SetWindowText(L"");
            CImageDataObject::InsertBitmap(fpRichEditOle, hBitmap);

            /* RichEditCtrl has it now */
            ::DeleteObject(hBitmap);
        }
    } else {
        /*
         * Stream the data in, using the appropriate format.  Since we don't
         * have the "replace selection" flag set, this replaces everything
         * that's currently in there.
         *
         * We can't use SetWindowText() unless we're willing to forgo viewing
         * of binary files in "raw" form.  There doesn't seem to be any other
         * difference between the two approaches.
         */
        const char* textBuf;
        long textLen;
        int streamFormat;

        textBuf = fpOutput->GetTextBuf();
        textLen = fpOutput->GetTextLen();
        streamFormat = SF_TEXT;
        if (fpOutput->GetOutputKind() == ReformatOutput::kOutputRTF)
            streamFormat = SF_RTF;
        if (fpOutput->GetTextLen() == 0) {
            if (zeroSourceLen) {
                textBuf = "(file is empty)";
                EnableFormatSelection(FALSE);
            } else {
                textBuf = "(converted output is empty)";
            }
            textLen = strlen(textBuf);
            emptyFlg = true;
        }
        if (fpOutput->GetOutputKind() == ReformatOutput::kOutputErrorMsg)
            EnableFormatSelection(FALSE);

        /* make sure the control will hold everything we throw at it */
        pEdit->LimitText(textLen+1);
        LOGI("Streaming %ld bytes (kind=%d)",
            textLen, fpOutput->GetOutputKind());

        /* clear this early to avoid loading onto yellow */
        if (errFlg)
            pEdit->SetBackgroundColor(FALSE, RGB(255, 255, 0));
        else if (emptyFlg)
            pEdit->SetBackgroundColor(FALSE, RGB(192, 192, 192));
        else
            pEdit->SetBackgroundColor(TRUE, 0);

        RichEditXfer xfer(textBuf, textLen);
        EDITSTREAM es;
        es.dwCookie = (DWORD) &xfer;
        es.dwError = 0;
        es.pfnCallback = RichEditXfer::EditStreamCallback;
        long count;
        count = pEdit->StreamIn(streamFormat, es);
        LOGI("StreamIn returned count=%ld dwError=%d", count, es.dwError);

        if (es.dwError != 0) {
            /* a -16 error can happen if the type is RTF but contents are not */
            char errorText[256];

            _snprintf(errorText, sizeof(errorText),
                "ERROR: failed while loading data (err=0x%08lx)\n"
                "(File contents might be too big for Windows to display)\n",
                es.dwError);
            RichEditXfer errXfer(errorText, strlen(errorText));
            es.dwCookie = (DWORD) &errXfer;
            es.dwError = 0;

            count = pEdit->StreamIn(SF_TEXT, es);
            LOGI("Error StreamIn returned count=%ld dwError=%d", count, es.dwError);

            errFlg = true;
        }

        //pEdit->SetSel(0, 0);
    }

    /* move us back to the top */
    pEdit->LineScroll(-pEdit->GetFirstVisibleLine());

    /* just in case it's trying to hold on to something */
    pEdit->EmptyUndoBuffer();

    /* work around bug that creates unnecessary scroll bars */
    pEdit->SetScrollRange(SB_VERT, 0, 0, TRUE);
    pEdit->SetScrollRange(SB_HORZ, 0, 0, TRUE);

    /* display the entire message in the user's selected font */
    if (!fpOutput->GetMultipleFontsFlag()) {
        // adjust the font, stripping default boldness from SF_TEXT
        NewFontSelected(fpOutput->GetOutputKind() != ReformatOutput::kOutputRTF);
    }

    /* enable/disable the scroll bars */
    //pEdit->EnableScrollBar(SB_BOTH, ESB_DISABLE_BOTH);

    if (errFlg)
        pEdit->SetBackgroundColor(FALSE, RGB(255, 255, 0));
    else if (emptyFlg)
        pEdit->SetBackgroundColor(FALSE, RGB(192, 192, 192));
    else
        pEdit->SetBackgroundColor(TRUE, 0);

    /*
     * Work around a Windows bug that prevents the scroll bars from
     * being displayed immediately.  This makes them appear, but the
     * vertical scroll bar comes out funky on short files (fixed with
     * the SetScrollRange call above).
     *
     * Best guess: when the edit box is resized, it chooses the scroll bar
     * configuration based on the currently-loaded data.  If you resize it
     * and *then* add data, you're stuck with the previous scroll bar
     * values.  This doesn't quite make sense though...
     *
     * This works:
     *  - Set up dialog.
     *  - Load data.
     *  - Do minor twiddling.
     *  - Resize box significantly.
     *
     * This works:
     *  - (box already has data in it)
     *  - Load new data.
     *  - Do minor twiddling.
     *
     * This doesn't:
     *  - Set up dialog
     *  - Resize box significantly.
     *  - Load data.
     *  - Do minor twiddling.
     *
     * There might be some first-time setup issues in here.  Hard to say.
     * Anything related to RichEdit controls is extremely fragile, and must
     * be tested with a variety of inputs, preference settings, and under
     * at least Win98 and Win2K (which are *very* different).
     *
     * TODO: re-evaluate all this without worrying about Win9x
     */
    if (fFirstResize) {
        /* adjust the size of the window to match the last size used */
        const Preferences* pPreferences = GET_PREFERENCES();
        long width = pPreferences->GetPrefLong(kPrFileViewerWidth);
        long height = pPreferences->GetPrefLong(kPrFileViewerHeight);
        CRect fullRect;
        GetWindowRect(&fullRect);
        //LOGI(" VFD pre-size %dx%d", fullRect.Width(), fullRect.Height());
        fullRect.right = fullRect.left + width;
        fullRect.bottom = fullRect.top + height;
        MoveWindow(fullRect, TRUE);

        editHadFocus = true;    // force focus on edit box

        fFirstResize = false;
    } else {
        /* this should be enough */
        ShiftControls(0, 0);
    }
        
    if (fpOutput->GetOutputKind() == ReformatOutput::kOutputBitmap) {
        /* get the cursor off of the image */
        pEdit->SetSel(-1, -1);

        /*
         * Tall Super Hi-Res graphics (e.g. Paintworks PNT) cause the edit
         * control to scroll to the bottom.  Move it back to the top.
         *
         * SetScrollInfo just moves the scrollbar without changing the
         * view position.
         */
        pEdit->SendMessage(WM_VSCROLL, SB_TOP, 0);
    }

    /*
     * We want the focus to be on the text window so keyboard selection
     * commands work.  However, it's also nice to be able to arrow through
     * the format selection box.
     */
    if (editHadFocus)
        pEdit->SetFocus();

    fTitle = fileName;
    //if (fpOutput->GetOutputKind() == ReformatOutput::kOutputText ||
    //  fpOutput->GetOutputKind() == ReformatOutput::kOutputRTF ||
    //  fpOutput->GetOutputKind() == ReformatOutput::kOutputCSV ||
    //  fpOutput->GetOutputKind() == ReformatOutput::kOutputBitmap ||
    //  fpOutput->GetOutputKind() == ReformatOutput::kOutputRaw)
    //{
        // not for error messages
        fTitle += _T(" [");
        fTitle += fpOutput->GetFormatDescr();
        fTitle += _T("]");
    //} else if (fpOutput->GetOutputKind() == ReformatOutput::kOutputRaw) {
    //  fTitle += _T(" [Raw]");
    //}

    CString winTitle = _T("File Viewer - ");
    winTitle += fTitle;
    SetWindowText(winTitle);

    /*
     * Enable or disable the next/prev buttons.
     */
    CButton* pButton;
    pButton = (CButton*) GetDlgItem(IDC_FVIEW_PREV);
    pButton->EnableWindow(fpSelSet->IterHasPrev());
    pButton = (CButton*) GetDlgItem(IDC_FVIEW_NEXT);
    pButton->EnableWindow(fpSelSet->IterHasNext());
}

void ViewFilesDialog::OnFviewNext(void)
{
    // handles "next" button; also called from DoDataExchange
    ReformatHolder::ReformatPart part;
    ReformatHolder::ReformatID id;
    int result;

    if (fBusy) {
        LOGI("BUSY!");
        return;
    }

    fBusy = true;

    if (!fpSelSet->IterHasNext()) {
        ASSERT(false);
        return;
    }

    /*
     * Get the pieces of the file.
     */
    SelectionEntry* pSelEntry = fpSelSet->IterNext();
    GenericEntry* pEntry = pSelEntry->GetEntry();
    result = ReformatPrep(pEntry);
#if 0
    {
        // for debugging -- simulate failure
        result = -1;
        delete fpHolder;
        fpHolder = NULL;
        delete fpOutput;
        fpOutput = NULL;
    }
#endif

    fBusy = false;
    if (result != 0) {
        ASSERT(fpHolder == NULL);
        ASSERT(fpOutput == NULL);
        return;
    }

    /*
     * Format a piece.
     */
    ConfigurePartButtons(pSelEntry->GetEntry());
    part = GetSelectedPart();
    id = ConfigureFormatSel(part);
    Reformat(pSelEntry->GetEntry(), part, id);

    DisplayText(pSelEntry->GetEntry()->GetDisplayName(),
        IsSourceEmpty(pSelEntry->GetEntry(), part));
}

void ViewFilesDialog::OnFviewPrev(void)
{
    ReformatHolder::ReformatPart part;
    ReformatHolder::ReformatID id;
    int result;

    if (fBusy) {
        LOGI("BUSY!");
        return;
    }

    fBusy = true;

    if (!fpSelSet->IterHasPrev()) {
        ASSERT(false);
        return;
    }

    /*
     * Get the pieces of the file.
     */
    SelectionEntry* pSelEntry = fpSelSet->IterPrev();
    GenericEntry* pEntry = pSelEntry->GetEntry();
    result = ReformatPrep(pEntry);

    fBusy = false;
    if (result != 0) {
        ASSERT(fpHolder == NULL);
        ASSERT(fpOutput == NULL);
        return;
    }

    /*
     * Format a piece.
     */
    ConfigurePartButtons(pEntry);
    part = GetSelectedPart();
    id = ConfigureFormatSel(part);
    Reformat(pEntry, part, id);

    DisplayText(pEntry->GetDisplayName(), IsSourceEmpty(pEntry, part));
}

void ViewFilesDialog::ConfigurePartButtons(const GenericEntry* pEntry)
{
    int id = 0;

    CButton* pDataWnd = (CButton*) GetDlgItem(IDC_FVIEW_DATA);
    CButton* pRsrcWnd = (CButton*) GetDlgItem(IDC_FVIEW_RSRC);
    CButton* pCmmtWnd = (CButton*) GetDlgItem(IDC_FVIEW_CMMT);
    ASSERT(pDataWnd != NULL && pRsrcWnd != NULL && pCmmtWnd != NULL);

    /* figure out what was checked before, ignoring if it's not available */
    if (pDataWnd->GetCheck() == BST_CHECKED && pEntry->GetHasDataFork())
        id = IDC_FVIEW_DATA;
    else if (pRsrcWnd->GetCheck() == BST_CHECKED && pEntry->GetHasRsrcFork())
        id = IDC_FVIEW_RSRC;
    else if (pCmmtWnd->GetCheck() == BST_CHECKED && pEntry->GetHasComment())
        id = IDC_FVIEW_CMMT;

    /* couldn't keep previous check, find a new one */
    if (id == 0) {
        if (pEntry->GetHasDataFork())
            id = IDC_FVIEW_DATA;
        else if (pEntry->GetHasRsrcFork())
            id = IDC_FVIEW_RSRC;
        else if (pEntry->GetHasComment())
            id = IDC_FVIEW_CMMT;
        // else leave it set to 0
    }

    /* set up the dialog */
    pDataWnd->SetCheck(BST_UNCHECKED);
    pRsrcWnd->SetCheck(BST_UNCHECKED);
    pCmmtWnd->SetCheck(BST_UNCHECKED);

    if (pEntry == NULL) {
        pDataWnd->EnableWindow(FALSE);
        pRsrcWnd->EnableWindow(FALSE);
        pCmmtWnd->EnableWindow(FALSE);
    } else {
        pDataWnd->EnableWindow(pEntry->GetHasDataFork());
        pRsrcWnd->EnableWindow(pEntry->GetHasRsrcFork());
        pCmmtWnd->EnableWindow(pEntry->GetHasComment());
    }

    if (id == IDC_FVIEW_RSRC)
        pRsrcWnd->SetCheck(BST_CHECKED);
    else if (id == IDC_FVIEW_CMMT)
        pCmmtWnd->SetCheck(BST_CHECKED);
    else
        pDataWnd->SetCheck(BST_CHECKED);
}

ReformatHolder::ReformatPart ViewFilesDialog::GetSelectedPart(void)
{
    CButton* pDataWnd = (CButton*) GetDlgItem(IDC_FVIEW_DATA);
    CButton* pRsrcWnd = (CButton*) GetDlgItem(IDC_FVIEW_RSRC);
    CButton* pCmmtWnd = (CButton*) GetDlgItem(IDC_FVIEW_CMMT);
    ASSERT(pDataWnd != NULL && pRsrcWnd != NULL && pCmmtWnd != NULL);

    if (pDataWnd->GetCheck() == BST_CHECKED)
        return ReformatHolder::kPartData;
    else if (pRsrcWnd->GetCheck() == BST_CHECKED)
        return ReformatHolder::kPartRsrc;
    else if (pCmmtWnd->GetCheck() == BST_CHECKED)
        return ReformatHolder::kPartCmmt;
    else {
        assert(false);
        return ReformatHolder::kPartData;
    }
}

int ViewFilesDialog::ReformatPrep(GenericEntry* pEntry)
{
    CWaitCursor waitc;      // can be slow reading data from floppy
    MainWindow* pMainWindow = GET_MAIN_WINDOW();
    int result;

    delete fpHolder;
    fpHolder = NULL;

    result = pMainWindow->GetFileParts(pEntry, &fpHolder);
    if (result != 0) {
        LOGI("GetFileParts(prev) failed!");
        ASSERT(fpHolder == NULL);
        return -1;
    }

    /* set up the ReformatHolder */
    MainWindow::ConfigureReformatFromPreferences(fpHolder);

    /* prep for applicability test */
    fpHolder->SetSourceAttributes(
        pEntry->GetFileType(),
        pEntry->GetAuxType(),
        MainWindow::ReformatterSourceFormat(pEntry->GetSourceFS()),
        pEntry->GetFileNameExtensionMOR());

    /* figure out which reformatters apply to this file */
    LOGD("Testing reformatters");
    fpHolder->TestApplicability();

    return 0;
}

int ViewFilesDialog::Reformat(const GenericEntry* pEntry,
    ReformatHolder::ReformatPart part, ReformatHolder::ReformatID id)
{
    CWaitCursor waitc;

    delete fpOutput;
    fpOutput = NULL;

    /* run the best one */
    fpOutput = fpHolder->Apply(part, id);

//bail:
    if (fpOutput != NULL) {
        // success -- do some sanity checks
        switch (fpOutput->GetOutputKind()) {
        case ReformatOutput::kOutputText:
        case ReformatOutput::kOutputRTF:
        case ReformatOutput::kOutputCSV:
        case ReformatOutput::kOutputErrorMsg:
            ASSERT(fpOutput->GetTextBuf() != NULL);
            ASSERT(fpOutput->GetDIB() == NULL);
            break;
        case ReformatOutput::kOutputBitmap:
            ASSERT(fpOutput->GetDIB() != NULL);
            ASSERT(fpOutput->GetTextBuf() == NULL);
            break;
        case ReformatOutput::kOutputRaw:
            // text buf might be NULL
            ASSERT(fpOutput->GetDIB() == NULL);
            break;
        }
        return 0;
    } else {
        /* shouldn't get here; handle it if we do */
        static const char* kFailMsg = "Internal error\r\n";
        fpOutput = new ReformatOutput;
        fpOutput->SetTextBuf((char*) kFailMsg, strlen(kFailMsg), false);
        fpOutput->SetOutputKind(ReformatOutput::kOutputErrorMsg);
        return -1;
    }
}

ReformatHolder::ReformatID ViewFilesDialog::ConfigureFormatSel(
    ReformatHolder::ReformatPart part)
{
    //ReformatHolder::ReformatID prevID = ReformatHolder::kReformatUnknown;
    ReformatHolder::ReformatID returnID = ReformatHolder::kReformatRaw;
    CComboBox* pCombo = (CComboBox*) GetDlgItem(IDC_FVIEW_FORMATSEL);

    LOGD("--- ConfigureFormatSel");

    //int sel;
    //sel = pCombo->GetCurSel();
    //if (sel != CB_ERR)
    //  prevID = (ReformatHolder::ReformatID) pCombo->GetItemData(sel);
    //LOGI("  prevID = %d", prevID);

    EnableFormatSelection(TRUE);
    pCombo->ResetContent();

    /*
     * Fill out the combo box with the reformatter entries.
     *
     * There's probably a way to do this that doesn't involve abusing
     * enums, but this'll do for now.
     */
    int applyIdx, idIdx;
    bool preferred = true;
    int comboIdx;
    for (applyIdx = ReformatHolder::kApplicMAX;
        applyIdx > ReformatHolder::kApplicNot; /*no incr*/)
    {
        if (applyIdx == ReformatHolder::kApplicMAX)
            goto skip;
        if (applyIdx == ReformatHolder::kApplicUnknown)
            goto skip;

        int testApplies;

        testApplies = applyIdx;
        if (preferred)
            testApplies |= ReformatHolder::kApplicPreferred;

        for (idIdx = 0; idIdx < ReformatHolder::kReformatMAX; idIdx++) {
            if (idIdx == ReformatHolder::kReformatUnknown)
                continue;

            ReformatHolder::ReformatApplies applies;

            applies = fpHolder->GetApplic(part,
                                        (ReformatHolder::ReformatID) idIdx);
            if ((int) applies == testApplies) {
                /* match! */
                CString str;
                //LOGI("MATCH at %d (0x%02x)", idIdx, testApplies);
                str.Format(L"%ls", ReformatHolder::GetReformatName(
                                        (ReformatHolder::ReformatID) idIdx));
                comboIdx = pCombo->AddString(str);
                pCombo->SetItemData(comboIdx, idIdx);

                /* define initial selection as best item */
                if (comboIdx == 0)
                    pCombo->SetCurSel(comboIdx);

                //if (idIdx == (int) prevID &&
                //  applyIdx == ReformatHolder::kApplicAlways)
                //{
                //  LOGI("  Found 'always' prevID, selecting");
                //  pCombo->SetCurSel(comboIdx);
                //}
            }
        }

skip:
        if (!preferred)
            applyIdx--;
        preferred = !preferred;
    }

    /* return whatever we now have selected */
    int sel = pCombo->GetCurSel();
    LOGD("  At end, sel is %d", sel);
    if (sel != CB_ERR)
        returnID = (ReformatHolder::ReformatID) pCombo->GetItemData(sel);

    return returnID;
}

void ViewFilesDialog::OnFormatSelChange(void)
{
    /*
     * The user has changed entries in the format selection drop box.
     *
     * Also called from the "quick change" buttons.
     */

    CComboBox* pCombo = (CComboBox*) GetDlgItem(IDC_FVIEW_FORMATSEL);
    ASSERT(pCombo != NULL);
    LOGD("+++ SELECTION IS NOW %d", pCombo->GetCurSel());

    SelectionEntry* pSelEntry = fpSelSet->IterCurrent();
    GenericEntry* pEntry = pSelEntry->GetEntry();
    ReformatHolder::ReformatPart part;
    ReformatHolder::ReformatID id;

    part = GetSelectedPart();
    id = (ReformatHolder::ReformatID) pCombo->GetItemData(pCombo->GetCurSel());
    Reformat(pEntry, part, id);

    DisplayText(pEntry->GetDisplayName(), IsSourceEmpty(pEntry, part));
}

void ViewFilesDialog::OnFviewData(void)
{
    ForkSelectCommon(ReformatHolder::kPartData);
}

void ViewFilesDialog::OnFviewRsrc(void)
{
    ForkSelectCommon(ReformatHolder::kPartRsrc);
}

void ViewFilesDialog::OnFviewCmmt(void)
{
    ForkSelectCommon(ReformatHolder::kPartCmmt);
}

void ViewFilesDialog::ForkSelectCommon(ReformatHolder::ReformatPart part)
{
    GenericEntry* pEntry;
    ReformatHolder::ReformatID id;

    LOGI("Switching to file part=%d", part);
    ASSERT(fpHolder != NULL);
    ASSERT(fpSelSet != NULL);
    ASSERT(fpSelSet->IterCurrent() != NULL);
    pEntry = fpSelSet->IterCurrent()->GetEntry();
    ASSERT(pEntry != NULL);

    id = ConfigureFormatSel(part);

    Reformat(pEntry, part, id);
    DisplayText(pEntry->GetDisplayName(), IsSourceEmpty(pEntry, part));
}

void ViewFilesDialog::OnFviewFmtBest(void)
{
    CComboBox* pCombo = (CComboBox*) GetDlgItem(IDC_FVIEW_FORMATSEL);
    pCombo->SetCurSel(0);       // best is always at the top
    OnFormatSelChange();
    pCombo->SetFocus();
}

void ViewFilesDialog::OnFviewFmtHex(void)
{
    CComboBox* pCombo = (CComboBox*) GetDlgItem(IDC_FVIEW_FORMATSEL);
    int sel = FindByVal(pCombo, ReformatHolder::kReformatHexDump);
    if (sel < 0)
        sel = 0;
    pCombo->SetCurSel(sel);
    OnFormatSelChange();
    pCombo->SetFocus();
}

void ViewFilesDialog::OnFviewFmtRaw(void)
{
    CComboBox* pCombo = (CComboBox*) GetDlgItem(IDC_FVIEW_FORMATSEL);
    int sel = FindByVal(pCombo, ReformatHolder::kReformatRaw);
    if (sel < 0)
        sel = 0;
    pCombo->SetCurSel(sel);
    OnFormatSelChange();
    pCombo->SetFocus();
}

void ViewFilesDialog::EnableFormatSelection(BOOL enable)
{
    GetDlgItem(IDC_FVIEW_FORMATSEL)->EnableWindow(enable);
    GetDlgItem(IDC_FVIEW_FMT_BEST)->EnableWindow(enable);
    GetDlgItem(IDC_FVIEW_FMT_HEX)->EnableWindow(enable);
    GetDlgItem(IDC_FVIEW_FMT_RAW)->EnableWindow(enable);
}

int ViewFilesDialog::FindByVal(CComboBox* pCombo, DWORD val)
{
    int count = pCombo->GetCount();
    int i;

    for (i = 0; i < count; i++) {
        if (pCombo->GetItemData(i) == val)
            return i;
    }
    return -1;
}

void ViewFilesDialog::OnFviewFont(void)
{
    // Choose a font, then apply the choice to all of the text in the box.

    LOGFONT logFont;
    CFont font;

    /*
     * Create a LOGFONT structure with the desired default characteristics,
     * then use that to initialize the font dialog.
     */
    CreateSimpleFont(&font, this, fTypeFace, fPointSize);
    font.GetLogFont(&logFont);

    CFontDialog fontDlg(&logFont);
    fontDlg.m_cf.Flags &= ~(CF_EFFECTS);

    if (fontDlg.DoModal() == IDOK) {
        //fontDlg.GetCurrentFont(&logFont);
        fTypeFace = fontDlg.GetFaceName();
        fPointSize = fontDlg.GetSize() / 10;
        LOGI("Now using %d-point '%ls'", fPointSize, (LPCWSTR) fTypeFace);

        NewFontSelected(false);
    }

}

void ViewFilesDialog::NewFontSelected(bool resetBold)
{
    CRichEditCtrl* pEdit = (CRichEditCtrl*) GetDlgItem(IDC_FVIEW_EDITBOX);
    ASSERT(pEdit != NULL);

    CHARFORMAT cf;
    cf.cbSize = sizeof(CHARFORMAT);
    cf.dwMask = CFM_FACE | CFM_SIZE;
    if (resetBold) {
        cf.dwMask |= CFM_BOLD;
        cf.dwEffects = 0;
    }
    ::lstrcpy(cf.szFaceName, fTypeFace);
    cf.yHeight = fPointSize * 20;       // in twips
    pEdit->SetSel(0, -1);               // select all
    pEdit->SetSelectionCharFormat(cf);
    pEdit->SetSel(0, 0);                // unselect
}


void ViewFilesDialog::OnFviewPrint(void)
{
    MainWindow* pMainWindow = GET_MAIN_WINDOW();
    CPrintDialog dlg(FALSE);    // use CPrintDialogEx for Win2K? CPageSetUpDialog?
    PrintRichEdit pre;
    CDC dc;
    int numPages;

    dlg.m_pd.nFromPage = dlg.m_pd.nMinPage = 1;
    dlg.m_pd.nToPage = dlg.m_pd.nMaxPage = 1;

    /*
     * Getting the expected number of pages requires a print test-run.
     * However, if we use GetDefaults to get the DC, the call to DoModal
     * returns immediately with IDCANCEL.  So, we do our pre-flighting
     * in a separate DC with a separate print dialog object.
     */
    {
        CPrintDialog countDlg(FALSE);
        CDC countDC;

        dlg.m_pd.hDevMode = pMainWindow->fhDevMode;
        dlg.m_pd.hDevNames = pMainWindow->fhDevNames;

        if (countDlg.GetDefaults() == TRUE) {
            CWaitCursor waitc;

            if (countDC.Attach(countDlg.GetPrinterDC()) != TRUE) {
                ASSERT(false);
            }
            pre.Setup(&countDC, this);
            pre.PrintPreflight(&fEditCtrl, &numPages);
            LOGI("Default printer generated %d pages", numPages);

            dlg.m_pd.nToPage = dlg.m_pd.nMaxPage = numPages;
        }

        pMainWindow->fhDevMode = dlg.m_pd.hDevMode;
        pMainWindow->fhDevNames = dlg.m_pd.hDevNames;
    }

    long startChar, endChar;
    fEditCtrl.GetSel(/*ref*/startChar, /*ref*/endChar);

    if (endChar != startChar) {
        LOGI("GetSel returned start=%ld end=%ld", startChar, endChar);
        dlg.m_pd.Flags &= ~(PD_NOSELECTION);
    }

    dlg.m_pd.hDevMode = pMainWindow->fhDevMode;
    dlg.m_pd.hDevNames = pMainWindow->fhDevNames;
    dlg.m_pd.Flags |= PD_USEDEVMODECOPIESANDCOLLATE;
    dlg.m_pd.Flags &= ~(PD_NOPAGENUMS);


    /*
     * Show them the print dialog.
     */
    if (dlg.DoModal() != IDOK)
        return;

    /*
     * Grab the chosen printer and prep ourselves.
     */
    if (dc.Attach(dlg.GetPrinterDC()) != TRUE) {
        CString msg;
        CheckedLoadString(&msg, IDS_PRINTER_NOT_USABLE);
        ShowFailureMsg(this, msg, IDS_FAILED);
        return;
    }
    pre.Setup(&dc, this);

    /*
     * Do the printing.
     */
    if (dlg.PrintRange())
        pre.PrintPages(&fEditCtrl, fTitle, dlg.GetFromPage(), dlg.GetToPage());
    else if (dlg.PrintSelection())
        pre.PrintSelection(&fEditCtrl, fTitle, startChar, endChar);
    else    // dlg.PrintAll()
        pre.PrintAll(&fEditCtrl, fTitle);

    pMainWindow->fhDevMode = dlg.m_pd.hDevMode;
    pMainWindow->fhDevNames = dlg.m_pd.hDevNames;
}

void ViewFilesDialog::OnFviewFind(void)
{
    // User hit the "Find..." button.  Open the modeless Find dialog.
    DWORD flags = 0;

    if (fpFindDialog != NULL)
        return;

    if (fFindDown)
        flags |= FR_DOWN;
    if (fFindMatchCase)
        flags |= FR_MATCHCASE;
    if (fFindMatchWholeWord)
        flags |= FR_WHOLEWORD;

    fpFindDialog = new CFindReplaceDialog;
    fpFindDialog->Create(TRUE,      // "find" only
                         fFindLastStr,  // default string to search for
                         NULL,      // default string to replace
                         flags,     // flags
                         this);     // parent
}

LRESULT ViewFilesDialog::OnFindDialogMessage(WPARAM wParam, LPARAM lParam)
{
    /*
     * Handle activity in the modeless "find" dialog.
     */

    assert(fpFindDialog != NULL);

    fFindDown = (fpFindDialog->SearchDown() != 0);
    fFindMatchCase = (fpFindDialog->MatchCase() != 0);
    fFindMatchWholeWord = (fpFindDialog->MatchWholeWord() != 0);

    if (fpFindDialog->IsTerminating()) {
        LOGI("VFD find dialog closing");
        fpFindDialog = NULL;
        return 0;
    }

    if (fpFindDialog->FindNext()) {
        fFindLastStr = fpFindDialog->GetFindString();
        FindNext(fFindLastStr, fFindDown, fFindMatchCase, fFindMatchWholeWord);
    } else {
        LOGI("Unexpected find dialog activity");
    }

    return 0;
}


void ViewFilesDialog::FindNext(const WCHAR* str, bool down, bool matchCase,
    bool wholeWord)
{
    LOGI("FindText '%ls' d=%d c=%d w=%d", str, down, matchCase, wholeWord);

    FINDTEXTEX findTextEx = { 0 };
    CHARRANGE selChrg;
    DWORD flags = 0;
    long start, result;

    if (down)
        flags |= FR_DOWN;
    if (matchCase)
        flags |= FR_MATCHCASE;
    if (wholeWord)
        flags |= FR_WHOLEWORD;

    fEditCtrl.GetSel(selChrg);
    if (selChrg.cpMin == selChrg.cpMax)
        start = selChrg.cpMin;      // start at caret
    else
        start = selChrg.cpMin +1;   // start past selection
    LOGD("  selection is %ld,%ld; start=%ld",
        selChrg.cpMin, selChrg.cpMax, start);

    findTextEx.lpstrText = str;
    findTextEx.chrg.cpMin = start;
    if (down) {
        findTextEx.chrg.cpMax = -1;
    } else {
        findTextEx.chrg.cpMax = 0;
    }
    LOGV("  using cpMin=%ld cpMax=%ld",
        findTextEx.chrg.cpMin, findTextEx.chrg.cpMax);

    result = fEditCtrl.FindText(flags, &findTextEx);
    if (result == -1) {
        LOGD("  not found, wrapping and retrying");
        /* didn't find it, wrap around to start/end and retry */
        if (down) {
            findTextEx.chrg.cpMin = 0;
            findTextEx.chrg.cpMax = -1;
        } else {
            findTextEx.chrg.cpMin = fEditCtrl.GetTextLength();
            findTextEx.chrg.cpMax = 0;
        }
        findTextEx.lpstrText = str;
        result = fEditCtrl.FindText(flags, &findTextEx);
    }

    LOGD("  result=%ld min=%ld max=%ld", result,
        findTextEx.chrgText.cpMin, findTextEx.chrgText.cpMax);
    if (result != -1) {
        /* select the text we found */
        fEditCtrl.SetSel(findTextEx.chrgText);
    } else {
        /* remove selection, leaving caret at start of sel */
        selChrg.cpMax = selChrg.cpMin;
        fEditCtrl.SetSel(selChrg);

        MainWindow* pMain = (MainWindow*)::AfxGetMainWnd();
        pMain->FailureBeep();
    }
}
