/* Status.c -- the status module of WizUnZip
 * Robert Heath. 1991.
 */

#include <sys\types.h>
#include <sys\stat.h>
#include <time.h>                
#include <string.h>
#include <ctype.h>
#include <io.h>
#include <stdio.h>
#include <stdarg.h>
#include "wizunzip.h"
#include "unzip.h"

#define STATUS_INCREMENT    512 /* incremental status data size     */
#define MAX_H_CHARS 160         /* max horizontal chars.            */

#define MAX_INDEX_ENTRIES 16    /* Message Window index max entries */

#define cchBufferMax 0xffffL    /* max Message Buffer size. Must fit 
                                 * within one memory segment!       */

#define cchTextOutMax 0x7fffL /* max no. bytes TextOut() accepts */

#define STDIO_BUF_SIZE (FILNAMSIZ+LONG_FORM_FNAME_INX) /* buffer size during printf or fprintf */

static short yClient;               /* height of client area */
static short nVscrollPos = 0;       /* scroll position of mesg. window  */
static short nNumLines = 0;         /* number of lines in buffer */
static short nVscrollMax;           /* max scroll position of mesg. window  */
static DWORD dwStatusSize = 0L;     /* status data size */
static DWORD dwBufferSize = 0L;     /* Status buffer size.  Never 
                                       exceeds cchBufferMax     */
static HANDLE hStatusBuffer;        /* global mesg. handle  */
static DWORD rgidwMsgWin[MAX_INDEX_ENTRIES]; /* max index entries   */
static short    cMsgWinEntries;         /* no. active index entries, with
                                       MAX_INDEX_ENTRIES as its max. 
                                       When set to 0, it's time to 
                                       re-index.*/

static short nLinesPerEntry;        /* lines per index entry    */

/* displayed when buffer shouldn't grow or can't grow                   */
static char __based(__segname("STRINGS_TEXT")) szClearBufferMsg[] =     
            "Clearing Status window to make room for more information.";

/* displayed when buffer shouldn't grow or can't grow                   */
static char __based(__segname("STRINGS_TEXT")) szCantClipboard[] =     
            "Cannot get enough memory to copy Status window to clipboard.";

struct KeyEntry
{
    WORD    wVirtKey;
    BOOL    bCntl;
    WORD    wMessage;
    WORD    wRequest;
} __based(__segname("STRINGS_TEXT")) KeyTable[] = 
{
    /* vertical scroll control */
    {VK_HOME,   TRUE,   WM_VSCROLL, SB_TOP },
    {VK_END,    TRUE,   WM_VSCROLL, SB_BOTTOM },
    {VK_PRIOR,  FALSE,  WM_VSCROLL, SB_PAGEUP },
    {VK_NEXT,   FALSE,  WM_VSCROLL, SB_PAGEDOWN },
    {VK_UP,     FALSE,  WM_VSCROLL, SB_LINEUP },
    {VK_DOWN,   FALSE,  WM_VSCROLL, SB_LINEDOWN },

    /* horizontal scroll control */
    {VK_HOME,   FALSE,  WM_HSCROLL, SB_TOP },
    {VK_END,    FALSE,  WM_HSCROLL, SB_BOTTOM },
    {VK_PRIOR,  TRUE,   WM_HSCROLL, SB_PAGEUP },
    {VK_NEXT,   TRUE,   WM_HSCROLL, SB_PAGEDOWN },
    {VK_LEFT,   FALSE,  WM_HSCROLL, SB_LINEUP },
    {VK_RIGHT,  FALSE,  WM_HSCROLL, SB_LINEDOWN },
} ;

#define NUMKEYS (sizeof(KeyTable)/sizeof(struct KeyEntry)) 

/* Forward Refs
 */
static void FreeStatusLog(void);

/* Globals */
BOOL bRealTimeMsgUpdate = TRUE; /* update messages window in real-time.
                                 * Reset by callers when update can be
                                 * be deferred.
                                 */
