//  SpruceBand.Sr - last modified:  November 20, 1978  7:06 AM

// B A N D -- Band management for scan
//   errors 900
get "Spruce.d"
get "SpruceFiles.d"
get "Alloc.Decl"
// outgoing procedures
external // defined here
	[
	BandInit
	BandClose
	BandBeginPage
	BandEndPage
	BandFlush
	FlushBuffers
	PageData		// Read/Write scan pass summary (~~ s/b in Check format!)
	winVec
	]
// incoming procedures
external // external
	[
//WINDOW
	CurrentPos
	PageToPos
	WindowCreateStream
	WindowCopy
	WindowFlush
	WindowGetBounds
	WindowGetPosition
	WindowNextPage
	WindowPositionPtr
	WindowRead
	WindowReadBlock
	WindowSetBounds
	WindowSetPage
	WindowSetPosition
	WindowWrite
	WindowWriteBlock
	PutEofError

//SPRUCE Utilities
	EmergencyOver
	SpruceCondition
	SpruceError
	FSGetRelease
	FSGetX
	FSGet
	FSPut
	Min
	Usc

//SPRUCEML
	Ugt
	DoubleAdd
	DoubleCop

// SpruceBandMl
	FlushChars
//OS
	MoveBlock
	Zero
	CallSwat
	Closes

//METER
	MeterBlock

//CURSOR
	CursorToggle
// ISF
	IndexedPageIO
	]
// incoming statics
external	[
	DPzero			//Double precision zero.
	BandFile
	BandWindow
	emergencyStorage
	nVisibleBands		//Number of bands required for this device
	nLeadingBands
	nTrailingBands
	Report
	DoEtherReport
	DoMeter
	DebugSystem
	onlyOnCopy
	OverlayTop
	PermanentBottom
	SpruceZone
	BandTable
	BandFree
	BandAvail
	CopyTable
	partNumber
	]
// internal statics
static		[
	BandBufList
	BandBuf
	BandBufSizeTotal
	BandSegments		//Number of "dumps" on disk
	bandOutPos		//Save position of "main" band output
	firstMergePage		//first page used for intermediate merge output
	winVec
	]
// buffer management values
manifest	[
	nahMergeOut = 4	// output file ahead count
	nahMergeIn = 5		// input file ditto
// Performance-related sizes:
	nMaxMergeInputs=4			//Max number of band segments to merge at once
	lnMaxMergeInputs=2
	BandBufSize = 1024+minLenSPrucePage // Max SPrucePage size
	MUDone = 0
	MUCant = 1
	]
// BandInit(),       BandClose(),       BandBeginPage()
let BandInit() be
[
	compileif BEMaxSize ge BandBufSize then [ foo=0 ]

	BandTable=FSGetX(nVisibleBands)
	CopyTable=FSGetX(nVisibleBands)
	BandBufList = 0; ReleaseBandBufs(true)
]

and BandClose() be
[
	BandTable = FSPut(BandTable)
	CopyTable = FSPut(CopyTable)
	ReleaseBandBufs(false)
]

and BandBeginPage() be // ~~ could easily eliminate
[
	BandSegments=0			//None on the disk.
]
// BandEndPage(page)		Segment Merging
//	Unless the entire page's band entries are in memory, we must now merge all the segments. There
// are BandSegments segments to merge; the first begins in page 1 of MergeFile, the 2d in page 2, etc.
// Each then continues at nMaxMergInputs-page intervals until its entries are exhausted. All segments
// have bands running from 0 to nVisibleBands-1. They must be merged into the main band file. Then
// the MergeFile must be discarded. Four segments should be sufficient for any envisioned use.

and BandEndPage(page) be
    [
    unless BandSegments do [ BandFlush(page); return ] // write entire page, return

    BandFlush(0)			//Go handle current buffer.
    CursorToggle(0)
    let p0, pn = table [ 0; 0 ], vec 2
    PageToPos(pn, firstMergePage, 0, wordItem, BandFile)
    WindowSetBounds(BandWindow, p0, pn)
    BandWindow>>SS.stepSize = 1	// back to normal page-sequencing
    WindowSetPosition(BandWindow, bandOutPos)
    BandFinal(page, 0)
    BandSegments = BandSegments-1
    let v = vec nMaxMergeInputs; winVec = v
    for s = 0 to BandSegments do
	[
	let pos = vec 2
	PageToPos(pos, firstMergePage+s, 0, wordItem, BandFile)
	winVec!s =
	    WindowCreateStream(BandFile, ksTypeReadOnly, 0, nahMergeIn, pos, 0, nMaxMergeInputs)
	]
    // Band Buffer asserted poised for next page, so one buffer is empty and initialized
    for b = 0 to nVisibleBands-1 do
	[
	// Bands from different segments must be merged in reverse order
	for s=BandSegments by -1 to 0 do
	    [ // eLen is length of (end of band) entry -- non-zero only during last iteration
	    let str, eLen, len = winVec!s, (s eq 0? 2, 0), WindowRead(str)+eLen
	    if BandAvail+len ge 0 % emergencyStorage eq 0 then FlushMergeBuffers(true)
	    BandAvail = BandAvail+len; if BandAvail ge 0 then
		SpruceCondition(906, ECFileTerminate, partNumber)
	    if len then WindowReadBlock(str, BandFree, len-eLen)
	    BandFree = BandFree+len
	    if eLen then BandFree!-2, BandFree!-1 = BEEndH, BEEndH // BandEnd simulation
	    ]
	]
    FlushMergeBuffers(false) // final writing
    for s=0 to BandSegments do Closes(winVec!s); winVec = 0
    WindowGetPosition(BandWindow, bandOutPos)
    WindowSetBounds(BandWindow)
    WindowSetPosition(BandWindow, bandOutPos)
    BandFinal(page, 1)
    AddBandBuf() // leave set up for next page
    ]

