/****************************************************************************
 *
 *  MODULE  : RIFFDISP.C
 *
 ****************************************************************************/

#include <win32.h>
#include <mmsystem.h>
#include <commdlg.h>
#include <vfw.h>
#include "riffdisp.h"

static  HWND        hwndPreview;
static  HANDLE      hdibPreview;
static  char        achPreview[MAX_PATH];
static  HFONT       hfontPreview;

static  HDRAWDIB    hdd;

#define GetHInstance()  (HINSTANCE)(SELECTOROF((LPVOID)&hwndPreview))

#define DibSizeImage(lpbi) (\
    (DWORD)(UINT)((((int)lpbi->biBitCount*(int)lpbi->biWidth+31)&~31)>>3) * \
    (DWORD)(UINT)lpbi->biHeight)

#define DibSize(lpbi) \
    (lpbi->biSize + ((int)lpbi->biClrUsed * sizeof(RGBQUAD)) + lpbi->biSizeImage)

#define DibNumColors(lpbi) \
    (lpbi->biBitCount <= 8 ? (1 << (int)lpbi->biBitCount) : 0)

/***************************************************************************
 *
 ****************************************************************************/

//#define FOURCC_RIFF mmioFOURCC('R','I','F','F')
#define FOURCC_AVI  mmioFOURCC('A','V','I',' ')
#define FOURCC_INFO mmioFOURCC('I','N','F','O')
#define FOURCC_DISP mmioFOURCC('D','I','S','P')
#define FOURCC_INAM mmioFOURCC('I','N','A','M')
#define FOURCC_ISBJ mmioFOURCC('I','S','B','J')

BOOL   PreviewOpen(HWND hwnd);
BOOL   PreviewFile(HWND hwnd, LPSTR szFile);
BOOL   PreviewPaint(HWND hwnd);
BOOL   PreviewClose(HWND hwnd);

HANDLE ReadDisp(LPSTR lpszFile, int cf, LPSTR pv, int iLen);
HANDLE ReadInfo(LPSTR lpszFile, FOURCC fcc, LPSTR pv, int iLen);
HANDLE GetRiffDisp(LPSTR lpszFile, LPSTR szText, int iLen);

/***************************************************************************
 *
 ****************************************************************************/

BOOL PreviewOpen(HWND hwnd)
{
    LOGFONT lf;

    if (hwndPreview)
        return FALSE;

    hwndPreview = hwnd;

    hdd = DrawDibOpen();

    SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lf), (LPVOID)&lf, 0);
    hfontPreview = CreateFontIndirect(&lf);
}

/***************************************************************************
 *
 ****************************************************************************/

BOOL PreviewClose(HWND hwnd)
{
    if (hwndPreview != hwnd)
        return FALSE;

    if (hdibPreview)
        GlobalFree(hdibPreview);

    if (hfontPreview)
        DeleteObject(hfontPreview);

    if (hdd)
        DrawDibClose(hdd);

    achPreview[0] = 0;
    hdd           = NULL;
    hwndPreview   = NULL;
    hdibPreview   = NULL;
    hfontPreview  = NULL;
}

/***************************************************************************
 *
 ****************************************************************************/

BOOL PreviewFile(HWND hwnd, LPSTR szFile)
{
    if (hwndPreview != hwnd)
        return FALSE;

    achPreview[0] = 0;

    if (hdibPreview)
        GlobalFree(hdibPreview);

    hdibPreview = NULL;

    if (szFile)
    {
        hdibPreview = GetRiffDisp(szFile, achPreview, NUMELMS(achPreview));
    }

    PreviewPaint(hwnd);
    return TRUE;
}

/***************************************************************************
 *
 ****************************************************************************/

