-- File: PupByteStreams.mesa,  Last Edit: HGM  January 20, 1981  12:56 AM
--  Initialize options,  HGM  February 24, 1981  2:31 PM

DIRECTORY
  Environment USING [Byte],
  Runtime USING [GlobalFrame],
  ByteBlt USING [ByteBlt],
  Stream USING [
    Byte, Word, Block, CompletionCode, defaultInputOptions, Handle, InputOptions,
    LongBlock, Object, ShortBlock, SSTChange, SubSequenceType, TimeOut],
  CommFlags USING [doDebug],
  DriverDefs USING [Glitch],
  PupPktDefs USING [
    PupPktStream, PupPktStreamAbort, PupPktStreamDestroy, PupPktStreamMake],
  PupStream USING [PupOpenMode],
  PupDefs USING [
    GetFreePupBuffer, ReturnFreePupBuffer, GetPupContentsBytes,
    SetPupContentsBytes, Pair, PupAddress, PupBuffer, PupSocketID, Tocks],
  PupTypes USING [fillInSocketID];

PupByteStreams: MONITOR
  IMPORTS Runtime, Stream, ByteBlt, DriverDefs, PupPktDefs, PupDefs
  EXPORTS PupStream =
  BEGIN OPEN DriverDefs, PupDefs;

  -- Manager data
  free: POINTER TO FRAME[PupByteStreams] ← LOOPHOLE[Runtime.GlobalFrame[
    PupByteStreamCreate]];

  GetInstance: ENTRY PROCEDURE RETURNS [him: POINTER TO FRAME[PupByteStreams]] =
    BEGIN
    IF free = NIL THEN
      BEGIN
      him ← NEW PupByteStreams;
      START him; -- Do initialization code
      RETURN;
      END;
    him ← free;
    free ← free.next;
    END;

  FreeInstance: ENTRY PROCEDURE [him: POINTER TO FRAME[PupByteStreams]] =
    BEGIN him.next ← free; free ← him; END;

  next: POINTER TO FRAME[PupByteStreams] ← NIL;

  myPupByteStream: Stream.Object ←
    [options: , getByte: GetByte, putByte: PutByte,
      getWord: GetWord, putWord: PutWord, get: GetBlock, put: PutBlock,
      setSST: SendMark, sendAttention: SendAttention,
      waitAttention: WaitAttention, delete: ];

  myPs: PupPktDefs.PupPktStream;

  -- Beware, things don't get reinitialized when a module is reused
  inputBuffer: PupBuffer;
  inputFinger: CARDINAL;
  inputBufferSize: CARDINAL;
  inputSST: Stream.SubSequenceType;
  outputBuffer: PupBuffer;
  outputFinger: CARDINAL; -- 0 if aData/aMark sent
  outputBufferSize: CARDINAL;

  HandleLooksLikeGarbage: PUBLIC ERROR = CODE;

  LeftAndRight: TYPE = MACHINE DEPENDENT RECORD [left, right: Environment.Byte];

  PupByteStreamCreate: PUBLIC PROCEDURE [remote: PupAddress, ticks: Tocks]
    RETURNS [Stream.Handle] =
    BEGIN
    RETURN[
      PupByteStreamMake[PupTypes.fillInSocketID, remote, ticks, sendRfc, [0, 0]]];
    END;

  PupByteStreamMake: PUBLIC PROCEDURE [
    local: PupSocketID, remote: PupAddress, ticks: Tocks,
    mode: PupStream.PupOpenMode, id: Pair] RETURNS [Stream.Handle] =
    BEGIN
    newPs: PupPktDefs.PupPktStream;
    him: POINTER TO FRAME[PupByteStreams];
    -- It is ok to UNWIND here if PupPktStreamMake doesn't work.
    newPs ← PupPktDefs.PupPktStreamMake[local, remote, ticks, mode, id];
    him ← GetInstance[];
    -- Initialization
    him.myPs ← newPs;
    him.myPupByteStream.options ← Stream.defaultInputOptions;
    him.myPupByteStream.delete ← Destroy;
    him.inputBuffer ← NIL;
    him.inputSST ← 0;
    him.outputBuffer ← NIL;
    him.outputFinger ← 0;
    him.outputBufferSize ← 0;
    RETURN[@him.myPupByteStream];
    END;

  PupByteStreamAbort: PUBLIC --ENTRY--PROCEDURE [bs: Stream.Handle, e: STRING] =
    BEGIN
    ENABLE UNWIND => NULL;
    him: POINTER TO FRAME[PupByteStreams] = LOOPHOLE[Runtime.GlobalFrame[bs.put]];
    IF CommFlags.doDebug AND bs.delete # Destroy THEN
      Glitch[HandleLooksLikeGarbage];
    PupPktDefs.PupPktStreamAbort[him.myPs, e];
    END;

  Destroy: PUBLIC --ENTRY--PROCEDURE [bs: Stream.Handle] =
    BEGIN
    ENABLE UNWIND => NULL;
    him: POINTER TO FRAME[PupByteStreams] = LOOPHOLE[Runtime.GlobalFrame[bs.put]];
    IF CommFlags.doDebug AND bs.delete # Destroy THEN
      Glitch[HandleLooksLikeGarbage];
    bs.delete ← NIL;
    PupPktDefs.PupPktStreamDestroy[him.myPs];
    IF him.inputBuffer # NIL THEN ReturnBuffer[@him.inputBuffer];
    IF him.outputBuffer # NIL THEN ReturnBuffer[@him.outputBuffer];
    FreeInstance[him];
    END;


  SendAttention: PROCEDURE [sH: Stream.Handle, byte: Stream.Byte] =
    BEGIN myPs.sendAttention[]; END;

  WaitAttention: PROCEDURE [sH: Stream.Handle] RETURNS [Stream.Byte] =
    BEGIN myPs.waitForAttention[]; RETURN[0]; END;

  GetByte: --ENTRY--PROCEDURE [sH: Stream.Handle] RETURNS [byte: Stream.Byte] =
    BEGIN
    ENABLE UNWIND => NULL;
    IF inputBuffer # NIL AND inputFinger + 2 < inputBufferSize THEN
      BEGIN -- "+2" lets GetBlock give back the buffer if we take the last byte
      byte ← inputBuffer.pupBytes[inputFinger];
      inputFinger ← inputFinger + 1;
      RETURN;
      END
    ELSE
      BEGIN
      array: PACKED ARRAY [0..1] OF Stream.Byte;
      [] ← sH.get[sH, [@array, 0, 1], [FALSE, FALSE, FALSE, TRUE, TRUE]];
      RETURN[array[0]];
      END;
    END;

  GetWord: --ENTRY--PROCEDURE [sH: Stream.Handle] RETURNS [word: Stream.Word] =
    BEGIN OPEN w: LOOPHOLE[word, LeftAndRight];
    w.left ← GetByte[sH];
    w.right ← GetByte[sH];
    END;

  GetBlock: --ENTRY--PROCEDURE [
    sH: Stream.Handle, block: Stream.Block, options: Stream.InputOptions]
    RETURNS [
      bytesTransferred: CARDINAL, why: Stream.CompletionCode,
      sst: Stream.SubSequenceType] =
    BEGIN
    ENABLE UNWIND => NULL;
    input: Stream.Block;
    moved: CARDINAL;
    bytesTransferred ← 0;
    sst ← inputSST;
    why ← normal;
    WHILE block.startIndex < block.stopIndexPlusOne DO
      UNTIL inputBuffer # NIL DO
	inputFinger ← 0;
	inputBuffer ← myPs.get[];
	IF inputBuffer = NIL THEN SIGNAL Stream.TimeOut[block.startIndex]
	ELSE
	  IF inputBuffer.pupType = mark OR inputBuffer.pupType = aMark THEN
	    BEGIN
	    sst ← inputSST ← inputBuffer.pupBytes[0];
	    ReturnBuffer[@inputBuffer];
	    IF options.signalSSTChange THEN
	      SIGNAL Stream.SSTChange[inputSST, block.startIndex]
	    ELSE BEGIN why ← sstChange; RETURN; END;
	    END;
	ENDLOOP;
      inputBufferSize ← GetPupContentsBytes[inputBuffer];
      input ←
	[blockPointer: @inputBuffer.pupBytes, startIndex: inputFinger,
	  stopIndexPlusOne: inputBufferSize];
      moved ← ByteBlt.ByteBlt[block, input];
      bytesTransferred ← bytesTransferred + moved;
      block.startIndex ← block.startIndex + moved;
      inputFinger ← inputFinger + moved;
      IF inputFinger = inputBufferSize THEN BEGIN ReturnBuffer[@inputBuffer]; END;
      IF inputBuffer = NIL AND block.startIndex < block.stopIndexPlusOne AND
	options.signalLongBlock THEN
	BEGIN SIGNAL Stream.LongBlock[block.startIndex]; END;
      IF inputBuffer = NIL AND options.terminateOnEndPhysicalRecord THEN
	BEGIN why ← endRecord; EXIT; END;
      ENDLOOP;
    IF inputBuffer # NIL AND options.signalShortBlock THEN
      BEGIN ERROR Stream.ShortBlock; END;
    END;

  PutByte: --ENTRY--PROCEDURE [sH: Stream.Handle, byte: Stream.Byte] =
    BEGIN
    ENABLE UNWIND => NULL;
    IF outputBuffer # NIL AND outputFinger + 2 < outputBufferSize THEN
      BEGIN -- "+2" lets PutBlock flush the buffer if we fill the last byte
      outputBuffer.pupBytes[outputFinger] ← byte;
      outputFinger ← outputFinger + 1;
      RETURN;
      END
    ELSE
      BEGIN
      array: PACKED ARRAY [0..1] OF Stream.Byte ← [byte, ];
      PutBlock[sH, [@array, 0, 1], FALSE];
      END;
    END;

  PutWord: --ENTRY--PROCEDURE [sH: Stream.Handle, word: Stream.Word] =
    BEGIN OPEN w: LOOPHOLE[word, LeftAndRight];
    sH.putByte[sH, w.left];
    sH.putByte[sH, w.right];
    END;

  PutBlock: --ENTRY--PROCEDURE [
    sH: Stream.Handle, block: Stream.Block, endPhysicalRecord: BOOLEAN] =
    BEGIN
    ENABLE UNWIND => NULL;
    output: Stream.Block;
    moved: CARDINAL;
    IF outputBufferSize = 0 THEN outputBufferSize ← myPs.getSenderSizeLimit[];
    WHILE block.startIndex < block.stopIndexPlusOne DO
      IF outputFinger = outputBufferSize THEN FlushOutputBuffer[];
      IF outputBuffer = NIL THEN
	BEGIN outputBuffer ← GetFreePupBuffer[]; outputFinger ← 0; END;
      output ←
	[blockPointer: @outputBuffer.pupBytes, startIndex: outputFinger,
	  stopIndexPlusOne: outputBufferSize];
      moved ← ByteBlt.ByteBlt[output, block];
      block.startIndex ← block.startIndex + moved;
      outputFinger ← outputFinger + moved;
      ENDLOOP;
    IF (endPhysicalRecord AND outputFinger # 0) OR outputFinger = outputBufferSize
      THEN
      BEGIN
      IF outputBuffer = NIL THEN
	BEGIN outputBuffer ← GetFreePupBuffer[]; outputFinger ← 0; END;
      IF endPhysicalRecord THEN outputBuffer.pupType ← aData;
      FlushOutputBuffer[];
      IF endPhysicalRecord THEN outputFinger ← 0;
      END;
    END;

  SendMark: --ENTRY--PROCEDURE [sH: Stream.Handle, sst: Stream.SubSequenceType] =
    BEGIN
    ENABLE UNWIND => NULL;
    FlushOutputBuffer[];
    outputFinger ← 0;
    myPs.putMark[sst];
    END;

  FlushOutputBuffer: PROCEDURE =
    BEGIN
    b: PupBuffer; -- don't leave outputBuffer dangling in case of StreamClosing
    IF outputBuffer = NIL THEN RETURN;
    b ← outputBuffer;
    outputBuffer ← NIL;
    SetPupContentsBytes[b, outputFinger];
    myPs.put[b];
    END;

  ReturnBuffer: PROCEDURE [p: POINTER TO PupBuffer] =
    BEGIN b: PupBuffer; b ← p↑; p↑ ← NIL; ReturnFreePupBuffer[b]; END;


  END.