/*
 * Windows Kermit
 * 
 * Written by William S. Hall
 *	      3665 Benton Street, #66
 *	      Santa Clara, CA 95051
 *
 * protocol function support module
 */

#define NOKANJI
#define NOATOM
#define NOMINMAX
#include <windows.h>
#include <stdlib.h>
#include <string.h>

#include "winasc.h"
#include "winkpr.h"
#include "winkpf.h"

extern int cid;

struct DisplayWnd {
    HWND hWnd;
    HANDLE hVidBuffer;
    BYTE *pVidBuffer;
    short Width, Height;
    short CharWidth, CharHeight;
    short Xpos, Ypos;
    short MaxLines, MaxCols;
    short MaxLineLength;
    short oCurrentLine;
    short oVidLastLine;
    short CurrLineOffset;
};

typedef struct DisplayWnd *PDISPLAYWND;
static struct DisplayWnd DWnd;

/* local functions */
static void near ResetFTParams(HWND hWnd, int mode);
BOOL FAR PASCAL krmShowChildren(HWND hWnd, LONG lParam);
static void NEAR krmGrayMenus(HWND hWnd, int mode, int style);
static void NEAR krmShowResult(HWND hWnd, int mode);
static void NEAR DoMCR(PDISPLAYWND pDWnd);
static void NEAR DoMLF(PDISPLAYWND pDWnd);
static void NEAR DoMBS(PDISPLAYWND pDWnd);
static void NEAR DoMTab(PDISPLAYWND pDWnd);
static void NEAR krmLongReplyUpdate(HDC hDC);
static void NEAR krmShowPackets(HWND hWnd, HDC hDC);

/* show packets when iconic */
static void NEAR krmShowPackets(HWND hWnd, HDC hDC)
{

    char numstr[10];
    register char *ptr;
    register int num;
    RECT rcPaint;

    GetClientRect(hWnd, (LPRECT)&rcPaint);
    
    num = unchar(sndpkt.pktbuf[2]);
    numstr[0] = sndpkt.pktbuf[3];
    numstr[1] = numstr[6] = '-';

    if (num < 10) {
        numstr[2] = '0';
        ptr = numstr + 3;
    }
    else
        ptr = numstr + 2;
    itoa(num, ptr, 10);

    numstr[4] = SP;

    numstr[5] = rcvpkt.type;
    if ((num = rcvpkt.num) < 10) {
        numstr[7] = '0';
	ptr = numstr + 8;
    }
    else
	ptr = numstr + 7;
    itoa(num, ptr, 10);

    DrawText(hDC, (LPSTR)numstr,-1,(LPRECT)&rcPaint,
        	     DT_LEFT | DT_NOCLIP | DT_WORDBREAK | DT_EXTERNALLEADING);
}

/* show large responses or data when iconic */
void krmShowTransferData(HWND hWnd, HDC hDC, BOOL iconic)
{

    if (iconic)
 	krmShowPackets(hWnd, hDC);
    else
        if (DWnd.hVidBuffer)
	    krmLongReplyUpdate(hDC);
}

/* show long responses */
static void NEAR krmLongReplyUpdate(HDC hDC)
{
    
    BYTE *pBuf = DWnd.pVidBuffer;		/* top of buffer */
    BYTE *pEnd = pBuf + DWnd.oVidLastLine;	/* last line in buffer */
    register BYTE *lineptr = pBuf + DWnd.oCurrentLine; /* current line in buf */
    short base = DWnd.Ypos;	/* starting screen position of current line */
    short lines = DWnd.MaxLines;	/* number of lines in tty window */
    short length = DWnd.MaxLineLength;	/* byte increment to next line */
    short cheight = DWnd.CharHeight;
    register int i;

    SetBkMode(hDC, TRANSPARENT);
    SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));

  /* loop through the lines, starting with the current one */
    for (i = 0; i < lines; i++) {
        TextOut(hDC, 0, base, lineptr, strlen(lineptr));
  /* update the line pointer */
	if ((lineptr -= length) < pBuf)
	    lineptr = pEnd;
	base -= cheight;	/* move the position of the next line */
    }
}