BOOL PreviewPaint(HWND hwnd)
{
    RECT    rc;
    RECT    rcPreview;
    RECT    rcImage;
    RECT    rcText;
    HDC     hdc;
    HBRUSH  hbr;
    int     dx;
    int     dy;
    LPBITMAPINFOHEADER lpbi;

    if (hwndPreview != hwnd)
        return FALSE;

    //
    // locate the preview in the lower corner of the dialog (below the
    // cancel button)
    //
//////!!! find a better way to do this.
    GetClientRect(hwnd, &rcPreview);
    GetWindowRect(GetDlgItem(hwnd, IDCANCEL), &rc);
    ScreenToClient(hwnd, (LPPOINT)&rc);
    ScreenToClient(hwnd, (LPPOINT)&rc+1);

    rcPreview.top   = rc.bottom + (rc.bottom - rc.top) + 12;
    rcPreview.left  = rc.left;
    rcPreview.right = rc.right;
    rcPreview.bottom -= 4;          // leave a little room at the bottom
//////

    hdc = GetDC(hwnd);
    hbr = (HBRUSH)DefWindowProc(hwnd, WM_CTLCOLOR, (WPARAM)hdc, MAKELONG(hwnd, CTLCOLOR_DLG));
    SelectObject(hdc, hfontPreview);
    SetStretchBltMode(hdc, COLORONCOLOR);

    InflateRect(&rcPreview, 4, 1);
    FillRect(hdc, &rcPreview, hbr);
    IntersectClipRect(hdc, rcPreview.left, rcPreview.top, rcPreview.right, rcPreview.bottom);
    InflateRect(&rcPreview, -4, -1);

    //
    // compute the text rect, using DrawText
    //
    rcText = rcPreview;
    rcText.bottom = rcText.top;

    DrawText(hdc, achPreview, -1, &rcText, DT_CALCRECT|DT_LEFT|DT_WORDBREAK);

    //
    // compute the image size
    //
    if (hdibPreview && hdd)
    {
        lpbi = (LPVOID)GlobalLock(hdibPreview);

#if 0
        //
        // DISP(CF_DIB) chunks are messed up they contain a DIB file! not
        // a CF_DIB, skip over the header if it is there.
        //
        if (lpbi->biSize != sizeof(BITMAPINFOHEADER))
            (LPSTR)lpbi += sizeof(BITMAPFILEHEADER);
#endif

        rcImage = rcPreview;

        //
        //  if wider than preview area scale to fit
        //
        if ((int)lpbi->biWidth > rcImage.right - rcImage.left)
        {
            rcImage.bottom = rcImage.top + MulDiv((int)lpbi->biHeight,rcImage.right-rcImage.left,(int)lpbi->biWidth);
        }
        //
        //  if x2 will fit then use it
        //
        else if ((int)lpbi->biWidth * 2 < rcImage.right - rcImage.left)
        {
            rcImage.right  = rcImage.left + (int)lpbi->biWidth*2;
            rcImage.bottom = rcImage.top + (int)lpbi->biHeight*2;
        }
        //
        //  else center the image in the preview area
        //
        else
        {
            rcImage.right  = rcImage.left + (int)lpbi->biWidth;
            rcImage.bottom = rcImage.top + (int)lpbi->biHeight;
        }

	if (rcImage.bottom > rcPreview.bottom - (rcText.bottom - rcText.top))
	{
	    rcImage.bottom = rcPreview.bottom - (rcText.bottom - rcText.top);

	    rcImage.right = rcPreview.left + MulDiv((int)lpbi->biWidth,rcImage.bottom-rcImage.top,(int)lpbi->biHeight);
	    rcImage.left = rcPreview.left;
	}
    }
    else
    {
        SetRectEmpty(&rcImage);
    }

    //
    //  now center
    //
    dx = ((rcPreview.right - rcPreview.left) - (rcText.right - rcText.left))/2;
    OffsetRect(&rcText, dx, 0);

    dx = ((rcPreview.right - rcPreview.left) - (rcImage.right - rcImage.left))/2;
    OffsetRect(&rcImage, dx, 0);

    dy  = rcPreview.bottom - rcPreview.top;
    dy -= rcImage.bottom - rcImage.top;
    dy -= rcText.bottom - rcText.top;

    if (dy < 0)
        dy = 0;
    else
        dy = dy / 2;

    OffsetRect(&rcImage, 0, dy);
    OffsetRect(&rcText, 0, dy + rcImage.bottom - rcImage.top + 2);

    //
    //  now draw
    //
    DrawText(hdc, achPreview, -1, &rcText, DT_LEFT|DT_WORDBREAK);

    if (hdibPreview && hdd)
    {
        DrawDibDraw(hdd,
                    hdc,
                    rcImage.left,
                    rcImage.top,
                    rcImage.right  - rcImage.left,
                    rcImage.bottom - rcImage.top,
                    lpbi,
                    NULL,
                    0,
                    0,
                    -1,
                    -1,
                    0);

        InflateRect(&rcImage, 1, 1);
        FrameRect(hdc, &rcImage, GetStockObject(BLACK_BRUSH));
    }

    ReleaseDC(hwnd, hdc);
    return TRUE;
}

/***************************************************************************
 *
 ****************************************************************************/

static UINT    (CALLBACK *lpfnOldHook)(HWND, UINT, WPARAM, LPARAM);

    /* Combo boxes */
#define cmb1        0x0470
#define cmb2        0x0471
    /* Listboxes */