BOOL gfCancelDisplay = FALSE;	/* cancel display if got in over our heads */

/* Clears status buffer. Frees buffer.
 */
static void FreeStatusLog(void)
{
    if (hStatusBuffer)
    {
        GlobalFree(hStatusBuffer);
        hStatusBuffer = (HANDLE)0;
    }
    dwStatusSize = 0L;      /* status data size             */
    dwBufferSize = 0L;      /* status buffer size           */
    nNumLines = 0;          /* number of lines in buffer    */
    nVscrollMax = 1;
    SetScrollRange(hWndStatus, SB_VERT, 0, 1, FALSE);
    nVscrollPos = 0;
    SetScrollPos(hWndStatus, SB_VERT, nVscrollPos, TRUE);
}

/* Update Message Window Position is called after adding 
 * a number of lines to the message window without updating it.
 * The function invalidates then updates the window.
 */
void UpdateMsgWndPos(void)
{
    nVscrollPos = max(0,(nNumLines-cLinesMessageWin+1));     /* set position to next to last line   */
    SetScrollPos(hWndStatus, SB_VERT, nVscrollPos, TRUE);
    InvalidateRect(hWndStatus, NULL, TRUE);
    UpdateWindow(hWndStatus);
}

/* Set Status Top Window Position is called after adding 
 * a number of lines to the message window without updating it.
 * Unlike UpdateMsgWndPos() above, this function sets the
 * status window position to the top.
 * The function invalidates then updates the window.
 */
void SetStatusTopWndPos(void)
{
    nVscrollPos = 0;     /* set position to next to top line   */
    SetScrollPos(hWndStatus, SB_VERT, nVscrollPos, TRUE);
    InvalidateRect(hWndStatus, NULL, TRUE);
    UpdateWindow(hWndStatus);
}

/* Add message line (or part of a line) to the global status buffer
 * that is the contents of the Message Window.
 * Assumes that global data is unlocked when called.
 */
void WriteStringToMsgWin(PSTR psz, BOOL bUpdate)
{
    WriteBufferToMsgWin(psz, strlen(psz), bUpdate);
}

/* Add message buffer (maybe part of a line) to the global status buffer
 * that is the contents of the Message Window.
 * Assumes that global data is unlocked when called.
 */
