/*****************************************************************************
*           Change Log
*  Date     | Change
*-----------+-----------------------------------------------------------------
* 26-Dec-85 | [1.238] Created
* 25-Feb-86 | [1.379] include <> => include ""
* 10-Nov-91 | [1.428] <jmn> converted to Microsoft C 6.0
* 18-Nov-91 | [1.428] <jmn> memory.h => mem1401.h, avoid ANSI name
* 17-Jan-95 | [1.600J] JRJ Implement tape CU instructions
*****************************************************************************/

#include <stddef.h>
#include <stdio.h>
#include <errno.h>

#include "boolean.h"

#include "btypes.h"
#include "mem1401.h"
#include "mach.h"
#include "diag.h"
#include "instr.h"
#include "ifetch.h"
#include "alerts.h"
#include "alert.h"
#include "button.h"
#include "color.h"

#include "io.h"
#include "tape.h"

/*****************************************************************************
				1401 Simulator

			   Control Unit Instruction


				 Control Unit
				 ------------
Instruction format

Mnemonic	Op Code	A-address d-char
--------        ------- --------- ------
CU		U	%xn	  d	

Function: Applies the control function of the d-character to the unit
designated by the A-address.

Word Marks:  Word marks are not affected


I-Add		A-Add		B-Add
-----           -----           -----
NSI		%xn		Bp

Chaining:  The instruction does not chain
	
*****************************************************************************/

extern boolean CU_illegal(unsigned char d);
extern boolean CU_backspace(unsigned char d);
extern boolean CU_skip_and_blank(unsigned char d);
extern boolean CU_write_tm(unsigned char d);
extern boolean CU_rewind(unsigned char d);
extern boolean CU_unload(unsigned char d);

static boolean (* dcodes[64]) (unsigned char d) = {
	CU_illegal,	/* 0	- sp	illegal		*/
	CU_illegal,	/* 1	- 1	illegal		*/
	CU_illegal,	/* 2	- 2	illegal		*/
	CU_illegal,	/* 3	- 3	illegal		*/
	CU_illegal,	/* 4	- 4	illegal		*/
	CU_illegal,	/* 5	- 5	illegal		*/
	CU_illegal,	/* 6	- 6	illegal		*/
	CU_illegal,	/* 7	- 7	illegal		*/
	CU_illegal,	/* 8	- 8	illegal		*/
	CU_illegal,	/* 9	- 9 	illegal		*/
	CU_illegal,	/* 10	- 0	illegal		*/
	CU_illegal,	/* 11	- #/=	illegal		*/
	CU_illegal,	/* 12	- @/'	illegal		*/
	CU_illegal,	/* 13	- :	illegal		*/
	CU_illegal,	/* 14	- >	illegal		*/
	CU_illegal,	/* 15	- tm	illegal		*/
	CU_illegal,	/* 16	- b	illegal		*/
	CU_illegal,	/* 17	- /	illegal		*/
	CU_illegal,	/* 18	- S	illegal		*/
	CU_illegal,	/* 19	- T	illegal		*/
	CU_unload,	/* 20	- U	rewind/unload	*/
	CU_illegal,	/* 21	- V	illegal		*/
	CU_illegal,	/* 22	- W	illegal		*/
	CU_illegal,	/* 23	- X	illegal		*/
	CU_illegal,	/* 24	- Y	illegal		*/
	CU_illegal,	/* 25	- Z	illegal		*/
	CU_illegal,	/* 26	- rm	illegal		*/
	CU_illegal,	/* 27	- ,	illegal		*/
	CU_illegal,	/* 28	- %/(	illegal		*/
	CU_illegal,	/* 29	- ws	illegal		*/
	CU_illegal,	/* 30	- \	illegal		*/
	CU_illegal,	/* 31	- seg mk illegal	*/
	CU_illegal,	/* 32	- - 	illegal		*/
	CU_illegal,	/* 33	- J	illegal		*/
	CU_illegal,	/* 34	- K	illegal		*/
	CU_illegal,	/* 35	- L	illegal		*/
	CU_write_tm,	/* 36	- M	write tape mark	*/
	CU_illegal,	/* 37	- N	illegal		*/
	CU_illegal,	/* 38	- O	illegal		*/
	CU_illegal,	/* 39	- P	illegal		*/
	CU_illegal,	/* 40	- Q	illegal		*/
	CU_rewind,	/* 41	- R	rewind		*/
	CU_illegal,	/* 42	- !	illegal		*/
	CU_illegal,	/* 43	- $	illegal		*/
	CU_illegal,	/* 44	- *	illegal		*/
	CU_illegal,	/* 45	- ]	illegal		*/
	CU_illegal,	/* 46	- ;	illegal		*/
	CU_illegal,	/* 47	- loz	illegal		*/
	CU_illegal,	/* 48	- &/+	illegal		*/
	CU_illegal,	/* 49	- A	illegal		*/
	CU_backspace,	/* 50	- B	backspace	*/
	CU_illegal,	/* 51	- C	illegal		*/
	CU_illegal,	/* 52	- D	illegal		*/
	CU_skip_and_blank, /* 53- E	skip and blank tape */
	CU_illegal,	/* 54	- F	illegal		*/
	CU_illegal,	/* 55	- G	illegal		*/
	CU_illegal,	/* 56	- H	illegal		*/
	CU_illegal,	/* 57	- I	illegal		*/
	CU_illegal,	/* 58	- ?	illegal		*/
	CU_illegal,	/* 59	- .	illegal		*/
	CU_illegal,	/* 60	- )	illegal		*/
	CU_illegal,	/* 61	- [	illegal		*/
	CU_illegal,	/* 62	- <	illegal		*/
	CU_illegal	/* 63	- gm	illegal		*/
};

