/*
 * Windows H19 Terminal Emulator Function Support Module
 * 
 * Written by William S. Hall
 *	      3665 Benton Street, #66
 *	      Santa Clara, CA 95051
 */

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

#include "winasc.h"
#include "winh19.h"
#include "win19d.h"

#if defined(KERMIT)
#include "winkpf.h"
extern krmState;
#endif

/* ansi emulation manifests */
#define MAX_PARAM_LIST	32
#define BEGIN_ANSI_COMMAND	10
#define ANSI_INDIGIT		11
#define ANSI_ENDDIGIT		12
#define ANSI_MODE		13
#define ANSI_SET		14
#define ANSI_MODE_INDIGIT	15
#define ANSI_SET_INDIGIT	16
#define ANSI_MODE_ENDDIGIT	17
#define ANSI_SET_ENDDIGIT	18

/* ansi emulation local parameters */
static int ParamListIndex;
static int ANSIParam;
static int ANSIParamList[MAX_PARAM_LIST + 1];
static int NEAR InDigitTransition(BYTE ch, unsigned instate, unsigned endstate, 
					void (NEAR *action)(BYTE));
static void NEAR PositionRow(BYTE y);
static void NEAR PositionCol(BYTE x);
static void NEAR SwitchActiveWindow(HWND hWnd);
static void NEAR ProcessESCCommand(BYTE ch);
static void NEAR MoveStatWindow(short height);
static void NEAR ProcessBaudChange(int);
static void NEAR IdentifyTerm(void);
static void NEAR ReportCursorPosition(short line, short col);
static BOOL NEAR DoKeyTranslation(unsigned message, WORD *wparam);
static void NEAR DoANSICommand(int state, BYTE ch);
static void NEAR ANSISetCommand(BYTE ch);
static void NEAR ANSIModeCommand(BYTE ch);
static void NEAR PlaceInList(int val);
static void NEAR ANSICommand(BYTE ch);
static void NEAR DoHeathCommand(int state, BYTE ch);
static BOOL NEAR InNumpadSet(unsigned val);
static WORD NEAR EditToNumpadShort(unsigned val);
static WORD NEAR NumpadToEdit(unsigned val);
static WORD NEAR EditToNumpadLong(unsigned val);

/* special key handler when off-line */
void NEAR H19LocalKeyDown(WORD keycode)
{

    switch (keycode) {
	case VK_UP:
	    SendMessage(hWndActive,WH19_COMMAND,H19_MOVECURSORUP,1L);
	    break;
	case VK_DOWN:
	    SendMessage(hWndActive,WH19_COMMAND,H19_MOVECURSORDOWN,1L);
	    break;
	case VK_RIGHT:
	    SendMessage(hWndActive,WH19_COMMAND,H19_MOVECURSORRIGHT,1L);
	    break;
	case VK_LEFT:
	    SendMessage(hWndActive,WH19_COMMAND,H19_MOVECURSORLEFT,1L);
	    break;
	case VK_HOME:
	    CD.ICToggle = (CD.ICToggle ? FALSE : TRUE);
	    break;
	case VK_END:
	    SendMessage(hWndActive, WH19_COMMAND, H19_INSERTLINE,1L);
	    break;
	case VK_PRIOR:
	    SendMessage(hWndActive, WH19_COMMAND,H19_DELETECHAR,1L);
	    break;
	case VK_NEXT:
	    SendMessage(hWndActive, WH19_COMMAND, H19_DELETELINE,1L);
	    break;
	case VK_CLEAR:
	    SwitchActiveWindow(TW.hWnd);
	    SendMessage(hWndActive, WH19_COMMAND, H19_CURSORHOME,0L);
	    break;
	case VK_F6:
	    if (GetKeyState(VK_SHIFT) & 0x8000)
		SendMessage(hWndActive, WH19_COMMAND, H19_CLRSCREEN,0L);
	    else
		SendMessage(hWndActive, WH19_COMMAND, H19_CLRTOENDOFSCREEN,0L);
	    break;

    }
}

/* communications processor */
BOOL NEAR DoMessage()
{

    MSG msg;

    if (PeekMessage((LPMSG)&msg,NULL,0,0,PM_REMOVE)) {
        if (msg.message == WM_QUIT)
	    exit((int)msg.wParam);
	if (TranslateAccelerator(MW.hWnd,hAccel,(LPMSG)&msg) == 0) {
	    if (DoKeyTranslation(msg.message, &msg.wParam))
                TranslateMessage((LPMSG)&msg);
            DispatchMessage((LPMSG)&msg);
	}
	return TRUE;
    }
    return FALSE;
}

/* return TRUE if key is to be translated */
static BOOL NEAR DoKeyTranslation(unsigned message, WORD *wparam)
{

    register unsigned shiftstate, numstate;
    int retval = TRUE;

    if ((message == WM_KEYDOWN) || (message == WM_KEYUP)) {
	if (*wparam == VK_CANCEL)
	     return FALSE;

	if (*wparam == VK_BACK) {
	    if (GetKeyState(VK_CONTROL) & 0x8000)
		return FALSE;
	    return TRUE;
	}

        numstate = GetKeyState(VK_NUMLOCK);
        shiftstate = (GetKeyState(VK_SHIFT) & 0x8000) >> 15;

	if (CD.ShiftedKeypad) {
	    if (numstate | shiftstate) {
	        if (InNumpadSet(*wparam))
		    *wparam = NumpadToEdit(*wparam);
		else
		    *wparam = EditToNumpadLong(*wparam);
	    }		
	    else
	        *wparam = EditToNumpadShort(*wparam);
	}
	else {
	    if (!(numstate | shiftstate))
	        *wparam = EditToNumpadLong(*wparam);
	    else
	        *wparam = EditToNumpadShort(*wparam);
	}
	if (CD.AltKeypad)
	    if (InNumpadSet(*wparam))
	        retval = FALSE;
    }
    return retval;
}