void WriteBufferToMsgWin(LPSTR pszBuffer, int nBufferLen, BOOL bUpdate)
{
    LPSTR   lpszT;              /* pointer into buffer                          */
    HANDLE hStatusBufferTmp;
    LPSTR lpGlobalBuffer;           /* pointer into global buffer               */
    DWORD dwNewSize = dwStatusSize + (DWORD)nBufferLen;
    int nIncrLines = 0;             /* incremental lines in buffer          */
    int nIncompleteExistingLine = 0; /* add -1 if incomplete existing last line */
    int nIncompleteAddedLine = 0;   /* add +1 if incomplete added last line     */
    DWORD dwRequestedSize;          /* Size needed to hold all data. Can't
                                       practically exceeded cchBufferMax.*/

	if (gfCancelDisplay)		/* if canceling display (in the middle of a lengthy operation) */
		return;					/* just discard data							*/

    if (!nBufferLen)    /* if no data       */
        return;         /* just beat it     */

    /* count LF's in buffer to later add to total                       */
    for (lpszT = pszBuffer; lpszT != NULL && (lpszT - pszBuffer) < nBufferLen; )
    {
        /* use memchr() for speed (?) considerations                    */
        if (lpszT = _fmemchr(lpszT, '\n', (size_t)(nBufferLen - (lpszT - pszBuffer))))
        {
            nIncrLines++;   /* tally line found */
            lpszT++;        /* point beyond LF for next pass */
        }
    }
    if (dwNewSize > dwBufferSize)   /* if won't fit or 1st time */
    {
        /* Round up if necessary to nearest whole increment */
        dwRequestedSize = ((dwNewSize + STATUS_INCREMENT - 1) / 
                            STATUS_INCREMENT) * STATUS_INCREMENT;
        if (hStatusBuffer)  /* if buffer exists, realloc */
        {
            if (dwRequestedSize <= cchBufferMax &&
                    (hStatusBufferTmp = GlobalReAlloc(hStatusBuffer,
                                dwRequestedSize, GMEM_MOVEABLE)))
            {
                /* successful re-allocation */
                hStatusBuffer = hStatusBufferTmp;
                dwBufferSize = dwRequestedSize;
            }
            else /* re-allocation failed, make last-ditch attempt! */
            {
                FreeStatusLog();        /* free own buffers */
                if (MessageBox (hWndMain, szClearBufferMsg,
                            "Note", MB_ICONINFORMATION | MB_OKCANCEL) == IDCANCEL)
					gfCancelDisplay = TRUE;

				else /* no cancel */
                	WriteBufferToMsgWin(pszBuffer, nBufferLen, bUpdate);

                return;
            }
        }
        else    /* 1st time */
        {
            if (hStatusBuffer = GlobalAlloc(GMEM_MOVEABLE,
                                dwRequestedSize))
            {
                dwBufferSize = dwRequestedSize; /* save it      */
            }
            else    /* 1st allocation failed! */
            {
                WinAssert(hStatusBuffer);
                return;
            }
        }
    }
    /* should be easy copy of data from here */
    lpGlobalBuffer = GlobalLock(hStatusBuffer);
    if (lpGlobalBuffer)
    {

        /* Account for partial lines existing and being added. */
        if (dwStatusSize  &&
            lpGlobalBuffer[dwStatusSize-1] != '\n')
                nIncompleteExistingLine-- ; /* subtract 1                   */

        if (pszBuffer[nBufferLen-1] != '\n') /* nBufferLen guaranteed >0 */
                nIncompleteAddedLine++ ;  /* add 1                  */

        /* copy data into global buffer                         */
        if (nBufferLen)   /* map to ANSI; if 0 don't copy; 0 means 65K  */
        {
            OemToAnsiBuff(pszBuffer, &lpGlobalBuffer[dwStatusSize], nBufferLen);    
        }
        /* bump no. lines accounting for incomplete lines       */
        nNumLines += (nIncrLines+nIncompleteExistingLine+nIncompleteAddedLine); 
        dwStatusSize = dwNewSize;       /* new data size counting end null  */
        GlobalUnlock(hStatusBuffer);
        nVscrollMax = max(1, nNumLines + 2 - yClient/dyChar);
        SetScrollRange(hWndStatus, SB_VERT, 0, nVscrollMax, FALSE);
        cMsgWinEntries = 0; /* re-index whenever more data is added         */
        if (bUpdate)        /* if requested to update message box           */
        {
            nVscrollPos = max(0,(nNumLines-cLinesMessageWin+1));     /* set position to next to last line   */
            SetScrollPos(hWndStatus, SB_VERT, nVscrollPos, TRUE);
            InvalidateRect(hWndStatus, NULL, TRUE);
            UpdateWindow(hWndStatus);
        }
    }
    else
    {
        WinAssert(lpGlobalBuffer);
    }
}