and FlushMergeBuffers(getAnother) be
	[
	// release all buffered pages for input streams to make room for output
	let posns = vec nMaxMergeInputs*2
	for s = 0 to BandSegments do
		[
		let str = winVec!s
		let pos = posns+s lshift 1
		// ~~ if stream at end of page, the GetPosition/SetPosition pair could advance to
		// ~~ the next page -- since these streams are not proceding through sequential
		// ~~ pages, this leads to an error. This solution is ugly.
		// WindowGetPosition(str, pos) // ~~ (the error did occur)
		pos!0 = str>>SS.sprucePage>>SPrucePage.pageNumber // ~~ needs abstraction
		pos!1 = CurrentPos(str)
		WindowFlush(str, true) // release the current page
		]
	FlushBuffers(getAnother) // write current buffer set
	unless getAnother return
	for s = 0 to BandSegments do
		[
		// WindowSetPosition(winVec!s, posns+s lshift 1) // ~~ other half of error
		let str, pos = winVec!s, posns+s lshift 1
		WindowSetPage(str, pos!0)
		WindowPositionPtr(str, pos!1)
		]
	]
// BandFlush( [page] )
// BandFlush is called for three reasons:
//   BandFlush() called when bands overflow during output.
//   BandFlush(0) called to flush final partial output before merger
//   BandFlush(page) called if we get to end of page before band overflow

// Two different loops.  If this is the final pass,  we can simply write the band lists out.  If this is not
// the final pass, we first count the number of words in the band entry (not counting the EndH)

and BandFlush(page; numargs n) be
    [
    if n eq 0 then page=0
    let final= n eq 1 // don't allocate more bands if BandFlush(0)
    unless final%not AddBandBuf(Yes) return
    final = page ne 0 // now final only if page argument supplied
    CursorToggle(BandSegments)
    test final then BandFinal(page, 0) or SetupMergeFile()
    for b=0 to nVisibleBands-1 do
	[
	// compute band size loop
	// *** p, nextP, and left must remain contiguous -- used in microcode ***
	let p, nextP, left = BandTable!b, nil, nil
	unless final do
	    [ 
	    let len=0
	    while p do
		[
		// ~~ stresses speed; must adjust when new types added -- knows abs. sizes and shapes
		let typeWd = @p
		len=len+(typeWd<0? 2, typeWd eq BERectangleH? 4, 2)
		p=p!-1
		]
	    unless final then WindowWrite(BandWindow, len)
	    p = BandTable!b
	    ]

	// Write band entries loop (microcode linkage)
	    [
	    unless p break
	    // ML linkage responsible for keeping stream data correct
	    // FlushChars updates p, nextP, and left (nextP not always derivable any longer)
	    // left is # words left to write in entry indicated by p
	    switchon FlushChars(lv p, lv BandWindow>>FS.fsp) into
		[
		case MUCant: // write current entry, maybe overlapped, go on to (now non-empty) next pp.
			WindowWriteBlock(BandWindow, p, left+1); endcase
		case MUDone: break
		default: SpruceError(102)
		]
	    p = nextP
	    ] repeat
	//Now write end code (only if final file)
	if final then BandEnd()
	]

    if final then BandFinal(page, 1)

    // Now reset band buffer stuff
    ReleaseBandBufs(true)
    ]
// SetupMergeFile()  BandFinal(page, last)  BandEnd()  AddBandBuf()
and SetupMergeFile() be // close main BandWindow, set it up on MergeSubfile, position appropriately
    [
    let t1 = BandFile>>SPruceFile.lnPageSize
    if BandSegments eq 0 then
	[
	bandOutPos = table [ 0; 0 ]
	WindowGetPosition(BandWindow, bandOutPos)
	let t2 = BandBufSizeTotal+nVisibleBands+2500
	t1 = (t2 rshift (t1-lnMaxMergeInputs))+nMaxMergeInputs-1
	let numPages = BandFile>>SPruceFile.numPages
	let remPages = numPages-BandWindow>>SS.sprucePage>>SPrucePage.pageNumber-1
	t1 = Min(t1, remPages)
	firstMergePage = numPages-t1
	BandWindow>>SS.stepSize = nMaxMergeInputs // interlace all intputs to merge
	]
    WindowSetPage(BandWindow, firstMergePage+BandSegments)
    BandSegments = BandSegments+1
    if BandSegments>nMaxMergeInputs then SpruceCondition(900, ECFileTerminate, partNumber)
    ]
  
