#include "ibm1130_defs.h"

/* ibm1130_cr.c: IBM 1130 1442 Card Reader simulator

   Copyright (c) 2002, Brian Knittel
   Based on PDP-11 simulator written by Robert M Supnik

NOTE - there is a problem with this code. The Device Status Word (DSW) is
computed from current conditions when requested by an XIO load status
command; the value of DSW available to the simulator's examine & save
commands may NOT be accurate. This should probably be fixed.

*  Update 2002-02-29: Added deck-list option. If you issue an attach
   command and specify the filename as "@filename", the named file is interpreted
   as a list of filenames to be read in sequence; the effect is that the reader
   sees the concatenation of all of the files named.  "reset" rewinds the deck
   list. Filenames can be followed by whitespace and the letter "a" or "b",
   which indicates "ascii to 029" or "binary", respectively. Example:
   
       attach cr @decklist

   where file "decklist" contains:
  
       file01 a
	   file02 b
	   file03 b
	   file04 b

   If "a" or "b" is not specified, the device mode setting is used.

   ('a' means 029, so, if you need 026 coding, specify the
   device default as the correct 026 code and omit the 'a' on the text files lines).

*  note: I'm not sure but I think we'll need a way to simulate the 'start'
   button. What may end up being necessary is to fake this in the 'attach'
   command. In a GUI build we may want to wait until they press a button.
   Have to research: does DMS issue a read request which is only
   satisfied when START is pressed, or does START cause an interrupt that
   then asks DMS to issue a read request. I think it's the former but need
   to check. After all the status register says "empty" and "not ready"
   when the hopper is empty. So what gives? On the 360 I think the start
   button causes issues some sort of attention request.
   
*  Card image format.
   Card files can be ascii text or binary.  There are several ASCII modes:
   CODE_029, CODE_26F, etc, corresponding to different code sets.
   Punch and reader modes can be set independently.

   The 1442 card read/punch has several cycles:

   feed cycle:	moves card from hopper to read station
   					  card from read station to punch station
					  card from punch station to stacker

   read or punch: operates on card at read or punch station (but not both).

   The simulator requires input cards to be read from the file attached
   to the card reader unit. A feed cycle reads one line (text mode) or
   160 bytes (binary mode) from the input file to the read station buffer,
   copies the read station buffer to the punch station buffer, and if
   the punch unit is attached to a file, writes the punch station buffer to
   the output file.
   
   The read and punch cycles operate on the appropriate card buffer.

   Detaching the card punch flushes the punch station buffer if necessary.

   As does the 1442, a read or punch cycle w/o a feed cycle causes a
   feed cycle first.

   A feed cycle on an empty deck (reader unattaced or at EOF) clears
   the appropriate buffer, so you can punch w/o attaching a deck to
   the card reader.

// -- this may need changing depending on how things work in hardware. TBD.
||  A read cycle on an empty deck causes an error.
||  Hmmm -- what takes the place of the Start button on
\\ the card reader?

  Binary format is stored using fwrite of short ints, in this format:

     1 1
	 2 2 0 1 2 3 4 5 6 7 8 9
	 * * * * * * * * * * * * 0 0 0 0

            MSB                                LSB
   byte 0   [ 6] [ 7] [ 8] [ 9]   0    0    0    0
   byte 1   [12] [11] [ 0] [ 1] [ 2] [ 3] [ 4] [ 5] 

   This means we can read words (little endian) and get this in memory:

       12 11 0 1 2 3 4 5 6 7 8 9 - - - -

   which is what the 1130 sees.

   ASCII can be read in blocks of 80 characters but can be terminated by newline prematurely.

   Booting: card reader IPL loads 80 columns (1 card) into memory starting
   at location 0 in a split fashion:

        ________________ _ _ _
       /
   12 |
   11 |
    0 |
    1 |
    2 |
    3 | Punched card
    4 |
    5 |
    6 |
    7 |
    8 |
    9 |
	  +------------------ - - -

	   12 11  0  1  2            3   4  5  6  7  8  9 
	    |  |  |  |  |  0  0  0  / \  |  |  |  |  |  |
	  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
	  | 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|12|13|14|15|
	  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
	  |  OPCODE      | F| Tag |  DISPLACEMENT		  |
	  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

   The zeros mean that all IPL instructions are short form,
   nonindexed. The 3 column is repeated in bits 8 and 9 so
   it's a sign bit.

   Boot command on a binary deck does this. Boot on an unattached
   reader loads the standard boot2 card image. Boot with an ASCII
   deck will not be very helpful.
*/

#define READ_DELAY		100
#define PUNCH_DELAY		300
#define FEED_DELAY		500