/****************************************************************************
*                                   inst_CU
* Result: boolean
*       true if operation succeeded
*	false if operation failed
* Effect:
*       Depends on d character
****************************************************************************/

boolean inst_CU()
    {
	boolean status = false;

     tell_op(op_A|op_d);

     switch(I_cycle)
	{ /* length dispatch */
	case 5: /* Normal */
		if(dcodes[d_char] == NULL) {
			/*	Missing d character	*/
			bad_d("No CU dispatch",d_char);
			alert(alert_process);
			return(false);
		}
		status = (*dcodes[d_char])(d_char);
		break;

	 default: /* illegal length */
		illegal_length();
		return false;
	} /* length dispatch */

     single_cycle_state = single_cycle_complete;
     tell_new_state("CU");

     return status;
    }


/*****************************************************************************
*		       		CU_illegal
* Inputs:
*	unsigned char d: d-char we have encountered
* Result:
*       false (always)
* Effect:
*	Initiates a process halt becuase of an illegal CU d character
*****************************************************************************/

boolean CU_illegal(unsigned char d)
{
	bad_d("CU",d);
	alert(alert_process);
	return(false);
}

/*****************************************************************************
*		       		CU_backspace
* Inputs:
*	unsigned char d: d-char we have encountered
* Result:
*       true unless we can't select the tape unit.
* Effect:
*	Initiates a backspace tape record operation
*****************************************************************************/

boolean CU_backspace(unsigned char d)
{
	tapedrive *tape = NULL;

	/*
		Select tape drive.  Quit if that fails (with
		tape alert set.  Because we aren't multitasking, we
		can't do what a real 1401 would do: just wait
	*/
	if((tape = tape_io_select()) == NULL) {
		return(false);
	}

	/*
		If the tape unit is not ready, return an error.
	*/

	if(!tape->loaded || !tape->start.active || tape->file == NULL) {
		sprintf(diag_buffer,"Tape unit %d Not Ready.\n",tape->unit);
		tell(diag_buffer);
		alert(alert_tape);
		return(false);
	}

	/*
		If at load point, this is a no-op.
	*/
	if(ftell(tape->file) == 0L) {
		return(true);
	}

	/*
		Do the backspace by taking two steps back and one forward
		until we hit BOT or an IRG or some other error occurs.
	*/

	while(true) {

		/*
			Take 2 steps back...
		*/

		if(fseek(tape->file,-2L,SEEK_CUR) != 0) {
			sprintf(diag_buffer,
				"Tape unit %d backspace %s error (seek).\n",
				strerror(errno),tape->unit);
			tell(diag_buffer);
			alert(alert_tape);
			return(false);
		}

		/*
			Check for BOT
		*/
		if(ftell(tape->file) == 0L) {
			tape->at_irg = false;
			tape->indicate.active = false;
			return(true);
		}

		/*
			Take one step forward.  If EOF, we are all done.
			It really shouldn't happen! (A REAL SHORT REEL).
		*/
		if(fread(&(tape->buff),1,1,tape->file) != 1) {
			if(!feof(tape->file)) {
				sprintf(diag_buffer,
				   "Tape unit %d backspace %s error. (read)\n",
				   strerror(errno),tape->unit);
				tell(diag_buffer);
				alert(alert_tape);
				return(false);
			}
			tape->indicate.active = true;
			return(true);
		}

		/*
			Check the byte for an IRG.  In which case we're done.
		*/
		if(tape->buff & TAPE_IRG) {
			tape->buff &= (~TAPE_IRG);
			tape->indicate.active = false;
			tape->at_irg = true;
			return(true);
		}
	}
}