/* support modules for key translation */
static BOOL NEAR InNumpadSet(unsigned val)
{

    return(((val >= VK_NUMPAD0) && (val <= VK_NUMPAD9)) || (val == VK_DECIMAL));

}

static WORD NEAR NumpadToEdit(unsigned val)
{

    switch (val) {
	case VK_NUMPAD1:
	    val = VK_END;
	    break;
	case VK_NUMPAD2:
	    val = VK_DOWN;
	    break;
	case VK_NUMPAD3:
	    val = VK_NEXT;
	    break;
	case VK_NUMPAD4:
	    val = VK_LEFT;
	    break;
	case VK_NUMPAD5:
	    val = VK_CLEAR;
	    break;
	case VK_NUMPAD6:
	    val = VK_RIGHT;
	    break;
	case VK_NUMPAD7:
	    val = VK_HOME;
	    break;
	case VK_NUMPAD8:
	    val = VK_UP;
	    break;
	case VK_NUMPAD9:
	    val = VK_PRIOR;
	    break;
    }
    return val;
}

static WORD NEAR EditToNumpadShort(unsigned val)
{
    switch (val) {
	case VK_DELETE:
	    val = VK_DECIMAL;
	    break;
	case VK_INSERT:
	    val = VK_NUMPAD0;
	    break;
    }
    return val;
}

static WORD NEAR EditToNumpadLong(unsigned val)
{

    switch (val) {
	case VK_DELETE:
	    val = VK_DECIMAL;
	    break;
	case VK_INSERT:
	    val = VK_NUMPAD0;
	    break;
	case VK_END:
	    val = VK_NUMPAD1;
	    break;
	case VK_DOWN:
	    val = VK_NUMPAD2;
	    break;
	case VK_NEXT:
	    val = VK_NUMPAD3;
	    break;
	case VK_LEFT:
	    val = VK_NUMPAD4;
	    break;
	case VK_CLEAR:
	    val = VK_NUMPAD5;
	    break;
	case VK_RIGHT:
	    val = VK_NUMPAD6;
	    break;
	case VK_HOME:
	    val = VK_NUMPAD7;
	    break;
	case VK_UP:
	    val = VK_NUMPAD8;
	    break;
	case VK_PRIOR:
	    val = VK_NUMPAD9;
	    break;
    }
    return val;
}

/* process string from comm port */
int NEAR H19StringInput(BYTE *str, short len)
{

    register BYTE *ptr;
    register short ctr;
    short state;
    BYTE ch;
    int numrem;

    while (len) {
	if (state = CD.CommandState) {
	    ch = *str & 0x7f;
	    if (CD.ANSIMode)
		DoANSICommand(state, ch);
	    else
		DoHeathCommand(state, ch);
	    str++;
	    len -= 1;
	}
	else {
	    ctr = 0;
	    ptr = str;
	    while (len) {
	        if ((*ptr &= 0x7f) != ESC) {
		    ctr += 1;
		    ptr++;
		    len -= 1;
		}
		else {
		    ptr++;
		    len -= 1;
		    CD.CommandState = ESC_COMMAND;
		    break;
		}
	    }
	    if (ctr) {
	        numrem = (int)SendMessage(hWndActive,WH19_STRINGINPUT,(WORD)ctr,
					(LONG)(LPSTR)str);
	        if (numrem)
		    return(len + numrem);		
	    }
	    str = ptr;
	}
    }
    return (len);
}

/* start ansi processing */
static void NEAR DoANSICommand(int state, BYTE ch)
{

    register int newstate;

    if (ch == CAN)
	newstate = NO_COMMAND;
    else if (ch == ESC)
	newstate = ESC_COMMAND;
    else {
        switch(state) {
	    case ESC_COMMAND:
		switch (ch) {
		    case '[':
			ParamListIndex = 0;
			ANSIParam = 0;
			ANSIParamList[0] = 0;
			newstate = BEGIN_ANSI_COMMAND;
			break;
		    case 'M':
			SendMessage(hWndActive, WH19_COMMAND,
					 H19_REVERSELINEFEED,0L);
			newstate = NO_COMMAND;
			break;
		    case '=' :		/* enter alternate keypad */
			CD.AltKeypad = TRUE;
			newstate = NO_COMMAND;
			break;
		    case '>' :		/* exit alternate keypad */
			CD.AltKeypad = FALSE;
			newstate = NO_COMMAND;
			break;
		    default:
			newstate = NO_COMMAND;
			break;
		}
		break;

	    case BEGIN_ANSI_COMMAND:
		switch(ch) {
		    case '?':
			newstate = ANSI_MODE;
			break;
		    case '>':
			newstate = ANSI_SET;
			break;
		    default:
			newstate = InDigitTransition(ch, ANSI_INDIGIT,
					ANSI_ENDDIGIT, ANSICommand);
			break;
		}
		break;

	    case ANSI_INDIGIT:
	    case ANSI_ENDDIGIT:
	    	newstate = InDigitTransition(ch, ANSI_INDIGIT,
				ANSI_ENDDIGIT, ANSICommand);
		break;	

	    case ANSI_MODE:
	    case ANSI_MODE_INDIGIT:
	    case ANSI_MODE_ENDDIGIT:
		newstate = InDigitTransition(ch, ANSI_MODE_INDIGIT, 
				ANSI_MODE_ENDDIGIT,ANSIModeCommand);
		break;

	    case ANSI_SET:
	    case ANSI_SET_INDIGIT:
	    case ANSI_SET_ENDDIGIT:
		newstate = InDigitTransition(ch, ANSI_SET_INDIGIT, 
				ANSI_SET_ENDDIGIT,ANSISetCommand);
		break;
	}
    }
    CD.CommandState = newstate;
}

