// SpruceStreams.Bcpl -- Spruce Streams -- built on "Spruce Files"
// Errors 2200

// This file contains a set of routines for implementing
// Spruce streams, one or more of which may
// rove over each Spruce File. There is also one function, 
// SetEof, that logically belongs with fast streams -- it is a
// specialization of SetEof -- Puts at eof becomes an error

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

// Spruce Streams

// incoming statics
external [
// from OS
	DefaultArgs; Zero; RetryCall; Endofs; Usc
	sysZone; SysErr; Gets; Puts
// From Spruce
	FSGet; FSGetX; FSPut; DPzero
// From SpruceFiles
	AccessSpruceFile; UnAccessSpruceFile; PutSprucePage; GetSprucePage
	FileLeng; InitSpruceFile
// From Spruce Utilities
	SpruceError; Umin; Min; Max; PosToPage; PageToPos; SpruceCondition
// From AltoByteBlt
	ByteBlt
// From SpruceMl -- Window nomenclature is temporary
	WindowRead; WindowReadByte
	WindowWrite; WindowWriteByte
	spruceStream

	// PageEvent // !!! monitoring stuff
	]

// outgoing procedures
external [
	WindowCreateStream; WindowSetPage; WindowPositionPtr; WindowSetPosition;
	WindowGetPosition; WindowFlush; WindowClose
	WindowReadBlock; WindowWriteBlock; WindowSetBounds; StreamError
	WindowNextPage; WindowGetBounds; WindowRead2Bytes; WindowCopy;
	WindowFile; WindowOddByte; WindowWordsPerPage
	CurrentPos // ~~ a good idea? rename?
	SetupWindowStream; EofError; PutEofError
	]
	
// Create stream -- optional arguments allow specification of type, item size,
// first position, direction to read (unused for now). Uses spruceFile's zone.
// Currently returns 0 only if file cannot be accessed according to type

let WindowCreateStream(spruceFile, type, itemSize, ahead, filePos, errRtn, stepSize; numargs na) = valof
	[
	let numBuffers = spruceFile>>SPruceFile.numBuffers
	// half the distance for trident -- adj. before default subst, since numBuffers already adjtd.
	if spruceFile>>SPruceFile.deviceCode eq DISKT80 then ahead = (ahead+(ahead<0? -1, 1)) rshift 1
	DefaultArgs(lv na, -1, ksTypeReadWrite, wordItem, numBuffers, DPzero, SysErr, 1)
	if ahead ge numBuffers then ahead = Max(1, numBuffers-1) // leave some room
	unless spruceFile>>SPruceFile.valid do InitSpruceFile(spruceFile)
	unless AccessSpruceFile(spruceFile,
	  (type eq ksTypeReadOnly? spruceReadAccess, spruceWriteAccess)) resultis 0
	let zone = spruceFile>>SPruceFile.zone
	let s = FSGetX(lSS,zone,0)
    // Modified code from InitializeFstream in FastStreams
	CopyTemplate(s, spruceStream, lFSx)
	s>>FSx.getOverflow = GetOverflow
	s>>FSx.putOverflow = PutOverflow
	s>>SS.getTwoBytesOverflow = GetTwoBytes // ~~ wouldn't fit in FSx
	s>>FSx.putTwoBytesOverflow = PutTwoBytes
	s>>FSx.itemSize = itemSize
	if itemSize eq wordItem then
		[
		s>>FSx.gets = WindowRead
		s>>FSx.puts = WindowWrite
		]
	s>>SS.fS.type = stTypeSpruce
	s>>SS.fS.error = errRtn
	s>>SS.fS.reset = WindowSetPosition // assumes 0 if only one arg
	s>>SS.fS.close = WindowClose
	s>>SS.spruceFile = spruceFile
	s>>SS.type = type
	s>>SS.ahead = ahead
	s>>SS.stepSize = stepSize
	s>>SS.lowPage = 1
	s>>SS.highPage = spruceFile>>SPruceFile.numPages
	s>>SS.highChars = spruceFile>>SPruceFile.numChars
	WindowSetPosition(s,filePos) // Reads in appropriate page, sets up fS, too
	resultis s
	]

and PutOverflow(s, datum) be // FS output overflow routine
	[
	if s>>FSx.eof then PutEofError(s)
	GetNextSprucePage(s, 0)
	RetryCall(s, datum)
	]

and GetOverflow(s, datum) be // FS input overflow routine
	[
	if s>>FSx.eof then EofError(s)
	GetNextSprucePage(s, 0)
	RetryCall(s, datum)
	]
	
