/*
 * This XPR is based on bmodem - the BIX modem program
 * 
 * by David Betz, BYTE Magazine/BIX
 * 
 * bmodem consists of three files: bmodem.c, dlink.c, mdio.c. bmodem.c and
 * mdio.c were substantially modified and merged into this file, xprxmodem.c
 * 
 * dlink.c was ANSIsized and made re-entrant for xprlib by m. boucher.
 * 
 * Modified substantially by W.G.J. Langeveld for use in VLT.
 * 
 * Transformed into XPR by Marc Boucher
 * (BIX: mboucher Internet: marc@CAM.ORG)
 * 
 */

static char _StAmP[] = "Copyright ©1990 Marc Boucher, All Rights Reserved -- Compiled: " __DATE__ " " __TIME__;

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>

#include "xprxmodem.h"

#include "proto-callback.h"
#include "proto-dlink.h"

long XProtocolCleanup(struct XPR_IO * IO)
{
	if (IO->xpr_data) {
		FreeMem(IO->xpr_data, sizeof(struct XProtocolData));
		IO->xpr_data = NULL;
	}
	return (1);
}

static char *MYstrtok(char *cmd, char *toks, char **ptr)
{
	char *pa;

	if (!cmd) {
		cmd = *ptr;
		if (!cmd)
			return (NULL);
	}
	while (*cmd && strchr(toks, (int) *cmd))
		++cmd;

	if (!*cmd)
		return (NULL);

	if (pa = strpbrk(cmd, toks))
		*pa++ = 0;
	*ptr = pa;
	return (cmd);
}

static void ParseConfigString(struct XPR_IO * IO, char *str)
{
	char *delim = ",";
	char *tokptr;
	char *dupstr, *zz;

	dupstr = AllocMem(strlen(str) + 1, MEMF_PUBLIC);
	if (!dupstr)
		return;

	strcpy(dupstr, str);

	zz = MYstrtok(dupstr, delim, &tokptr);

	if (zz)
		do {
			switch (toupper((int) *zz)) {
			case 'T':
				switch (toupper((int) *(zz + 1))) {
				case '0':
					IO->xpr_data->ascii = 0;
					break;
				case '1':
					IO->xpr_data->ascii = 1;
					break;
				default:
					DisplayBeep(NULL);
					break;
				}
				break;
			case 'C':
				switch (*(zz + 1)) {
				case '0':
					IO->xpr_data->crc_conf = 0;
					break;
				case '1':
					IO->xpr_data->crc_conf = 1;
					break;
				default:
					DisplayBeep(NULL);
					break;
				}
				break;
			case 'K':
				switch (*(zz + 1)) {
				case '0':
					IO->xpr_data->big = 0;
					break;
				case '1':
					IO->xpr_data->big = 1;
					break;
				default:
					DisplayBeep(NULL);
					break;
				}
				break;
			default:
				DisplayBeep(NULL);
				break;
			}
			zz = MYstrtok(0, delim, &tokptr);
		} while (zz);

	FreeMem(dupstr, strlen(str) + 1);
}