long FAR PASCAL StatusProc(HWND hWnd, WORD wMessage, WORD wParam, LONG lParam) 
{
    short xClient ;             /* size of client area  */
    HDC     hDC;                /* device context       */
    PAINTSTRUCT ps;
    struct KeyEntry __far *pKE;     /* pointer to key entry     */
    LPSTR   lpStatusBuffer;     /* pointer to global msg. buffer */
    int     nMenuItemCount;     /* no. items in System menu before deleting separators  */
    BOOL    bCntl;              /* control shift pressed ?  */
    static short nHscrollMax;
    static short nHscrollPos;
    static short nMaxWidth;     /* in pixels    */
    short nVscrollInc;
    short nHscrollInc;
    short i, x, y, nPaintBeg, nPaintEnd;
    HMENU   hSysMenu;           /* this guy's system menu   */

    switch (wMessage)
    {
    case WM_CREATE:
        nMaxWidth = MAX_H_CHARS * dxChar;
        nVscrollPos = 0;
        nVscrollMax = max(1,nNumLines);
        SetScrollRange(hWnd, SB_VERT, 0, nVscrollMax, FALSE);
        SetScrollPos(hWnd, SB_VERT, 0, TRUE);

        /* Remove system menu items to limit user actions on status window */
        hSysMenu = GetSystemMenu(hWnd, FALSE);
        DeleteMenu(hSysMenu, SC_SIZE, MF_BYCOMMAND);
        DeleteMenu(hSysMenu, SC_MOVE, MF_BYCOMMAND);
        DeleteMenu(hSysMenu, SC_CLOSE, MF_BYCOMMAND);
        DeleteMenu(hSysMenu, SC_TASKLIST, MF_BYCOMMAND);

        /* walk thru menu and delete all separator bars */
        for (nMenuItemCount = GetMenuItemCount(hMenu);
            nMenuItemCount ; nMenuItemCount--)
        {
            if (GetMenuState(hSysMenu, nMenuItemCount-1, MF_BYPOSITION) & MF_SEPARATOR)
            {
                DeleteMenu(hSysMenu, nMenuItemCount-1, MF_BYPOSITION);
            }
        }
        return 0;

    case WM_SIZE:
        xClient = LOWORD(lParam);/* x size of client area */
        yClient = HIWORD(lParam);/* y size of client area */

        nVscrollMax = max(1, nNumLines + 2 - yClient/dyChar);
        nVscrollPos = min(nVscrollPos, nVscrollMax);

        SetScrollRange(hWnd, SB_VERT, 0, nVscrollMax, FALSE);
        SetScrollPos(hWnd, SB_VERT, nVscrollPos, TRUE);

        nHscrollMax = max(0, 2 + (nMaxWidth - xClient) / dxChar);
        nHscrollPos = min(nHscrollPos, nHscrollMax);

        SetScrollRange(hWnd, SB_HORZ, 0, nHscrollMax, FALSE);
        SetScrollPos(hWnd, SB_HORZ, nHscrollPos, TRUE);

        return 0;

    case WM_SYSCOMMAND:
        switch ((wParam & 0xFFF0))
        {
        case SC_RESTORE:    /* alert parent         */
            PostMessage(hWndMain, WM_COMMAND, IDM_SPLIT, 0L);
            break;
        case SC_MAXIMIZE:
            PostMessage(hWndMain, WM_COMMAND, IDM_MAX_STATUS, 0L);
            break;
        default:
            return DefWindowProc(hWnd, wMessage, wParam, lParam);
        }
        break;
    case WM_COMMAND:
        if (wParam == IDM_CLEAR_STATUS)
        {
            FreeStatusLog();
            InvalidateRect(hWnd, NULL, TRUE);
           	UpdateWindow(hWnd);
        }
        break;
    case WM_VSCROLL:    /* scroll bar action on list box */
        switch (wParam)
        {
        case SB_TOP:
            nVscrollInc = -nVscrollPos;
            break;
        case SB_BOTTOM:
            nVscrollInc = nVscrollMax - nVscrollPos;
            break;
        case SB_LINEUP:
            nVscrollInc = -1;
            break;
        case SB_LINEDOWN:
            nVscrollInc = 1;
            break;
        case SB_PAGEUP:
            nVscrollInc = min(-1, -yClient/dyChar);
            break;
        case SB_PAGEDOWN:
            nVscrollInc = max(1, yClient/dyChar);
            break;
        case SB_THUMBPOSITION:
            nVscrollInc = LOWORD(lParam) - nVscrollPos;
            break;
        default:    /* END_SCROLL comes thru here               */
            nVscrollInc = 0;
        }

        if (nVscrollInc = max(-nVscrollPos,
                                min(nVscrollInc, nVscrollMax - nVscrollPos)))
        {
            nVscrollPos += nVscrollInc;
            ScrollWindow(hWnd, 0, -dyChar * nVscrollInc, NULL, NULL);
            SetScrollPos(hWnd, SB_VERT, nVscrollPos, TRUE);
            UpdateWindow(hWnd);
        }
        return 0;

    case WM_HSCROLL:    /* scroll bar action on list box */
        switch (wParam)
        {
        case SB_TOP:
            nHscrollInc = -nHscrollPos;
            break;
        case SB_BOTTOM:
            nHscrollInc = nHscrollMax - nHscrollPos;
            break;
        case SB_LINEUP:
            nHscrollInc = -1;
            break;
        case SB_LINEDOWN:
            nHscrollInc = 1;
            break;
        case SB_PAGEUP:
            nHscrollInc = -8;
            break;
        case SB_PAGEDOWN:
            nHscrollInc = 8;
            break;
        case SB_THUMBPOSITION:
            nHscrollInc = LOWORD(lParam) - nHscrollPos;
            break;
        default:
            return DefWindowProc(hWnd, wMessage, wParam, lParam);
        }

        if (nHscrollInc = max(-nHscrollPos,
                                min(nHscrollInc, nHscrollMax - nHscrollPos)))
        {
            nHscrollPos += nHscrollInc;
            ScrollWindow(hWnd, -dxChar * nHscrollInc, 0, NULL, NULL);
            SetScrollPos(hWnd, SB_HORZ, nHscrollPos, TRUE);
        }
        return 0;

    case WM_KEYDOWN:
        bCntl = (BOOL)(GetKeyState(VK_CONTROL) < 0 ? TRUE : FALSE);
        for (i = 0, pKE = KeyTable; i < NUMKEYS; i++, pKE++)
        {
            if ((wParam == pKE->wVirtKey) && (bCntl == pKE->bCntl))
            {
                SendMessage(hWnd, pKE->wMessage, pKE->wRequest, 0L);
                break;
            }
        }
        break;
    case WM_PAINT:
        if (!hStatusBuffer)         /* if nothing to paint  */
        {
            return DefWindowProc(hWnd, wMessage, wParam, lParam);
        }
        {
            REGISTER LPSTR lpsz;            /* current char                 */
            LPSTR lpszNextLF;       /* address of next '\n' in buffer */
            LPSTR lpszStart;    /* paint starting character     */
            LPSTR lpszLineCur;  /* beginning of current line    */
            HANDLE hNew;
            DWORD   cchLine;    /* length of current line       */
            short  nLinesSinceLastEntry; /* lines since last entry */
            DWORD  dwSearchLen; /* length for _fmemchr() to search */

            lpszStart = NULL; /* paint starting character       */
            lpStatusBuffer = GlobalLock(hStatusBuffer);
            WinAssert(lpStatusBuffer);  /* DEBUG */
            hDC = BeginPaint(hWnd, &ps);
            WinAssert(hDC);             /* DEBUG */
            hNew = SelectObject( hDC, hFixedFont);
            WinAssert(hNew);
            nPaintBeg = max(0, nVscrollPos+ps.rcPaint.top/dyChar -1);
            nPaintEnd = min(nNumLines, nVscrollPos + ps.rcPaint.bottom/dyChar);
            if (nPaintBeg >= nPaintEnd) /* if no painting to do ...     */
            {
                EndPaint(hWnd, &ps);
                GlobalUnlock(hStatusBuffer);
                return 0;
            }
            if (!cMsgWinEntries)    /* re-index whenever more data is added */
            {
                /* Round up to make lines_per_entry encompass all
                 * possible lines in buffer.
                 */
                nLinesPerEntry = (nNumLines+MAX_INDEX_ENTRIES-1) / MAX_INDEX_ENTRIES; 
                if (!nLinesPerEntry)    /* if zero                  */ 
                    nLinesPerEntry++;   /* set to 1 as minimum      */ 

                /* Count lines from beginning of buffer to:
                 * 1) mark beginning of paint sequence (lpszStart) and
                 * 2) periodically save buffer index in MsgWinIndex[] table.
                 */
                for (lpsz = lpStatusBuffer, i = 0, nLinesSinceLastEntry = 0;
                     (DWORD)(lpsz - lpStatusBuffer) < dwStatusSize ; i++)
                {
                    /* We are at the 1st character position in the line  */
                    if (i == nPaintBeg) /* Starting point for paint ? */
                        lpszStart = lpsz;   /* If so, mark starting point */

                    /* Entry time ? */
                    if (!nLinesSinceLastEntry++ && 
                        cMsgWinEntries < MAX_INDEX_ENTRIES)
                    {
                        rgidwMsgWin[cMsgWinEntries] = 
                                (DWORD)(lpsz - lpStatusBuffer); /* save index */
                        cMsgWinEntries++;
                    }

                    if (nLinesSinceLastEntry >= nLinesPerEntry)
                        nLinesSinceLastEntry = 0;

                    /* Use _fmemchr() to find next LF.  
                     * It's probably optimized for searches.
                     */
                    dwSearchLen = dwStatusSize - 
                                        (DWORD)(lpsz - lpStatusBuffer);
                    if ((lpszNextLF = _fmemchr(lpsz, '\n', (size_t)dwSearchLen)))
                        lpsz = ++lpszNextLF;    /* use next char as beg of line */
                    else /* use lpsz with incremented value */
                        lpsz += dwSearchLen;

                } /* bottom of still-counting-lines loop */

                WinAssert(lpszStart);
                lpsz = lpszStart;       /* restore starting point           */
                WinAssert((DWORD)lpsz >= (DWORD)lpStatusBuffer &&
                   (DWORD)lpsz < (DWORD)&lpStatusBuffer[dwStatusSize]); 

            }   /* bottom of need-to-build-index block  */
            else    /* index is still valid */
            {
                short nIndexEntry;

                /* Find index of line number which is equal to or just
                 * below the starting line to paint. Work backwards
                 * thru the table. Here, "i" is the line no. corresponding
                 * to the current table index.
                 */
                for (nIndexEntry = cMsgWinEntries - 1,
                     i = nIndexEntry * nLinesPerEntry; 
                        nIndexEntry >= 0 && 
                        nPaintBeg < i ;
                        nIndexEntry--, i -= nLinesPerEntry )
                {
                    ;
                }

                WinAssert(nIndexEntry >= 0);
                WinAssert(i <= nPaintBeg);

                /* OK, we've got a head start on the search.
                 * Start checking characters from the position found
                 * in the index table.
                 */
                for (lpsz = &lpStatusBuffer[rgidwMsgWin[nIndexEntry]];
                     i < nPaintBeg && 
                        (DWORD)(lpsz - lpStatusBuffer) < dwStatusSize;
                     ++i)
                {
                    /* Find length of current line.  Use _fmemchr() to 
                     * find next LF.  
                     */
                    dwSearchLen = dwStatusSize - 
                                    (DWORD)(lpsz - lpStatusBuffer);
                    if ((lpszNextLF = _fmemchr(lpsz, '\n', (size_t)dwSearchLen)) != NULL)
                        lpsz = ++lpszNextLF; /* point to next char. past '\n'   */

                    else /* If search fails, pretend LF exists, go past it. */
                        lpsz += dwSearchLen;
 
                }
            } /* bottom of index-is-still-valid block. */

            /* At this point we've got the buffer address, lpsz, for the 1st
             * line at which we begin painting, nPaintBeg.
             */
            for (i = nPaintBeg; 
                 (i < nPaintEnd) && 
                 ((DWORD)(lpsz  - lpStatusBuffer) >=  0L) &&    
                 ((DWORD)(lpsz  - lpStatusBuffer) < dwStatusSize) ; 
                 ++i)
            {
                lpszLineCur = lpsz;
                /* Find length of current line. Use _fmemchr() to find next LF.
                 */
                dwSearchLen = dwStatusSize - (DWORD)(lpsz - lpStatusBuffer);
                if ((lpszNextLF = _fmemchr(lpsz, '\n', (size_t)dwSearchLen)) == NULL)
                {
                    /* If search fails, pretend we found LF, we won't 
                     * display it anyway.
                     */
                    lpszNextLF = lpsz + dwSearchLen;
                }
                WinAssert((DWORD)lpszNextLF >= (DWORD)lpszLineCur); /* should be non-negative   */
                WinAssert((DWORD)lpszNextLF >= (DWORD)lpStatusBuffer && /* DEBUG */
                   (DWORD)lpszNextLF <= (DWORD)&lpStatusBuffer[dwStatusSize]); 

                x = dxChar * (1 - nHscrollPos);
                y = dyChar * (1 - nVscrollPos + i);
                cchLine = (DWORD)(lpszNextLF - lpszLineCur);/* calc length*/
                /* don't display '\r'   */
                if (cchLine && lpszLineCur[cchLine-1] == '\r')
                    cchLine--;

                /* may be displaying long lines if binary file */
                if (cchLine > cchTextOutMax) 
                    cchLine = cchTextOutMax;

                TabbedTextOut(hDC, x, y, lpszLineCur, (int)cchLine, 0, NULL, 0);
                lpsz = ++lpszNextLF; /* point to char. past '\n' */
            }
            EndPaint(hWnd, &ps);
            GlobalUnlock(hStatusBuffer);
            return 0;
        }
        break;
    case WM_CLOSE:
        DestroyWindow(hWnd);
        break;
    case WM_DESTROY:
        FreeStatusLog();
        break;
    default:
        return DefWindowProc(hWnd, wMessage, wParam, lParam);
    }
    return 0L;
}

