//
//	A little "hack" for printing BCD tapes, with special options for 1400 series
//	systems that used word separator characters for word marks (1401, 1410)
//	Output is translated from ASCII to BCD.  Word Marks are *NOT* indicated.
//


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

#include "UBCD.H"

#define TAPE_IRG 0200
#define TAPE_C_BIT 0100

#define STORAGE_WM_BIT 0x80

#define BCD_TM 017
#define BCD_WS 035
#define BCD_AS 020

#define TAPE_BUFFER_SIZE 100000L

char load_mode = 0;		// 1 to process word separators
char parity = 0;			// 0 for Even, 1 for ODD
char verbose = 1;			// 1 for verbose mode (default), 0 for "quiet" mode
int print_len = 72;		// How much data to print, per record
long print_start = 0;	//	Offset to start printing at
long record_start = 1;	//	Assume we start with first record
int record_count = -1;	//	Assume we process all records

//
//	Parity table for the 64 BCD characters (without parity bit,
//	wordmark bits, etc).
//

int parity_table[64] = {
	0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,
	1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0
};

int main(int argc, char **argv)
{
	FILE *tape;

	unsigned char far *tape_buffer;
   unsigned char far *tape_char;

   long record = 0;
	int eof = 0;

   long offset = 0;
   long mem_offset;

   int wm = 0;
   int at_irg = 0;

   unsigned char bcd_char,junk;

   void usage();

   int tape_parity(unsigned char c);
   void do_print(unsigned char *buffer,long offset,int print_len);

   //	Do options processing

   while(--argc && **(++argv) == '-') {
   	switch(tolower((*argv)[1])) {
      case 'l':
      	load_mode = 1;
         break;
      case 'o':
      	parity = 1;		// Odd parity
         break;
      case 'p':
      	print_len = atoi(&(*argv)[2]);
         break;
      case 's':
      	print_start = atoi(&(*argv)[2]);
         break;
      case 'q':
      	verbose = 0;
         break;
      case 'r':
      	record_start = atoi(&(*argv)[2]);
         break;
      case 'c':
      	record_count = atoi(&(*argv)[2]);
         break;
      default:
      	fprintf(stderr,"Unknown option: %s\n",*argv);
         usage();
      }
   }

   if(argc != 1) {
   	usage();
   }

   if(verbose) {
	   printf("Tape file name is %s\n",*argv);
   	printf("Print length per record is %d starting at offset %d\n\n",
   		print_len,print_start);
      printf("Starting at record %ld for %d records\n",record_start,record_count);
   }

   if((tape = fopen(*argv,"rb")) == NULL) {
   	fprintf(stderr,"Can't open tape file %s: ",*argv);
      perror("");
      exit(1);
   }

   if((tape_buffer = (unsigned char far *)farcalloc(TAPE_BUFFER_SIZE,1)) == NULL) {
   	fprintf(stderr,"farcalloc of tape buffer failed...\n");
      exit(1);
   }

   if(fread(&bcd_char,1,1,tape) != 1) {
   	perror("C I/O error reading first tape char: ");
      free(tape_buffer);
      exit(1);
   }

   //	Set up to be at an IRG initially

   at_irg = 1;

	//	This is the main loop for processing the tape - character by character

	while(!eof) {

   	//	Handle end of record processing (including first time thru)

      if(at_irg) {
      	at_irg = 0;							// reset EOR indicator (esp. 1st time)
         ++record;							//	bump record number
         offset = 0;							//	reset offset in record
         mem_offset = 0;					// reset offset in memory
         tape_char = tape_buffer;		//	prime the bump
         bcd_char &= (~TAPE_IRG);		// check if next character is EOR too

         //	Handle tape mark processing (always at beginning of record)

         if(bcd_char == BCD_TM) {
         	printf("Tape mark...\n");
            //
            //	Throw away (0x0F) character after tape mark -- garbage
            //
            if(fread(&junk,1,1,tape) != 1) {
            	perror("C I/O error reading char after tape mark: ");
               exit(1);
            }
         }
      }

      //	Process ordinary characters (not at end of record last time thru)

      else {
      	if(fread(&bcd_char,1,1,tape) != 1) {
         	if(feof(tape)) {
            	eof = 1;
            }
            else {
            	perror("C I/O error reading tape char: ");
               exit(1);
            }
         }
      }

      //	Check to see if we are at a new end of record.  Handle accordingly
      //	(For example, we print when we reach end of recrod).

      at_irg = bcd_char & TAPE_IRG;
      if(at_irg && load_mode && wm) {
         printf("** Trailing word separator in load mode.\n");
      }

      if(at_irg) {

         //	Do the printing.  This could be replaced by a special
         //	analysis routine, if you like.

			if(record >= record_start &&
            (record_count == -1 || record < record_start+record_count)) {
	        	if(verbose) {
		      	printf("Record %ld: length %ld",record,offset);
   		      if(load_mode) {
      		   	printf(", length in memory: %ld",mem_offset);
         		}
		         printf("\n");
   	      }

	         do_print(tape_buffer,print_start,
   	      	(print_len < mem_offset-print_start+1) ?
      	      	print_len : mem_offset-print_start+1 );

         }
      	continue;
      }

		//	Check for parity errors, and notify, then strip the parity bit

      if(tape_parity(bcd_char) != parity) {
         printf("** Parity error record %ld offset %ld\n",record,offset);
      }
      bcd_char &= (~TAPE_C_BIT);

      ++offset;

      //	Check for word separator.  If we found one, and we are in load
      //	mode, don't store it, just remember it was here

      if(load_mode && (bcd_char == BCD_WS)) {
      	wm = 1;
         continue;
      }

      //	Store character into buffer, in ASCII

      ++mem_offset;
      if(mem_offset >= TAPE_BUFFER_SIZE) {
      	fprintf(stderr,"Internal error: offset >= buffer size.\n");
         exit(1);
      }

		*tape_char = bcd_ascii[bcd_char];

      //	If we are remembering a word separator, add the WM bit

      if(wm) {
      	*tape_char |= STORAGE_WM_BIT;
      }

      //	Prepare for next character

		++tape_char;
      wm = 0;

   }

   printf("EOF after %d records\n",record);

	free(tape_buffer);
   fclose(tape);
   printf("Done.\n");
   return(0);
}