/* support modules for ansi emulation */
static int NEAR InDigitTransition(BYTE ch, unsigned instate, unsigned endstate, 
					void (NEAR *action)(BYTE))
{
    register int state;

    if (isdigit(ch)) {
        ANSIParam = 10 * ANSIParam + ch - '0';
	state = instate;
    }
    else if (ch == ';') {
        PlaceInList(ANSIParam);
	state = endstate;
    }
    else {
    	PlaceInList(ANSIParam);
	(*action)(ch);
	state = NO_COMMAND;
    }
    return state;
}

static void NEAR ANSISetCommand(BYTE ch)
{

    register int i;

    if (ch == 'h')
	for (i = 0; i < ParamListIndex; i++)
	    ProcessSetCommand(ANSIParamList[i]);
    else if (ch == 'l')
	for (i = 0; i < ParamListIndex; i++)
	    ProcessResetCommand(ANSIParamList[i]);

}

static void NEAR ANSIModeCommand(BYTE ch)
{

    register int i;
    
    if (ch == 'h') {
	for (i = 0; i < ParamListIndex; i++) {
	    switch (ANSIParamList[i]) {
	        case 2:
		    CD.ANSIMode = FALSE;
		    return;
	        case 7:
	            CD.WrapAround = TRUE;
		    break;
	    }
	}
    }
    else if (ch == 'l') {
	for (i = 0; i < ParamListIndex; i++) {
	    switch (ANSIParamList[i]) {
	        case 7:
	            CD.WrapAround = FALSE;
	            break;
	    }
        }
    }
}

static void NEAR PlaceInList(int val)
{

    if (ParamListIndex < MAX_PARAM_LIST)
	ANSIParamList[ParamListIndex++] = val;
    ANSIParam = 0;

}