/* Map CR to CRLF is called by the printf and fprintf clones below
 * to guarantee that lines added to the Status window will be
 * properly terminated DOS-style in case they are copied 
 * to the Windows clipboard.
 * The converts the buffer in-place, provided it won't
 * grow beyond the original buffer allocation.
 */
static void MapCRtoCRLF(PSTR pszOrigBuffer);
static void MapCRtoCRLF(PSTR pszOrigBuffer)
{
PSTR pC, pDest;
UINT cBufLen = 0;	/* no. chars in buffer, including final null */
UINT cLFs = 0; 	/* no. LF's in string. \n is linefeed */

	for (pC = pszOrigBuffer; *pC; pC++)
	{
		cBufLen++;
		if (*pC == '\n')	/* found a linefeed */
			cLFs++;

	}
	cBufLen++;	/* buffer length includes final null */
	pC = &pszOrigBuffer[cBufLen-1]; /* point to old end's null char */
 	if (cBufLen + cLFs > STDIO_BUF_SIZE) /* room to add CR's ? */
		return; 				/* if not, don't bother */

	/* copy data back-to-front, effectively inserting CR before LF */
	pDest = &pszOrigBuffer[cBufLen+cLFs-1]; /* point to new end */
	for  (; cBufLen; pC--, cBufLen--)
	{
		*pDest-- = *pC ;	/* copy data byte */
		if (*pC == '\n')	/* was that a linefeed ? */
			*pDest-- = '\r';	/* if so, insert CR */

	}
}


