// W I N D O W
//   errors 500
//
//Window routines for accessing disk files.
//
//WindowInit(file [,number-of-buffers [,strategy [,filetype]]])
//	Initializes a window on the given number of page buffers (default
//	is 1), with the given paging strategy, which is:
//		WINDOWNoStrategy	-- none default
//		WINDOWPageAhead	-- sequential file
//	If there is a filetype, set the type of the file to it.
//	Returns a pointer to a "window" structure
//
//WindowClose(window [,length])
//	Closes the window, and affects the length of the file if a second
//	argument is provided:
//	   0	Do not change length of file (default)
//	  -1	Truncate to present position
//	 else	Length is a double-precision length to use.
//
//WindowFlush(window)
//	Makes sure all dirty pages are on the disk.
//
//WindowChangeType(window,strategy [,file-type])
//	Changes windowing strategy if strategy ne 0.
//	Changes file-type if file-type ne 0 (also flushes pages if type
//		indeed needs to be changed).
//
//WindowSetPosition(window,dppos)
//WindowGetPosition(window,dppos)
//	Routines for setting and reading the (double-precision) file
//	position (units are words).
//
//WindowRead(window)
//WindowWrite(window,value)
//	Routines for reading or writing a single (aligned) word.
//
//WindowReadBlock(window,address,length)
//WindowWriteBlock(window,address,length)
//	Routines for reading or writing a vector of words.
//
//WindowReadByte(window)
//WindowRead2Bytes(window)
//	Special routines for reading bytes (8-bit) from the file.
//	Warning: if file is left at an odd byte position, strange things
//	may happen on subsequent operations.
//
//WindowCopy(fromwindow,towindow,length)
//	Copies great quantities of data -- length is double-precision.
//
//In order to make efficient access to window buffers (rather than using
// the read and write word routines, the following conventions are 
// available:
//	call  window>>W.Offset the "offset"
//	call  window>>W.Base the "base"
//
//To read a word:
//	if offset=0 then WindowNext(window,false)
//	w=base!offset
//	offset=offset+1
//
//To write a word:
//	if offset=0 then WindowNext(window,true)
//	base!offset=w
//	offset=offset+1
//
//For convenience, if WindowNext has additional arguments, they are the
// addresses of "cached" values of window>>W.Offset and window>>W.Base,
// and the cached values are fixed up. Note: the call WindowDirty(window)
// is used after a bunch of writes with the above technique to mark the
// current buffer dirty (this is also accomplished by making the WindowNext
// argument "true").
//
//Note: The buffer size used in a window and the page size of the under-
// lying file need not be the same, but the code here assumes that they
// are.

get "pressinternals.df"

//
// outgoing procedures
external
	[
	WindowInit
	WindowClose
	WindowFlush

	WindowSetPosition
	WindowGetPosition

	WindowRead
	WindowWrite
	WindowReadBlock
	WindowWriteBlock
	WindowReadByte
	WindowRead2Bytes

	WindowNext
	WindowDirty
	WindowCopy
	]

// outgoing statics
//external
//	[
//	]
//static
//	[
//	]

// incoming procedures
external
	[
//Free storage
	FSGetX
	FSPut

//PRESS
	PressError
	DblShift

//PRESSML
	DoubleAdd; DoubleSub; DoubleCop

//OS
	MoveBlock
	SetBlock
	Zero

//File operations
	FileWritePage
	FileReadPage
	FileStuff
	]

// incoming statics
//external
//	[
//	]

// internal statics
//static
//	[
//	]


manifest
 [
   WINDOWheader=(offset W.NumBlocks/16)+1	//Minimum header for window str.
   CLEAN=0
   DIRTY=1
 ]



let WindowInit(file,numbuffers,strategy,filetype;numargs n) = valof
  //allocates window and numbuffers (default 1) page(s)
  //of core space.  Initializes window information.
  //resultis window, a pointer to structure info.
 [
   if n ls 2 then numbuffers=1
   if n ls 3 then strategy=WINDOWNoStrategy
   if n eq 4 then file>>F.Type=filetype

   let pagesize=FileStuff(file)
   let window=FSGetX(WINDOWheader+numbuffers*(size block/16))
   window>>W.BufSize=pagesize
   window>>W.LogBufSize=(pagesize eq 256)? 8,10
   window>>W.CurBuf=1
   window>>W.CurPN=-1		//So will start out at zero.
   window>>W.File=file
   window>>W.Offset=0		//So will trap right away.
   window>>W.WhichByte=0
   window>>W.Strategy=strategy
   window>>W.NumBlocks=numbuffers
   for b=1 to numbuffers do
    [
	window>>W.CoreAdr↑b=FSGetX(pagesize)
	window>>W.PN↑b=-1
	window>>W.Age↑b=0
	window>>W.Status↑b=CLEAN
    ]
   resultis window
 ]

