// P R I N T   
// errors 1000
//

get "spruce.d"
get "SpruceFiles.d"
get "Orbit.d"

// outgoing procedures
external
	[
	DrivePrinter
	]


// incoming procedures
external
	[
	SpruceError
	Max
	Min
	InitRam
//SprucePrint
	AddToBin
	Announce
	AwaitPageSync
	PrintNext
	ReadBands
	ReadClock
	ReadFont
//CURSOR
	CursorChar
	CursorDigit
// SprucePrintRes
	DoFunc
	ROSStatus
// Timer (debug only)
	SetTimer
	TimerHasExpired
//OS
	Zero
	DisableInterrupts
	EnableInterrupts
	]

// incoming statics
external
	[
	scanTicks		// # of 38 usec. ticks in 4 printer scan lines.
	logBandRecordSize
	bandRecordSize
	nPagesPrinted
	ros12
	ros13
	Debug
	DebugSystem
	Func
	debugTrail
	knockResult
	numPrinted
	numMustPrint
	Capabilities
	InitMeasure //  engine control monitoring stuff
	CloseMeasure
	TickMeasure
	Measure
	measureTable
	xmFonts	// Font data will be read into Bank 1 via Bank 0, if set
	nRecs
	LowAdr
	FAvalue
	nBands
	stopsPrinting
	]


// internal statics
static
	[
	revolution
	revsPerPage = 3
	rosCount = 0 // Pimlico/puffin command count
	roscprob = 0
	printSeq =10
	// delay ~~~~ debugging: delay per page to simulate printing
	debDelay = 0 // delay no delay std. 
	// delay ~~~~
	]

// File-wide structure and manifest declarations.

manifest [
	sigband = 180
	eTicks = 4
	timeOut=15*27
	printHysteresis = 15 // print this many before allowing suspension, each time in
	  // ~~ above number may want to increase for large runs, or something?
	RTC=#430
	debugTrailSize = 10*3

	// Failure Code ranges -- see ROSCheck
	maxNotReady = 9 // not ready, but probably will be soon
	maxManual = 19 // Manual intervention required
	maxJam = 99	// Error during operation
	minDocumentError = 300 // Document inherently hard to print

// printSeq values:
	warmup = 1
	standby = 2
	printing = 3
	endofrun = 4
	lowpaper = 5
	badfeed = 6
	badstrip =7
	clearing = 8
	malfClear = 9
		]


// -----------------------------------------------------------
// Print		Initialization
// -----------------------------------------------------------

// Returns -1 if printing is successfully completed, else nPagesPrinted, so can pick up where we left off.