/* Printf buffers the current output and counts the number of lines
 * within it.  It makes sure there is enough space in the global
 * buffer, then copies the buffered data to the global buffer.
 * It then triggers a repaint of the status buffer.
 */
int __far __cdecl printf(const char *format, ...)
{
    va_list argptr;
    HANDLE hMemory;
    PSTR pszBuffer;

    va_start(argptr, format);
    hMemory = LocalAlloc(LMEM_MOVEABLE, STDIO_BUF_SIZE);
    WinAssert(hMemory);
    if (!hMemory)
    {
        return 0;
    }
    pszBuffer = (PSTR)LocalLock(hMemory);
    WinAssert(pszBuffer);
    vsprintf(pszBuffer, format, argptr);
    va_end(argptr); 
    WinAssert(strlen(pszBuffer) < STDIO_BUF_SIZE);  /* raise STDIO_BUF_SIZE ?   */
	MapCRtoCRLF(pszBuffer);			/* map CR's to CRLF's */
    WriteStringToMsgWin(pszBuffer, bRealTimeMsgUpdate);
    LocalUnlock(hMemory);
    LocalFree(hMemory);
    return 0;
}

/* fprintf clone for code in unzip.c, etc. */
int __far __cdecl fprintf(FILE *file, const char *format, ...)
{
    va_list argptr;
    HANDLE hMemory;
    PSTR pszBuffer;

    va_start(argptr, format);
    hMemory = LocalAlloc(LMEM_MOVEABLE, STDIO_BUF_SIZE);
    WinAssert(hMemory);
    if (!hMemory)
    {
        return 0;
    }
    pszBuffer = (PSTR)LocalLock(hMemory);
    WinAssert(pszBuffer);
    vsprintf(pszBuffer, format, argptr);
    va_end(argptr); 
    WinAssert(strlen(pszBuffer) < STDIO_BUF_SIZE);  /* raise STDIO_BUF_SIZE ?   */
	MapCRtoCRLF(pszBuffer);		/* map CR's to CRLF's */
    WriteStringToMsgWin(pszBuffer, bRealTimeMsgUpdate);
    LocalUnlock(hMemory);
    LocalFree(hMemory);
}