long XProtocolSetup(struct XPR_IO * IO)
{
	long (*xupdate) (), (*xgets) (), (*xoptions) () = NULL;
	struct XPR_UPDATE xpru;
	char buffy[128];

	if ((xupdate = IO->xpr_update) == NULL)
		return (0);
	if ((xgets = IO->xpr_gets) == NULL)
		return (0);

	if (IO->xpr_extension >= 1)
		xoptions = IO->xpr_options;

	/*
	 * Allocate a bit of memory for a data buffer
	 */
	if (IO->xpr_data == NULL) {
		IO->xpr_data = AllocMem(sizeof(struct XProtocolData), MEMF_PUBLIC | MEMF_CLEAR);
		if (!IO->xpr_data) {
			xpru.xpru_updatemask = XPRU_ERRORMSG;
			xpru.xpru_errormsg = "XModem - Out of memory!";
			calla(xupdate, &xpru);
			return (0L);
		}
		IO->xpr_data->buffer = &IO->xpr_data->packet[3];
	}
	/* If setup string isn't handed to us, ask questions */

	if (IO->xpr_filename == NULL) {
		if (xoptions) {
			struct xpr_option *opti[4];

			struct xpr_option opti3 =
			{"Text Mode", XPRO_BOOLEAN, NULL, 4};
			struct xpr_option opti2 =
			{"1K Blocks", XPRO_BOOLEAN, NULL, 4};
			struct xpr_option opti1 =
			{"CRC", XPRO_BOOLEAN, NULL, 4};
			struct xpr_option opti0 =
			{"XModem options:", XPRO_HEADER, NULL, 0};

			/* kludge because of manx aztec 5.0b stupid bug */
			opti[0] = &opti0;
			opti[1] = &opti1;
			opti[2] = &opti2;
			opti[3] = &opti3;

			opti1.xpro_value = AllocMem(4, MEMF_PUBLIC);
			if (opti1.xpro_value) {
				if (IO->xpr_data->crc_conf)
					strcpy(opti1.xpro_value, "on");
				else
					strcpy(opti1.xpro_value, "off");
			}
			opti2.xpro_value = AllocMem(4, MEMF_PUBLIC);
			if (opti2.xpro_value) {
				if (IO->xpr_data->big)
					strcpy(opti2.xpro_value, "on");
				else
					strcpy(opti2.xpro_value, "off");
			}
			opti3.xpro_value = AllocMem(4, MEMF_PUBLIC);
			if (opti3.xpro_value) {
				if (IO->xpr_data->ascii)
					strcpy(opti3.xpro_value, "on");
				else
					strcpy(opti3.xpro_value, "off");
			}

			callad(xoptions, &opti, IO->xpr_finfo?3:4);

			if (
				   (!strcmp(opti1.xpro_value, "yes")) ||
				   (!strcmp(opti1.xpro_value, "on"))
				)
				IO->xpr_data->crc_conf = 1;
			else
				IO->xpr_data->crc_conf = 0;

			if (
				   (!strcmp(opti2.xpro_value, "yes")) ||
				   (!strcmp(opti2.xpro_value, "on"))
				)
				IO->xpr_data->big = 1;
			else
				IO->xpr_data->big = 0;

			if (
				   (!strcmp(opti3.xpro_value, "yes")) ||
				   (!strcmp(opti3.xpro_value, "on"))
				)
				IO->xpr_data->ascii = 1;
			else
				IO->xpr_data->ascii = 0;

			if (opti3.xpro_value)
				FreeMem(opti3.xpro_value, 4);
			if (opti2.xpro_value)
				FreeMem(opti2.xpro_value, 4);
			if (opti1.xpro_value)
				FreeMem(opti1.xpro_value, 4);
		} else {
			sprintf(buffy, "T%d,C%d,K%d", IO->xpr_data->ascii, IO->xpr_data->crc_conf, IO->xpr_data->big);
			callaa(xgets, "Enter XModem config string (A<0|1>,C<0|1>,K<0|1>)", buffy);
			ParseConfigString(IO, buffy);
		}
	} else
		ParseConfigString(IO, IO->xpr_filename);

	return (1);
}

void PutCH(struct XPR_IO * IO, int ch)
{
	long (*xfwrite) ();

	if ((xfwrite = IO->xpr_fwrite) == NULL)
		return;

	if (IO->xpr_data->savech == '\032')
		return;

	if (IO->xpr_data->savech == '\r') {
		if (ch != '\n') {
			unsigned char blou = IO->xpr_data->savech;

			calladda(xfwrite, &blou, 1, 1, (void *) IO->xpr_data->fp);
		}
		IO->xpr_data->savech = 0;
	}
	if (IO->xpr_data->ascii) {
		if (ch == '\r' || ch == '\032')
			IO->xpr_data->savech = ch;
		else {
			unsigned char blou = ch;

			calladda(xfwrite, &blou, 1, 1, (void *) IO->xpr_data->fp);
		}
	} else {
		unsigned char blou = ch;

		calladda(xfwrite, &blou, 1, 1, (void *) IO->xpr_data->fp);
	}
}