/* process Kermit menu items */
void krmProcessKermitMenu(HWND hWnd, WORD menuitem)
{

    HANDLE hInstance = (HANDLE)GetWindowWord(hWnd, GWW_HINSTANCE);
    FARPROC fp;
    LPSTR boxstr;

    switch (menuitem) {

	case IDM_MXPARAMS:
	    fp = MakeProcInstance((FARPROC)SetMiscParams, hInstance);
	    DialogBox(hInstance, MAKEINTRESOURCE(DT_KRM_MXPARAMS),hWnd,fp);
	    FreeProcInstance(fp);
	    break;

	case IDM_CANCELFILE:
	    Kermit.fileabort = TRUE;
	    break;

	case IDM_CANCELBATCH:
	    Kermit.batchabort = TRUE;
	    break;

	case IDM_ERRORCANCEL:
	    Kermit.protocolabort = TRUE;
	    break;

	case IDM_CANCELPROTOCOL:
	    clsif(); clsof(TRUE);
	    ResetFTParams(hWnd, krmState);
	    krmState = 0;
	    break;

	case IDM_KRM_RECEIVE:
	    Kermit.sstate = 'v';
	    krmState = menuitem;	    
	    break;

	case IDM_KRM_FINISH:
	case IDM_KRM_LOGOUT:
	case IDM_KRM_BYE:
	    Kermit.sstate = 'g';
	    krmState = menuitem;
	    break;

	case IDM_KRM_GET:
	    Kermit.remotecmd = 'R';
	    Kermit.ids_title = IDS_KRM_GETFILES;
	    Kermit.nullstrOK = FALSE;
	    Kermit.sstate = 'r';
	    boxstr = MAKEINTRESOURCE(DT_KRM_GETFILE);
	    fp = MakeProcInstance((FARPROC)GetFileList, hInstance);
	    if (DialogBox(hInstance, boxstr, hWnd, fp))
		krmState = menuitem;
	    FreeProcInstance(fp);
	    break;

	case IDM_KRM_REMOTEHOST:
	    Kermit.remotecmd = 'C';
	    Kermit.ids_title = IDS_KRM_REMOTEHOST;
	    Kermit.nullstrOK = FALSE;
	    Kermit.sstate = 'c';
	    boxstr = MAKEINTRESOURCE(DT_KRM_GETFILE);
	    fp = MakeProcInstance((FARPROC)GetFileList, hInstance);
	    if (DialogBox(hInstance, boxstr, hWnd, fp))
		krmState = menuitem;
	    FreeProcInstance(fp);
	    break;

	case IDM_KRM_SEND:
	    Kermit.sstate = 's';
	    boxstr = MAKEINTRESOURCE(DT_KRM_SENDFILE);
	    fp = MakeProcInstance((FARPROC)SendFileDlgProc, hInstance);
	    if (DialogBox(hInstance, boxstr, hWnd, fp))
		krmState = menuitem;
	    FreeProcInstance(fp);
	    break;

	case IDM_KRM_CWD:
	    Kermit.sstate = 'g';
	    boxstr = MAKEINTRESOURCE(DT_KRM_REMOTE2);
	    fp = MakeProcInstance((FARPROC)krmRemoteChdir, hInstance);
	    if (DialogBox(hInstance, boxstr, hWnd, fp))
		krmState = menuitem;
	    FreeProcInstance(fp);
	    break;

	case IDM_KRM_REMOTEDIR:
	    Kermit.remotecmd = 'D';
	    Kermit.ids_title = IDS_KRM_REMOTEDIR;
	    Kermit.nullstrOK = TRUE;
	    Kermit.sstate = 'g';
  	    boxstr = MAKEINTRESOURCE(DT_KRM_GETFILE);
	    fp = MakeProcInstance((FARPROC)krmRemoteCmdDlgBox, hInstance);
	    if (DialogBox(hInstance, boxstr, hWnd, fp))
		krmState = menuitem;
	    FreeProcInstance(fp);
	    break;

	case IDM_KRM_REMOTETYPE:
	    Kermit.remotecmd = 'T';
	    Kermit.ids_title = IDS_KRM_REMOTETYPE;
	    Kermit.nullstrOK = FALSE;
	    Kermit.sstate = 'g';
  	    boxstr = MAKEINTRESOURCE(DT_KRM_GETFILE);
	    fp = MakeProcInstance((FARPROC)krmRemoteCmdDlgBox, hInstance);
	    if (DialogBox(hInstance, boxstr, hWnd, fp))
		krmState = menuitem;
	    FreeProcInstance(fp);
	    break;

	case IDM_KRM_REMOTEHELP:
	    Kermit.remotecmd = 'H';
	    Kermit.ids_title = IDS_KRM_REMOTEHELP;
	    Kermit.nullstrOK = TRUE;
	    Kermit.sstate = 'g';
  	    boxstr = MAKEINTRESOURCE(DT_KRM_GETFILE);
	    fp = MakeProcInstance((FARPROC)krmRemoteCmdDlgBox, hInstance);
	    if (DialogBox(hInstance, boxstr, hWnd, fp))
		krmState = menuitem;
	    FreeProcInstance(fp);
	    break;

	case IDM_KRM_REMOTEDEL:
	    Kermit.remotecmd = 'E';
	    Kermit.ids_title = IDS_KRM_REMOTEDEL;
	    Kermit.nullstrOK = FALSE;
	    Kermit.sstate = 'g';
  	    boxstr = MAKEINTRESOURCE(DT_KRM_GETFILE);
	    fp = MakeProcInstance((FARPROC)krmRemoteCmdDlgBox, hInstance);
	    if (DialogBox(hInstance, boxstr, hWnd, fp))
		krmState = menuitem;
	    FreeProcInstance(fp);
	    break;

	case IDM_KRM_REMOTEWHO:
	    Kermit.remotecmd = 'W';
	    Kermit.ids_title = IDS_KRM_REMOTEWHO;
	    Kermit.nullstrOK = TRUE;
	    Kermit.sstate = 'g';
  	    boxstr = MAKEINTRESOURCE(DT_KRM_GETFILE);
	    fp = MakeProcInstance((FARPROC)krmRemoteCmdDlgBox, hInstance);
	    if (DialogBox(hInstance, boxstr, hWnd, fp))
		krmState = menuitem;
	    FreeProcInstance(fp);
	    break;

	case IDM_KRM_REMOTESPACE:
	    Kermit.remotecmd = 'U';
	    Kermit.ids_title = IDS_KRM_REMOTESPACE;
	    Kermit.nullstrOK = TRUE;
	    Kermit.sstate = 'g';
  	    boxstr = MAKEINTRESOURCE(DT_KRM_GETFILE);
	    fp = MakeProcInstance((FARPROC)krmRemoteCmdDlgBox, hInstance);
	    if (DialogBox(hInstance, boxstr, hWnd, fp))
		krmState = menuitem;
	    FreeProcInstance(fp);
	    break;
    }
}