static void NEAR ANSICommand(BYTE ch)
{

    register int val1 = ANSIParamList[0]; 
    register int val2 = ANSIParamList[1];
    int i;
    
    switch(ch) {
	case 'A' :		/* move cursor up */
	    if (val1 == 0)
		val1 = 1;
	    SendMessage(hWndActive, WH19_COMMAND, H19_MOVECURSORUP,(LONG)val1);
	    break;
	case 'B' :		/* move cursor down */
	    if (val1 == 0)
		val1 = 1;
	    SendMessage(hWndActive, WH19_COMMAND, H19_MOVECURSORDOWN,(LONG)val1);
	    break;
	case 'C' :
	    if (val1 == 0)
		val1 = 1;
    	    SendMessage(hWndActive,WH19_COMMAND,H19_MOVECURSORRIGHT,(LONG)val1);
	    break;
	case 'D' :
	    if (val1 == 0)
		val1 = 1;
	    SendMessage(hWndActive,WH19_COMMAND,H19_MOVECURSORLEFT,(LONG)val1);
	    break;
	case 'f':
	case 'H':
	    switch(ParamListIndex) {
		case 0:
		    PositionRow(0);
		    PositionCol(0);
		    break;
		case 1:
		    if (val1 == 0)
			val1 = 1;
		    PositionRow(LOBYTE(val1 - 1));
		    PositionCol(0);
		    break;
		default:
		    if (val1 == 0)
			val1 = 1;
		    if (val2 == 0)
			val2 = 1;
		    PositionRow(LOBYTE(val1 - 1));
		    PositionCol(LOBYTE(val2 - 1));
		    break;
	    }
	    break;
	case 'J':
	    if (ParamListIndex == 0)
		SendMessage(hWndActive,WH19_COMMAND,H19_CLRTOENDOFSCREEN,0L);
	    else {
	        switch(val1) {
		    case 0:
		        SendMessage(hWndActive,WH19_COMMAND,H19_CLRTOENDOFSCREEN,0L);
			break;
		    case 1:
		        SendMessage(hWndActive, WH19_COMMAND, H19_CLRTOTOPOFSCREEN,0L);
		        break;
		    case 2:
			SendMessage(hWndActive, WH19_COMMAND, H19_CLRSCREEN,0L);
			break;
		}
	    }
	    break;
	case 'K':
	    if (ParamListIndex == 0)
		SendMessage(hWndActive, WH19_COMMAND, H19_CLRTOENDOFLINE,0L);
	    else {
	        switch(val1) {
		    case 0:
		        SendMessage(hWndActive, WH19_COMMAND, H19_CLRTOENDOFLINE,0L);
			break;
		    case 1:
		        SendMessage(hWndActive, WH19_COMMAND, H19_CLRTOSTARTOFLINE,0L);
			break;
		    case 2:
			SendMessage(hWndActive, WH19_COMMAND, H19_ERASELINE,0L);
			break;
		}
	    }
	    break;
	case 'L' :		/* insert line */
	    if (val1 == 0)
		val1 = 1;
	    SendMessage(hWndActive, WH19_COMMAND, H19_INSERTLINE,(LONG)val1);
	    break;
	case 'M' :		/* delete line */
	    if (val1 == 0)
		val1 = 1;
	    SendMessage(hWndActive, WH19_COMMAND, H19_DELETELINE,(LONG)val1);
	    break;

	case 'P' :		/* delete char */
	    if (val1 == 0)
		val1 = 1;
	    SendMessage(hWndActive, WH19_COMMAND,H19_DELETECHAR,(LONG)val1);
	    break;
	case 'h' :
	    for (i = 0; i < ParamListIndex; i++) {
	        switch (ANSIParamList[i]) {
		    case 2:
		        CD.KeyboardDisabled = TRUE;
		        break;
		    case 4:
		        CD.ICToggle = TRUE;
		        break;
		    case 20:
		        CD.CRonLF = TRUE;
		        break;
		}
	    }
	    break;
	case 'l' :
	    for (i = 0; i < ParamListIndex; i++) {
	        switch (ANSIParamList[i]) {
		    case 2:
		        CD.KeyboardDisabled = FALSE;
		        break;
		    case 4:
		        CD.ICToggle = FALSE;
		        break;
		    case 20:
		        CD.CRonLF = FALSE;
		        break;
		}
	    }
	    break;
	case 'm':
	    for (i = 0; i < ParamListIndex; i++) {
	        switch (ANSIParamList[i]) {
		    case 0:
		        CD.InverseVideo = FALSE;
		        CD.CharAttribute = 0;
		        break;
		    case 7:
		        CD.InverseVideo = TRUE;
		        CD.CharAttribute = 0x80;
		        break;
		    case 10:
		        CD.GraphicsMode = TRUE;
		        break;
		    case 11:
		        CD.GraphicsMode = FALSE;
		        break;
		}
	    }
	    break;
	case 'n' :
	    switch(val1) {
		case 6:
		    if (CD.LineState == IDM_ONLINE) {
			short currow, curcol;
			SendMessage(hWndActive, WH19_CURSORPOSITION,
				    H19_GETCURSOR,MAKELONG(&currow, &curcol));
			ReportCursorPosition(currow,curcol);
		    }
	    }
	    break;
	case 'p':
	    SendScreen(&TW);
	    break;
	case 'q':
	    SendStatusLine(&SW);
	    break;
	case 'r':
	    ProcessBaudChange(val1 - 1);
	    break;
	case 's' :
	    SendMessage(hWndActive, WH19_CURSORPOSITION,H19_SAVECURSOR, 0L);
	    break;
	case 'u' :
	    PositionRow((BYTE)CD.CurSaveRow);
	    PositionCol((BYTE)CD.CurSaveCol);
	    break;
	case 'z':
	    ResetTerminal();
	    break;
    }
}

/* Heath emulation modules */
static void NEAR DoHeathCommand(int state, BYTE ch)
{

    if (ch == CAN)
	CD.CommandState = NO_COMMAND;
    else if (ch == ESC)
	CD.CommandState = ESC_COMMAND;
    else {
        switch(state) {
            case ESC_COMMAND:
		ProcessESCCommand(ch);
		break;
	    case YPOS_COMMAND:
		PositionRow((BYTE)(ch - SP));
		break;
	    case XPOS_COMMAND:
		PositionCol((BYTE)(ch - SP));
		break;
	    case SET_COMMAND:
		ProcessSetCommand((int)(ch - '0'));
		break;
	    case RESET_COMMAND:
		ProcessResetCommand((int)(ch - '0'));
		break;
	    case SETBAUD_COMMAND:
		ProcessBaudChange((int)(ch - 'A'));
		break;
	}
    }
}

static void NEAR PositionRow(BYTE y)
{

    register short maxlines = TW.MaxLines;

    CD.CommandState = XPOS_COMMAND;

    if ((y >= 0) && (y < (BYTE)maxlines)) {
	SwitchActiveWindow(TW.hWnd);
	SendMessage(TW.hWnd,WH19_COMMAND,H19_POSITIONCURSORROW,(LONG)y);
    }
    else if (y == (BYTE)maxlines)
	if (CD.StatOpen)
	    SwitchActiveWindow(SW.hWnd);

}

static void NEAR PositionCol(BYTE x)
{

    register short cols = TW.MaxCols;

    CD.CommandState = NO_COMMAND;

    if ((x < 0) || (x >= (BYTE)cols))
	x = (BYTE)(cols - 1);

    SendMessage(hWndActive, WH19_COMMAND, H19_POSITIONCURSORCOL, (LONG)x);

}

static void NEAR SwitchActiveWindow(HWND hWnd)
{

    if (hWndActive != hWnd) {
	SendMessage(hWndActive, WH19_CARETFUNCTION, H19_DESTROYCARET, 
			(LONG)CD.OwnCaret);
	hWndActive = hWnd;
	SendMessage(hWndActive, WH19_CARETFUNCTION, H19_CREATECARET,
			(LONG)CD.OwnCaret);
    }	
}