and GetNextSprucePage(s, nextPage) be // FS overflow routine
	[
	// nextPage:
	//	-1: Cleanup request -- write dirty pages, but retain current position
	//	-2: Write dirty pages, then set Eof true and forget current position.
	//	>0: use as next page request
	//	0: get current page+1
	let spruceFile = s>>SS.spruceFile
	let sP = s>>SS.sprucePage
	if sP then
		[
		sP>>SPrucePage.dirty = Dirty(s)
		// if sP>>SPrucePage.dirty then PageEvent(s, sP>>SPrucePage.pageNumber, true) // !!!
		PutSprucePage(spruceFile, sP)
		]
	SetDirty(s,false)
	if nextPage eq -2 then [ SetEof(s, true); return ] // flush and forget
	let pageNo = nextPage>0? nextPage,
			sP? sP>>SPrucePage.pageNumber+s>>SS.stepSize+nextPage, s>>SS.lowPage
	let lpP1 = Max(s>>SS.lowPage-1, Min(s>>SS.highPage+1, pageNo+s>>SS.ahead))
	sP = pageNo < s>>SS.lowPage % pageNo > s>>SS.highPage? 0,
		GetSprucePage(spruceFile, pageNo, lpP1-pageNo, s>>SS.type ne ksTypeWriteBeforeRead)
	s>>SS.sprucePage = sP
	test sP then [
	  if nextPage<0 return // Was just flushing buffers
	  // PageEvent(s, pageNo, false) // !!!
	  let numChars = sP>>SPrucePage.numChars
	  if pageNo eq s>>SS.highPage then numChars = s>>SS.highChars
	  SetupWindowStream(s, sP>>SPrucePage.buffer, 0, numChars) ]
	or SetEof(s,true) // WARNING: no official stream access to any page
	]

and WindowWriteBlock(s, addr, length) = WindowReadBlock(s, addr, length, true)

and WindowReadBlock(s, addr, length, write;numargs na) = valof
	[
	// ~~ assumes length le 2↑14 words
	if not length % Endofs(s) resultis 0 // zero length file or currently at end -- files do not extend
	if na<4 then write = false
	unless s>>SS.fS.type eq stTypeSpruce do SpruceError(2200,s)
	let sF = s>>SS.spruceFile
	let pos = CurrentPos(s)
	let numToMove = length lshift 1
	let thisToMove, addrPos = 0, 0
	// while numToMove do
		[
		let sP, pageNo = s>>SS.sprucePage, sP>>SPrucePage.pageNumber
		unless sP do SpruceError(2250)
		let numChars = sP>>SPrucePage.numChars
		if pageNo eq s>>SS.highPage then numChars = s>>SS.highChars
		thisToMove = Umin(numChars-pos, numToMove)
		let buf = sP>>SPrucePage.buffer
		if thisToMove then
		   test write eq 0 then ByteBlt(addr, addrPos, buf, pos, thisToMove)
			or [ ByteBlt(buf, pos, addr, addrPos, thisToMove);
			     SetDirty(s, true) ]
		addrPos = addrPos+thisToMove
		numToMove = numToMove-thisToMove
	// loop control
		if numToMove eq 0 % pageNo eq s>>SS.highPage break
		pos = 0
		GetNextSprucePage(s,pageNo+s>>SS.stepSize)
		] repeat
	WindowPositionPtr(s,pos+thisToMove)
	resultis length-(numToMove rshift 1) // inaccurate if odd-aligned word move
	]

and WindowSetPosition(s, pos, itemSize; numargs na) be
// pos MUST be a pointer to a two word file position (ref itemSize),
// or omitted (def. 0)
	[
	let p = vec 1; Zero(p,2); if na<2 then pos = p
	if na < 3 then itemSize = wordItem
	let pn, chars = nil, nil
	let sF = s>>SS.spruceFile
	PosToPage(pos, lv pn, lv chars, itemSize, sF)
	WindowSetPage(s, pn)
	unless s>>SS.sprucePage test chars then EofError(s) or return
	WindowPositionPtr(s, chars)
	]

and WindowSetPage(s, pn) be
	[
// error if out of range
	if pn < s>>SS.lowPage % pn > s>>SS.highPage then
	   test pn eq s>>SS.highPage+1 &
		s>>SS.highChars eq (s>>SS.spruceFile>>SPruceFile.pageSize lshift 1)
		  ifso pn = -2
		  ifnot SpruceCondition(2210, ECFatal+ECTestRead, s, pn)
	GetNextSprucePage(s, pn)
	unless pn eq -2 do
		WindowPositionPtr(s, pn eq s>>SS.lowPage? s>>SS.lowChars, 0)
	]