/* init protocol */
void tinit(HWND hWnd, int mode)
{

    HANDLE hInstance = (HANDLE)GetWindowWord(hWnd, GWW_HINSTANCE);
    FARPROC fp;
    TEXTMETRIC TM;
    HDC hDC;

    Kermit.fpTimer = MakeProcInstance((FARPROC)krmDoTimeout, hInstance);

    hDC = GetDC(hWnd);
    GetTextMetrics(hDC, &TM);
    Kermit.DispCharWidth = TM.tmAveCharWidth;
    Kermit.DispCharHeight = TM.tmHeight + TM.tmExternalLeading;
    ReleaseDC(hWnd, hDC);

    krmGrayMenus(hWnd, mode, MF_GRAYED);

    fp = MakeProcInstance((FARPROC)krmShowChildren, hInstance);
    EnumChildWindows(hWnd, fp, (LONG)SW_HIDE);
    FreeProcInstance(fp);

    switch(mode) {
	case IDM_KRM_SEND:
	    if (Kermit.waitsendtime > 0) {
		Kermit.waitsend = TRUE;
	        SetTimer(hWnd,KRM_WAITSEND,Kermit.waitsendtime,Kermit.fpTimer);
	    }
	case IDM_KRM_RECEIVE:
	case IDM_KRM_GET:
	    fpXfer = MakeProcInstance(krmXferDlgBox, hInstance);
	    hWndXfer = CreateDialog(hInstance,MAKEINTRESOURCE(DT_KRM_XFER),
						hWnd,fpXfer);
	    break;
    }

    Kermit.numtry = 0;
    Kermit.pktnum = 0;
    Kermit.pktcount = 0;

    Kermit.retrycount = 0;
    Kermit.bytesmoved = 0;
    Kermit.filesize = 0;
    Kermit.percentage = 0;

    Kermit.displayfile = 0;

    Kermit.inpacket = FALSE;
    Kermit.fileabort = FALSE;
    Kermit.batchabort = FALSE;
    Kermit.protocolabort = FALSE;
    Kermit.newname_flag = FALSE;

    Kermit.chksumtype = 1;
    remote.chksumtype = local.chksumtype;

    Kermit.ebqflg = FALSE;
    Kermit.ebq = 0;
    Kermit.rqf = -1;
    local.binquote = 'Y';

    Kermit.rptflg = FALSE;

    Kermit.hInFile = Kermit.hOutFile = NULL;
    Kermit.filename = NULL;

    Kermit.mstimeout = 1000 * remote.timeout;
    Kermit.timeout = FALSE;

    rcvpkt.state = PS_SYNCH;
    rcvpkt.type = 'N';
    rcvpkt.num = 0;

    sndpkt.len = 0;
}