int GetCH(struct XPR_IO * IO)
{
	unsigned char chc;
	int ch;
	long (*xfread) ();

	if ((xfread = IO->xpr_fread) == NULL)
		return (0L);

	if (IO->xpr_data->savech) {
		ch = IO->xpr_data->savech;
		IO->xpr_data->savech = 0;
	} else {
		if (!calladda(xfread, &chc, 1, 1, (void *) IO->xpr_data->fp))
			ch = EOF;
		else
			ch = (int) chc;

		if (IO->xpr_data->ascii) {
			if (ch == '\n') {
				IO->xpr_data->savech = ch;
				ch = '\r';
			} else if (ch == EOF) {
				IO->xpr_data->savech = EOF;
				ch = '\032';
			}
		}
	}
	return (ch);
}

void SerialTransparent(struct XPR_IO * IO)
{
	if (IO->xpr_setserial) {
		IO->xpr_data->serialbits = calld(IO->xpr_setserial, -1);
		calld(IO->xpr_setserial, ((IO->xpr_data->serialbits) & ~(1 + 2 + 4 + 64 + 256 + 1024 + 2048 + 4096)) | 128);
	}
}

void SerialRestore(struct XPR_IO * IO)
{
	if (IO->xpr_setserial)
		calld(IO->xpr_setserial, IO->xpr_data->serialbits);
}

long XProtocolSend(struct XPR_IO * IO)
{
	long brkflag = 0, size=0, oldascii;
	long (*xupdate) (), (*xswrite) (), (*xfopen) (), (*xfclose) (),
	 (*xfread) (), (*xsread) (), (*xfinfo) (), (*xchkabort) (void);
	struct XPR_UPDATE xpru;

	if ((xupdate = IO->xpr_update) == NULL)
		return (0L);
	if ((xswrite = IO->xpr_swrite) == NULL)
		return (0L);
	if ((xfopen = IO->xpr_fopen) == NULL)
		return (0L);
	if ((xfclose = IO->xpr_fclose) == NULL)
		return (0L);
	if ((xfread = IO->xpr_fread) == NULL)
		return (0L);
	if ((xsread = IO->xpr_sread) == NULL)
		return (0L);
	if ((xchkabort = IO->xpr_chkabort) == NULL)
		return (0L);

	oldascii=IO->xpr_data->ascii;

	if(xfinfo = IO->xpr_finfo) {
		size = callad(xfinfo, IO->xpr_filename, 1);	/* get file size */
		switch(callad(xfinfo, IO->xpr_filename, 2)) {	/* get file type */
			case 1: IO->xpr_data->ascii=0; break; /* Binary */
			case 2: IO->xpr_data->ascii=1; break; /* Text */
			default: break; /* uh? */
		}
	}

	IO->xpr_data->fp = callaa(xfopen, IO->xpr_filename, "r");
	if (!IO->xpr_data->fp) {
		xpru.xpru_updatemask = XPRU_ERRORMSG | XPRU_FILENAME;
		xpru.xpru_errormsg = "Failed to open input file";
		xpru.xpru_filename = IO->xpr_filename;
		calla(xupdate, &xpru);
		IO->xpr_data->ascii=oldascii;
		return (0L);
	}
	/* Initialize display */
	bm_infoinit(IO, 1, size);

	SerialTransparent(IO);

	if (!dl_snd(IO, GetCH))
		brkflag = 2;

	SerialRestore(IO);

	if (IO->xpr_data->abort == 1)
		brkflag = 1;

	calla(xfclose, (void *) IO->xpr_data->fp);

	xpru.xpru_updatemask = XPRU_MSG;
	switch (brkflag) {
	case 0:
		xpru.xpru_msg = "Done";
		break;
	case 1:
		xpru.xpru_msg = "Aborted";
		break;
	case 2:
		xpru.xpru_msg = "Failed";
		break;
	}
	calla(xupdate, &xpru);

	IO->xpr_data->ascii=oldascii;

	if (brkflag)
		return (0);
	else
		return (1);
}