// #define IS_ONLINE(u) (((u)->flags & (UNIT_ATT|UNIT_DIS)) == UNIT_ATT)

static t_stat cr_svc      (UNIT *uptr);
static t_stat cr_reset    (DEVICE *dptr);
static t_stat cr_set_code (UNIT *uptr, int32 match);
static t_stat cr_attach   (UNIT *uptr, char *cptr);
static t_stat cr_detach   (UNIT *uptr);

static t_stat cp_reset	  (DEVICE *dptr);
static t_stat cp_set_code (UNIT *uptr, int32 match);
static t_stat cp_detach   (UNIT *uptr);

static int16 cr_dsw  = 0;									/* device status word */
static int32 cr_wait = READ_DELAY;							/* read per-column wait */
static int32 cf_wait = PUNCH_DELAY;							/* punch per-column wait */
static int32 cp_wait = FEED_DELAY;							/* feed op wait */

#define UNIT_V_OPERATION   (UNIT_V_UF + 0)					/* operation in progress */
#define UNIT_V_CODE		   (UNIT_V_UF + 2)
#define UNIT_V_EMPTY	   (UNIT_V_UF + 4)

#define UNIT_V_LASTPUNCH   (UNIT_V_UF + 0)					/* bit in unit_cp flags */

#define UNIT_OP			 (3u << UNIT_V_OPERATION)			/* two bits */
#define UNIT_CODE		 (3u << UNIT_V_CODE)				/* two bits */
#define UNIT_EMPTY		 (1u << UNIT_V_EMPTY)

#define UNIT_LASTPUNCH	 (1u << UNIT_V_LASTPUNCH)

#define OP_IDLE		 	 (0u << UNIT_V_OPERATION)
#define OP_READING	 	 (1u << UNIT_V_OPERATION)
#define OP_PUNCHING	 	 (2u << UNIT_V_OPERATION)
#define OP_FEEDING	 	 (3u << UNIT_V_OPERATION)

#define SET_OP(op) {cr_unit.flags &= ~UNIT_OP; cr_unit.flags |= op;}

#define CODE_029 		 (0u << UNIT_V_CODE)
#define CODE_026F		 (1u << UNIT_V_CODE)
#define CODE_026C		 (2u << UNIT_V_CODE)
#define CODE_BINARY		 (3u << UNIT_V_CODE)

#define SET_CODE(un,cd) {un.flags &= ~UNIT_CODE; un.flags |= cd;}

#define COLUMN		u4										/* column field in unit record */

UNIT cr_unit = { UDATA (&cr_svc, UNIT_ATTABLE, 0) };
UNIT cp_unit = { UDATA (NULL,    UNIT_ATTABLE, 0) };

MTAB cr_mod[] = {
	{ UNIT_CODE, CODE_029,		"029",		"029",		&cr_set_code},
	{ UNIT_CODE, CODE_026F,		"026F",		"026F",		&cr_set_code},
	{ UNIT_CODE, CODE_026C, 	"026C", 	"026C",		&cr_set_code},
	{ UNIT_CODE, CODE_BINARY,	"BINARY",	"BINARY",	&cr_set_code},
	{ 0 }  };

MTAB cp_mod[] = {
	{ UNIT_CODE, CODE_029,		"029",		"029",		&cp_set_code},
	{ UNIT_CODE, CODE_026F,		"026F",		"026F",		&cp_set_code},
	{ UNIT_CODE, CODE_026C, 	"026C", 	"026C",		&cp_set_code},
	{ UNIT_CODE, CODE_BINARY,	"BINARY",	"BINARY",	&cp_set_code},
	{ 0 }  };

REG cr_reg[] = {
	{ HRDATA (CRDSW,   cr_dsw,  16) },					/* device status word */
	{ DRDATA (CRTIME,  cr_wait, 24), PV_LEFT },			/* operation wait */
	{ DRDATA (CFTIME,  cf_wait, 24), PV_LEFT },			/* operation wait */
	{ NULL }  };

REG cp_reg[] = {
	{ DRDATA (CPTIME,  cp_wait, 24), PV_LEFT },			/* operation wait */
	{ NULL }  };

DEVICE cr_dev = {
	"CR", &cr_unit, cr_reg, cr_mod,
	1, 16, 16, 1, 16, 16,
	NULL, NULL, cr_reset,
	cr_boot, cr_attach, cr_detach};

DEVICE cp_dev = {
	"CP", &cp_unit, cp_reg, cp_mod,
	1, 16, 16, 1, 16, 16,
	NULL, NULL, cp_reset,
	NULL, NULL, cp_detach};