/* hide or show terminal and status windows */
BOOL FAR PASCAL krmShowChildren(HWND hWnd, LONG lParam)
{
    ShowWindow(hWnd, LOWORD(lParam));
    return TRUE;
}

/* gray or restore menus */
static void NEAR krmGrayMenus(HWND hWnd, int mode, int style)
{

    register int i;
    register HMENU hMenu = GetMenu(hWnd);
    int opstyle, checkstyle;

    opstyle = (style == MF_GRAYED) ? MF_ENABLED : MF_GRAYED;
    checkstyle = (style == MF_GRAYED) ? MF_CHECKED : MF_UNCHECKED;
    
    for (i = IDM_KRM_SEND; i <= IDM_KRM_REMOTEHOST; i++)
	EnableMenuItem(hMenu, i, style);
    for (i = IDM_CANCELFILE; i <= IDM_CANCELPROTOCOL; i++)
	EnableMenuItem(hMenu, i, opstyle);
    CheckMenuItem(hMenu, mode, checkstyle);
}

/* show result of transfer based on exit state */
static void NEAR krmShowResult(HWND hWnd, int mode)
{

    int len;
    int style;
    int msgnum;
    char szMessage[80];
    char szCaption[40];
    HANDLE hInstance = (HANDLE)GetWindowWord(hWnd, GWW_HINSTANCE);

    if ((msgnum = Kermit.sstate) >= 0)
	msgnum = KRM_USERCNX;

    if (msgnum == KRM_COMPLETE)
	style = MB_OK | MB_ICONASTERISK;
   else
        style = MB_OK | MB_ICONEXCLAMATION;

  /* get the string corresponding to msgnum */
    LoadString(hInstance, IDS_KRM_KERMIT, (LPSTR)szCaption, sizeof(szCaption));
    len = strlen(szCaption);
    LoadString(hInstance, mode, (LPSTR)(szCaption+len),sizeof(szCaption) - len);
    if (((msgnum == KRM_COMPLETE) && (rcvpkt.len > 0)) ||
		((msgnum == KRM_ERROR_PKT) && (rcvpkt.len > 0)))  
	strcpy(szMessage,rcvpkt.data);
    else
        LoadString(hInstance, msgnum, (LPSTR)szMessage, sizeof(szMessage));

  /* put up this simple message box */
    if (Kermit.bell)
	MessageBeep(style);

    MessageBox(hWnd,(LPSTR)szMessage, (LPSTR)szCaption, style);
}

