/*
 * Copyright 1996, 1999, 2000, 2001, 2002 by Paul Mattes.
 *  Permission to use, copy, modify, and distribute this software and its
 *  documentation for any purpose and without fee is hereby granted,
 *  provided that the above copyright notice appear in all copies and that
 *  both that copyright notice and this permission notice appear in
 *  supporting documentation.
 *
 * x3270, c3270, s3270 and tcl3270 are distributed in the hope that they will
 * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the file LICENSE
 * for more details.
 */

/*
 *	ft_cut.c
 *		File transfer, data movement logic, CUT version
 */

#include <errno.h>

#include "globals.h"

#if defined(X3270_FT) /*[*/

#include "appres.h"
#include "ctlr.h"
#include "3270ds.h"

#include "actionsc.h"
#include "ctlrc.h"
#include "ft_cutc.h"
#include "ft_cut_ds.h"
#include "ftc.h"
#include "kybdc.h"
#include "popupsc.h"
#include "tablesc.h"
#include "telnetc.h"
#include "trace_dsc.h"
#include "utilc.h"

static Boolean cut_xfer_in_progress = False;

/* Data stream conversion tables. */

#define NQ		4	/* number of quadrants */
#define NE		77	/* number of elements per quadrant */
#define OTHER_2		2	/* "OTHER 2" quadrant (includes NULL) */
#define XLATE_NULL	0xc1	/* translation of NULL */

static char alphas[NE + 1] =
" ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789%&_()<+,-./:>?";

static struct {
	unsigned char selector;
	unsigned char xlate[NE];
} conv[NQ] = {
    {	0x5e,	/* ';' */
	{ 0x40,0xc1,0xc2,0xc3, 0xc4,0xc5,0xc6,0xc7, 0xc8,0xc9,0xd1,0xd2,
	  0xd3,0xd4,0xd5,0xd6, 0xd7,0xd8,0xd9,0xe2, 0xe3,0xe4,0xe5,0xe6,
	  0xe7,0xe8,0xe9,0x81, 0x82,0x83,0x84,0x85, 0x86,0x87,0x88,0x89,
	  0x91,0x92,0x93,0x94, 0x95,0x96,0x97,0x98, 0x99,0xa2,0xa3,0xa4,
	  0xa5,0xa6,0xa7,0xa8, 0xa9,0xf0,0xf1,0xf2, 0xf3,0xf4,0xf5,0xf6,
	  0xf7,0xf8,0xf9,0x6c, 0x50,0x6d,0x4d,0x5d, 0x4c,0x4e,0x6b,0x60,
	  0x4b,0x61,0x7a,0x6e, 0x6f }
    },
    {	0x7e,	/* '=' */
	{ 0x20,0x41,0x42,0x43, 0x44,0x45,0x46,0x47, 0x48,0x49,0x4a,0x4b,
	  0x4c,0x4d,0x4e,0x4f, 0x50,0x51,0x52,0x53, 0x54,0x55,0x56,0x57,
	  0x58,0x59,0x5a,0x61, 0x62,0x63,0x64,0x65, 0x66,0x67,0x68,0x69,
	  0x6a,0x6b,0x6c,0x6d, 0x6e,0x6f,0x70,0x71, 0x72,0x73,0x74,0x75,
	  0x76,0x77,0x78,0x79, 0x7a,0x30,0x31,0x32, 0x33,0x34,0x35,0x36,
	  0x37,0x38,0x39,0x25, 0x26,0x27,0x28,0x29, 0x2a,0x2b,0x2c,0x2d,
	  0x2e,0x2f,0x3a,0x3b, 0x3f }
    },
    {	0x5c,	/* '*' */
	{ 0x00,0x00,0x01,0x02, 0x03,0x04,0x05,0x06, 0x07,0x08,0x09,0x0a,
	  0x0b,0x0c,0x0d,0x0e, 0x0f,0x10,0x11,0x12, 0x13,0x14,0x15,0x16,
	  0x17,0x18,0x19,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
	  0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
	  0x00,0x00,0x00,0x00, 0x00,0x3c,0x3d,0x3e, 0x00,0xfa,0xfb,0xfc,
	  0xfd,0xfe,0xff,0x7b, 0x7c,0x7d,0x7e,0x7f, 0x1a,0x1b,0x1c,0x1d,
	  0x1e,0x1f,0x00,0x00, 0x00 }
    },
    {	0x7d,	/* '\'' */
	{ 0x00,0xa0,0xa1,0xea, 0xeb,0xec,0xed,0xee, 0xef,0xe0,0xe1,0xaa,
	  0xab,0xac,0xad,0xae, 0xaf,0xb0,0xb1,0xb2, 0xb3,0xb4,0xb5,0xb6,
	  0xb7,0xb8,0xb9,0x80, 0x00,0xca,0xcb,0xcc, 0xcd,0xce,0xcf,0xc0,
	  0x00,0x8a,0x8b,0x8c, 0x8d,0x8e,0x8f,0x90, 0x00,0xda,0xdb,0xdc,
	  0xdd,0xde,0xdf,0xd0, 0x00,0x00,0x21,0x22, 0x23,0x24,0x5b,0x5c,
	  0x00,0x5e,0x5f,0x00, 0x9c,0x9d,0x9e,0x9f, 0xba,0xbb,0xbc,0xbd,
	  0xbe,0xbf,0x9a,0x9b, 0x00 }
    }
};
static char table6[] =
    "abcdefghijklmnopqrstuvwxyz&-.,:+ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";

