// 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
	printerForward
	Debug
	DebugSystem
	signalBand
	Func
	debugTrail

	knockResult
	numPrinted
	numMustPrint
	InitMeasure // Dover 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
	[
		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?
// sFix ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// sFix ~~ At present, Sequoia "not ready" bit never presents itself
	// sFix ~~ Some print sequences require an engine runout, followed
	// sFix ~~  by a restart -- this delay waits for shutdown before continuing
	readyTimeOut // sFix ~~ set 0 at start, non-zero for outer loop continuation
	sReadyTimeOut=2*27+20 // sFix ~~ can tune this from Swat until it's right
	// sFix ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// delay ~~~~ debugging: delay per page to simulate printing
	debDelay = 0 // delay no delay std. 
	// delay ~~~~
	]

// File-wide structure and manifest declarations.

manifest [
	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

		]

// ROSCheck values
structure CT:
 	[
 	invert		bit 1 // bit is on when things are good
 	malFnReq	bit 1 // Dover malfunction bit must also be on or no error
 	failureCode	bit 6
 	wordIndex	bit 4
 	bitIndex	bit 4
 	]

manifest // factors for creating table entries
	[
	Invert		= #100000
	MalFnReq	= #40000
	Code		= 256
	Word		= 16
	Bit		= 1
	]



// -----------------------------------------------------------
// 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
[
// ******** See SpruceMeasure -- don't move declarations of next five local variables ********
	let malfunction, proceedCode, nonF, coldStart, consecutiveErrors = 0, 0, 0, 0, 0
// ********
	let CommTab=vec 10
	CommTab=(CommTab+1)&(-2)

	// If monitoring performance, these are real
	InitializeHardware(false)
	InitMeasure(measureTable, 2000, (lv malfunction)-1)

	consecutiveErrors=0
	let knockKnock = false		// time to let somebody in if true
	let stopPrint, okPrint = #60000, #60001 

// Print (cont.)		Slow Loop

// Loop out to here when a new font set is required, or when
// needing to completely restart the printer.
[Slow
// Find out which page to print next:
	let copy = nil
	let page=PrintNext(pDoc, lv copy, nPagesPrinted)
	if page eq 0 then [ knockKnock = false; break ]
	if knockKnock then break // go let somebody in

// Read in font and relocate the pointers to characters:
	let font=page>>PageG.fontLoad*(size FontG/16)+pDoc>>DocG.Fonts
	// Next call returns location of ICC table,
	let 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 buffers are. 
	let nRecords= xmFonts? (pDoc>>DocG.ICCtotal+bandRecordSize-1) rshift logBandRecordSize,
				font>>FontG.nRecords
	let nfRecs=(nRecs-nRecords)/2 // max allowable band list size for normal alternation
	let LeftOver = LowAdr+(nRecords lshift logBandRecordSize)
	let LeftOverGuard = LeftOver+(loSize&(-4))-4
	let buf1 = (LeftOver+loSize+8)&(-2) // ~~ all the 4's, 8's and 10's in here are cowardly slack
	let buf2=buf1+nfRecs lshift logBandRecordSize
	let curbuf= buf1 // first real data imaged from buf1

// Now read in the bands for this page
	ReadBands(page, curbuf, nfRecs*2)
// Initialize the Orbit,  etc.
	InitializeHardware(false)
	let np = nil
	let tim = @RTC
// Check for problems, wait for ready indication
  // wait for ready or some serious condition or for long enough
   unless Debug do
	// sFix ~~~~~ possibly wait for shutdown ~~~~~~~~~~~~~~~~~~
	[ // sFix ~~~~~
	while (@RTC-tim) < readyTimeOut loop
	readyTimeOut = sReadyTimeOut
	// sFix ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	[
	np = ROSCheck(lvFailureCode, nPagesAlreadyPrinted, false)
	if np eq -1 break // ready
	if @lvFailureCode > maxNotReady % (@RTC-tim) > timeOut resultis np
	] repeat
	] // sFix ~~~~~

// 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)

// Now feed first sheet:
	DoFunc(fControl, 21b)
	DoFunc(fROSCommand, adBufferReset)
	FeedASheet()
	AwaitPageSync(0)	//Wait for sendVideo to go off.
// Print (cont.)		Fast Loop

[RunningLoop
// Calculate a ROS command table for the device:
		CommTab!0=signalBand
		CommTab!1=stopPrint		//Stop unless proven otherwise (below)
		CommTab!2=-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
	
// Proceed code:
// 0: Charge on ahead, full speed
// 1: Pause because cannot fit bands or font for next page,
//     or because printing is done.

// 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 	 then knockKnock = true

// Find next page to work on:
	proceedCode=1
	let nextbuf=curbuf xor buf1 xor buf2
	let nextPage=PrintNext(pDoc,lv copy, nPagesPrinted)

	if (not knockKnock) & nextPage then
		//Unless this page gets messed up we will continue printing.
		[
		if (nextPage>>PageG.fontLoad eq page>>PageG.fontLoad) &
		(nextPage>>PageG.nRecords le nfRecs) then
			[
			proceedCode = 0

// Read in the bands for the next page:
			CommTab!1=okPrint // don't stop
			ReadBands(nextPage, nextbuf, nfRecs)
			]
		]

// Print (cont.)		Wait for completion, analyze results

// Now see if we actually printed this page properly.  First,
// wait for Orbit to announce it is finished.
	let imageTimeout = 10*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
	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 // then check possible next page data enclobberment
	   if ((proceedCode&1) eq 0) & nextbuf eq buf1 then proceedCode = 1 ]
	if nonF then @lvFailureCode = nonF
	if (stopsPrinting eq binFull) & (@lvFailureCode eq nonF)  then @lvFailureCode = 130
	AddToBin(1500)
// Now try for more specific engine status check

	np=ROSCheck(lvFailureCode, nPagesAlreadyPrinted, true)
// Now wait for page sync to go off
	unless AwaitPageSync(0) then @lvFailureCode = 16

//Now decide what to do:
	if proceedCode then Announce(proceedCode)
	malfunction = @lvFailureCode
	if malfunction ge minDocumentError then malfunction = 0 // will not stop

// Now react to the analysis: if at this point nothing is wrong and sheets are
// feeding, steam ahead. Otherwise, return to the slow loop if data is not available
// or if paper misfed. Return if there was a serious malfunction, or if things look
// odd several times consecutively (paper failed to feed is the only case, for now)

	Measure(#23,(@(#123))+1) // force random event
	unless malfunction do
	  [  
	    if proceedCode eq 0  then
		  [
		  curbuf=nextbuf
		  consecutiveErrors=0
		  loop
		  ]
	    if proceedCode eq 1 then  break  //Out of RunningLoop
	  ]
	  
	Measure(#23,(@(#123))+1) // force random event
		InitializeHardware(true)
		// if malfunction detected outside of ROSCheck, set np
		if np eq -1 then np = nPagesPrinted-1
		// account for previous increment
		np = Max(np-1, nPagesAlreadyPrinted)
		CloseMeasure() // if measure code loaded, this is real, else dummy
		resultis np
]RunningLoop repeat

]Slow repeat

	InitializeHardware(true)
	CloseMeasure()
	resultis (knockKnock % @lvFailureCode)? nPagesPrinted, -1
]