void __far __cdecl perror(const char *parm1)
{
    printf(parm1);
}

/* Copy contents of status window to clipboard.  Called on receipt of IDM_COPY message.
 */
void CopyStatusToClipboard(HWND hWnd)
{
HANDLE  hGlobalMemory; /* memory handles		*/
LPSTR	lpGlobalMemory, lpStatusBuffer ; /* memory pointers	*/
DWORD  dwTextSize = dwStatusSize;

	if (dwTextSize > 0x10000 - 2)
		dwTextSize = 0x10000 - 2;

	if (!(hGlobalMemory = GlobalAlloc(GHND, dwTextSize+1L)))
	{
		MessageBox (hWnd, szCantClipboard,
                            "Error", MB_ICONEXCLAMATION | MB_OK);

	}
	else /* Got it! */
	{
		lpGlobalMemory = GlobalLock(hGlobalMemory);
		assert(lpGlobalMemory);	/* DEBUG */
        lpStatusBuffer = GlobalLock(hStatusBuffer);
		assert(lpStatusBuffer);	/* DEBUG */
		_fmemcpy((void __far *)lpGlobalMemory, (void __far *)lpStatusBuffer, (size_t)dwTextSize);
		lpGlobalMemory[dwTextSize] = '\0'; /* CF_TEXT requires null */
		GlobalUnlock(hStatusBuffer);	/* unlock status buffer */
		GlobalUnlock(hGlobalMemory); 
		OpenClipboard(hWnd);
		EmptyClipboard();
		SetClipboardData(CF_TEXT, hGlobalMemory);
		CloseClipboard();
	}
}


/* Returns TRUE if Status window has anything in it; i.e. is non-clear.
 */
BOOL StatusInWindow(void)
{
	return((BOOL)dwStatusSize);
}