#define CR_DSW_READ_RESPONSE			0x8000		/* device status word bits */
#define CR_DSW_PUNCH_RESPONSE			0x4000
#define CR_DSW_ERROR_CHECK				0x2000
#define CR_DSW_LAST_CARD				0x1000
#define CR_DSW_OP_COMPLETE				0x0800
#define CR_DSW_FEED_CHECK				0x0100
#define CR_DSW_BUSY						0x0002
#define CR_DSW_NOT_READY				0x0001

typedef struct {
	int		hollerith;
	char	ascii;
} CPCODE;

static CPCODE cardcode_029[] =
{
	0x0000,		' ',
	0x8000, 	'&',	 		// + in 026 Fortran
	0x4000,		'-',
	0x2000,		'0',
	0x1000,		'1',
	0x0800,		'2',
	0x0400,		'3',
	0x0200,		'4',
	0x0100,		'5',
	0x0080,		'6',
	0x0040,		'7',
	0x0020,		'8',
	0x0010,		'9',
	0x9000, 	'A',
	0x8800,		'B',
	0x8400,		'C',
	0x8200,		'D',
	0x8100,		'E',
	0x8080,		'F',
	0x8040,		'G',
	0x8020,		'H',
	0x8010,		'I',
	0x5000, 	'J',
	0x4800,		'K',
	0x4400,		'L',
	0x4200,		'M',
	0x4100,		'N',
	0x4080,		'O',
	0x4040,		'P',
	0x4020,		'Q',
	0x4010,		'R',
	0x3000, 	'/',
	0x2800,		'S',
	0x2400,		'T',
	0x2200,		'U',
	0x2100,		'V',
	0x2080,		'W',
	0x2040,		'X',
	0x2020,		'Y',
	0x2010,		'Z',
	0x0820,		':',
	0x0420,		'#',		// = in 026 Fortran
	0x0220,		'@',		// ' in 026 Fortran
	0x0120,		'\'',
	0x00A0,		'=',
	0x0060,		'"',
	0x8820,		'c',		// cent
	0x8420,		'.',
	0x8220,		'<',		// ) in 026 Fortran
	0x8120,		'(',
	0x80A0,		'+',
	0x8060,		'|',
	0x4820,		'!',
	0x4420,		'$',
	0x4220,		'*',
	0x4120,		')',
	0x40A0,		';',
	0x4060,		'n',		// not
	0x2820,		'x',		// what?
	0x2420,		',',
	0x2220,		'%',		// ( in 026 Fortran
	0x2120,		'_',
	0x20A0,		'>',
	0x2060,		'>',
};

static CPCODE cardcode_026F[] =		// 026 fortran
{
	0x0000,		' ',
	0x8000, 	'+',
	0x4000,		'-',
	0x2000,		'0',
	0x1000,		'1',
	0x0800,		'2',
	0x0400,		'3',
	0x0200,		'4',
	0x0100,		'5',
	0x0080,		'6',
	0x0040,		'7',
	0x0020,		'8',
	0x0010,		'9',
	0x9000, 	'A',
	0x8800,		'B',
	0x8400,		'C',
	0x8200,		'D',
	0x8100,		'E',
	0x8080,		'F',
	0x8040,		'G',
	0x8020,		'H',
	0x8010,		'I',
	0x5000, 	'J',
	0x4800,		'K',
	0x4400,		'L',
	0x4200,		'M',
	0x4100,		'N',
	0x4080,		'O',
	0x4040,		'P',
	0x4020,		'Q',
	0x4010,		'R',
	0x3000, 	'/',
	0x2800,		'S',
	0x2400,		'T',
	0x2200,		'U',
	0x2100,		'V',
	0x2080,		'W',
	0x2040,		'X',
	0x2020,		'Y',
	0x2010,		'Z',
	0x0420,		'=',
	0x0220,		'\'',		// ' in 026 Fortran
	0x8420,		'.',
	0x8220,		')',
	0x4420,		'$',
	0x4220,		'*',
	0x2420,		',',
	0x2220,		'(',
};