static int quadrant = -1;
static unsigned long expanded_length;
static char *saved_errmsg = CN;

#define XLATE_NBUF	4
static int xlate_buffered = 0;			/* buffer count */
static int xlate_buf_ix = 0;			/* buffer index */
static unsigned char xlate_buf[XLATE_NBUF];	/* buffer */

static void cut_control_code(void);
static void cut_data_request(void);
static void cut_retransmit(void);
static void cut_data(void);

static void cut_ack(void);
static void cut_abort(const char *s, unsigned short reason);

static unsigned from6(unsigned char c);
static int xlate_getc(void);

/*
 * Convert a buffer for uploading (host->local).  Overwrites the buffer.
 * Returns the length of the converted data.
 * If there is a conversion error, calls cut_abort() and returns -1.
 */
static int
upload_convert(unsigned char *buf, int len)
{
	unsigned char *ob0 = buf;
	unsigned char *ob = ob0;

	while (len--) {
		unsigned char c = *buf++;
		char *ixp;
		int ix;
		int oq = -1;

	    retry:
		if (quadrant < 0) {
			/* Find the quadrant. */
			for (quadrant = 0; quadrant < NQ; quadrant++) {
				if (c == conv[quadrant].selector)
					break;
			}
			if (quadrant >= NQ) {
				cut_abort(get_message("ftCutConversionError"),
				    SC_ABORT_XMIT);
				return -1;
			}
			continue;
		}

		/* Make sure it's in a valid range. */
		if (c < 0x40 || c > 0xf9) {
			cut_abort(get_message("ftCutConversionError"),
			    SC_ABORT_XMIT);
			return -1;
		}

		/* Translate to a quadrant index. */
		ixp = strchr(alphas, ebc2asc[c]);
		if (ixp == (char *)NULL) {
			/* Try a different quadrant. */
			oq = quadrant;
			quadrant = -1;
			goto retry;
		}
		ix = ixp - alphas;

		/*
		 * See if it's mapped by that quadrant, handling NULLs
		 * specially.
		 */
		if (quadrant != OTHER_2 && c != XLATE_NULL &&
		    !conv[quadrant].xlate[ix]) {
			/* Try a different quadrant. */
			oq = quadrant;
			quadrant = -1;
			goto retry;
		}

		/* Map it. */
		c = conv[quadrant].xlate[ix];
		if (ascii_flag && cr_flag && (c == '\r' || c == 0x1a))
			continue;
		if (ascii_flag && remap_flag)
			c = ft2asc[c];
		*ob++ = c;
	}

	return ob - ob0;
}

