/*
 * Copyright (c) 1988 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by the University of California, Berkeley.  The name of the
 * University may not be used to endorse or promote products derived
 * from this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#ifndef lint
static char sccsid[] = "@(#)outbound.c	4.1 (Berkeley) 12/4/88";
#endif /* not lint */

#include <stdio.h>

#include "../general/general.h"

#include "hostctlr.h"
#include "oia.h"
#include "screen.h"
#include "../api/ebc_disp.h"

#include "../general/globals.h"
#include "externs.h"
#include "declare.h"

#define SetHighestLowest(position) { \
					if (position < Lowest) { \
					    Lowest = position; \
					} \
					if (position > Highest) { \
					    Highest = position; \
					} \
				    }


static int	LastWasTerminated = 1;	/* was "control" = 1 last time? */

/* some globals */

#if	!defined(PURE3274)
int	OutputClock;		/* what time it is */
int	TransparentClock;		/* time we were last in transparent */
#endif	/* !defined(PURE3274) */

char CIABuffer[64] = {
    0x40, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
    0xc8, 0xc9, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
    0x50, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
    0xd8, 0xd9, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
    0x60, 0x61, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
    0xe8, 0xe9, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
    0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
    0xf8, 0xf9, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f
};

static struct orders_def orders_def[] = ORDERS_DEF;

/*
 * init_ctlr()
 *
 *	Initialize all data from the 'data' portion to their startup values.
 */

void
init_ctlr()
{
    LastWasTerminated = 1;
    init_inbound();
    init_oia();
}


FieldInc(position)
register int	position;		/* Position in previous field */
{
    register ScreenImage *ptr;

    ptr = (ScreenImage *)memNSchr((char *)Host+position+1, ATTR_MASK,
			HighestScreen()-position, ATTR_MASK, sizeof Host[0]);
    if (ptr == 0) {
	ptr = (ScreenImage *)memNSchr((char *)Host+LowestScreen(), ATTR_MASK,
			position-LowestScreen(), ATTR_MASK, sizeof Host[0]);
	if (ptr == 0) {
	    return LowestScreen();
	}
    }
    return ptr-Host;
}

FieldDec(position)
int	position;
{
    register ScreenImage *ptr;

    ptr = (ScreenImage *)memNSchr((char *)(Host+position)-1, ATTR_MASK,
			position-LowestScreen(), ATTR_MASK, -sizeof Host[0]);
    if (ptr == 0) {
	ptr = (ScreenImage *)memNSchr((char *)Host+HighestScreen(), ATTR_MASK,
			HighestScreen()-position, ATTR_MASK, -sizeof Host[0]);
	if (ptr == 0) {
	    return LowestScreen();
	}
    }
    return ptr-Host;
}

/* Clear3270 - called to clear the screen */

void
Clear3270()
{
    ClearArray(Host);
    DeleteAllFields();		/* get rid of all fields */
    BufferAddress = SetBufferAddress(0,0);
    CursorAddress = SetBufferAddress(0,0);
    Lowest = LowestScreen();
    Highest = HighestScreen();
}

/* AddHost - called to add a character to the buffer.
 *	We use a macro in this module, since we call it so
 *	often from loops.
 *
 *	NOTE: It is a macro, so don't go around using AddHost(p, *c++), or
 *	anything similar.  (I don't define any temporary variables, again
 *	just for the speed.)
 */
void
AddHost(position, character)
int	position;
char	character;
{
#   define	AddHostA(p,c)					\
    {								\
	if (IsStartField(p)) {					\
	    DeleteField(p);					\
	    Highest = HighestScreen();				\
	    Lowest = LowestScreen();				\
	    SetHighestLowest(p);				\
	}							\
	SetHost(p, c);						\
    }
#   define	AddHost(p,c)					\
    {								\
	if (c != GetHost(p)) {					\
	    SetHighestLowest(p);				\
	}							\
	AddHostA(p,c);						\
    }	/* end of macro of AddHost */

    AddHost(position, character);
}