static CPCODE cardcode_026C[] =		// 026 commercial
{
	0x0000,		' ',
	0x8000, 	'+',
	0x4000,		'-',
	0x2000,		'0',
	0x1000,		'1',
	0x0800,		'2',
	0x0400,		'3',
	0x0200,		'4',
	0x0100,		'5',
	0x0080,		'6',
	0x0040,		'7',
	0x0020,		'8',
	0x0010,		'9',
	0x9000, 	'A',
	0x8800,		'B',
	0x8400,		'C',
	0x8200,		'D',
	0x8100,		'E',
	0x8080,		'F',
	0x8040,		'G',
	0x8020,		'H',
	0x8010,		'I',
	0x5000, 	'J',
	0x4800,		'K',
	0x4400,		'L',
	0x4200,		'M',
	0x4100,		'N',
	0x4080,		'O',
	0x4040,		'P',
	0x4020,		'Q',
	0x4010,		'R',
	0x3000, 	'/',
	0x2800,		'S',
	0x2400,		'T',
	0x2200,		'U',
	0x2100,		'V',
	0x2080,		'W',
	0x2040,		'X',
	0x2020,		'Y',
	0x2010,		'Z',
	0x0420,		'=',
	0x0220,		'\'',		// ' in 026 Fortran
	0x8420,		'.',
	0x8220,		')',
	0x4420,		'$',
	0x4220,		'*',
	0x2420,		',',
	0x2220,		'(',
};

static int16 ascii_to_card[256];

CPCODE *cardcode;
int ncardcode;
int32 active_cr_code;			/* the code most recently specified */
FILE *deckfile = NULL;

static int16 punchstation[80];
static int16 readstation[80];
static enum {STATION_EMPTY, STATION_LOADED, STATION_READ, STATION_PUNCHED} punchstate = STATION_EMPTY, readstate = STATION_EMPTY;

static t_bool nextdeck (void);
static void checkdeck (void);

/* lookup_codetable - use code flag setting to get code table pointer and length */

static t_bool lookup_codetable (int32 match, CPCODE **pcode, int *pncode)
{
	switch (match) {
		case CODE_029:
			*pcode  = cardcode_029;
			*pncode = sizeof(cardcode_029) / sizeof(CPCODE);
			break;

		case CODE_026F:
			*pcode  = cardcode_026F;
			*pncode = sizeof(cardcode_026F) / sizeof(CPCODE);
			break;

		case CODE_026C:
			*pcode  = cardcode_026C;
			*pncode = sizeof(cardcode_026C) / sizeof(CPCODE);
			break;

		case CODE_BINARY:
			*pcode  = NULL;
			*pncode = 0;
			break;

		default:
			printf("Eek! Undefined code table index");
			return FALSE;
	}
	return TRUE;
}

t_stat set_active_cr_code (int match)
{
	CPCODE *code;
	int i, ncode;

	active_cr_code = match;

	if (! lookup_codetable(match, &code, &ncode))
		return SCPE_ARG;

	memset(ascii_to_card, 0, sizeof(ascii_to_card));

	for (i = 0; i < ncode; i++)		// set ascii to card code table
		ascii_to_card[code[i].ascii] = (int16) code[i].hollerith;

	return SCPE_OK;
}

t_stat cr_set_code (UNIT *uptr, int32 match)
{
	return set_active_cr_code(match);
}

t_stat cp_set_code (UNIT *uptr, int32 match)
{
	CPCODE *code;
	int ncode;

	if (! lookup_codetable(match, &code, &ncode))
		return SCPE_ARG;

	cardcode  = code;				// save code table for punch output
	ncardcode = ncode;

	return SCPE_OK;
}

t_stat load_cr_boot (int drvno)
{
	/* this is from the "boot2" cold start card. Columns have been */
	/* expanded already from 12 to 16 bits. */

	static unsigned short boot2_data[] = {
		0xc80a, 0x18c2, 0xd008, 0xc019, 0x8007, 0xd017, 0xc033, 0x100a,
		0xd031, 0x7015, 0x000c, 0xe800, 0x0020, 0x08f8, 0x4828, 0x7035,
		0x70fa, 0x4814, 0xf026, 0x2000, 0x8800, 0x9000, 0x9800, 0xa000,
		0xb000, 0xb800, 0xb810, 0xb820, 0xb830, 0xb820, 0x3000, 0x08ea,
		0xc0eb, 0x4828, 0x70fb, 0x9027, 0x4830, 0x70f8, 0x8001, 0xd000,
		0xc0f4, 0xd0d9, 0xc01d, 0x1804, 0xe8d6, 0xd0d9, 0xc8e3, 0x18d3,
		0xd017, 0x18c4, 0xd0d8, 0x9016, 0xd815, 0x90db, 0xe8cc, 0xd0ef,
		0xc016, 0x1807, 0x0035, 0x00d0, 0xc008, 0x1803, 0xe8c4, 0xd00f,
		0x080d, 0x08c4, 0x1003, 0x4810, 0x70d9, 0x3000, 0x08df, 0x3000,
		0x7010, 0x00d1, 0x0028, 0x000a, 0x70f3, 0x0000, 0x00d0, 0xa0c0
	};
	int i;

	if (drvno >= 0)					/* if specified, set toggle switches to disk drive no */
		CES = drvno;				/* so BOOT DSK1 will work correctly */

	IAR = 0;						/* clear IAR */

	for (i = 0; i < 80; i++)		/* copy memory */
		WriteW(i, boot2_data[i]);

#ifdef GUI_SUPPORT
	remark_cmd("Loaded BOOT2 cold start card\n");
#endif
	return SCPE_OK;
}