void ProcessSetCommand(int ch)
{

    CD.CommandState = NO_COMMAND;

    switch(ch) {
	case 1 :		/* enable status line */
	    if (!CD.StatOpen) {
		CD.StatOpen = TRUE;
		memset(SW.pVidBuffer, SP, TW.MaxCols);
                MoveStatWindow(MW.Height);
		InvalidateRect(SW.hWnd, (LPRECT)NULL, TRUE);
		UpdateWindow(SW.hWnd);
		if (hWndActive == SW.hWnd)
		    SendMessage(hWndActive,WH19_CARETFUNCTION,H19_SHOWCARET,0L);
		SendMessage(hWndActive, WH19_COMMAND, H19_ADJUSTWINDOW,0L);
	    }
	    break;

	case 2 :		/* no key click */
	    CD.KeyClick = FALSE;
	    break;

	case 3 :		/* hold screen mode */
	    CD.HoldScreen = TRUE;
	    break;

	case 4 :		/* block cursor */
	    if (!CD.BlockCursor) {
		CD.BlockCursor = TRUE;
	        SendMessage(hWndActive,WH19_CARETFUNCTION,H19_DESTROYCARET,
				(LONG)CD.OwnCaret);
	        SendMessage(hWndActive,WH19_CARETFUNCTION,H19_CREATECARET,
				(LONG)CD.OwnCaret);
	    }
	    break;

	case 5 :		/* cursor off */
	    SendMessage(hWndActive,WH19_CARETFUNCTION,H19_HIDECARET,0L);
	    CD.CursorOff = TRUE;
	    break;

	case 6 :		/* keypad shifted */
	    CD.ShiftedKeypad = TRUE;
	    break;

	case 7 :		/* alternate keypad */
	    CD.AltKeypad = TRUE;
	    break;	    

	case 8 :		/* auto lf on cr */
	    CD.LFonCR = TRUE;
	    break;

	case 9 :		/* auto cr on lf */
	    CD.CRonLF = TRUE;	
	    break;
    }
}

void ProcessResetCommand(int ch)
{

    CD.CommandState = NO_COMMAND;

    switch(ch) {
	case 1 :
	    if (CD.StatOpen) {
		CD.StatOpen = FALSE;
		memset(SW.pVidBuffer, SP, TW.MaxCols);
	        InvalidateRect(SW.hWnd, (LPRECT)NULL, TRUE);
		UpdateWindow(SW.hWnd);
                MoveStatWindow(MW.Height);
		if (hWndActive == SW.hWnd)
		    SendMessage(hWndActive,WH19_CARETFUNCTION,H19_HIDECARET,0L);
	    }
	    break;

	case 2 :		/* enable key click */
	    CD.KeyClick = TRUE;
	    break;

	case 3 :		/* exit hold screen mode */
	    CD.HoldScreen = FALSE;
	    break;

	case 4 :
	    if (CD.BlockCursor) {
		CD.BlockCursor = FALSE;
	        SendMessage(hWndActive, WH19_CARETFUNCTION,H19_DESTROYCARET, 
				(LONG)CD.OwnCaret);
	        SendMessage(hWndActive, WH19_CARETFUNCTION,H19_CREATECARET,
				(LONG)CD.OwnCaret);
	    }
	    break;

	case 5 :		/* cursor on */
	    CD.CursorOff = FALSE;
	    SendMessage(hWndActive,WH19_CARETFUNCTION,H19_SHOWCARET,0L);
	    break;

	case 6 :		/* keypad unshifted */
	    CD.ShiftedKeypad = FALSE;
	    break;

	case 7 :		/* exit alternate keypad */
	    CD.AltKeypad = FALSE;
	    break;
	    
	case 8 :		/* no auto lf on cr */
	    CD.LFonCR = FALSE;
	    break;

	case 9 :		/* no auto cr on lf */
	    CD.CRonLF = FALSE;
	    break;
    }
}