/* reset things after transaction */
static void near ResetFTParams(HWND hWnd, int mode)
{

    FARPROC fp;
    HANDLE hInstance = (HANDLE)GetWindowWord(hWnd, GWW_HINSTANCE);

    KillTimer(hWnd, KRM_WAITPACKET);
    krmGrayMenus(hWnd, mode, MF_ENABLED);
    krmShowResult(hWnd, mode);

    switch(mode) {
	case IDM_KRM_RECEIVE:
	case IDM_KRM_GET:
	case IDM_KRM_SEND:
	    if (hWndXfer != NULL) {
		DestroyWindow(hWndXfer);
		hWndXfer = NULL;
		FreeProcInstance(fpXfer);
	    }
	    break;

	case IDM_KRM_BYE:
	    if (Kermit.sstate == KRM_COMPLETE)
	        PostMessage(hWnd, WM_SYSCOMMAND, SC_CLOSE,0L);
	    break;
    }

    if (DWnd.hVidBuffer != NULL) {
	DWnd.pVidBuffer = NULL;
	LocalUnlock(DWnd.hVidBuffer);
	DWnd.hVidBuffer = LocalFree(DWnd.hVidBuffer);
	InvalidateRect(hWnd, (LPRECT)NULL, TRUE);
    }

    fp = MakeProcInstance((FARPROC)krmShowChildren, hInstance);
    EnumChildWindows(hWnd, fp, (LONG)SW_RESTORE);
    FreeProcInstance(fp);

    if (Kermit.hRemoteCommand != NULL) {
	Kermit.pRemoteCommand = NULL;
	LocalUnlock(Kermit.hRemoteCommand);
	Kermit.hRemoteCommand = LocalFree(Kermit.hRemoteCommand);
    }

    if (Kermit.hfilelist != NULL) {
	Kermit.pfilelist = NULL;
	LocalUnlock(Kermit.hfilelist);
	Kermit.hfilelist = LocalFree(Kermit.hfilelist);
    }
    FreeProcInstance(Kermit.fpTimer);
}

/* adjust height of long displays if window is resized */
void krmAdjustHeight(short width, short height)
{
     
    DWnd.Width = width;
    DWnd.Height = height;
    DWnd.Ypos = height - DWnd.CharHeight - 1;
    InvalidateRect(DWnd.hWnd, (LPRECT)NULL, TRUE);
}

/* init display for long replies */
BOOL krmInitMainDisplay(HWND hWnd, short cwidth, short cheight)
{

    HDC hDC;
    RECT drect;

    DWnd.hWnd = hWnd;
    DWnd.CharWidth = cwidth;
    DWnd.CharHeight = cheight;

    if (DWnd.MaxCols == 0) {
	hDC = GetDC(hWnd);
	DWnd.MaxCols = GetDeviceCaps(hDC, HORZRES) / cwidth; 
	DWnd.MaxLines = GetDeviceCaps(hDC, VERTRES) / cheight;
    	DWnd.MaxLineLength = DWnd.MaxCols + 1;
	ReleaseDC(hWnd, hDC);
    }
    GetClientRect(hWnd, (LPRECT)&drect);
    DWnd.Width = drect.right;
    DWnd.Height = drect.bottom;

    if (DWnd.hVidBuffer = LocalAlloc(LPTR,DWnd.MaxLines * DWnd.MaxLineLength)) {
	DWnd.pVidBuffer = LocalLock(DWnd.hVidBuffer);
	DWnd.oCurrentLine = 0;
	DWnd.oVidLastLine = DWnd.MaxLineLength * (DWnd.MaxLines - 1);
	DWnd.CurrLineOffset = 0;
	DWnd.Xpos = 0;
    	DWnd.Ypos = DWnd.Height - DWnd.CharHeight - 1;
	return TRUE;
    }
    return (FALSE);
}