t_stat cr_boot (int unitno)
{
	t_stat rval;
	short buf[80];
	int i;

	if ((rval = reset_all(0)) != SCPE_OK)
		return rval;

	if (! (cr_unit.flags & UNIT_ATT))			// no deck; load standard boot anyway
		return load_cr_boot(-1);

	if ((active_cr_code & UNIT_CODE) != CODE_BINARY) {
		printf("Can only boot from card reader when set to BINARY mode");
		return SCPE_IOERR;
	}

	if (fread(buf, sizeof(short), 80, cr_unit.fileref) != 80)
		return SCPE_IOERR;

	IAR = 0;									/* Program Load sets IAR = 0 */

	for (i = 0; i < 80; i++)					/* shift 12 bits into 16 */
		WriteW(i, (buf[i] & 0xF800) | ((buf[i] & 0x0400) ? 0x00C0 : 0x0000) | ((buf[i] & 0x03F0) >> 4));

	return SCPE_OK;
}

char card_to_ascii (int16 hol)
{
	int i;

	for (i = 0; i < ncardcode; i++)
		if (cardcode[i].hollerith == hol)
			return cardcode[i].ascii;

	return ' ';
}

/* feedcycle - move cards to next station */

static void feedcycle (t_bool load, t_bool punching)
{
	char buf[84], *x;
	int i, nread, nwrite, ch;

	/* write punched card if punch is attached to a file */
	if (cp_unit.flags & UNIT_ATT) {
		if (punchstate != STATION_EMPTY) {
			if ((cp_unit.flags & UNIT_CODE) == CODE_BINARY) {
				fwrite(punchstation, sizeof(short), 80, cp_unit.fileref);
			}
			else {
				for (i = 80; --i >= 0; ) {		/* find last nonblank column */
					if (buf[i] != 0)
						break;
				}

				/* i is now index of last character to output or -1 if all blank */

				for (nwrite = 0; nwrite <= i; nwrite++)	{			/* convert characters */
					buf[nwrite] = card_to_ascii(punchstation[nwrite]);
				}

				/* nwrite is now number of characters to output */

				buf[nwrite++] = '\n';				/* append newline */
				fwrite(buf, sizeof(char), nwrite, cp_unit.fileref);
			}
		}
	}

	if (! load)			// all we wanted to do was flush the punch
		return;

	/* slide cards from reader to punch. If we know we're punching,
	 * generate a blank card in any case. Otherwise, it should take two feed
	 * cycles to get a read card from the hopper to punch station */

	if (readstate == STATION_EMPTY) {
		if (punching) {
			memset(punchstation, 0, sizeof(punchstation));
			punchstate = STATION_LOADED;
		}
		else
			punchstate = STATION_EMPTY;
	}
	else {
		memcpy(punchstation, readstation, sizeof(punchstation));
		punchstate = STATION_LOADED;
	}

	/* load card into read station */

again:		/* jump here if we've loaded a new deck after emptying the previous one */

	if (cr_unit.flags & UNIT_ATT) {

		memset(readstation, 0, sizeof(readstation));		/* blank out the card image */

		if (cr_unit.fileref == NULL) {
			nread = 0;
		}
		else if ((active_cr_code & UNIT_CODE) == CODE_BINARY) {	/* binary read is straightforward */
			nread = fread(readstation, sizeof(short), 80, cr_unit.fileref);
		}
		else {												/* text read is harder: */
			if (fgets(buf, 81, cr_unit.fileref) == NULL)	/* read up to 80 chars */
				nread = 0;									/* hmm, end of file */
			else {											/* check for newline */
				if ((x = strchr(buf, '\r')) == NULL)
					x = strchr(buf, '\n');

				if (x == NULL) {							/* there were no delimiters, check for newline after the 80 chars, eat if present */
					ch = getc(cr_unit.fileref);
					if (ch != '\r' && ch != '\n' && ch != EOF)
						ungetc(ch, cr_unit.fileref);

					nread = 80;
				}
				else {
					*x = ' ';								/* replace with blank */
					nread = x-buf+1;
				}
			}

			upcase(buf);									/* force uppercase */

			for (i = 0; i < nread; i++)						/* convert ascii to punch code */
				readstation[i] = ascii_to_card[buf[i]];
		}

		if (nread <= 0) {									/* set hopper flag accordingly */
			if (deckfile != NULL && nextdeck())
				goto again;

			SETBIT(cr_unit.flags, UNIT_EMPTY);
			readstate = STATION_EMPTY;
		}
		else {
			CLRBIT(cr_unit.flags, UNIT_EMPTY);
			readstate = STATION_LOADED;
		}
	}
	else
		readstate = STATION_EMPTY;

	cr_unit.COLUMN = -1;								/* neither device is currently cycling */
	cp_unit.COLUMN = -1;
}