/* Convert a buffer for downloading (local->host). */
static int
download_convert(unsigned const char *buf, unsigned len, unsigned char *xobuf)
{
	unsigned char *ob0 = xobuf;
	unsigned char *ob = ob0;

	while (len--) {
		unsigned char c = *buf++;
		unsigned char *ixp;
		unsigned ix;
		int oq;

		/* Handle nulls separately. */
		if (!c) {
			if (quadrant != OTHER_2) {
				quadrant = OTHER_2;
				*ob++ = conv[quadrant].selector;
			}
			*ob++ = XLATE_NULL;
			continue;
		}

		/* Translate. */
		if (ascii_flag && remap_flag)
			c = asc2ft[c];

		/* Quadrant already defined. */
		if (quadrant >= 0) {
			ixp = (unsigned char *)memchr(conv[quadrant].xlate, c,
							NE);
			if (ixp != (unsigned char *)NULL) {
				ix = ixp - conv[quadrant].xlate;
				*ob++ = asc2ebc[(int)alphas[ix]];
				continue;
			}
		}

		/* Locate a quadrant. */
		oq = quadrant;
		for (quadrant = 0; quadrant < NQ; quadrant++) {
			if (quadrant == oq)
				continue;
			ixp = (unsigned char *)memchr(conv[quadrant].xlate, c,
				    NE);
			if (ixp == (unsigned char *)NULL)
				continue;
			ix = ixp - conv[quadrant].xlate;
			*ob++ = conv[quadrant].selector;
			*ob++ = asc2ebc[(int)alphas[ix]];
			break;
		}
		if (quadrant >= NQ) {
			quadrant = -1;
			fprintf(stderr, "Oops\n");
			continue;
		}
	}
	return ob - ob0;
}

/*
 * Main entry point from ctlr.c.
 * We have received what looks like an appropriate message from the host.
 */
void
ft_cut_data(void)
{
	switch (ea_buf[O_FRAME_TYPE].cc) {
	    case FT_CONTROL_CODE:
		cut_control_code();
		break;
	    case FT_DATA_REQUEST:
		cut_data_request();
		break;
	    case FT_RETRANSMIT:
		cut_retransmit();
		break;
	    case FT_DATA:
		cut_data();
		break;
	    default:
		trace_ds("< FT unknown 0x%02x\n", ea_buf[O_FRAME_TYPE].cc);
		cut_abort(get_message("ftCutUnknownFrame"), SC_ABORT_XMIT);
		break;
	}
}

/*
 * Process a control code from the host.
 */
static void
cut_control_code(void)
{
	unsigned short code;
	char *buf;
	char *bp;
	int i;

	trace_ds("< FT CONTROL_CODE ");
	code = (ea_buf[O_CC_STATUS_CODE].cc << 8) |
		ea_buf[O_CC_STATUS_CODE + 1].cc;
	switch (code) {
	    case SC_HOST_ACK:
		trace_ds("HOST_ACK\n");
		cut_xfer_in_progress = True;
		expanded_length = 0;
		quadrant = -1;
		xlate_buffered = 0;
		cut_ack();
		ft_running(True);
		break;
	    case SC_XFER_COMPLETE:
		trace_ds("XFER_COMPLETE\n");
		cut_ack();
		cut_xfer_in_progress = False;
		ft_complete((String)NULL);
		break;
	    case SC_ABORT_FILE:
	    case SC_ABORT_XMIT:
		trace_ds("ABORT\n");
		cut_xfer_in_progress = False;
		cut_ack();
		if (ft_state == FT_ABORT_SENT && saved_errmsg != CN) {
			buf = saved_errmsg;
			saved_errmsg = CN;
		} else {
			bp = buf = Malloc(81);
			for (i = 0; i < 80; i++)
				*bp++ = ebc2asc[ea_buf[O_CC_MESSAGE + i].cc];
			*bp-- = '\0';
			while (bp >= buf && *bp == ' ')
				*bp-- = '\0';
			if (bp >= buf && *bp == '$')
				*bp-- = '\0';
			while (bp >= buf && *bp == ' ')
				*bp-- = '\0';
			if (!*buf)
				strcpy(buf, get_message("ftHostCancel"));
		}
		ft_complete(buf);
		Free(buf);
		break;
	    default:
		trace_ds("unknown 0x%04x\n", code);
		cut_abort(get_message("ftCutUnknownControl"), SC_ABORT_XMIT);
		break;
	}
}