/* returns the number of characters consumed */
int
DataFromNetwork(Buffer, count, control)
char	*Buffer;				/* what the data is */
register int	count;				/* and how much there is */
int	control;				/* this buffer ended block? */
{
    int origCount;
    register unsigned char *buffer = (unsigned char *)Buffer;
    register int c;
    register int i;
    static int Command;
    static int Wcc;

    origCount = count;

    /*
     * If this is the start of a new data stream, then look
     * for an op-code and (possibly) a WCC.
     */
    if (LastWasTerminated) {

	if (count < 2) {
	    if (count == 0) {
		ExitString("Short count received from host!\n", 1);
		return(count);
	    }
	    Command = buffer[0];
	    switch (Command) {		/* This had better be a read command */
	    case CMD_READ_MODIFIED:
	    case CMD_SNA_READ_MODIFIED:
	    case CMD_SNA_READ_MODIFIED_ALL:
		SetOiaOnlineA(&OperatorInformationArea);
		SetOiaModified();
		DoReadModified(Command);
		break;
	    case CMD_READ_BUFFER:
	    case CMD_SNA_READ_BUFFER:
		SetOiaOnlineA(&OperatorInformationArea);
		SetOiaModified();
		DoReadBuffer();
		break;
	    default:
		{
		    char s_buffer[100];

		    sprintf(s_buffer,
			"Unexpected read command code 0x%x received.\n",
								    Command);
		    ExitString(s_buffer, 1);
		    break;
		}
	    }
	    return(1);			/* We consumed everything */
	}
	Command = buffer[0];
	Wcc = buffer[1];
	if (Wcc & WCC_RESET_MDT) {
	    i = c = WhereAttrByte(LowestScreen());
	    do {
		if (HasMdt(i)) {
		    TurnOffMdt(i);
		}
		i = FieldInc(i);
	    } while (i != c);
	}

	switch (Command) {
	case CMD_ERASE_WRITE:
	case CMD_ERASE_WRITE_ALTERNATE:
	case CMD_SNA_ERASE_WRITE:
	case CMD_SNA_ERASE_WRITE_ALTERNATE:
	    {
		int newlines, newcolumns;

		SetOiaOnlineA(&OperatorInformationArea);
		ResetOiaTWait(&OperatorInformationArea);
		SetOiaModified();
		if ((Command == CMD_ERASE_WRITE)
				|| (Command == CMD_SNA_ERASE_WRITE)) {
		    newlines = 24;
		    newcolumns = 80;
		} else {
		    newlines = MaxNumberLines;
		    newcolumns = MaxNumberColumns;
		}
		if ((newlines != NumberLines)
				|| (newcolumns != NumberColumns)) {
			/*
			 * The LocalClearScreen() is really for when we
			 * are going from a larger screen to a smaller
			 * screen, and we need to clear off the stuff
			 * at the end of the lines, or the lines at
			 * the end of the screen.
			 */
		    LocalClearScreen();
		    NumberLines = newlines;
		    NumberColumns = newcolumns;
		    ScreenSize = NumberLines * NumberColumns;
		}
		Clear3270();
#if	!defined(PURE3274)
		if (TransparentClock == OutputClock) {
		    TransStop();
		}
#endif	/* !defined(PURE3274) */
		break;
	    }

	case CMD_ERASE_ALL_UNPROTECTED:
	case CMD_SNA_ERASE_ALL_UNPROTECTED:
	    SetOiaOnlineA(&OperatorInformationArea);
	    ResetOiaTWait(&OperatorInformationArea);
	    SetOiaModified();
	    CursorAddress = HighestScreen()+1;
	    for (i = LowestScreen(); i <= HighestScreen(); i = ScreenInc(i)) {
		if (IsUnProtected(i)) {
		    if (CursorAddress > i) {
			CursorAddress = i;
		    }
		    AddHost(i, '\0');
		}
		if (HasMdt(i)) {
		    TurnOffMdt(i);
		}
	    }
	    if (CursorAddress == HighestScreen()+1) {
		CursorAddress = SetBufferAddress(0,0);
	    }
	    UnLocked = 1;
	    AidByte = 0;
	    ResetOiaSystemLocked(&OperatorInformationArea);
	    SetOiaModified();
	    TerminalIn();
	    break;
	case CMD_WRITE:
	case CMD_SNA_WRITE:
	    SetOiaOnlineA(&OperatorInformationArea);
	    ResetOiaTWait(&OperatorInformationArea);
	    SetOiaModified();
	    break;
	default:
	    {
		char s_buffer[100];

		sprintf(s_buffer,
			"Unexpected write command code 0x%x received.\n",
								Command);
		ExitString(s_buffer, 1);
		break;
	    }
	}

	count -= 2;			/* strip off command and wcc */
	buffer += 2;

    } else {
#if	!defined(PURE3274)
	if (TransparentClock == OutputClock) {
	    TransOut(buffer, count, -1, control);
	    count = 0;
	}
#endif	/* !defined(PURE3274) */
    }
    LastWasTerminated = 0;		/* then, reset at end... */

    while (count) {
	count--;
	c = *buffer++;
	if (IsOrder(c)) {
	    /* handle an order */
	    switch (c) {
#		define Ensure(x)	if (count < x) { \
					    if (!control) { \
						return(origCount-(count+1)); \
					    } else { \
						/* XXX - should not occur */ \
						count = 0; \
						break; \
					    } \
					}
	    case ORDER_SF:
		Ensure(1);
		c = *buffer++;
		count--;
		if ( ! (IsStartField(BufferAddress) &&
					FieldAttributes(BufferAddress) == c)) {
		    SetHighestLowest(BufferAddress);
		    NewField(BufferAddress,c);
		}
		BufferAddress = ScreenInc(BufferAddress);
		break;
	    case ORDER_SBA:
		Ensure(2);
		i = buffer[0];
		c = buffer[1];
#if	!defined(PURE3274)
		/* Check for transparent write */
		if ((i == 0) && ((c == 0) || (c == 1) || (c == 5))) {
		    TransparentClock = OutputClock+1;
		    TransOut(buffer+2, count-2, c, control);
		    buffer += count;
		    count -= count;
		    break;
		}
#endif	/* !defined(PURE3274) */
		BufferAddress = Addr3270(i, c);
		buffer += 2;
		count -= 2;
		break;
	    case ORDER_IC:
		CursorAddress = BufferAddress;
		break;
	    /*
	     * XXX - PT is supposed to null fill the screen buffer
	     * under certain draconian conditions.
	     */
	    case ORDER_PT:
		i = BufferAddress;
		do {
		    if (IsStartField(i)) {
			if (!IsProtected(ScreenInc(i))) {
			    break;
			}
		    }
		    i = ScreenInc(i);
		} while (i != HighestScreen());
		BufferAddress = ScreenInc(i);
		break;
	    case ORDER_RA:
		Ensure(3);
		i = Addr3270(buffer[0], buffer[1]);
		c = buffer[2];
		if (c == ORDER_GE) {
		    Ensure(4);
		    c = buffer[3];
		    buffer += 4;
		    count -= 4;
		} else {
		    buffer += 3;
		    count -= 3;
		}
		do {
		    AddHost(BufferAddress, ebc_disp[c]);
		    BufferAddress = ScreenInc(BufferAddress);
		} while (BufferAddress != i);
		break;
	    case ORDER_EUA:    /* (from [here,there), ie: half open interval] */
		Ensure(2);
		/*
		 * Compiler error - msc version 4.0:
		 *			"expression too complicated".
		 */
		i = WhereAttrByte(BufferAddress);
		c = FieldAttributes(i);
		i = Addr3270(buffer[0], buffer[1]);
		do {
		    if (IsStartField(BufferAddress)) {
			c = FieldAttributes(BufferAddress);
		    } else if (!IsProtectedAttr(BufferAddress, c)) {
			AddHost(BufferAddress, 0);
		    }
		    BufferAddress = ScreenInc(BufferAddress);
		} while (i != BufferAddress);
		buffer += 2;
		count -= 2;
		break;
	    case ORDER_GE:
		Ensure(2);
		/* XXX Should do SOMETHING! */
		/* XXX buffer += 0; */
		/* XXX count -= 0; *//* For now, just use this character */
		break;
	    case ORDER_YALE:		/* special YALE defined order */
		Ensure(2);	/* need at least two characters */
		if (*buffer == 0x5b) {
		    i = OptOrder(buffer+1, count-1, control);
		    if (i == 0) {
			return(origCount-(count+1));	/* come here again */
		    } else {
			buffer += 1 + i;
			count  -= (1 + i);
		    }
		}
		break;
	    default:
		{
		    char s_buffer[100];
		    static struct orders_def unk_order
						= { 0, "??", "(unknown)" };
		    struct orders_def *porder = &unk_order;
		    int s_i;

		    for (s_i = 0; s_i <= highestof(orders_def); s_i++) {
			if (orders_def[s_i].code == c) {
			    porder = &orders_def[s_i];
			    break;
			}
		    }
		    sprintf(s_buffer,
			"Unsupported order '%s' (%s, 0x%x) received.\n",
			porder->long_name, porder->short_name, c);
		    ExitString(s_buffer, 1);
		    /*NOTREACHED*/
		}
	    }
	    if (count < 0) {
		count = 0;
	    }
	} else {
	    /* Data comes in large clumps - take it all */
	    i = BufferAddress;
	    AddHostA(i, ebc_disp[c]);
	    SetHighestLowest(i);
	    i = ScreenInc(i);
	    c = *buffer;
	    while (count && !IsOrder(c)) {
		AddHostA(i, ebc_disp[c]);
		i = ScreenInc(i);
		if (i == LowestScreen()) {
		    SetHighestLowest(HighestScreen());
		}
		count--;
		buffer++;
		c = *buffer;
	    }
	    SetHighestLowest(i);
	    BufferAddress = i;
	}
    }
    if (count == 0) {
	if (control) {
#if	!defined(PURE3274)
	    OutputClock++;		/* time rolls on */
#endif	/* !defined(PURE3274) */
	    if (Wcc & WCC_RESTORE) {
#if	!defined(PURE3274)
		if (TransparentClock != OutputClock) {
		    AidByte = 0;
		}
#else	/* !defined(PURE3274) */
		AidByte = 0;
#endif	/* !defined(PURE3274) */
		UnLocked = 1;
		ResetOiaSystemLocked(&OperatorInformationArea);
		SetOiaModified();
		SetPsModified();
		TerminalIn();
	    }
	    if (Wcc & WCC_ALARM) {
		RingBell((char *)0);
	    }
	}
	LastWasTerminated = control;	/* state for next time */
	return(origCount);
    } else {
	return(origCount-count);
    }
}

/*
 * Init3270()
 *
 * Initialize any 3270 (controller) variables to an initial state
 * in preparation for accepting a connection.
 */

void
Init3270()
{
    int i;

    OptInit();		/* initialize mappings */

    ClearArray(Host);

    ClearArray(Orders);
    for (i = 0; i <= highestof(orders_def); i++) {
	Orders[orders_def[i].code] = 1;
    }

    DeleteAllFields();		/* Clear screen */
    Lowest = HighestScreen()+1;
    Highest = LowestScreen()-1;
    CursorAddress = BufferAddress = SetBufferAddress(0,0);
    UnLocked = 1;
#if	!defined(PURE3274)
    OutputClock = 1;
    TransparentClock = -1;
#endif	/* !defined(PURE3274) */
    SetOiaReady3274(&OperatorInformationArea);
}


void
Stop3270()
{
    ResetOiaReady3274(&OperatorInformationArea);
}