let DrivePrinter(pDoc, nPagesAlreadyPrinted, lvFailureCode) = valof
[
	let measureVec = vec 5
	let CommTab=vec 10
	CommTab=(CommTab+1)&(-2)
	let knockKnock = false		// time to let somebody in if true

// We expect that at this time the printer's transports have been cleared from previous runs, and that we are
// starting on a fresh sheet. Since there are three images for each sheet, revolution should be 0
	nPagesPrinted = (nPagesPrinted/revsPerPage)*revsPerPage
	revolution=0  

// the command to puffin to select color depends on revolution
// the command to determine what to do with the paper depends on revolution
	let color = vec 5
	let paperAction = vec 5
	test revsPerPage eq 3 ifso
		[
		color!0 = #63041		//magenta for first rev
		color!1 = #63021		//yellow for second rev
		color!2 = #63101		//cyan for third revolution
		color!3 = #63041		//magenta for first rev, next sheet
		color!4 = #63021		//yellow for second rev, next sheet
		paperAction!0 = #63203		//feed paper on rev 1
		paperAction!1 = #63003		//noaction on rev 2
		paperAction!2 = #63103		//strip paper on rev 3
		paperAction!3 = #63203		//feed paper on rev 1, next sheet
		paperAction!4 = #63003		//noaction on rev 2, next sheet
		]
	ifnot
		[
		color!0 = #63041		//magenta for first rev
		color!1 = #63021		//yellow for second rev
		color!2 = #63101		//cyan for third revolution
		color!3 = #63201		//black for fourth revolution
		color!4 = #63041		//magenta for first rev, next sheet
		color!5 = #63021		//yellow for second rev, next sheet
		paperAction!0 = #63203		//feed paper on rev 1
		paperAction!1 = #63003		//noaction on rev 2
		paperAction!2 = #63003		//noaction on rev 3
		paperAction!3 = #63103		//strip paper on rev 4
		paperAction!4 = #63203		//feed on rev 1, next sheet
		paperAction!5 = #63003		//noaction on rev 2, next sheet
		]
// During the run we need to track the location of sheets we have already printed.  Bits 13, 14 and 15 of xportModel <on>
// represent sheets in transports C, B and A, respectively. xportModel is shifted left after every revolution;
// '1' is or'd onto it after revolutions where paperAction was 'strip'.
	let xportModel = 0
	InitializeHardware(true)
	InitMeasure(measureTable, 2000, measureVec)
	ROSCommand(#63010, lv roscprob)     // reset remote printing queue
	roscprob = roscprob&3		//don't care if these commands ignored
	if roscprob then [ @lvFailureCode  = roscprob+77; CloseMeasure(); resultis nPagesPrinted ]
	let needFonts, startRun, needInit =true, true, true
	let nC, copy, FontTable, nfRecs, LeftOver, LeftOverGuard = nil, nil, nil, nil, nil, nil
	let curbuf, buf1 = nil, nil
	let np, tim = nil, nil
	let nextPage=PrintNext(pDoc, lv copy, nPagesPrinted)
	let nextNext=PrintNext(pDoc, lv nC, nPagesPrinted+1)
// Print (cont.)		

[PrintLoop
// Find out which page to print next:
	let page=nextPage
	if page eq 0 then [ knockKnock = false; break ]
	if knockKnock then break // go let somebody in
	if stopsPrinting eq binFull do [ @lvFailureCode = 130; break]

// Read in font and relocate the pointers to characters:
  if needFonts do
	[
	let font=page>>PageG.fontLoad*(size FontG/16)+pDoc>>DocG.Fonts
		// Next call returns location of ICC table,
		FontTable = ReadFont(font, LowAdr, nRecs)
		unless xmFonts do for i=0 to pDoc>>DocG.ICCtotal-1 do // if rasters are in bank 1, they start at 0
			FontTable!i=FontTable!i + LowAdr
	FontTable=FontTable-100000b

// Figure out where the buffer is.
		let nRecords= xmFonts? (pDoc>>DocG.ICCtotal+bandRecordSize-1) rshift logBandRecordSize,
				font>>FontG.nRecords
	nfRecs=(nRecs-nRecords)
	LeftOver=LowAdr+(nRecords lshift logBandRecordSize)
	LeftOverGuard = LeftOver+(loSize&(-4))-4
	curbuf = (LeftOver+loSize+8)&(-2) // ~~ all the 4's, 8's and 10's in here are cowardly slack
	needFonts = false
	]

// Now read in the bands for this page
  ReadBands(page, curbuf, nfRecs)

  if needInit  do  
	[
// Initialize the Orbit,  etc.
	InitializeHardware(false)
	let np = nil
	let tim = @RTC
 // wait for ready or some serious condition or for long enough
	unless Debug do
		[
		np = ROSCheck(lvFailureCode, nPagesAlreadyPrinted,  standby, lv xportModel)
		if np eq -1 break // ready
		if np ge 0 resultis np
		//result was -2:  not ready yet, but may be soon.  Have we waited long enough?
		if (@RTC - tim) > timeOut do [ @lvFailureCode = 5; CloseMeasure(); resultis nPagesPrinted]
		] repeat

//   establish queue entries: a null entry and the first two real entries

	ROSCommand(#63020, lv roscprob)	// for null revolution 
	ROSCommand(#63020, lv roscprob)	// append new queue entry 
	ROSCommand(#63204, lv roscprob)		//lightsource = video
	ROSCommand(color!revolution, lv roscprob)  //select toner to use this rev
	ROSCommand(paperAction!revolution, lv roscprob)  //feed, strip or nothing
	ROSCommand(#63020, lv roscprob)	// append new queue entry 
	ROSCommand(#63204, lv roscprob)		//lightsource = video
	ROSCommand(color!(revolution+1), lv roscprob)  //select toner to use next rev
	ROSCommand(paperAction!(revolution+1), lv roscprob)  //feed, strip or nothing
	ROSCommand(#63006, lv roscprob)		// start engine

	if roscprob do [@lvFailureCode = roscprob+74; break]
// Try to empty Orbit buffers to clear them.  This is to minimize toner dumping on first (blank) page.
	compileif loSize ls 256 then [ foo=nil ]
	for i=0 to 31 do DoFunc(fReadBlock, 256, LeftOver)

	AwaitPageSync(0)	//Wait for sendVideo to go off.
	needInit = false
	]
// Print (cont.)		

	//now set up to build the next entry while bands for this image are going out -  action disabled for now.
		CommTab!0=-1
		CommTab!1=#63020		//append new queue entry
		CommTab!2=sigband - 10  // will be set to 3 when we're ready
		CommTab!3=#63204		//lightsource ← setvideo
		CommTab!4=sigband - 20 
		CommTab!5=color!(revolution+2)
		CommTab!6=sigband - 30
		CommTab!7=paperAction!(revolution+2)
		CommTab!8=-1

// Start Orbit working on the present page.
	DoFunc(fControl, #21)		//Reset, clear behind
	DoFunc(fROSCommand, adBufferReset)

	LeftOver!0=0			//Initialize LeftOver table
	LeftOverGuard!1=-1
	tim=@RTC
// Start the microcode!!!!
	DoFunc(fSlot, CommTab, 20000, nBands-1, FAvalue, LeftOver,
			FontTable, curbuf-1, nil, nil, (copy lshift 5)+4)

if Debug then // ~~delay
	[ // ~~delay
	while (@RTC-tim) < debDelay loop	// ~~delay
	tim = @RTC // ~~delay don't invoke timeout code below
	] // ~~delay
	
// Assume the page we are printing will be OK:
		nPagesPrinted=nPagesPrinted+1
		CursorDigit(nPagesPrinted)
// See who's (maybe) knocking at my door
// This logic is copied from SpruceUtilsRes: if we are not planning to defer to spooler,
//  don't trouble to shut down here, just to find that out there. Would be too expensive
	if (DebugSystem&#30) eq 0 & numPrinted ge numMustPrint & knockResult &
	(nPagesPrinted-nPagesAlreadyPrinted) > printHysteresis &
	 revolution eq revsPerPage  //stripping -- to prevent clearing a partially printed sheet
	 then knockKnock = true

	nextPage, copy = nextNext, nC
	nextNext = PrintNext(pDoc, lv nC, nPagesPrinted+1)

	if (not knockKnock) & nextNext then	//Unless this page gets messed up we will continue printing.
		//We want to be sure that puffin's queue won't empty.  Tell orbit to send out the 'append q entry' now.
		[
		   CommTab!0 = sigband  //enable interband ROS command
		   rosCount = rosCount + 4  //and keep in sync
		   if (nextPage>>PageG.fontLoad ne page>>PageG.fontLoad)  then needFonts = true
 		]
// Print (cont.)		Now see if we actually printed this page properly.

//   First, wait for Orbit to announce it is finished.
	let imageTimeout = 15*27
	while @#720 ne 0 do if (@RTC-tim) gr imageTimeout then
		[
		Blast()		// clear hardware, #720
		 // Machine dead or Orbit got behind and it wasn't detected
		 // Will generally be overriden by more specific analysis
		@lvFailureCode = 19 // Manual intervention required
		break
		]
// Check Orbit status
	let nonF = 0
	let stat=Func!9		//Orbit printing status.
	// Fatal error detected by microcode -- invalid Band entry -- possibly broken processor
	if stat<<STATUS.invalidBandEntry then
		[ InitializeHardware(true); 
		  if LeftOverGuard!1 eq -1 then SpruceError(1010, @1, @2)  //else, assume that
			//bad band entry is simply result of leftover overflow
		]
	test stat<<STATUS.prematurePageAbort then [ Announce(16); nonF = 301 ]
	  or if stat<<STATUS.behind then [ Announce(16); nonF = 300 ]
	if stat<<STATUS.timeout then [ Announce(17); @lvFailureCode = 18 ]
	if LeftOverGuard!1 ne -1 then	  [ Announce(18); nonF = 310 ]
	if nonF then @lvFailureCode = nonF
// Now try for more specific engine status check
	let running = printing
	if nextPage eq 0 do  running = endofrun
	np=ROSCheck(lvFailureCode, nPagesAlreadyPrinted, running, lv xportModel)

// Now wait for page sync to go off
	unless AwaitPageSync(0) then @lvFailureCode = 16

//Now decide what to do:
	let malfunction = @lvFailureCode
	if malfunction ge minDocumentError then malfunction = 0 // will not stop
	if revolution eq 3 then AddToBin(50)	//if stripping, add to count of sheets out

// Now react to the analysis: if at this point nothing is wrong and sheets are feeding, steam ahead.

	test malfunction
	ifnot 
	  [  //adjust  model of Puffin's transport system
	    xportModel = (xportModel lshift 1)+(revolution eq (revsPerPage-1)?1,0)
	    revolution = (revolution+1) rem revsPerPage  //puffin drum revolution
	  ]
	ifso
	[
	InitializeHardware(true)
	let backup =  nPagesPrinted-1 rem revsPerPage	// if malfunction detected outside of ROSCheck, set np

	if malfunction eq 3 % malfunction eq 9 % malfunction eq 1 do
		[
		test nextPage 
		ifnot  [ CloseMeasure(); resultis -1 ] 	//no more to do 
		ifso 
			[
			nPagesPrinted = nPagesPrinted - backup
			break  //Out of RunningLoop
			]
		]
	if np eq -1 then np = nPagesPrinted-backup
	// account for previous increment
	nPagesPrinted = Max(np-1, nPagesAlreadyPrinted)
	break
	]

]PrintLoop repeat

	InitializeHardware(true)
	CloseMeasure()
	resultis (knockKnock % @lvFailureCode)? nPagesPrinted, -1
]

// --------------------------------------------------------------------------
// nPagesPrinted = ROSCheck(lvFailureCode, nPagesAlreadyPrinted, running, 
//		lvXportModel)     added for puffin
// --------------------------------------------------------------------------

// analyze ROS status
// nPagesPrinted = -1 if there are no malfunctions, ROS is ready to print
//			otherwise, the number of pages to report as having been printed
// @lvFailureCode will contain an analysis of the current state. The code values are
//			described below.
// nPagesAlreadyPrinted places a lower bound on the nPagesPrinted result. This 
//			prevents loops through this code from backing up too far.
// running		true if caller thinks main drive is allowed to be on for "ready"
//			result, false otherwise (waiting for ready).

// ROSCheck first screens the current status for any malfunction indications, and
// returns right away if there are none. Otherwise, it applies the current status
// against a printer-dependent table of possible conditions, yielding a failure
// code and a new page value -- the last page certain to have been reliably
// printed. One possible condition is "no malfunction detected", which will only
// be issued for printers that give no positive indication when they are merely
// busy doing normal things.
// N.B. @lvFailureCode is not changed when no conditions are found

// The failure codes divide into ranges:
// 0-9		Not ready, but no indication of problems or will clear up automatically
//		(includes fuser cold, warmup mode, clearing revolutions, etc.)
// 10-19	Manual intervention required before machine can become ready
//		(paper low, power not there, etc.)
// 20-99	Jams and other operational malfunctions (many codes provided so that
//		each device can report machine-dependent jam explanations)
// 100-200	Inherent problems (e.g., insufficient bandwidth somewhere) that are
//		not curable by mere mortals. Attempts to improve the situation should be
//		abandoned.
// 200-300	Inherent problems encountered, but they did not terminate printing. One
//		should expect that one or more pages were improperly imaged.

// A check, designed chiefly for Dover, verifies that the laser is on and the polygon is
// scanning (SOS/EOS are seeing things), by making sure that the low four bits of the line
// count (indicated in status word) are  changing. The code reads the line count, waits
// approximately the time needed for four scan lines (generous margin), then reads the
// count again, reporting a problem if the values are equal. This test happens only when
// the running parameter is false. The wait time (scanTicks) is computed in PrintInit, as:

// ResolutionS is scaled by 10 as stored, PaperSpeedInches by 100. The (fast 10 bits of)
// the RTC ticks at 38 usec. intervals. Thus the # ticks wanted for four lines is:

//   ( 4 lines x 10↑6 usec/sec x  1/38 tick/usec.) / ((PaperSpeedInches/100 x ResolutionS/10) lines/sec)

// or about (106 x 10↑3) / (PaperSpeedInches/100 x ResolutionS/10)   ticks -- about 30 for Dover.

// This is only approximate because ROSStatus time, about 10 ticks for Dover, is not included.
// Part of the reason for waiting four line times is to account for possible delays in obtaining
// the correct line count status. Later, this method can be used to check for correct polygon speed.
// The scanTicks static is shifted left by 6 to match the clock reading function's results

// ROSCheck (cont)

and ROSCheck(lvFailureCode, nPagesAlreadyPrinted, expected, lvXportModel) = valof
[
ros12 = ROSStatus(12)
ros13 = ROSStatus(13)
let  scanning =  true
let mode, mainSeq, subSeq, malFnStatus = nil, nil, nil, nil
malFnStatus, subSeq, mainSeq, mode = ros13&#377, (ros13 rshift 8)&7, (ros13 rshift 11)&7, ros13 rshift 14

switchon mainSeq into
	[
	case 1: case 2: printSeq = warmup; endcase		// poweron, warmup
	case 3: printSeq =standby; endcase				// 
	case 4: switchon subSeq into
		[
		case 0: printSeq = printing; endcase			// 
		case 1: printSeq = endofrun; endcase			// 
		case 2: printSeq = lowpaper; endcase
		case 3: printSeq = badfeed; endcase
		case 4: printSeq = badstrip; endcase
		case 5: printSeq = clearing; endcase
		default: printSeq = 86					// subSeq error
		]; endcase
	case 5: printSeq = malfClear; endcase				// 
	default: printSeq = 88						//Mainseq error
	]

// ready if not in local mode and if printer-specific tests indicate ready
let ready = 
	(printSeq eq expected) & // puffin is where we think it should be
           malFnStatus eq 0 & (ros12 & #17377) eq rosCount & //no malf and no queue problems
		mode eq 1 // and puffin is in remote print mode

if ready & (printSeq ne printing) then
	[
	  let lineCount = ROSStatus(7) & #170000
	  let rtc = ReadClock()
	  until (ReadClock()-rtc) ge scanTicks loop
	  scanning = (ROSStatus(7)&#170000) ne lineCount
	]

if ready & scanning % Debug resultis -1

// Analyze tables to identify the problem or condition preventing ready
let tab =  table [ // error code,, machine code
	10 lshift 8 + #32 // 1A 2/2 low paper.
	52 lshift 8 + #00 // 00 X missed.
	53 lshift 8 + #04 // 04 malfunction memory set.
	54 lshift 8 + #10 // 08 1/1 FIRE!!!!!!!!
	55 lshift 8 + #16 // 0E 1/4 Transport A.
	56 lshift 8 + #34 // 1C 2/3 Paper grip.
	57 lshift 8 + #36 // 1E 2/4 Transport B.
	58 lshift 8 + #50 // 28 3/1 Fuser interlock open.
	59 lshift 8 + #54 // 2C 3/3 Bottom tray interlock open.
	60 lshift 8 + #56 // 2E 3/4 Transport C jam.
	61 lshift 8 + #74 // 3C 4/3 Front door open.
	62 lshift 8 + #116// 4E 5/4 Toner call.
	63 lshift 8 + #150// 68 7/1 Functional interlock open.
	0  ]

// Condition interpreter
let x = EngineCommErr(ros12)
if x ne 0 do [ x = x+71 ]
let code = 0
	test mode ne 1 then code = 11		//~~~~~~~~~~~~~~~~~~
	 or test ros12 < 0 then code = 50
	 or test malFnStatus eq 0 then  code = printSeq > malfClear? printSeq, x 
	 or test #200 le malFnStatus & malFnStatus le #240 then code = 64
                 // 80-A0 T events missed
	 or while @tab do
		[
		let entry = @tab
		if malFnStatus eq (entry&#377) then [ code = entry rshift 8; break ]
		tab = tab + 1
		]
// ROSCheck (cont)

// Codes generated elsewhere:
//	7 -- no specific malfunction detected, but something is wrong
//	16 -- page sync never turned off
//	17 -- several consecutive unidentified errors
//	18 -- Orbit timed out -- page sync never arrived, or engine died or got behind
//	19 -- Alto code timed out -- same symptoms
//	300 -- Orbit got behind, but page terminated normally
//	301 -- Orbit got behind, had to quit early
//	310 -- Left over table got too large. Please report to fixer.

// No other explicit problems, report local mode, or laser off, or unknown problem
	unless code do 
		[
		test scanning ifnot code = 1
		ifso
		[
		if printSeq ne expected resultis -2
		code =7
		]
		]
	@lvFailureCode = code
 	let revsPerPage = 3 + (Capabilities &mBlackHousing)?1,0
	let backup = nPagesPrinted rem revsPerPage  // reprint sheet on drum if any
	switchon code into
		[
		case 58 to 65:
		case 52 to 54:  //reprint sheet at C transport if any
			if @lvXportModel & 4 ne 0 do backup = backup + revsPerPage
		case 57:  //reprint sheet at B transport if any
			if @lvXportModel & 2 ne 0 do backup = backup + revsPerPage
		case 55:  //reprint sheet at A transport if any
			if @lvXportModel & 1 ne 0 do backup = backup + revsPerPage
		case 2: case 7: case 8: case 11 to 51:
			if nPagesPrinted rem revsPerPage eq 0 do
				//reprint sheet we think we should have stripped from drum
			  backup = backup + revsPerPage
		]
	@lvXportModel = 0
	resultis Max(nPagesAlreadyPrinted, nPagesPrinted-backup)
]
// -----------------------------------------
// Hardware setup for various devices.
// -----------------------------------------

and InitializeHardware(resetEngineSeq) be
[	if Debug return
	InitRam(0)			//Reset all RAM tasks, restart Trident if necessary
	DoFunc(fControl, 1)		//Reset Orbit

	if resetEngineSeq  do
	[
	ROSCommand(#60000, lv roscprob)		// Reset command count
	if roscprob eq 0 do
		[
		ROSCommand(#63050, lv roscprob)			// Read out malfunction data, clear malf.
		roscprob = roscprob & 3
		]
	]
]

and Blast() be InitializeHardware(false)

and ROSCommand(x, lvError; numargs na) be
	[
	test x eq #60000 ifso roscprob, rosCount = 0, 0
	ifnot rosCount = rosCount + 1
	DoFunc(fROSCommand, x)
	unless (na ne 2) % Debug do
		[
		let tim, good = @RTC, false
		until (@RTC - tim) ge eTicks do 
			[ 
			ros12 = ROSStatus(12)
			if (rosCount & #377) eq (ros12 & #17377) then [ good = true; break ]
			 ]
		if not good then @lvError = EngineCommErr(ros12)
		]
	]

and let EngineCommErr(x) = valof
	[
	if (x & #10000) eq #10000  do
		[
		if (x & #4000) eq #4000 resultis 3
		if (x & #2000) eq #2000 resultis 2
		if (x & #1000) eq #1000  resultis 3
		resultis 4
		]
	if (x& #377) ne (rosCount & #377) resultis 1
	resultis 0
	]


//BWB. November 13, 1978  12:36 PM derived from spruceprint
// December 11, 1978 reset remote queue doesn't append q entry anymore.  Polishit up some.
// January 2, 1979  3:41 PM modify ROSCheck to preserve status 
// February 23, 1979  4:09 PM cleanup
// March 7, 1979  2:11 PM add 4-color capability
// April 27, 1979  11:45 AM, add Dover Engine Control Monitoring code, see SpruceMeasure, by Swinehart
// 	 add ability to place fonts in bank 1 -- see all uses of xmFonts
// 	 repair bug in BankBlt interface for xmFonts stuff
// May 17, 1979  4:43 PM repair errors
// May 23, 1979  3:08 PM, fix measure interface
// August 1, 1979  3:14 PM, mBlack became mBlackHousing ??!!
// February 2, 1981  4:30 PM,  don't swat if bad band entry probably due to leftover overflow