/*
 * Process a data request from the host.
 */
static void
cut_data_request(void)
{
	unsigned char seq = ea_buf[O_DR_FRAME_SEQ].cc;
	int count;
	unsigned char cs;
	int c;
	int i;
	unsigned char attr;

	trace_ds("< FT DATA_REQUEST %u\n", from6(seq));
	if (ft_state == FT_ABORT_WAIT) {
		cut_abort(get_message("ftUserCancel"), SC_ABORT_FILE);
		return;
	}

	/* Copy data into the screen buffer. */
	count = 0;
	while (count < O_UP_MAX && (c = xlate_getc()) != EOF) {
		ctlr_add(O_UP_DATA + count, c, 0);
		count++;
	}

	/* Check for errors. */
	if (ferror(ft_local_file)) {
		int j;
		char *msg;

		/* Clean out any data we may have written. */
		for (j = 0; j < count; j++)
			ctlr_add(O_UP_DATA + j, 0, 0);

		/* Abort the transfer. */
		msg = xs_buffer("read(%s): %s", ft_local_filename,
		    strerror(errno));
		cut_abort(msg, SC_ABORT_FILE);
		Free(msg);
		return;
	}

	/* Send special data for EOF. */
	if (!count && feof(ft_local_file)) {
		ctlr_add(O_UP_DATA, EOF_DATA1, 0);
		ctlr_add(O_UP_DATA+1, EOF_DATA2, 0);
		count = 2;
	}

	/* Compute the other fields. */
	ctlr_add(O_UP_FRAME_SEQ, seq, 0);
	cs = 0;
	for (i = 0; i < count; i++)
		cs ^= ea_buf[O_UP_DATA + i].cc;
	ctlr_add(O_UP_CSUM, asc2ebc[(int)table6[cs & 0x3f]], 0);
	ctlr_add(O_UP_LEN, asc2ebc[(int)table6[(count >> 6) & 0x3f]], 0);
	ctlr_add(O_UP_LEN+1, asc2ebc[(int)table6[count & 0x3f]], 0);

	/* XXX: Change the data field attribute so it doesn't display. */
	attr = ea_buf[O_DR_SF].fa;
	attr = (attr & ~FA_INTENSITY) | FA_INT_ZERO_NSEL;
	ctlr_add_fa(O_DR_SF, attr, 0);

	/* Send it up to the host. */
	trace_ds("> FT DATA %u\n", from6(seq));
	ft_update_length();
	expanded_length += count;
	action_internal(Enter_action, IA_FT, CN, CN);
}

/*
 * (Improperly) process a retransmit from the host.
 */
static void
cut_retransmit(void)
{
	trace_ds("< FT RETRANSMIT\n");
	cut_abort(get_message("ftCutRetransmit"), SC_ABORT_XMIT);
}

/*
 * Convert an encoded integer.
 */
static unsigned
from6(unsigned char c)
{
	char *p;

	c = ebc2asc[c];
	p = strchr(table6, c);
	if (p == CN)
		return 0;
	return p - table6;
}

/*
 * Process data from the host.
 */