#ifdef NO_USE_FOR_THIS_CURRENTLY

/* this routine should probably be hooked up to the GUI somehow */

/* NPRO - nonprocess runout, flushes out the reader/punch */

static void npro (void)
{
	if (cr_unit.flags & UNIT_ATT)
		fseek(cr_unit.fileref, 0, SEEK_END);		/* push reader to EOF */
	if (deckfile != NULL)
		fseek(deckfile, 0, SEEK_END);				/* skip to end of deck list */

	if (punchstate == STATION_PUNCHED)
		feedcycle(FALSE, FALSE);					/* flush out card just punched */

	readstate = punchstate = STATION_EMPTY;
	cr_unit.COLUMN = -1;							/* neither device is currently cycling */
	cp_unit.COLUMN = -1;
    SETBIT(cr_unit.flags, UNIT_EMPTY);				/* set hopper empty */
}

#endif

/* skipbl - skip leading whitespace in a string */

static char * skipbl (char *str)
{
	while (*str && *str <= ' ')
		str++;

	return str;
}

/* alltrim - remove all leading and trailing whitespace from a string */

static char * alltrim (char *str)
{
	char *s, *lastnb;

	if ((s = skipbl(str)) != str)			/* slide down over leading whitespace */
		strcpy(str, s);

	for (lastnb = str-1, s = str; *s; s++)	/* point to last nonblank characteter in string */
		if (*s > ' ')
			lastnb = s;

	lastnb[1] = '\0';						/* clip just after it */

	return str;
}

/* checkdeck - set hopper empty status based on condition of current reader file */

static void checkdeck (void)
{
	t_bool empty;

	if (cr_unit.fileref == NULL) {					/* there is no open file */
		empty = TRUE;
	}
	else {
		fseek(cr_unit.fileref, 0, SEEK_END);
		empty = ftell(cr_unit.fileref) <= 0;		/* see if file has anything) */
		fseek(cr_unit.fileref, 0, SEEK_SET);		/* rewind deck */
	}

	if (empty) {
		SETBIT(cr_unit.flags, UNIT_EMPTY);
		if (cr_unit.fileref != NULL)				/* real file but it's empty, hmmm, try another */
			nextdeck();
	}
	else
		CLRBIT(cr_unit.flags, UNIT_EMPTY);
}

/* nextdeck - attempt to load a new file from the deck list into the hopper */

static t_bool nextdeck (void)
{
	char buf[200], *e;
	int code;

	if (deckfile == NULL)					/* we can't help */
		return FALSE;

	code = cr_unit.flags & UNIT_CODE;		/* default code */

	if (cr_unit.fileref != NULL) {
		fclose(cr_unit.fileref);
		cr_unit.fileref = NULL;
	}

	for (;;) {								/* get a filename */
		if (fgets(buf, sizeof(buf), deckfile) == NULL)
			break;							/* oops, no more names */

		alltrim(buf);
		if (! *buf)
			continue;						/* empty line */

		e = buf + strlen(buf) - 1;			/* last character in name */
		if (e > (buf+1) && e[-1] <= ' ') {	/* if there is at least a name + blank + character, and 2nd to last is blank */
			if (*e == 'b' || *e == 'B') {
				code = CODE_BINARY;
				e[-1] = '\0';				/* clip at the space and re-trim */
				alltrim(buf);
			}
			else if (*e == 'a' || *e == 'A') {
				code = CODE_029;
				e[-1] = '\0';
				alltrim(buf);
			}
		}

		if ((cr_unit.fileref = fopen(buf, "rb")) == NULL)
			printf("File '%s' specified in deck file '%s' cannot be opened\n", buf, cr_unit.filename+1);
		else
			break;
	}

	checkdeck();

	set_active_cr_code(code);					/* set specified code */

	return (cr_unit.flags & UNIT_EMPTY) == 0;	/* return TRUE if a deck has been loaded */
}