// --------------------------------------------------------------------------
// nPagesPrinted = ROSCheck(lvFailureCode, nPagesAlreadyPrinted, running, )
// --------------------------------------------------------------------------

// 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.

// The machine-dependent control table is constructed using this pattern:

// structure CT:
// 	[
// 	invert		bit 1
// 	failureCode	bit 7
// 	wordIndex	bit 4
// 	bitIndex	bit 4
// 	]

// invert means that the bit is on when the condition is not a problem (e.g., "Ready")
// failureCode should be reported when the condition specified by the bitIndex'th
//		bit inROS status word wordIndex is a problem.
// (Pimlico and Puffin use a different format:
//  right byte is status code returned from machine,
//	left byte is status code returned from ROSCheck

// 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, running) = valof
[

let ros8 = ROSStatus(8)
let ros9 = ROSStatus(9)
let localMode, scanning = nil, true
let seqStatus, malFnStatus = nil, nil

// ready if not in local mode and if printer-specific tests indicate ready
let ready =  (ros9&(running? #100, #300)) eq 0

if ready then
	[
	localMode = (ROSStatus(0)&#20000) ne 0
	unless running do
	  [
	  let lineCount = ROSStatus(7) & #170000
	  let rtc = ReadClock()
	  until (ReadClock()-rtc) ge scanTicks loop
	  scanning = (ROSStatus(7)&#170000) ne lineCount
	  ]
	if localMode % not scanning then ready = false
	]

if ready % Debug resultis -1

// Analyze tables to identify the problem or condition preventing ready

let tab =  table [
	 4*Code   +   8*Word + 13*Bit // Fuser not warm
	10*Code   +   8*Word +  5*Bit // Check paper tray
	30*Code   +   9*Word +  2*Bit // Jam
	31*Code   +   9*Word +  9*Bit // Unexplained Malfunction
	 1*Code   +   9*Word +  8*Bit // Not ready
	0  ]

let bits = table [ #100000;
	  #40000;	  #20000;	  #10000
	   #4000;	   #2000;	   #1000
	    #400;	    #200;	    #100
	     #40;	     #20;	     #10
	      #4;	      #2;	      #1	]

let code = 0

		while @tab do
			[
			let entry = @tab
			let isBit = (ROSStatus(entry<<CT.wordIndex) & bits!(entry<<CT.bitIndex)) ne 0? 1, 0
		   if isBit ne entry<<CT.invert  &  (entry<<CT.malFnReq eq 0 % (ros9&#100) ne 0) then
				[ code = entry<<CT.failureCode; break ]
			tab = tab + 1
			]

// No other explicit problems, report local mode, or laser off, or unknown problem
unless code do code = localMode? 11, scanning eq 0? 13, 7
@lvFailureCode = code
 let backup = code le maxManual? 0, code < 30? 2, 1
resultis Max(nPagesAlreadyPrinted, nPagesPrinted-backup)
]
// -----------------------------------------
// Engine controls of various sorts.
// -----------------------------------------

and FeedASheet() be 
	[
		DoFunc(fROSCommand, 60000b)
		DoFunc(fROSCommand, 60001b)
	]

and InitializeHardware(stop) be
[	if Debug return
	InitRam(0)			//Reset all RAM tasks, restart Trident if necessary

	DoFunc(fControl, 1)		//Reset Orbit

	if stop then DoFunc(fROSCommand, 60000b)
]

and Blast() be InitializeHardware(false)

// May 22, 1979  1:16 PM first cut
// February 2, 1981  4:32 PM,  don't swat if bad band entry probably due to leftover overflow