and WindowClose(window; numargs n) be
 [
   if n gr 1 then [ PressError(500)
	WindowFlush(window); return ]
   WindowFlush(window)
   for b=1 to window>>W.NumBlocks do
      [
      FSPut(window>>W.CoreAdr↑b)
      ]
   FSPut(window)
 ]

and WindowFlush(window) be
 [
   for b=1 to window>>W.NumBlocks do
      [
      let pn=window>>W.PN↑b
      let ca=window>>W.CoreAdr↑b
      if window>>W.Status↑b eq DIRTY then
	FileWritePage(window>>W.File,pn,ca)
      ]
 ]

and WindowChangeType(window,strategy,filetype; numargs n) be
 [
   if n eq 2 then filetype=0
   let f=window>>W.File
   if filetype ne 0 & filetype ne f>>F.Type then
     [
      WindowFlush(window)		//Flush dirty pages.
      f>>F.Type=filetype
     ]
   if strategy then window>>W.Strategy=strategy
 ]

and WindowSetPosition(window,filepos) be
 [
   let t=vec 1
   t!0=(filepos!0)&#377		//Mask off any "file number" code
   t!1=filepos!1

//Calculate word position:
   let wordnum=(t!1)&(window>>W.BufSize-1)
   window>>W.Offset=-window>>W.BufSize+wordnum
   window>>W.WhichByte=0		//Use new byte, left half

//And page number:
   let page=DblShift(t,window>>W.LogBufSize)
   WindowSetUp(window,page)
 ]

and WindowGetPosition(window,filepos) be
 [
   if window>>W.CurPN eq -1 then
     [
      filepos!0=0; filepos!1=0	//File at beginning.
      return
     ]
   let offsetv=vec 1
   offsetv!0=0
   offsetv!1=window>>W.BufSize+window>>W.Offset
   filepos!0=0
   filepos!1=window>>W.CurPN
   DblShift(filepos,-window>>W.LogBufSize)
   DoubleAdd(filepos,offsetv)
 ]

and WindowNext(window,dirtyflag,lvbase,lvoffset; numargs n) = valof
 [
   let c=window>>W.CurBuf
   if dirtyflag then window>>W.Status↑c=DIRTY
   window>>W.Offset=-window>>W.BufSize
   WindowSetUp(window,window>>W.CurPN+1)	//Next page!
   if n gr 2 then
	[
	@lvbase=window>>W.Base
	@lvoffset=window>>W.Offset
	]
   resultis window>>W.CurPN
 ]

// Work-horse routine that sets up a window for access to a given page.
// The Offset entry is assumed already set.

and WindowSetUp(window,page) be
 [
//Find current page (if buffered, and around), or oldest one if not.
   let current=window>>W.CurBuf
   let curAge=window>>W.Age↑current
   let lowestAge=curAge

   let b=nil
   let found=false
   for nb=1 to window>>W.NumBlocks do
      [
      if page eq window>>W.PN↑nb then
	[
	b=nb
	found=true
	break
	]
      if window>>W.PN↑nb eq -1 % window>>W.Age↑nb le lowestAge then
	[
	lowestAge=window>>W.Age↑nb
	b=nb
	]
      ]

   let ca=window>>W.CoreAdr↑b

unless found then
   [
   let pn=window>>W.PN↑b
//If page we are replacing is dirty, write it out.
   if window>>W.Status↑b eq DIRTY & pn ne -1 then 
	FileWritePage(window>>W.File,pn,ca)

//Read in desired page (no-op if file is ReadOnly)
   FileReadPage(window>>W.File,page,ca)
   window>>W.PN↑b=page
   window>>W.Status↑b=CLEAN
   ]

   window>>W.Base=ca+window>>W.BufSize
   window>>W.Age↑b=curAge+1
   window>>W.CurBuf=b
   window>>W.CurPN=page
 ]

and WindowDirty(window) be
 [
   let c=window>>W.CurBuf
   window>>W.Status↑c=DIRTY
 ]

//Routines for reading and writing single and multiple words, using
// the basic routines above.

and WindowRead(window) = valof
 [
   if window>>W.Offset eq 0 then WindowNext(window,false)
   let off=window>>W.Offset
   let rslt=(window>>W.Base)!off
   window>>W.Offset=off+1
   resultis rslt
 ]

and WindowWrite(window,value,Baselv,Offsetlv;numargs n) be
 [
   if window>>W.Offset eq 0 then WindowNext(window,true)
   let off=window>>W.Offset
   (window>>W.Base)!off=value
   window>>W.Offset=off+1
   window>>W.Status↑(window>>W.CurBuf)=DIRTY
 ]