static void NEAR ProcessESCCommand(BYTE ch)
{

    CD.CommandState = NO_COMMAND;

    switch(ch) {
	case '#' :		/* transmit page */
	    SendScreen(&TW);
	    break;

	case '<' :		/* enter ansi mode */
	    CD.ANSIMode = TRUE;
	    break;

	case '=' :		/* enter alternate keypad */
	    CD.AltKeypad = TRUE;
	    break;

	case '>' :		/* exit alternate keypad */
	    CD.AltKeypad = FALSE;
	    break;

	case '@' :		/* enter insert char mode */
	    CD.ICToggle = TRUE;
	    break;

	case 'A' :		/* move cursor up */
	    SendMessage(hWndActive, WH19_COMMAND, H19_MOVECURSORUP,1L);
	    break;

	case 'B' :		/* move cursor down */
	    SendMessage(hWndActive, WH19_COMMAND, H19_MOVECURSORDOWN,1L);
	    break;

	case 'C' :		/* move cursor right */
	    SendMessage(hWndActive, WH19_COMMAND, H19_MOVECURSORRIGHT,1L);
	    break;

	case 'D' :		/* move curosr left */
	    SendMessage(hWndActive, WH19_COMMAND, H19_MOVECURSORLEFT,1L);
	    break;

	case 'E' :		/* home and clear */
	    SendMessage(hWndActive, WH19_COMMAND, H19_CLRSCREEN,0L);
	    break;

	case 'F' :		/* enter graphics mode */
	    CD.GraphicsMode = TRUE;
	    break;

	case 'G' :		/* exit graphics mode */
	    CD.GraphicsMode = FALSE;
	    break;

	case 'H' :		/* home cursor */
	    SwitchActiveWindow(TW.hWnd);
	    SendMessage(hWndActive, WH19_COMMAND, H19_CURSORHOME,0L);
	    break;

	case 'I' :		/* reverse line feed */
	    SendMessage(hWndActive, WH19_COMMAND, H19_REVERSELINEFEED,0L);
	    break;
	
	case 'J' :		/* clear to end of screen */
	    SendMessage(hWndActive, WH19_COMMAND, H19_CLRTOENDOFSCREEN,0L);
	    break;

	case 'K' :		/* clear to end of line */
	    SendMessage(hWndActive, WH19_COMMAND, H19_CLRTOENDOFLINE,0L);
	    break;
	    
	case 'L' :		/* insert line */
	    SendMessage(hWndActive, WH19_COMMAND, H19_INSERTLINE,1L);
	    break;

	case 'M' :		/* delete line */
	    SendMessage(hWndActive, WH19_COMMAND, H19_DELETELINE,1L);
	    break;

	case 'N' :		/* delete char */
	    SendMessage(hWndActive, WH19_COMMAND,H19_DELETECHAR,1L);
	    break;

	case 'O' :		/* exit insert char mode */
	    CD.ICToggle = FALSE;
	    break;

	case 'Y' :		/* position cursor */
	    CD.CommandState = YPOS_COMMAND;
	    break;

	case 'Z' :		/* identify as VT52 */
	    if (CD.LineState == IDM_ONLINE)
	        IdentifyTerm();	
	    break;

	case '[' :		/* enter hold screen mode */
	    CD.HoldScreen = TRUE;
	    break;

	case '\\' :		/* exit hold screen mode */
	    CD.HoldScreen = FALSE;
	    break;

	case ']' :		/* transmit status line */
	    SendStatusLine(&SW);
	    break;
		
	case 'b' :		/* clear to screen top */
	    SendMessage(hWndActive, WH19_COMMAND, H19_CLRTOTOPOFSCREEN,0L);
	    break;

	case 'j' :		/* save cursor position */
	    SendMessage(hWndActive, WH19_CURSORPOSITION,H19_SAVECURSOR, 0L);
	    break;

	case 'k' :		/* set to saved position */
	    PositionRow((BYTE)CD.CurSaveRow);
	    PositionCol((BYTE)CD.CurSaveCol);
	    break;

	case 'l' :		/* erase entire line */
	    SendMessage(hWndActive, WH19_COMMAND, H19_ERASELINE,0L);
	    break;

	case 'n' :		/* report cursor position */
	    if (CD.LineState == IDM_ONLINE) {
		short currow, curcol;
	        SendMessage(hWndActive, WH19_CURSORPOSITION,H19_GETCURSOR,
				MAKELONG(&currow, &curcol));
		ReportCursorPosition(currow,curcol);
	    }
	    break;

	case 'o' :		/* clear to start of line */
	    SendMessage(hWndActive, WH19_COMMAND, H19_CLRTOSTARTOFLINE,0L);
	    break;
	    
	case 'p' :		/* enter inverse video */
	    CD.InverseVideo = TRUE;
	    CD.CharAttribute = 0x80;
	    break;

	case 'q' :		/* exit inverse video */
	    CD.CharAttribute = 0;
	    CD.InverseVideo = FALSE;
	    break;

	case 'r' :		/* set baud rate */
	    CD.CommandState = SETBAUD_COMMAND;
	    break;

	case 't' :		/* enter shifted keypad */
	    CD.ShiftedKeypad = TRUE;
	    break;

	case 'u' :		/* exit shifted keypad */
	    CD.ShiftedKeypad = FALSE;
	    break;

	case 'v' :		/* set wraparound */
	    CD.WrapAround = TRUE;
	    break;

	case 'w':		/* no wrap */
	    CD.WrapAround = FALSE;
	    break;

	case 'x' :		/* set command */
	    CD.CommandState = SET_COMMAND;
	    break;

	case 'y' :		/* reset command */
	    CD.CommandState = RESET_COMMAND;
	    break;	

	case 'z' :		/* reset to power up conditions */
	    ResetTerminal();
	    break;

	case '}' :		/* disable keyboard */
	    CD.KeyboardDisabled = TRUE;
	    break;

	case '{' :		/* enable keyboard */
	    CD.KeyboardDisabled = FALSE;
	    break;
    }
}

/* paint command */
void NEAR MainWndPaint(hWnd, hDC)
HWND hWnd;
HDC hDC;
{
    register BOOL iconic = IsIconic(hWnd);
    char *ptr;
    RECT prect;

#if defined(KERMIT)
    if (krmState)
        krmShowTransferData(hWnd, hDC, iconic);
    else
#endif

        if (iconic) {
	    GetClientRect(hWnd, (LPRECT)&prect);
	    ptr = szWinTitle;
	    Rectangle(hDC,prect.left,prect.top,prect.right,prect.bottom);
	    prect.top += 1;
	    DrawText(hDC, (LPSTR)ptr,4,(LPRECT)&prect,
	        DT_CENTER | DT_NOCLIP | DT_EXTERNALLEADING);
	    prect.top = prect.bottom/2;
	    ptr += strlen(szWinTitle) - 5;
	    DrawText(hDC, (LPSTR)ptr,4,(LPRECT)&prect,
	        DT_CENTER | DT_NOCLIP);
	}
}