#define lst1        0x0460
#define lst2        0x0461
    /* Edit controls */
#define edt1        0x0480

#define ID_TIMER    1234
#define PREVIEWWAIT 1000

WORD FAR PASCAL _export GetFileNamePreviewHook(HWND hwnd, unsigned msg, WORD wParam, LONG lParam)
{
    int i;
    char ach[80];

    switch (msg) {
        case WM_COMMAND:
            switch (wParam)
            {
                case lst1:
                    if (HIWORD(lParam) == LBN_SELCHANGE)
                    {
                        KillTimer(hwnd, ID_TIMER);
                        SetTimer(hwnd, ID_TIMER, PREVIEWWAIT, NULL);
                    }
                    break;

                case IDOK:
                case IDCANCEL:
                    KillTimer(hwnd, ID_TIMER);
                    PreviewFile(hwnd, NULL);
                    break;

                case cmb1:
                case cmb2:
                case lst2:
                    if (HIWORD(lParam) == LBN_SELCHANGE)
                    {
                        KillTimer(hwnd, ID_TIMER);
                        PreviewFile(hwnd, NULL);
                    }
                    break;
            }
            break;

        case WM_TIMER:
            if (wParam == ID_TIMER)
            {
                KillTimer(hwnd, ID_TIMER);

                ach[0] = 0;
                i = (int)SendDlgItemMessage(hwnd, lst1, LB_GETCURSEL, 0, 0L);
                SendDlgItemMessage(hwnd, lst1, LB_GETTEXT, i, (LONG)(LPSTR)ach);
                PreviewFile(hwnd, ach);
                return TRUE;
            }
            break;

        case WM_QUERYNEWPALETTE:
        case WM_PALETTECHANGED:
        case WM_PAINT:
            PreviewPaint(hwnd);
            break;

        case WM_INITDIALOG:
            PreviewOpen(hwnd);

            if (!lpfnOldHook)
                return TRUE;

            break;

        case WM_DESTROY:
            PreviewClose(hwnd);
            break;
    }

    if (lpfnOldHook)
        return (*lpfnOldHook)(hwnd, msg, wParam, lParam);
    else
        return FALSE;
}

/**************************************************************************
* @doc EXTERNAL GetOpenFileNamePreview
*
* @api BOOL | GetOpenFileNamePreview | This function is similar
*      to <f GetOpenFileName> defined in COMMDLG.DLL except that
*      it has a preview window to display the movie about to be opened.
*
* @parm LPOPENFILENAME | lpofn | Points to an <t OPENFILENAME> structure
*       used to initialize the dialog box. On return, the structure
*       contains information about the user's file selection.
*
* @rdesc Returns true if a file was opened.
*
* @comm For more information on this function, see the description for
*       <f GetOpenFileName>.
*
* @xref <f GetOpenFileName>
*
*************************************************************************/
BOOL FAR PASCAL GetOpenFileNamePreview(LPOPENFILENAME lpofn)
{
    BOOL fHook;
    BOOL f;

    if (hwndPreview)
        return GetOpenFileName(lpofn);

    fHook = (BOOL)(lpofn->Flags & OFN_ENABLEHOOK);

    if (fHook)
        lpfnOldHook = lpofn->lpfnHook;

    (FARPROC)lpofn->lpfnHook = MakeProcInstance((FARPROC)GetFileNamePreviewHook, GetHInstance());
    lpofn->Flags |= OFN_ENABLEHOOK;

    f = GetOpenFileName(lpofn);

    FreeProcInstance((FARPROC)lpofn->lpfnHook);

    if (fHook)
        lpofn->lpfnHook = lpfnOldHook;
    else
        lpofn->Flags &= ~OFN_ENABLEHOOK;

    return f;
}

HANDLE AVIFirstFrame(LPSTR szFile)
{
    HANDLE h = NULL;
    LPBITMAPINFOHEADER lpbi;
    DWORD dwSize;
    PAVISTREAM pavi;
    PGETFRAME pg;

    if (AVIStreamOpenFromFile(&pavi, szFile, streamtypeVIDEO, 0, OF_READ, NULL) == AVIERR_OK)
    {
	pg = AVIStreamGetFrameOpen(pavi, NULL);
	if (pg) {
	    lpbi = AVIStreamGetFrame(pg, 0);

	    if (lpbi)
	    {
		dwSize = lpbi->biSize + lpbi->biSizeImage + lpbi->biClrUsed * sizeof(RGBQUAD);
		h = GlobalAlloc(GHND, dwSize);

		if (h)
		    hmemcpy(GlobalLock(h), lpbi, dwSize);
	    }

	    AVIStreamGetFrameClose(pg);
	}
        AVIStreamClose(pavi);
    }

    return h;
}