static t_stat cr_reset (DEVICE *dptr)
{
	cr_set_code(&cr_unit, active_cr_code & UNIT_CODE);	/* reset to specified code table */

	readstate  = STATION_EMPTY;

	cr_dsw = 0;
	sim_cancel(&cr_unit);							/* cancel any pending ops */
	calc_ints();

	SET_OP(OP_IDLE);

	SETBIT(cr_unit.flags, UNIT_EMPTY);				/* assume hopper empty */

	if (cr_unit.flags & UNIT_ATT) {
//		if (deckfile != NULL) {
//			fseek(deckfile, 0, SEEK_SET);
//			nextdeck();
//		}
//		else 
//			checkdeck();

		if (cr_unit.fileref != NULL)
			feedcycle(FALSE, FALSE);
	}

	cr_unit.COLUMN = -1;							/* neither device is currently cycling */

	return SCPE_OK;
}

static t_stat cp_reset (DEVICE *dptr)
{
	cp_set_code(&cp_unit, cp_unit.flags & UNIT_CODE);
	punchstate = STATION_EMPTY;

	cp_unit.COLUMN = -1;
	return SCPE_OK;
}

static t_stat cr_attach (UNIT *uptr, char *cptr)
{
	t_stat rval;
	t_bool use_decklist;

// no - don't cancel pending read?
//	sim_cancel(uptr);								/* cancel pending operations */

	cr_detach(uptr);								/* detach file and possibly deckfile */

	cptr = skipbl(cptr);							/* skip any leading whitespace */

	use_decklist = (*cptr == '@');					/* filename starts with @: it's a deck list */
	if (use_decklist)
		cptr++;

	if ((rval = attach_unit(uptr, cptr)) != SCPE_OK)
		return rval;

	if (use_decklist) {								/* if we skipped the '@', store the actually-specified name */
		strncpy(uptr->filename, cptr-1, CBUFSIZE);
		deckfile = cr_unit.fileref;					/* save the deck file stream in our local variable */
		cr_unit.fileref  = NULL;
		nextdeck();
	}
	else
		checkdeck();

// no - don't reset the reader
//	cr_reset(&cr_dev);								/* reset the whole thing */
//	cp_reset(&cp_dev);

	return SCPE_OK;
}

static t_stat cr_detach   (UNIT *uptr)
{
	if (deckfile != NULL) {
		fclose(deckfile);
		deckfile = NULL;
	}

	return detach_unit(uptr);
}

static t_stat cp_detach   (UNIT *uptr)
{
	if (cp_unit.flags & UNIT_ATT)
		if (punchstate == STATION_PUNCHED)
			feedcycle(FALSE, FALSE);						/* flush out card just punched */

	return detach_unit(uptr);
}

static void op_done (void)
{
	SET_OP(OP_IDLE);
	SETBIT(cr_dsw, CR_DSW_OP_COMPLETE);
	SETBIT(ILSW[4], ILSW_4_1442_CARD);
	calc_ints();
}

static t_stat cr_svc (UNIT *uptr)
{
	switch (cr_unit.flags & UNIT_OP) {
		case OP_IDLE:
			break;

		case OP_FEEDING:
			op_done();
			break;

		case OP_READING:
			if (readstate == STATION_EMPTY) {			/* read active but no cards? hang */
				sim_activate(&cr_unit, cf_wait);
				break;
			}

			if (++cr_unit.COLUMN < 80) {
				SETBIT(cr_dsw, CR_DSW_READ_RESPONSE);
				SETBIT(ILSW[0], ILSW_0_1442_CARD);
				calc_ints();
				sim_activate(&cr_unit, cr_wait);
			}
			else {
				readstate = STATION_READ;
				op_done();
			}
			break;

		case OP_PUNCHING:
			if (punchstate == STATION_EMPTY) {			/* punch active but no cards? hang */
				sim_activate(&cr_unit, cf_wait);
				break;
			}

			if (cp_unit.flags & UNIT_LASTPUNCH) {
				punchstate = STATION_PUNCHED;
				op_done();
			}
			else {
				SETBIT(cr_dsw, CR_DSW_PUNCH_RESPONSE);
				SETBIT(ILSW[0], ILSW_0_1442_CARD);
				calc_ints();
				sim_activate(&cr_unit, cp_wait);
			}
			break;
	}

	return SCPE_OK;
}