static void
cut_data(void)
{
	static unsigned char cvbuf[O_RESPONSE - O_DT_DATA];
	unsigned short raw_length;
	int conv_length;
	register int i;

	trace_ds("< FT DATA\n");
	if (ft_state == FT_ABORT_WAIT) {
		cut_abort(get_message("ftUserCancel"), SC_ABORT_FILE);
		return;
	}

	/* Copy and convert the data. */
	raw_length = from6(ea_buf[O_DT_LEN].cc) << 6 |
		     from6(ea_buf[O_DT_LEN + 1].cc);
	if ((int)raw_length > O_RESPONSE - O_DT_DATA) {
		cut_abort(get_message("ftCutOversize"), SC_ABORT_XMIT);
		return;
	}
	for (i = 0; i < (int)raw_length; i++)
		cvbuf[i] = ea_buf[O_DT_DATA + i].cc;

	if (raw_length == 2 && cvbuf[0] == EOF_DATA1 && cvbuf[1] == EOF_DATA2) {
		trace_ds("< FT EOF\n");
		cut_ack();
		return;
	}
	conv_length = upload_convert(cvbuf, raw_length);
	if (conv_length < 0)
		return;

	/* Write it to the file. */
	if (fwrite((char *)cvbuf, conv_length, 1, ft_local_file) == 0) {
		char *msg;

		msg = xs_buffer("write(%s): %s", ft_local_filename,
		    strerror(errno));
		cut_abort(msg, SC_ABORT_FILE);
		Free(msg);
	} else {
		ft_length += conv_length;
		ft_update_length();
		cut_ack();
	}
}

/*
 * Acknowledge a host command.
 */
static void
cut_ack(void)
{
	trace_ds("> FT ACK\n");
	action_internal(Enter_action, IA_FT, CN, CN);
}

/*
 * Abort a transfer in progress.
 */
static void
cut_abort(const char *s, unsigned short reason)
{
	/* Save the error message. */
	Replace(saved_errmsg, NewString(s));

	/* Send the abort sequence. */
	ctlr_add(RO_FRAME_TYPE, RFT_CONTROL_CODE, 0);
	ctlr_add(RO_FRAME_SEQ, ea_buf[O_DT_FRAME_SEQ].cc, 0);
	ctlr_add(RO_REASON_CODE, HIGH8(reason), 0);
	ctlr_add(RO_REASON_CODE+1, LOW8(reason), 0);
	trace_ds("> FT CONTROL_CODE ABORT\n");
	action_internal(PF_action, IA_FT, "2", CN);

	/* Update the in-progress pop-up. */
	ft_aborting();
}

/*
 * Get the next translated character from the local file.
 * Returns the character (in EBCDIC), or EOF.
 */
static int
xlate_getc(void)
{
	int r;
	int c;
	unsigned char cc;
	unsigned char cbuf[4];
	int nc;

	/* If there is a data buffered, return it. */
	if (xlate_buffered) {
		r = xlate_buf[xlate_buf_ix];
		xlate_buf_ix++;
		xlate_buffered--;
		return r;
	}

	/* Get the next byte from the file. */
	c = fgetc(ft_local_file);
	if (c == EOF)
		return c;
	ft_length++;

	/* Expand it. */
	if (ascii_flag && cr_flag && !ft_last_cr && c == '\n') {
		nc = download_convert((unsigned const char *)"\r", 1, cbuf);
	} else {
		nc = 0;
		ft_last_cr = (c == '\r');
	}

	/* Convert it. */
	cc = (unsigned char)c;
	nc += download_convert(&cc, 1, &cbuf[nc]);

	/* Return it and buffer what's left. */
	r = cbuf[0];
	if (nc > 1) {
		int i;

		for (i = 1; i < nc; i++)
			xlate_buf[xlate_buffered++] = cbuf[i];
		xlate_buf_ix = 0;
	}
	return r;
}

#endif /*]*/