and WindowPositionPtr(s, newPos) be
	[
	let sP = s>>SS.sprucePage
	unless sP do SpruceError(2250)
	let pageNo = sP>>SPrucePage.pageNumber
	let numChars =
	  pageNo eq s>>SS.highPage? s>>SS.highChars, sP>>SPrucePage.numChars
	if newPos > numChars % (pageNo eq s>>SS.lowPage & 
		newPos < s>>SS.lowChars)  then
		 SpruceCondition(2220, ECFatal+ECTestRead, s, newPos, pageNo)
	SetEof(s, false)
	SetupWindowStream(s, sP>>SPrucePage.buffer, newPos, numChars)
	]

and WindowGetPosition(s, pos, itemSize; numargs na) = valof
	[
	let v = vec 1; if na < 2 % pos eq 0 then pos = v
	if na < 3 then itemSize = wordItem
	resultis
	  PageToPos(pos, s>>SS.sprucePage>>SPrucePage.pageNumber,
	    CurrentPos(s), itemSize, s>>SS.spruceFile)
	]

// set or reset the eof flag -- like OS fast streams, except Puts at eof is error

and SetEof(s, newValue) be if s>>FSx.eof ne newValue then
	[
  // extracted from faststreams
	s>>FSx.eof=newValue
	s>>FSx.gets=(newValue ?
	  (valof [ s>>FSx.savedGets=s>>FSx.gets; resultis EofError ]),
	  s>>FSx.savedGets)
  // end of extract
	s>>SS.fS.puts=(newValue ?
	  (valof [ s>>SS.savedPuts=s>>SS.fS.puts; resultis PutEofError ]),
	  s>>SS.savedPuts)
	if newValue then s>>SS.sprucePage = 0 // nichts to write
	]

and WindowFlush(s, forget; numargs na) be
	[
	if na < 2 then forget = false
	GetNextSprucePage(s,(forget? -2, -1)) // Flush dirty
	// ~~ When becomes an issue, wait for all to be done
	]

and WindowClose(s) = valof
	[
	WindowFlush(s, true) // flag for "don't update checkpoint?"
	let sF = s>>SS.spruceFile
	UnAccessSpruceFile(sF)
	// additional checkpoint information -- stream gone
	FSPut(s, sF>>SPruceFile.zone)
	resultis 0
	]

and WindowSetBounds(s, lowPos, highPos, itemSize; numargs na) be
	[
	let defB = vec 1
	if na<4 then itemSize = wordItem
	let sF = s>>SS.spruceFile
	if na<2 then
		[
		lowPos = DPzero
		highPos = defB
		FileLeng(sF, defB)
		]
	WindowFlush(s, true) // Flush and forget
	PosToPage(lowPos, lv s>>SS.lowPage, lv s>>SS.lowChars, itemSize, sF)
	PosToPage(highPos, lv s>>SS.highPage, lv s>>SS.highChars, itemSize, sF)
	]

and WindowGetBounds(s, lowPos, highPos, itemSize; numargs na) be
	[
	if na<4 then itemSize = wordItem
	let sF = s>>SS.spruceFile
	PageToPos(lowPos, s>>SS.lowPage, s>>SS.lowChars, itemSize, sF)
	if na ge 3 & highPos then
		PageToPos(highPos, s>>SS.highPage, s>>SS.highChars, itemSize, sF)
	]

and WindowOddByte(s) = s>>FSx.charPtr < 0

and WindowNextPage(s) = valof
	[
	let sP = s>>SS.sprucePage
	unless sP do PutEofError(s)
	let pn = sP>>SPrucePage.pageNumber+s>>SS.stepSize
	if CurrentPos(s) eq 0 resultis pn-1
	WindowSetPage(s, pn)
	resultis pn
	]

and WindowWordsPerPage(s) = s>>SS.spruceFile>>SPruceFile.pageSize


// Temporary functions for compatibility with "Press" version of Spruce

and WindowRead2Bytes(s) = WindowRead(s)

and WindowFile(s) = s>>SS.spruceFile // DIFFERENT structure from "Press" version

// ------------------------------------------------------
and WindowCopy(fromS, toS, len) be
// ------------------------------------------------------
	[
	let fromSp = fromS>>SS.sprucePage
	unless fromSp do EofError(fromS)
	let cPos = CurrentPos(fromS) rshift 1
	let thisToMove = Umin(len, (fromSp>>SPrucePage.numChars rshift 1)-cPos)
	if WindowWriteBlock(toS, fromSp>>SPrucePage.buffer+cPos, thisToMove)
		ne thisToMove then EofError(toS)
	len = len - thisToMove
	WindowPositionPtr(fromS, (cPos+thisToMove) lshift 1)
	unless len return
	WindowNextPage(fromS)
	] repeat