void xio_1142_card (int32 addr, int32 func, int32 modify)
{
	char msg[80];
	int ch;
	int16 wd;
	t_bool lastcard;

	switch (func) {
		case XIO_SENSE_DEV:
			if (cp_unit.flags & UNIT_ATT)
				lastcard = FALSE;					/* if punch file is open, assume infinite blank cards in reader */
			else if (readstate == STATION_EMPTY || (cr_unit.flags & UNIT_ATT) == 0)
				lastcard = TRUE;					/* if nothing to read, hopper's empty */
			else if ((ch = getc(cr_unit.fileref)) == EOF)
				lastcard = TRUE;					/* there is nothing left to read for a next card */
			else {
				ungetc(ch, cr_unit.fileref);		/* put character back; hopper's not empty */
				lastcard = FALSE;
			}

			CLRBIT(cr_dsw, CR_DSW_LAST_CARD|CR_DSW_BUSY|CR_DSW_NOT_READY);

			if (lastcard)
				SETBIT(cr_dsw, CR_DSW_LAST_CARD);

			if ((cr_unit.flags & UNIT_OP) != OP_IDLE)
				SETBIT(cr_dsw, CR_DSW_BUSY|CR_DSW_NOT_READY);
			else if (readstate == STATION_EMPTY && punchstate == STATION_EMPTY && ! lastcard)
				SETBIT(cr_dsw, CR_DSW_NOT_READY);

			if (modify & 0x01) {					/* reset interrupts */
				CLRBIT(cr_dsw, CR_DSW_READ_RESPONSE|CR_DSW_PUNCH_RESPONSE);
				CLRBIT(ILSW[0], ILSW_0_1442_CARD);
			}

			if (modify & 0x02) {
				CLRBIT(cr_dsw, CR_DSW_OP_COMPLETE);
				CLRBIT(ILSW[4], ILSW_4_1442_CARD);
			}

			ACC = cr_dsw;							/* return the DSW */
			break;

		case XIO_READ:								/* get card data into word pointed to in IOCC packet */
			if (cr_unit.flags & OP_READING) {
				if (cr_unit.COLUMN < 0) {
					xio_error("1442: Premature read!");
				}
				else if (cr_unit.COLUMN < 80) {
					WriteW(addr, readstation[cr_unit.COLUMN]);
				}
				else if (cr_unit.COLUMN == 80) {
					xio_error("1442: Read past column 80!");
					cr_unit.COLUMN++;		// don't report it again
				}
			}
			else {
				xio_error("1442: Read when not in a read cycle!");
			}
			break;

		case XIO_WRITE:
			if (cr_unit.flags & OP_PUNCHING) {
				if (cp_unit.COLUMN < 0) {
					xio_error("1442: Premature write!");
				}
				else if (cp_unit.flags & UNIT_LASTPUNCH) {
					xio_error("1442: Punch past last-punch column!");
					cp_unit.COLUMN = 81;
				}
				else if (cp_unit.COLUMN < 80) {
					wd = ReadW(addr);			/* store one word to punch buffer */
					punchstation[cp_unit.COLUMN] = wd & 0xFFF0;
					if (wd & 0x0008)			/* mark this as last column to be punched */
						SETBIT(cp_unit.flags, UNIT_LASTPUNCH);
				}
				else if (cp_unit.COLUMN == 80) {
					xio_error("1442: Punch past column 80!");
					cp_unit.COLUMN++;			// don't report it again
				}
			}
			else {
				xio_error("1442: Write when not in a punch cycle!");
			}
			break;

		case XIO_CONTROL:
			switch (modify & 7) {
				case 1:								/* start punch */
					if (punchstate != STATION_LOADED)
						feedcycle(TRUE, TRUE);

					SET_OP(OP_PUNCHING);
					cp_unit.COLUMN = -1;

					CLRBIT(cp_unit.flags, UNIT_LASTPUNCH);

					sim_cancel(&cr_unit);
					sim_activate(&cr_unit, cp_wait);
					break;

				case 2:								/* feed cycle */
					feedcycle(TRUE, FALSE);

					SET_OP(OP_FEEDING);

					sim_cancel(&cr_unit);
					sim_activate(&cr_unit, cf_wait);
					break;

				case 4:								/* start read */
					if (readstate != STATION_LOADED)
						feedcycle(TRUE, FALSE);

					SET_OP(OP_READING);
					cr_unit.COLUMN = -1;

					sim_cancel(&cr_unit);
					sim_activate(&cr_unit, cr_wait);
					break;

				case 0:
					break;

				default:
					sprintf(msg, "1442: Multiple operations in XIO_CONTROL: %x", modify);
					xio_error(msg);
					return;
			}

			break;

		default:
			sprintf(msg, "Invalid 1442 XIO function %x", func);
			xio_error(msg);
			break;
	}
}