/* called when a system key is activated */
long NEAR MainSysCommand(hWnd,message,wParam,lParam)
HWND hWnd;
unsigned message;
WORD wParam;
LONG lParam;
{

    FARPROC fp;

    switch (wParam) {
	case IDM_ABOUT:
	    fp = MakeProcInstance((FARPROC)AboutBoxProc, hInst);
	    DialogBox(hInst, MAKEINTRESOURCE(DT_ABOUT),hWnd,fp);
	    FreeProcInstance(fp);
	    return TRUE;
	default:
	    return(DefWindowProc(hWnd,message,wParam,lParam));
    }
    return (0L);
}

/* window has been resized */
void NEAR SizeWindow(width, height, code)
WORD width;
WORD height;
WORD code;
{

#if defined(KERMIT)
    if (krmState)
	krmAdjustHeight((short)width, (short)height);
#endif
	
    switch (code) {
	case SIZEICONIC:
	    break;
	default:
	    MW.Height = height;
	    MW.Width = width;
	    MW.BottomTextLine = height / TW.CharHeight * TW.CharHeight - 
					TW.CharHeight;
	    MW.SCBottomTextLine = MW.SCTopTextLine + MW.BottomTextLine;
	    if (IsWindow(hWndActive))
		SendMessage(hWndActive, WH19_CARETFUNCTION,H19_DESTROYCARET,
				(LONG)CD.OwnCaret);
	    if (IsWindow(TW.hWnd)) {
	        MoveWindow(TW.hWnd,0,0,TW.Width,TW.Height,TRUE);
	        if (IsWindow(SW.hWnd))
	            MoveStatWindow(MW.Height);
	    }
	    if (IsWindow(hWndActive))
		SendMessage(hWndActive, WH19_CARETFUNCTION,H19_CREATECARET,
				(LONG)CD.OwnCaret);
	    break;
    }
}

/* move status window */
static void NEAR MoveStatWindow(short height)
{

    register short statpos = TW.Height;
    register short statheight = TW.CharHeight;

    CD.StatOverlayTerm = FALSE;

    if ((statpos + statheight) > height) {
        if (CD.StatOpen) {
	    statpos = MW.BottomTextLine;
	    statheight = MW.Height - MW.BottomTextLine;
	    CD.StatOverlayTerm = TRUE;
	}
	else {
	    statpos = MW.BottomTextLine + TW.CharHeight;
	    statheight = height - statpos;
	}
    }
    MoveWindow(SW.hWnd,0,statpos,TW.Width,statheight,TRUE);
}

/* menu handler */
void NEAR WndCommand(hWnd, wparam, lparam)
HWND hWnd;
WORD wparam;
LONG lparam;
{

    register HMENU hMenu = GetMenu(hWnd);
    HCURSOR hCurOld;
    FARPROC fp;
    
    switch (wparam) {

	case IDM_CLEARCOM:
	    FlushComm(cid,0);
	    FlushComm(cid,1);
	    EscapeCommFunction(cid,SETXON);
	    if (CD.LineState == IDM_ONLINE)
		TransmitCommChar(cid, XON);
	    break;

	case IDM_RESET:
            ResetTerminal();
	    break;

        case IDM_OFFLINE:
            SetWindowLong(MW.hWnd, GWL_WNDPROC, (LONG)MainWndSubclassProc);
	    hCurOld = SetCursor(LoadCursor((HANDLE)NULL,IDC_WAIT));
	    SendMessage(hWndActive, WH19_CARETFUNCTION, H19_HIDECARET, 0L);
	    ChangeMenu(hMenu,IDM_OFFLINE,(LPSTR)szOnline,IDM_ONLINE,
				MF_BYCOMMAND | MF_CHANGE);
	    DrawMenuBar(hWnd);
	    CD.LineState = IDM_ONLINE;
	    SendMessage(hWndActive, WH19_CARETFUNCTION, H19_SHOWCARET, 0L);
	    SetCursor(hCurOld);
	    break;
	case IDM_ONLINE:
	    SetWindowLong(MW.hWnd, GWL_WNDPROC, (LONG)fpTerminal);
	    hCurOld = SetCursor(LoadCursor((HANDLE)NULL,IDC_WAIT));
	    SendMessage(hWndActive, WH19_CARETFUNCTION, H19_HIDECARET, 0L);
	    ChangeMenu(hMenu,IDM_ONLINE,(LPSTR)szOffline,IDM_OFFLINE,
		        	MF_BYCOMMAND | MF_CHANGE);
	    DrawMenuBar(hWnd);
	    CD.LineState = IDM_OFFLINE;
	    SendMessage(hWndActive, WH19_CARETFUNCTION, H19_SHOWCARET, 0L);
	    SetCursor(hCurOld);
	    break;
	case IDM_COMM:
	    fp = MakeProcInstance((FARPROC)SetCommParams, hInst);
	    DialogBox(hInst, MAKEINTRESOURCE(DT_COMM),hWnd,fp);
	    FreeProcInstance(fp);
	    break;
	case IDM_TERM:
	    fp = MakeProcInstance((FARPROC)SetTermParams, hInst);
	    DialogBox(hInst, MAKEINTRESOURCE(DT_TERM),hWnd,fp);
	    FreeProcInstance(fp);
	    break;
	case IDM_SPECIALKEYS:
	    fp = MakeProcInstance((FARPROC)SetStringParams, hInst);
	    DialogBox(hInst, MAKEINTRESOURCE(DT_STRING),hWnd,fp);
	    FreeProcInstance(fp);
	    break;
	case IDM_COPY:
	    SendMessage(TW.hWnd, WH19_SLAPSCREEN, 0, 0L);
	    break;	    	    
	case IDM_PASTE:
	    if (OpenClipboard(hWnd)) {
	        LPSTR lpClip, lpDest;
		BYTE ch;

		hClipData = GetClipboardData(CF_TEXT);
		GB.lBufSize = GlobalSize(hClipData);
		if (GB.hBuf == NULL) {
	    	    GB.hBuf = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT,
								GB.lBufSize);
		    if (GB.hBuf != NULL) {
		        GB.lBufHead = GB.lBufTail = 0;
		        lpClip = GlobalLock(hClipData);
		        lpDest = GlobalLock(GB.hBuf);			    
		        while(ch = *lpClip++) {
			    if (ch != LF) {
			        *lpDest++ = ch;
			        GB.lBufTail += 1;
			    }
			}
			GlobalUnlock(hClipData);
			GlobalUnlock(GB.hBuf);
	            }
		}
	 	CloseClipboard();
	    }
	    break;
#if defined(KERMIT)
	default:
	    krmProcessKermitMenu(hWnd, wparam);
	    break;
#endif	
    }
}

