-- File: CoreStreams.mesa
-- Edited by Levin:  22-Oct-80  8:46:04
-- Edited by Schroeder:  January 13, 1981  11:39 AM
-- Edited by Brotz:  28-Oct-80 13:51:44


DIRECTORY
  ByteBltDefs,
  crD: FROM "CoreDefs",
  csD: FROM "CoreStreamDefs",
  exD: FROM "ExceptionDefs",
  Inline,
  ovD: FROM "OverviewDefs",
  Storage,
  Stream,
  tfD: FROM "TempFileDefs";

CoreStreams: PROGRAM
  IMPORTS ByteBltDefs, crD, exD, Inline, Storage, tfD
  EXPORTS csD
  SHARES crD =

  BEGIN OPEN csD;

  StreamHandle: TYPE = POINTER TO CStream;

  CStream: PUBLIC TYPE = RECORD [
    fh: crD.UFileHandle,
    fileType: {csOpened, userOpened, temp},
    implicitBuffer: BOOLEAN, 
    type: StreamType,
    mode: StreamMode,
    bufferState: {empty, loaded, dirty},
    nPages: CARDINAL,
    buffer: POINTER TO Buffer,
    filePage: crD.PageNumber,
    positionByte: CARDINAL,
    bytesInBuffer: CARDINAL,
    newEOF: Position,
    checkPointEOF: Position,
    checkPointPosition: Position];

  Buffer: TYPE = RECORD [
    SELECT OVERLAID StreamType FROM
      byte => [bytes: PACKED ARRAY OF CHARACTER],
      word => [words: ARRAY OF WORD],
      ENDCASE];

  bytesPerPage: CARDINAL = 512;
  wordsPerPage: CARDINAL = bytesPerPage/2;


  Error: PUBLIC ERROR [reason: ovD.ErrorCode] = CODE;

  OpenFromName:  PUBLIC PROCEDURE [
      name: STRING, user: crD.DMSUser, type: StreamType,
      mode: StreamMode, nPages: CARDINAL, buffer: POINTER←NIL]
      RETURNS [sh: StreamHandle]=
    BEGIN
    fh: crD.UFileHandle;
    fileMode: crD.OpenMode =
      (IF mode = read THEN read ELSE update);
    error: ovD.ErrorCode;
    [error, fh] ← crD.OpenFile[user, name, fileMode];
    ReportError[error];
    sh ← Open[fh, type, mode, nPages, buffer];
    sh.fileType ← csOpened;
    END;

  Open: PUBLIC PROCEDURE [
      fh: crD.UFileHandle, type: StreamType, mode: StreamMode,
      nPages: CARDINAL, buffer: POINTER ← NIL]
      RETURNS [sh: StreamHandle] =
    BEGIN
    IF mode#read AND fh#NIL AND fh.access#update
      THEN Oops[];
    sh ← Storage.Node[SIZE[CStream]];
    sh↑ ← [fh: NIL, fileType: userOpened, implicitBuffer: FALSE,
      type: byte, mode: read, bufferState: empty, nPages: 1,
      buffer: NIL, filePage: 0, positionByte: 0, bytesInBuffer: 0,
      newEOF: 0, checkPointEOF: 0, checkPointPosition: 0 ];
    sh.fh ← fh;
    sh.implicitBuffer ← buffer=NIL;
    sh.type ← type;
    sh.mode ← mode;
    sh.nPages ← nPages;
    sh.buffer ←  IF sh.implicitBuffer
      THEN Storage.Pages[nPages]
      ELSE buffer;
    IF sh.fh = NIL
      THEN sh.fileType ← temp
      ELSE IF mode # overwrite THEN BEGIN
        eofPage: crD.PageNumber;
        eofByte: CARDINAL;
        [ , eofPage, eofByte] ← crD.UFileLength[sh.fh];
        sh.newEOF ← sh.checkPointEOF ←
          MapPageByteToPosition[eofPage, eofByte];
        IF mode = append THEN BEGIN
          sh.checkPointPosition ← sh.checkPointEOF;
          sh.filePage ← eofPage;
          sh.positionByte ← eofByte;
          END;
        END;
    END;  -- of Open --

  Checkpoint: PUBLIC PROCEDURE [sh: StreamHandle] =
    BEGIN
    IF sh.mode = read THEN RETURN;
    UpdateNewEOF[sh];
    sh.checkPointEOF ← sh.newEOF;
    sh.checkPointPosition ←
      MapPageByteToPosition[sh.filePage, sh.positionByte];
    IF sh.checkPointPosition > sh.checkPointEOF THEN Oops[];
    IF sh.fh # NIL THEN BEGIN
      eofPage: crD.PageNumber;
      eofByte: CARDINAL;
      WriteBufferIfDirty[sh];
      [ , eofPage, eofByte] ← crD.UFileLength[sh.fh];
      IF sh.newEOF < MapPageByteToPosition[eofPage, eofByte]
        THEN BEGIN
          error: ovD.ErrorCode;
          [eofPage, eofByte] ← MapPositionToPageByte[sh.newEOF];
          error ← crD.UFileTruncate[eofPage, eofByte, sh.fh];
          ReportError[error];
          END;
      END;
    END;

  Destroy: PUBLIC PROCEDURE [sh: StreamHandle] =
    BEGIN
    IF sh.implicitBuffer THEN Storage.FreePages[sh.buffer];
    SELECT sh.fileType FROM
      csOpened => [] ← crD.CloseFile[sh.fh];
      temp => IF sh.fh # NIL THEN tfD.FreeTempFile[sh.fh];
      ENDCASE; --userOpened--
    Storage.Free[sh];
    END;

  Reset: PUBLIC PROCEDURE [sh: StreamHandle] =
    BEGIN
    eofPage: crD.PageNumber;
    eofByte: CARDINAL;
    sh.newEOF ← sh.checkPointEOF;
    [eofPage,eofByte] ← MapPositionToPageByte[sh.newEOF];
    SELECT eofPage FROM
      < sh.filePage =>  --  checkPointEOF is before buffer
        BEGIN
          sh.bufferState ← empty;
          sh.bytesInBuffer ← 0;
          END;
      < sh.filePage+sh.nPages =>  -- checkPointEOF is in buffer
        BEGIN
          IF sh.bytesInBuffer # 0 THEN sh.bytesInBuffer ←
            (eofPage-sh.filePage)*bytesPerPage+eofByte;
          IF sh.bytesInBuffer = 0 THEN sh.bufferState ← loaded;
          END;
      ENDCASE;  -- checkPointEOF is beyond buffer
    Reposition[sh, sh.checkPointPosition];
    END;

  GetType: PUBLIC PROCEDURE [sh: StreamHandle]
      RETURNS [StreamType] =
    {RETURN[sh.type]};

  Read: PUBLIC PROCEDURE [sh: StreamHandle]
    RETURNS [item: UNSPECIFIED] =
    BEGIN
    IF sh.positionByte = sh.nPages * bytesPerPage
      OR sh.bufferState = empty
        THEN PrepareBuffer[sh];
    IF sh.positionByte >= sh.bytesInBuffer
      THEN ReportError[ovD.endOfStream];
    IF sh.type = byte
      THEN BEGIN
	   item ← sh.buffer.bytes[sh.positionByte];
	   sh.positionByte ← sh.positionByte + 1;
	   END
      ELSE BEGIN
	   item ← sh.buffer.words[sh.positionByte/2];
	   sh.positionByte ← sh.positionByte + 2;
	   END;
    END;

  Write: PUBLIC PROCEDURE [sh: StreamHandle,
      item: UNSPECIFIED] =
    BEGIN
    IF sh.mode = read THEN Oops[];
    IF sh.positionByte = sh.nPages * bytesPerPage
      OR sh.bufferState = empty
        THEN PrepareBuffer[sh];
    IF sh.type = byte
      THEN BEGIN
	   sh.buffer.bytes[sh.positionByte] ← item;
	   sh.positionByte ← sh.positionByte + 1;
	   END
      ELSE BEGIN
	   sh.buffer.words[sh.positionByte/2] ← item;
	   sh.positionByte ← sh.positionByte + 2;
	   END;
    sh.bytesInBuffer ← MAX[sh.bytesInBuffer, sh.positionByte];
    sh.bufferState ← dirty;
    END;


  ReadBlock: PUBLIC PROCEDURE [sh: StreamHandle, to: POINTER,
    start, nItems: CARDINAL] RETURNS [n: CARDINAL] =
    BEGIN
    sink, source: Stream.Block;
    bytesCopied: CARDINAL;
    sink ← MakeStreamBlock[sh, to, start, nItems];
    source.blockPointer ← LONG[sh.buffer];
    n ← sink.startIndex;
    UNTIL sink.startIndex = sink.stopIndexPlusOne DO
      PrepareBuffer[sh];
      source.startIndex ← sh.positionByte;
      source.stopIndexPlusOne ← sh.bytesInBuffer;
      IF source.startIndex = source.stopIndexPlusOne THEN EXIT;
      bytesCopied ← ByteBltDefs.ByteBlt[to: sink, from: source];
      sink.startIndex ← sink.startIndex + bytesCopied; 
      sh.positionByte ← sh.positionByte + bytesCopied;
      ENDLOOP;
    n ← sink.startIndex - n;
    IF sh.type = word THEN n ← (n+1)/2;
    END;  -- of ReadBlock --

  WriteBlock: PUBLIC PROCEDURE [ sh: StreamHandle,
      from: POINTER, start, nItems: CARDINAL] =
    BEGIN
    IF sh.mode=read THEN Oops[];
    CopyBlockToCStream
      [MakeStreamBlock[sh, from, start, nItems], sh];
    END;  -- of WriteBlock --

  StreamCopy: PUBLIC PROCEDURE [from, to: StreamHandle,
    fromItems: LONG CARDINAL] =
    BEGIN
    Work: PROCEDURE[source: Stream.Block] =
      {CopyBlockToCStream[source, to]};
    IF to.mode=read OR
      (to.type=word AND from.type=byte AND fromItems MOD 2 = 1)
        THEN Oops[];
    ReadStream[from, fromItems, Work]
    END;  -- of StreamCopy --

  ReadStream: PUBLIC PROCEDURE [sh: StreamHandle,
      nItems: LONG CARDINAL,
      AcceptBlock: PROCEDURE[Stream.Block]] =
    BEGIN
    source: Stream.Block;
    items: LONG CARDINAL ←
      IF sh.type=word THEN 2*nItems ELSE nItems;
    source.blockPointer ← LONG[sh.buffer];
    DO
      copyCount: CARDINAL;
      PrepareBuffer[sh];
      copyCount ← Inline.LowHalf[
        MIN[sh.bytesInBuffer-sh.positionByte, items]];
      source.startIndex ← sh.positionByte;
      source.stopIndexPlusOne ← source.startIndex + copyCount;
      AcceptBlock[source];
      IF copyCount = 0 THEN EXIT;
      sh.positionByte ← sh.positionByte + copyCount;
      items ← items - copyCount; 
      ENDLOOP;
    END;

  GetLength: PUBLIC PROCEDURE [sh: StreamHandle]
      RETURNS [Position] =
    BEGIN
    UpdateNewEOF[sh];
    RETURN [IF sh.type = word
      THEN sh.newEOF/2 ELSE sh.newEOF];
    END;  -- of GetLength --

  GetPosition: PUBLIC PROCEDURE [sh: StreamHandle]
      RETURNS [Position] =
    {RETURN[IF sh.type = byte
      THEN MapPageByteToPosition[sh.filePage, sh.positionByte]
      ELSE MapPageWordToPosition[sh.filePage, sh.positionByte/2]]};

  SetPosition: PUBLIC PROCEDURE [sh: StreamHandle,
      position: Position] =
    BEGIN
    bytePosition: Position =
      (IF sh.type = word THEN position*2 ELSE position);
    IF sh.mode = read AND bytePosition > sh.newEOF THEN Oops[];
    Reposition[sh, bytePosition];
    END;

  MapPageWordToPosition: PUBLIC PROCEDURE [page: CARDINAL,
      word: CARDINAL] RETURNS [Position] =
    {RETURN[LONG[page] * wordsPerPage + word]};

  MapPositionToPageWord: PUBLIC PROCEDURE [position: Position]
    RETURNS [page: CARDINAL, word: [0..wordsPerPage)] =
    {[page, word] ← Inline.LongDivMod[position, wordsPerPage]};

  MapPageByteToPosition: PUBLIC PROCEDURE [page: CARDINAL,
      byte: CARDINAL] RETURNS [Position] =
    {RETURN[LONG[page] * bytesPerPage + byte]};

  MapPositionToPageByte: PUBLIC PROCEDURE [position: Position]
      RETURNS [page: CARDINAL, byte: [0..bytesPerPage)] =
    {[page, byte] ← Inline.LongDivMod[position, bytesPerPage]};


  -- internal procedures

  Reposition: PROCEDURE [sh: StreamHandle, bp: Position] = 
    BEGIN
    page: crD.PageNumber;
    byte: CARDINAL;
    [page, byte] ← MapPositionToPageByte[bp];
    IF page IN [sh.filePage .. sh.filePage+sh.nPages)
      THEN sh.positionByte ←
        (page - sh.filePage)*bytesPerPage + byte
      ELSE BEGIN
        UpdateNewEOF[sh];
        WriteBufferIfDirty[sh];
        sh.filePage ← page;
        sh.positionByte ← byte;
        sh.bufferState ← empty;
        sh.bytesInBuffer ← 0;
        END;
    END;

  MakeStreamBlock: PROCEDURE [sh: StreamHandle, p: POINTER,
    s, n: CARDINAL] RETURNS[b: Stream.Block] = 
    BEGIN
    b.blockPointer ← LONG[p];
    b.startIndex ←  (IF sh.type = byte THEN s ELSE s*2);
    b.stopIndexPlusOne ← b.startIndex +
      (IF sh.type = byte THEN n ELSE n*2);
    END;

  CopyBlockToCStream: PROCEDURE [source: Stream.Block,
      sh: StreamHandle] = 
    BEGIN
    sink: Stream.Block;
    bytesCopied: CARDINAL;
    sink.blockPointer ← LONG[sh.buffer];
    UNTIL source.startIndex = source.stopIndexPlusOne DO
      PrepareBuffer[sh];
      sink.startIndex ← sh.positionByte;
      sink.stopIndexPlusOne ← sh.nPages * bytesPerPage;
      bytesCopied ← ByteBltDefs.ByteBlt[to: sink, from: source];
      source.startIndex ← source.startIndex + bytesCopied; 
      sh.positionByte ← sh.positionByte + bytesCopied;
      sh.bytesInBuffer ← MAX[sh.bytesInBuffer, sh.positionByte];
      sh.bufferState ← dirty;
      ENDLOOP;
    END;

  PrepareBuffer: PROCEDURE [sh: StreamHandle] =
    BEGIN
    IF sh.positionByte = sh.nPages * bytesPerPage
      THEN BEGIN
        UpdateNewEOF[sh];
        WriteBufferIfDirty[sh];
        sh.filePage ← sh.filePage + sh.nPages;
        sh.positionByte ← 0;
        sh.bufferState ← empty
        END;
    IF sh.bufferState = empty THEN BEGIN
      bufferPosition: LONG CARDINAL =
        MapPageByteToPosition[sh.filePage, 0];
      bufferBytes: CARDINAL = sh.nPages * bytesPerPage;
      sh.bytesInBuffer ← Inline.LowHalf [MIN[bufferBytes,
        MAX[sh.newEOF, bufferPosition] - bufferPosition]];
      IF sh.bytesInBuffer > 0 THEN BEGIN
        error: ovD.ErrorCode;
        [error, ] ←
          crD.ReadPages[sh.buffer, bufferBytes, sh.filePage, sh.fh];
        ReportError[error];
        END;
      sh.bufferState ← loaded;
      END;
    END;

  WriteBufferIfDirty: PROCEDURE [sh: StreamHandle] =
    BEGIN
    IF sh.bufferState = dirty THEN BEGIN
      error: ovD.ErrorCode;
      IF sh.fh = NIL THEN sh.fh ← tfD.AllocateTempFile[];
      error ←
        crD.WritePages[sh.buffer, sh.bytesInBuffer, sh.filePage, sh.fh];
      ReportError[error];
      sh.bufferState ← loaded;
      END;
    END;  -- of WriteBufferIfDirty --

  UpdateNewEOF: PROCEDURE [sh: StreamHandle] =
    BEGIN
    IF sh.bufferState = dirty THEN
      sh.newEOF ← MAX[sh.newEOF, MapPageByteToPosition[
        sh.filePage, sh.bytesInBuffer]];
    END;  -- of UpdateNewEOF --

  ReportError: PROCEDURE [error: ovD.ErrorCode] =
    {IF error # ovD.ok THEN ERROR Error[error]};

  Oops: PROCEDURE = {exD.SysBug[]};


  END.  -- of CoreStreams --