// Fast stream auxiliary routines, extracted from FastStreams and modified

and GetTwoBytes(s) = WindowReadByte(s) lshift 8 + WindowReadByte(s)

and PutTwoBytes(s, w) = WindowWriteByte(s, w rshift 8) & WindowWriteByte(s,w&#377)

and CopyTemplate(dest, template, length) be
 for i=0 to length-1 do
	[
	let t=template!i
	if (t & #177700) ne 0 then t=rv t
	dest!i=t
	]

and CurrentPos(s)= 
	(s>>FSx.endPos) + ((s>>FSx.count+1) lshift 1) - (s>>FSx.charPtr < 0? 1, 0)

and EofError(s) be SpruceCondition(2270, ECTestRead+ECFatal, s)

and PutEofError(s) be SpruceCondition(2260, ECTestWrite+ECFatal, s)

and StreamError(s) be s>>FS.error(s, 1303)

and Dirty(s)=s>>FSx.dirty ne 0

and SetDirty(s, v) be s>>FSx.dirty=v

and SetupWindowStream(s, wordBase, currentPos, endPos) be
	[
//   endPos must be even, the first character not in the range
//   charPtr ← -1 if currentPos is odd, else 0
//   wordPtr ← address of word containing the character preceding the first
//	in-range character
//   count ← -(# complete in-range words + 1)
	let charPtr = -(currentPos&1)
	s>>FSx.charPtr = charPtr
	currentPos = currentPos rshift 1 // word pos
	s>>FSx.wordPtr = wordBase + currentPos - (1+charPtr)
	unless (endPos&1) eq 0 do SpruceCondition(2240,ECTestRead+ECFatal, s)
	s>>FSx.endPos = endPos& #177776 // now even, can continue manually
	s>>FSx.count = - ((endPos rshift 1)-currentPos+charPtr+1)
	]

// June 27, 1977  11:41 AM, major revision (see 6-9-77 archive); adapt to
//	new ML, prepare for word-based positioning
// June 27, 1977  2:49 PM, sign problems with charPtr
// June 27, 1977  7:40 PM, fix CurrentPos for odd-positioned stream
// June 27, 1977  9:16 PM, repair odd-aligned problems
// June 27, 1977  10:47 PM, use Window nomenclature, modify calling sequences
// June 27, 1977  11:58 PM, enforce upper boundary in GetNextSprucePage
// July 5, 1977  8:49 AM, revise error codes to match "Press" approach
// July 5, 1977  11:02 PM, 7-5 hardcopy modifications (vbl page size, etc.)
// July 11, 1977  7:02 PM, sprucePage contains data buffer
// July 15, 1977  9:21 AM, CurrentPos made external
// July 27, 1977  11:08 PM, WindowFlush no longer forgets file position
// July 28, 1977  5:04 PM, repair page ref count problem
// July 30, 1977  2:32 PM, WindowGetBounds bugs
// August 1, 1977  2:41 PM, WindowRead, WindowWrite, etc., when called directly,
//		did not trigger EofError
// August 4, 1977  10:15 PM, use FS for windowCopy buffer, not vec
// August 31, 1977  11:48 AM, NextPage doesn't leave blank pages; WindowCopy insists on success
// September 9, 1977  11:09 PM, distinguish read EOF from write EOF, enable bands file
// 					output overflow test
// January 17, 1978  3:10 PM, adjust to even boundary after odd boundary SetupWindow error
// March 11, 1978  1:57 PM, can give WindowCopy a buffer to work in, guaranteed  ge 1K
// September 5, 1978  9:32 AM, Read/WriteBlock return 0 if at EOF when called
// October 5, 1978  2:30 PM, add PageEvent !!!!!! for analysis of stream behavior
// October 5, 1978  10:44 PM, implement ksTypeWriteBeforeRead
// October 5, 1978  11:48 PM, in ksTypeWriteBeforeRead, WriteBlock uses IndexedIO directly~~!!
// October 6, 1978  12:48 AM, rescind same -- not worth it
// October 9, 1978  12:03 PM, new GetSprucePage interface for concurrent processing
// October 9, 1978  10:00 PM, pull events back out
// October 16, 1978  2:39 PM, put events back in
// October 24, 1978  1:47 PM, implement the silly stepSize thing -- non-compact streams
// October 25, 1978  10:38 AM, WindowCopy does not use intermediary
// November 5, 1978  12:37 PM, comment out event reporting
//