and WindowWriteBlock(W,corestart,length) be
 [
   let off=W>>W.Offset
   if (length+off) le 0 then
    [
      MoveBlock(W>>W.Base+off,corestart,length)
      W>>W.Offset=length+off
      WindowDirty(W)
      return
    ]
   unless off eq 0 do		//Write out odd stuff in window space
    [
      MoveBlock(W>>W.Base+off,corestart,-off)
      W>>W.Offset=0		//Ditto
      WindowDirty(W)
    ]

   let evenpages=(length+off) rshift W>>W.LogBufSize
   unless evenpages eq 0 do //Spew out all of these pages together
    [
      let firstpage=W>>W.CurPN+1
      let lastpage=firstpage+evenpages-1
      FileWritePage(W>>W.File,firstpage,corestart-off,evenpages)
      for i=1 to W>>W.NumBlocks do//Invalidate old core copies of pages
       [
	let thispage=W>>W.PN↑i
	if (thispage ge firstpage)&(thispage le lastpage)
	   then W>>W.PN↑i=-1
       ]
      W>>W.CurPN=lastpage
    ]

   let StillLeft=(length+off)&(W>>W.BufSize-1) //odd words left to write out
   unless StillLeft eq 0 do
    [
      WindowNext(W,false)
      let off=W>>W.Offset
      MoveBlock(W>>W.Base+off,corestart+length-StillLeft,StillLeft)
      W>>W.Offset=off+StillLeft
      WindowDirty(W)
    ]

 ]

and WindowReadBlock(W,corestart,length) be
 [
   let off=W>>W.Offset
   if (length+off) le 0 then
    [
      MoveBlock(corestart,W>>W.Base+off,length)
      W>>W.Offset=length+off
      return
    ]
   unless off eq 0 do	//Read out odd stuff in window space
    [
      MoveBlock(corestart,W>>W.Base+off,-off)
      W>>W.Offset=0
    ]

   let evenpages=(length+off) rshift W>>W.LogBufSize
   unless evenpages eq 0 do //Spew in all of these pages together
    [
      let firstpage=W>>W.CurPN+1
      let lastpage=firstpage+evenpages-1
      //some of the pages in the block to be read may already be in core
      //this is unlikely, however.  Because most of the disk time is taken
      //in the initial SEEK, we'll write out any such pages in order to be
      //able to read in the whole contiguous block
      for i=1 to W>>W.NumBlocks do
       [
	let P=W>>W.PN↑i
	if (P ge firstpage)&(P le lastpage)&(W>>W.Status↑i eq DIRTY)
	 then FileWritePage(W>>W.File,W>>W.PN↑i,W>>W.CoreAdr↑i)
       ]
      FileReadPage(W>>W.File,firstpage,corestart-off,evenpages)
      W>>W.CurPN=lastpage
    ]

   let StillLeft=(length+off)&(W>>W.BufSize-1) //Odd words left to read in
   unless StillLeft eq 0 do
    [
      WindowNext(W,false)
      let off=W>>W.Offset
      MoveBlock(corestart+length-StillLeft,W>>W.Base+off,StillLeft)
      W>>W.Offset=off+StillLeft
    ]

 ]

and WindowCopy(fromw,tow,len) be
 [
   let dp=vec 1
   DoubleCop(dp,len)
   let b=vec 1024
   let dl=table [ 0;1024 ]
   while dp!0 ge 0 do
	[
	let l=dp!1
	if DoubleSub(dp,dl) ge 0 then l=1024
	WindowReadBlock(fromw,b,l)
	WindowWriteBlock(tow,b,l)
	]
 ]

and WindowReadByte(window) = valof
 [
   window>>W.ByteCount=window>>W.ByteCount+1
   test window>>W.WhichByte eq 0 then
	[				//Get a new word.
	window>>W.WhichByte=1
	if window>>W.Offset eq 0 then WindowNext(window,false)
	resultis ((window>>W.Base)!(window>>W.Offset)) rshift 8
	]
   or
	[				//Right byte
	window>>W.WhichByte=0
	let off=window>>W.Offset
	let a=((window>>W.Base)!off) &#377
	window>>W.Offset=off+1
	resultis a
	]
 ]

and WindowRead2Bytes(window) = valof
 [
   test window>>W.WhichByte eq 0 then
	[				//Read a whole word.
	window>>W.ByteCount=window>>W.ByteCount+2
	resultis WindowRead(window)
	]
   or
	[
	resultis (WindowReadByte(window) lshift 8)+
		WindowReadByte(window)
	]
 ]