void usage()
{
	fprintf(stderr,"Usage: bcdtape [-l] [-o] [-p#] [-s#] [-r#] [-c#] <tapefile>\n");
   fprintf(stderr,"	-l: Process word separators (load mode)\n");
   fprintf(stderr,"	-o: Process tape in odd parity\n");
   fprintf(stderr,"	-s#: Start printing record at offset #\n");
   fprintf(stderr,"	-p#: Print # characters per record\n");
   fprintf(stderr,"	-r#: Start printing at record #\n");
   fprintf(stderr,"	-c#: Print # records\n");
	exit(1);
}

int tape_parity(unsigned char c)
{
	int bits = 0;
	unsigned char bit;

   if(TAPE_C_BIT &c) {
   	bits=1;
   }

   bits += parity_table[c & 077];

   return(bits % 2);
}

//	Analysis / print routine

void do_print(unsigned char *tape_buffer,long print_start,int print_len)
{
 	long i,j;
   long print_end,line_end;

   //	If we are in load mode, show the word marks, too.

   print_end = print_start+print_len;

	for(i=print_start; i < print_start+print_len; i += 80) {

     	line_end = (print_end) > i+80 ? i+80 : print_end;

		//	If load mode, print wordmarks as "v"

	   if(load_mode) {
   		for(j = i; j < line_end; ++j) {
      		printf("%c",(tape_buffer[j] & STORAGE_WM_BIT) ? 'v' : ' ');
	      }
	   	printf("\n");
	   }

		//	Print the record itself, in ASCII, stripping the word-mark bit

	   for(j=i; j < line_end; ++j) {
	   	printf("%c",tape_buffer[j] & 0x7f);
	   }

      printf("\n");
   }

   printf("\n\n");
}