/****************************************************************************
 *
 *  get both the DISP(CF_DIB) and the DISP(CF_TEXT) info in one pass, this is
 *  much faster than doing multiple passes over the file.
 *
 ****************************************************************************/

HANDLE GetRiffDisp(LPSTR lpszFile, LPSTR szText, int iLen)
{
    HMMIO       hmmio;
    MMCKINFO    ck;
    MMCKINFO    ckINFO;
    MMCKINFO    ckRIFF;
    HANDLE	h = NULL;
    LONG        lSize;
    DWORD       dw;
    HCURSOR     hcur = NULL;

    if (szText)
        szText[0] = 0;

    /* Open the file */
    hmmio = mmioOpen(lpszFile, NULL, MMIO_ALLOCBUF | MMIO_READ);

    if (hmmio == NULL)
        return NULL;

    mmioSeek(hmmio, 0, SEEK_SET);

    /* descend the input file into the RIFF chunk */
    if (mmioDescend(hmmio, &ckRIFF, NULL, 0) != 0)
        goto error;

    if (ckRIFF.ckid != FOURCC_RIFF)
        goto error;

    while (!mmioDescend(hmmio, &ck, &ckRIFF, 0))
    {
        if (ck.ckid == FOURCC_DISP)
        {
            if (hcur == NULL)
                hcur = SetCursor(LoadCursor(NULL, IDC_WAIT));

            /* Read dword into dw, break if read unsuccessful */
            if (mmioRead(hmmio, (LPVOID)&dw, sizeof(dw)) != sizeof(dw))
                goto error;

            /* Find out how much memory to allocate */
            lSize = ck.cksize - sizeof(dw);

            if ((int)dw == CF_DIB && h == NULL)
            {
                /* get a handle to memory to hold the description and lock it down */
                if ((h = GlobalAlloc(GHND, lSize+4)) == NULL)
                    goto error;

                if (mmioRead(hmmio, GlobalLock(h), lSize) != lSize)
                    goto error;
            }
            else if ((int)dw == CF_TEXT && szText[0] == 0)
            {
                if (lSize > iLen-1)
                    lSize = iLen-1;

                szText[lSize] = 0;

                if (mmioRead(hmmio, szText, lSize) != lSize)
                    goto error;
            }
        }
        else if (ck.ckid    == FOURCC_LIST &&
                 ck.fccType == FOURCC_INFO &&
                 szText[0]  == 0)
        {
            while (!mmioDescend(hmmio, &ckINFO, &ck, 0))
            {
                switch (ckINFO.ckid)
                {
                    case FOURCC_INAM:
//                  case FOURCC_ISBJ:

                        lSize = ck.cksize;

                        if (lSize > iLen-1)
                            lSize = iLen-1;

                        szText[lSize] = 0;

                        if (mmioRead(hmmio, szText, lSize) != lSize)
                            goto error;

                        break;
                }

                if (mmioAscend(hmmio, &ckINFO, 0))
                    break;
            }
        }

        //
        // if we have both a picture and a title, then exit.
        //
        if (h != NULL && szText[0] != 0)
            break;

        /* Ascend so that we can descend into next chunk
         */
        if (mmioAscend(hmmio, &ck, 0))
            break;
    }

    goto exit;

error:
    if (h)
        GlobalFree(h);

    h = NULL;
    ckRIFF.fccType = 0;

exit:
    mmioClose(hmmio, 0);

    //
    // !!!we need a way to preview other file types!
    // !!!what about text.
    //
    if (h == NULL && ckRIFF.fccType == FOURCC_AVI)
    {
        if (hcur == NULL)
            hcur = SetCursor(LoadCursor(NULL, IDC_WAIT));

        h = AVIFirstFrame(lpszFile);
    }

    //
    // verify and correct the DIB
    //
    if (h)
    {
        LPBITMAPINFOHEADER lpbi;

        lpbi = (LPVOID)GlobalLock(h);

        if (lpbi->biSize < sizeof(BITMAPINFOHEADER))
            goto error;

        if (lpbi->biClrUsed == 0)
            lpbi->biClrUsed = DibNumColors(lpbi);

        if (lpbi->biSizeImage == 0)
            lpbi->biSizeImage = DibSizeImage(lpbi);

        if (DibSize(lpbi) > GlobalSize(h))
            goto error;
    }

    if (hcur)
        SetCursor(hcur);

    return h;
}