/*****************************************************************************
*		       		CU_skip_and_blank
* Inputs:
*	unsigned char d: d-char we have encountered
* Result:
*       true (always)
* Effect:
*	Initiates a skip and blank tape operation, which, because PC output
*	tapes have no bad spots, does absolutely nothing.
*****************************************************************************/

boolean CU_skip_and_blank(unsigned char d)
{
	return(true);
}


/*****************************************************************************
*		       		CU_write_tm
* Inputs:
*	unsigned char d: d-char we have encountered
* Result:
*       true unless some PC write error occurs on output.
* Effect:
*	Initiates a write tape mark operation
*****************************************************************************/

boolean CU_write_tm(unsigned char d)
{
	tapedrive *tape = NULL;

	/*
		Select tape drive.  Quite if that fails (with tape alert
		set.  Because we aren't multitasking, we can't wait like
		a real 1401 would.
	*/
	if((tape = tape_io_select()) == NULL) {
		return(false);
	}

	/*
		If the tape unit is not ready or is RO, return an error.
	*/
	if(!tape->loaded || !tape->start.active || tape->file == NULL) {
		sprintf(diag_buffer,"Tape unit %d Not Ready.\n",tape->unit);
		tell(diag_buffer);
		alert(alert_tape);
		return(false);
	}

	/*
		If tape is Read Only, set transfer error and return.
	*/
	if(tape->readonly) {
		tape_transfer_error = true;
		alert(alert_tape);
		return(true);
	}


	/*
		Write out the Tape Mark.
	*/
	tape->buff = BCD_TM | TAPE_IRG;
	tape_transfer_error = false;
	tape->indicate.active = false;
	tape->at_irg = false;
	tape_io_char_write(tape);
	return(true);
}


/*****************************************************************************
*		       		CU_rewind
* Inputs:
*	unsigned char d: d-char we have encountered
* Result:
*       true unless an error occurs.
* Effect:
*	Initiates a rewind tape operation
*****************************************************************************/

boolean CU_rewind(unsigned char d)
{
	tapedrive *tape = NULL;

	/*
		Select tape drive.  Quit if that fails, with an error.
		(Normally, a real 1401 would just wait, but we can't).
	*/
	if((tape = tape_io_select()) == NULL) {
		return(false);
	}

	/*
		If the tape unit is not ready, return an error.
	*/
	if(!tape->loaded || !tape->start.active || tape->file == NULL) {
		sprintf(diag_buffer,"Tape unit %d Not Ready.\n",tape->unit);
		tell(diag_buffer);
		alert(alert_tape);
		return(false);
	}

	/*
		Rewind!
	*/
	if(fseek(tape->file,0L,SEEK_SET) != 0) {
		sprintf(diag_buffer,"Tape unit %d rewind % error (seek).\n",
			strerror(errno),tape->unit);
		tell(diag_buffer);
		alert(alert_tape);
		return(false);
	}

	tape->indicate.active = 0;
	tape_transfer_error = 0;
	tape->at_irg = false;
	return(true);
}


/*****************************************************************************
*		       		CU_unload
* Inputs:
*	unsigned char d: d-char we have encountered
* Result:
*       true, unless an error occurs.
* Effect:
*	Initiates a rewind and unload tape operation
*****************************************************************************/

boolean CU_unload(unsigned char d)
{
	tapedrive *tape = NULL;

	/*
		Select tape drive.  Quit if that fails, with an error.
		(Normally, a real 1401 would just wait, but we can't).
	*/
	if((tape = tape_io_select()) == NULL) {
		return(false);
	}

	/*
		If the tape unit is not ready, return an error.
	*/
	if(!tape->loaded || !tape->start.active || tape->file == NULL) {
		sprintf(diag_buffer,"Tape unit %d Not Ready.\n",tape->unit);
		tell(diag_buffer);
		alert(alert_tape);
		return(false);
	}

	/*
		Do the rewind and unload operation.
		If it failed, we snapped the tape!
		In which case, we still continue on.
	*/

	tape_transfer_error = false;
	if(fclose(tape->file) != 0) {
		sprintf(diag_buffer,"Tape SNAPPED (close error %s) unit %d.\n",
			strerror(errno),tape->unit);
		tape_transfer_error = true;
		tell(diag_buffer);
		alert(alert_tape);
	}

	tape->file = NULL;
	tape->loaded = false;
	tape->readonly = false;
	tape->indicate.border = COLOR_GREEN;
	tape->indicate.active = false;
	tape->start.active = false;
	tape->at_irg = false;
	return(true);
}