and BandFinal(page, last) be
[
	if last then for i=1 to nTrailingBands do BandEnd()
	let rec=WindowNextPage(BandWindow)
	test last eq 0 then page>>PageG.bandPos=rec
	   or page>>PageG.nRecords=rec-page>>PageG.bandPos
	unless last then for i=1 to nLeadingBands do BandEnd()
]

and BandEnd() be
	for i=0 to 1 do WindowWrite(BandWindow, BEEndH)

and AddBandBuf(useFontWidthStorage; numargs na) = valof
	[
	if na<1 then useFontWidthStorage = No
	unless emergencyStorage resultis false
	let bb = FSGet(BandBufSize)
	if not bb&useFontWidthStorage then [ FSGetRelease(0); bb = FSGet(BandBufSize) ]
	unless bb resultis false
	BandBufSizeTotal = BandBufSizeTotal+BandBufSize
	bb!0 = 0
	test BandBuf then BandBuf!0 = bb or BandBufList = bb
	BandBuf = bb
	BandFree, BandAvail = BandBuf+2, -BandBufSize+BEMaxSize+1
	resultis true
	]

and ReleaseBandBufs(reInitialize) be
    [
    if BandTable then Zero(BandTable, nVisibleBands)
    let nextBuf = BandBufList
    while nextBuf do
	[ let buf = nextBuf; nextBuf = @nextBuf; FSPut(buf) ]
    BandBufList, BandBufSizeTotal, BandBuf = 0, 0, 0
    EmergencyOver()
    if reInitialize then AddBandBuf()
    ]
// Used to obtain these buffers for alternate uses during merging and font copying

and FlushBuffers(getAnother) be
	[
	if BandBuf then BandBuf!1 = BandFree-BandBuf-2
	if getAnother&AddBandBuf() return
	let nextBuf, buf = BandBufList, nil
	while nextBuf do
		[
		buf, nextBuf = nextBuf, @nextBuf
		let len = buf!1
		if len then unless WindowWriteBlock(BandWindow, buf+2, len) eq len do
			PutEofError(BandWindow)
		]
	ReleaseBandBufs(getAnother)
	]

// ------------------------------------------------------
and PageData(pDoc) = valof test pDoc then // ~~ This is all wrong, given cp exist
// ------------------------------------------------------
[
	WindowSetPosition(BandWindow, DPzero)
	WindowWriteBlock(BandWindow, pDoc, size DocG/16)
	WindowWriteBlock(BandWindow, pDoc>>DocG.Pages, pDoc>>DocG.nPages*(size PageG/16))
	WindowWriteBlock(BandWindow, pDoc>>DocG.Fonts, pDoc>>DocG.nFontLoads*(size FontG/16))
	resultis nil
]
 or
[
	let p=OverlayTop
	// ~~ BandFile c/b truncated, or replaced by map here
	IndexedPageIO(BandFile>>SPruceFile.map, 1, p, 4, isfRead)
	let pagelen=p>>DocG.nPages*(size PageG/16)
	let fontlen=p>>DocG.nFontLoads*(size FontG/16)
	let totlen=pagelen+fontlen+(size DocG/16)
	p>>DocG.Fonts=PermanentBottom-fontlen
	p>>DocG.Pages=PermanentBottom-fontlen-pagelen
	if Ugt(totlen, 4*256) then SpruceError(100)
	PermanentBottom=PermanentBottom-totlen
	MoveBlock(PermanentBottom, p, totlen)
	resultis PermanentBottom
]
// ------- History . . .

// DCS, July 27, 1977  11:11 PM, minor (Window stream) revisions
// August 4, 1977  10:18 PM, remove unused 500 word vector
// September 26, 1977  8:59 AM, handle "only on copy"
// September 27, 1977  10:38 AM, "only on copy" when merges required
// September 28, 1977  10:14 AM, speed up band flush
// October 9, 1977  3:47 PM, repair many Branch reloc. problems
// January 23, 1978  9:53 AM, move PageData here, at least for the present
// March 11, 1978  1:51 PM, diminish likelihood of storage bombing
// October 5, 1978  9:27 PM, make winVec static for external monitoring
// October 15, 1978  3:30 PM, modify buffering for fast files
// October 23, 1978  12:04 PM, use microcode (via asm code in SpruceBandMl) for buffer flushing
// October 24, 1978  8:04 AM, remove linkage method for only-on-copy branching -- use CopyTable
// October 25, 1978  7:33 AM, vastly simplify and enhance merge loop -- interlaced streams
// October 26, 1978  10:42 PM, cache merge input in large buffers, code shared with FontMake
// November 3, 1978  10:32 AM, issue end-of-band entry only after all segments merged
// November 4, 1978  12:28 AM, toss out fonts when entering merge mode, to minimize merges
// November 20, 1978  7:06 AM, FlushMergeBuffers must avoid destroying odd page succession for
//	merge streams
//