/* communications processor */
void NEAR ProcessComm()
{

    COMSTAT ComStatus;
    int num;
    register int retresult = 0;
    register int result = 0;
    
    static char Buffer[RXQUESIZE];
    static int bufsize = 0;

    if ((CD.LineState == IDM_ONLINE) && !CD.ScrollLock) {
	if (bufsize == 0) {
            GetCommError(cid, (COMSTAT FAR *)&ComStatus);
            if (num = ComStatus.cbInQue) {
		num = min(num, BUFSIZE);
	        if ((result = ReadComm(cid, (LPSTR)Buffer, num)) < 0) {
		    result = -result;
		    Buffer[result] = NUL;
		}
	    }
	}
	else
	    result = bufsize;

#if defined(KERMIT)
	if (krmState)
	    krmKermitDispatch(MW.hWnd, Buffer, result);
        else 
#endif

	if (result) {
	    retresult = H19StringInput(Buffer, result);

	    if (retresult) {
		bufsize = retresult;
		memmove(Buffer, Buffer+result-retresult,retresult);
		Buffer[bufsize] = 0;
	    }
	    else
		bufsize = 0;
	}
	else if (GB.hBuf) {
	    BYTE FAR *tbuf;
	    LONG BufBytesRemaining;
	    int count;

	    BufBytesRemaining = GB.lBufTail - GB.lBufHead;
	    if (BufBytesRemaining > 0) {
	        tbuf = GlobalLock(GB.hBuf) + GB.lBufHead;
		if (BufBytesRemaining > INT_MAX)
		    count = TW.MaxCols;
		else
		    count = min(TW.MaxCols, (int)LOWORD(BufBytesRemaining));
		WriteToPort(cid, tbuf, count);
		GB.lBufHead += count;
		BufBytesRemaining = GB.lBufTail - GB.lBufHead;
	        GlobalUnlock(GB.hBuf);
	    }
	    if (BufBytesRemaining <= 0) 
	        GB.hBuf = GlobalFree(GB.hBuf);
	}	    
    }
}

/* write to comm port */
void NEAR WriteToPort(cid, buf, len)
short cid;
BYTE FAR *buf;
int len;
{

    COMSTAT mystat;
    register int room;

    GetCommError(cid, (COMSTAT FAR *)&mystat);
    room = TXQUESIZE - mystat.cbOutQue;
    
    while (room < len) {
	if (!DoMessage()) {
            GetCommError(cid, (COMSTAT FAR *)&mystat);
	    room = TXQUESIZE - mystat.cbOutQue;
	}
    }
    WriteComm(cid, (LPSTR)buf, len);
}

/* change baud rate */
static void NEAR ProcessBaudChange(int ch)
{

    WORD rate;
     
    CD.CommandState = NO_COMMAND;

    if ((ch >= 0) && (ch < BAUDTABLESIZE)) {
	CommData.BaudRate = rate = BaudRateTable[ch];
	CommData.StopBits = ONESTOPBIT;
	if (rate == 110)
	    CommData.StopBits = TWOSTOPBITS;
	SetCommState((DCB FAR *)&CommData);
    }
}

/* report vt52 */
static void NEAR IdentifyTerm()
{
    BYTE outstr[5];

    outstr[0] = ESC;
    outstr[1] = '/';
    outstr[2] = 'K';
    
    WriteToPort(cid, (BYTE FAR *)outstr, 3);
}

/* report cursor position */
static void NEAR ReportCursorPosition(short line, short col)
{

    BYTE outstr[20];
    register BYTE *outstrptr = outstr;

    *outstrptr++ = ESC;

    if (CD.ANSIMode) {
	*outstrptr++ = '[';
	itoa(line+1, outstrptr,10);
	outstrptr += strlen(outstrptr);
	*outstrptr++ = ';';
	itoa(col+1, outstrptr,10);
	outstrptr += strlen(outstrptr);
	*outstrptr++ = 'R';
    }
    else {
        *outstrptr++ = 'Y';
        *outstrptr++ = (BYTE)(SP + line);
        *outstrptr++ = (BYTE)(SP + col);
    }
    *outstrptr = NUL;
    WriteToPort(cid, (BYTE FAR *)outstr, strlen(outstr));
}