/* display received strings on long replies */
void krmMainStringDisplay(HWND hWnd, BYTE *str, short len)
{

    HDC hDC;
    register BYTE *ptr;
    register short ctr;
    short cols = DWnd.MaxCols;
    short width = DWnd.Width;
    short toff, txpos;
    BYTE *tBuf;
 
    while (len) {
	ptr = str;
	ctr = 0;
	txpos = DWnd.Xpos;
        tBuf = DWnd.pVidBuffer + DWnd.oCurrentLine;
	toff =  DWnd.CurrLineOffset;
  /* first loop on any printable characters until none left or end of line */

	while ((*ptr &= 0x7f) >= SP) {
	    if ((len) && (toff < cols))	{
		ctr += 1;
		*(tBuf + toff++) = *ptr++;	/* store them in the buffer */
		txpos += DWnd.CharWidth;
		len -= 1;
	    }
	    else		/* quit if we find a control character */
		break;
	}
	if (ctr) {		/* if we have some characters, show them */
	    if (!IsIconic(hWnd)) {
	        hDC = GetDC(hWnd);
		SetBkMode(hDC, TRANSPARENT);
		SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));
	        TextOut(hDC, DWnd.Xpos, DWnd.Ypos, (LPSTR)str, ctr);
	        ReleaseDC(hWnd, hDC);
	    }
    /* if not at end of line yet */
	    if (toff < cols) {		/* update buffer and caret variables */ 
		DWnd.CurrLineOffset = toff;
		DWnd.Xpos = txpos;
	    }
	    else {		/* end of line, so wrap */
		DoMCR(&DWnd);
		DoMLF(&DWnd);
	    }
	}
	while ((*ptr &= 0x7f) < SP) {	/* now loop on any control characters */
	    if (len) {
		switch (*ptr) {
		    case BEL:
			MessageBeep(0);
			break;
		    case HT:
			DoMTab(&DWnd);
			break;
		    case BS:
			DoMBS(&DWnd);	
			break;
		    case LF:
			DoMLF(&DWnd);
			break;
		    case CR:
			DoMCR(&DWnd);
			break;
		}
		len -= 1;		/* reduce buffer count */
		ptr++;			/* update pointer */
	    }
	    else
		break;			/* no more control characters */
	}
	str = ptr;	/* reset string pointer and do again if len > 0 */
    }
}

/* do carriage return */
static void NEAR DoMCR(PDISPLAYWND pDWnd)
{

    pDWnd->Xpos = pDWnd->CurrLineOffset = 0;

}

/* Do a line feed */
static void NEAR DoMLF(PDISPLAYWND pDWnd)
{

    register BYTE *pCurr;
    register int i;
    short cols = pDWnd->MaxCols;
    short offset;
    RECT scrollrect;


  /* update the circular text buffer parameters */

    if ((pDWnd->oCurrentLine += pDWnd->MaxLineLength) > pDWnd->oVidLastLine)
	pDWnd->oCurrentLine = 0;
    pCurr = pDWnd->pVidBuffer + pDWnd->oCurrentLine;
    offset = pDWnd->CurrLineOffset;

  /* fill in any places from beginning of line with spaces */

    for (i = 0; i < offset; i++)
	*(pCurr + i) = SP;

  /* fill in the rest with nuls */

    for (i = offset; i < cols; i++)
        *(pCurr + i) = NUL;

  /* now move the tty window up exactly one line and refresh it */
    if (!IsIconic(pDWnd->hWnd)) {
        GetClientRect(pDWnd->hWnd, (LPRECT)&scrollrect);
    	ScrollWindow(pDWnd->hWnd,0,-pDWnd->CharHeight,
		     (LPRECT)&scrollrect,(LPRECT)NULL);
    	UpdateWindow(pDWnd->hWnd);
    }
}

/* Horizontal tab */
static void NEAR DoMTab(PDISPLAYWND pDWnd)
{

    RECT myrect;
    BYTE *pCurr = pDWnd->pVidBuffer + pDWnd->oCurrentLine;
    short cols = pDWnd->MaxCols;
    register short xpos = pDWnd->Xpos;		
    register short curoffset = pDWnd->CurrLineOffset;

    if (curoffset < (cols - 1)) {
        do {
	    if (*(pCurr + curoffset) == NUL)  /* if null, replace with space */
	        *(pCurr + curoffset) = SP;
	    curoffset += 1;		/* update offsets */
	    xpos += DWnd.CharWidth;
        } while ((curoffset % 8 != 0) && (curoffset < (cols - 1)));

    /* now invalidate the part of the screen affected */
   	if (!IsIconic(pDWnd->hWnd)) {
	    SetRect((LPRECT)&myrect, pDWnd->Xpos, pDWnd->Ypos, pDWnd->Width,
							pDWnd->Height);
	    InvalidateRect(pDWnd->hWnd, (LPRECT)&myrect, TRUE);
	}
    /* finally, reset the tty window variables */
        pDWnd->Xpos = xpos;
        pDWnd->CurrLineOffset = curoffset;
    }
}

/* Backspace */
static void NEAR DoMBS(PDISPLAYWND pDWnd)
{

  /* don't back up if at beginning of line! */
    if (pDWnd->CurrLineOffset > 0) {
	pDWnd->Xpos -= pDWnd->CharWidth;
	pDWnd->CurrLineOffset -= 1;
    }
}