long XProtocolReceive(struct XPR_IO * IO)
{
	long brkflag = 0, oldascii;
	long (*xupdate) (), (*xswrite) (), (*xfopen) (), (*xfclose) (),
	 (*xfwrite) (), (*xsread) (), (*xfinfo) ();
	struct XPR_UPDATE xpru;

	if ((xupdate = IO->xpr_update) == NULL)
		return (0L);
	if ((xswrite = IO->xpr_swrite) == NULL)
		return (0L);
	if ((xfopen = IO->xpr_fopen) == NULL)
		return (0L);
	if ((xfclose = IO->xpr_fclose) == NULL)
		return (0L);
	if ((xfwrite = IO->xpr_fwrite) == NULL)
		return (0L);
	if ((xsread = IO->xpr_sread) == NULL)
		return (0L);

	oldascii=IO->xpr_data->ascii;

	if(xfinfo = IO->xpr_finfo) switch(callad(xfinfo, IO->xpr_filename, 2)) {	/* get file type */
		case 1: IO->xpr_data->ascii=0; break; /* Binary */
		case 2: IO->xpr_data->ascii=1; break; /* Text */
		default: break; /* uh? */
	}

	IO->xpr_data->fp = callaa(xfopen, IO->xpr_filename, "w");
	if (IO->xpr_data->fp == NULL) {
		xpru.xpru_updatemask = XPRU_ERRORMSG | XPRU_FILENAME;
		xpru.xpru_errormsg = "Failed to open output file";
		xpru.xpru_filename = IO->xpr_filename;
		calla(xupdate, &xpru);
		IO->xpr_data->ascii=oldascii;
		return (0L);
	}
	IO->xpr_data->savech = 0;

	/* Initialize display */
	bm_infoinit(IO, 0, 0);

	SerialTransparent(IO);

	if (!dl_rcv(IO, PutCH))
		brkflag = 2;

	SerialRestore(IO);

	if (IO->xpr_data->abort == 1)
		brkflag = 1;

	/*
	 * Close the file
	 */
	calla(xfclose, (void *) IO->xpr_data->fp);

	xpru.xpru_updatemask = XPRU_MSG;
	switch (brkflag) {
	case 0:
		xpru.xpru_msg = "Done";
		break;
	case 1:
		xpru.xpru_msg = "Aborted";
		break;
	case 2:
		xpru.xpru_msg = "Failed";
		break;
	}

	calla(xupdate, &xpru);

	IO->xpr_data->ascii=oldascii;

	if (brkflag) {
		long (*xsflush)(void);

		if (xsflush = IO->xpr_sflush) xsflush();
		return (0);
	} else return (1);
}

/**
*
*   The following is stuff replacing mdio.c
*
**/

/*
 * md_get - get a character from the modem with timeout
 */
int md_get(struct XPR_IO * IO, int tmo)
{
	int ch;
	unsigned char chc;
	long howmany;
	long (*xchkabort) (void);

	xchkabort = IO->xpr_chkabort;

	if (xchkabort())
		if (!IO->xpr_data->abort)
			IO->xpr_data->abort = 1;

	if (IO->xpr_data->abort) {
		calladd(IO->xpr_sread, &chc, 1, 1);
		return (DT_ERR);
	}
	howmany = calladd(IO->xpr_sread, &chc, 1, tmo * 1000000L);
	ch = chc;

	if (!howmany)
		return (DT_TIME);
	return (ch);
}

/*
 * md_put - output a character to the modem
 */
void md_put(struct XPR_IO * IO, int ch)
{
	unsigned char chc = ch;

	callad(IO->xpr_swrite, &chc, 1);
}

/*
 * md_write - write a packet to the modem
 */
void md_write(struct XPR_IO * IO, char *buf, int len)
{
	callad(IO->xpr_swrite, buf, len);
}
