-- FEPSIvOutputImpl.mesa
-- Copyright (C) 1985 by Xerox Corporation.  All rights reserved.
-- last edited by castillo   10-Jul-85  9:57:12

DIRECTORY
  Environment USING [
    Block, bitsPerByte, bitsPerWord, Byte, bytesPerPage, bytesPerWord, PageCount,
    wordsPerPage],
  FEPSIvOutput,
  FEPSMergerOps USING [OutputH],
  Inline USING [BITAND, BITSHIFT, HighHalf, LowByte, LowHalf],
  Interpress USING [
    LongOperator, ShortNumber, shortNumberBias, ShortOperator, Token],
  NSFile USING [Access, Error, Handle, nullHandle],
  NSSegment USING [
    ByteCount, defaultID, Map, Origin, PageCount, PageNumber, SetSizeInBytes],
  Runtime USING [CallDebugger],
  Space USING [Activate, Interval, Kill, nullInterval, Unmap],
  Stream USING [CompletionCode, Handle, GetBlock],
  XeroxCompress USING [PutBitsProc];

FEPSIvOutputImpl: PROGRAM
  IMPORTS Inline, NSFile, NSSegment, Runtime, Space, Stream
  EXPORTS FEPSIvOutput
  SHARES Interpress =
  BEGIN OPEN FEPSIvOutput, Env: Environment, IP: Interpress;

  -- This module exports the interface FEPSIvOutput.  The procedures
  -- in the interface implements the FEPS output routines whose
  -- primary function is to write data to the output interleaved
  -- master.  This implementation uses NS Filing routines to
  -- do the I/O.

  -- =========
  -- CONSTANTS
  -- =========
  readWrite: NSFile.Access = [read: TRUE, write: TRUE];

  -- =====
  -- TYPES
  -- =====
  Window: TYPE = RECORD [page: NSSegment.PageNumber, size: NSSegment.PageCount];

  -- ================
  -- GLOBAL VARIABLES
  -- ================
  bitBuf: CARDINAL ← 0;  -- buffer containing word being build
  bitPos: CARDINAL ← 0;  -- current bit position within bitBuf
  bufSize: Env.PageCount ← 0;  -- size of buffer (ie., window)
  bufAddr: LONG POINTER TO CARDINAL ← NIL;  -- start of buffer
  currWindow: Window ← [0, 0];
  currWrdPtr: LONG POINTER TO CARDINAL ← NIL;  -- pointer to current word
  file: NSFile.Handle ← NSFile.nullHandle;  -- current file handle
  fileSize: NSSegment.PageCount ← 0;  -- current size of file
  endWrdPtr: LONG POINTER TO CARDINAL ← NIL;  -- pointer to last word in buffer
  growSize: NSSegment.PageCount ← 0;  -- size file should grow by
  mapUnit: Space.Interval ← Space.nullInterval;  -- current mapUnit

  -- ======
  -- SIGNAL
  -- ======
  Exception: PUBLIC SIGNAL [code: ExceptionKind] = CODE;

  -- ==========
  -- PROCEDURES
  -- ==========

  -- =====
  AppendShortNumber: PROCEDURE [n: IP.ShortNumber] =
    BEGIN
    shortSeq: shortNumber IP.Token ← [shortNumber[number: n + IP.shortNumberBias]];
    PutWord[shortSeq];
    END;  -- AppendShortNumber

  -- =====
  ByteAlign: PROCEDURE = INLINE
    BEGIN IF bitPos MOD Env.bitsPerByte # 0 THEN PutBits[0, 4]; END;  -- ByteAlign

  -- =====
  BringNewWindow: PROCEDURE [window: Window]
    RETURNS [addr: LONG POINTER, endAddr: LONG POINTER, newWindow: Window] =
    BEGIN
    origin: NSSegment.Origin ← [
      file, window.page, window.size, NSSegment.defaultID];
    IF mapUnit.pointer # NIL THEN mapUnit.pointer ← Space.Unmap[mapUnit.pointer];
    mapUnit ← NSSegment.Map[origin: origin, access: readWrite];
    Space.Activate[mapUnit];
    newWindow.page ← window.page;
    newWindow.size ← mapUnit.count;
    addr ← mapUnit.pointer;
    endAddr ← addr + (newWindow.size * Env.wordsPerPage);
    END;  -- BringNewWindow

  -- =====
  GetNextWindow: PROCEDURE =
    BEGIN
    newPageNum: NSSegment.PageNumber ← currWindow.page + currWindow.size;
    -- see if we have enough pages allocated to the file
    IF newPageNum + bufSize > fileSize THEN {
      IF mapUnit.pointer # NIL THEN { -- unmap first in case of no resources
        mapUnit.pointer ← Space.Unmap[mapUnit.pointer];
	mapUnit ← Space.nullInterval;};
      fileSize ← GrowFileSize[fileSize, growSize];};
    -- change mapping
    [bufAddr, endWrdPtr, currWindow] ← BringNewWindow[[newPageNum, bufSize]];
    currWrdPtr ← bufAddr;
    END;  -- GetNextWindow

  -- =====
  GrowFileSize: PROCEDURE [size: NSSegment.PageCount, incr: NSSegment.PageCount]
    RETURNS [newSize: NSSegment.PageCount] =
    BEGIN
    -- This routine will increase the size of a file by "incr".
    -- If the request cannot be satisfied, then the increment is
    -- reduced by two.  If the increment is bufSize and there is 
    -- still no enough disk space, then signal is raised.  The 
    -- routine catching the signal may wait until disk space is
    -- available. 
    newIncr: NSSegment.PageCount ← incr;
    minSize: NSSegment.PageCount ← bufSize;
    NSSegment.SetSizeInBytes[
      file: file, bytes: (size + newIncr) * Env.bytesPerPage !
      NSFile.Error => {
        WITH errorRec: error SELECT FROM
          space => {
	    IF errorRec.problem = mediumFull THEN {
              newIncr ← newIncr / 2; -- try getting smaller chunk
              IF newIncr < minSize THEN {
                -- indicate that we can't
                SIGNAL Exception[noResources]; newIncr ← incr};
              RETRY;} -- assume that we have enough now
	    ELSE SIGNAL Exception[unknown];
	    } -- space
          ENDCASE => SIGNAL Exception[unknown];
        }];
    newSize ← size + newIncr;
    END;  -- GrowFileSize

  -- =================
  -- PUBLIC PROCEDURES
  -- =================

  -- =====
 CloseOutput: PUBLIC PROCEDURE =
    BEGIN
    byteSize: NSSegment.ByteCount;
    -- flush out bit buffer
    -- Assume that it is byte aligned now (kluge because we
    -- know it is true due to its use).
    IF bitPos # 0 THEN currWrdPtr↑ ← bitBuf;
    byteSize ←
      (currWindow.page * Env.bytesPerPage) +
        ((currWrdPtr - bufAddr) * Env.bytesPerWord);
    IF bitPos # 0 THEN byteSize ← byteSize + 1;
    IF mapUnit.pointer # NIL THEN
      mapUnit.pointer ← Space.Unmap[mapUnit.pointer];
    mapUnit ← Space.nullInterval;
    NSSegment.SetSizeInBytes[file: file, bytes: byteSize];
    END;  -- CloseOutput
    
  -- =====
  EmptyOutput: PUBLIC PROCEDURE =
    BEGIN
    IF mapUnit.pointer # NIL THEN {
      Space.Kill[mapUnit];
      mapUnit.pointer ← Space.Unmap[mapUnit.pointer];};
    mapUnit ← Space.nullInterval;
    NSSegment.SetSizeInBytes[file: file, bytes: 0];
    END; -- EmptyOutput

  -- =====    
  GetCurrentPosition: PUBLIC PROCEDURE RETURNS [pos: Position] =
    BEGIN
    ByteAlign[];
    pos.offset ← (currWrdPtr - bufAddr) * Env.bytesPerWord;
    IF bitPos # 0 THEN pos.offset ← pos.offset + 1;
    -- Get the nearest page
    pos.page ← currWindow.page + (pos.offset / Env.bytesPerPage);
    pos.offset ← pos.offset MOD Env.bytesPerPage;
    END;  -- GetCurrentPosition

  -- =====
  Initialize: PUBLIC PROCEDURE [
    output: FEPSMergerOps.OutputH, initialSize: NSSegment.PageCount,
    growIncrement: NSSegment.PageCount, bufferSize: NSSegment.PageCount] =
    BEGIN
    origin: NSSegment.Origin;
    growSize ← growIncrement;
    bufSize ← bufferSize;
    file ← output.ivMasterH;
    fileSize ← GrowFileSize[0, MAX[initialSize, bufSize]];
    -- create the mapUnit and map to start of file
    origin ← [file: file, base: 0, count: bufSize];
    mapUnit ← NSSegment.Map[origin: origin, access: readWrite];
    --  ***** TEMPORARY *****
    IF mapUnit.count # origin.count THEN
      Runtime.CallDebugger["Map unit mismatch in Initialize."L];
    Space.Activate[mapUnit];
    currWindow ← [0, mapUnit.count];
    currWrdPtr ← bufAddr ← mapUnit.pointer;
    endWrdPtr ← bufAddr + (currWindow.size * Env.wordsPerPage);
    bitBuf ← 0;
    bitPos ← 0;
    END;  -- Initialize

  -- =====    
  PutBits: PUBLIC XeroxCompress.PutBitsProc =
    BEGIN
    remainder: INTEGER;
    currBitPos: INTEGER ← bitPos + bits;  -- current bit position
    remainder ← Env.bitsPerWord - currBitPos;
    -- Processing is funtion of resulting buffer word position
    SELECT TRUE FROM
      (remainder > 0) =>  -- Incomplete
        BEGIN
        bitBuf ← bitBuf + Inline.BITSHIFT[val, remainder];
        bitPos ← currBitPos;
        END;
      (remainder = 0) =>  -- Full
        BEGIN
        currWrdPtr↑ ← bitBuf + val;
        currWrdPtr ← currWrdPtr + 1;
        IF currWrdPtr = endWrdPtr THEN GetNextWindow[];
        bitBuf ← 0;
        bitPos ← 0;
        END;
      ENDCASE =>  -- Overflow
        BEGIN
        bitPos ← -remainder;
        currWrdPtr↑ ← bitBuf + Inline.BITSHIFT[val, remainder];
        bitBuf ← Inline.BITSHIFT[val, Env.bitsPerWord - bitPos];
        currWrdPtr ← currWrdPtr + 1;
        IF currWrdPtr = endWrdPtr THEN GetNextWindow[];
        END;

    END;  -- PutBits

  -- ===== 
  PutByte: PUBLIC PROCEDURE [val: CARDINAL] =
    BEGIN
    -- see if at byte boundary
    ByteAlign[];
    PutBits[val, 8];
    END;  -- PutByte

  -- =====
  PutWord: PUBLIC PROCEDURE [val: UNSPECIFIED] =
    BEGIN ByteAlign[]; PutBits[val, 16]; END;  -- PutWord

  -- =====  
  Retrieve: PUBLIC PROCEDURE [inputSH: Stream.Handle, byteCount: LONG CARDINAL] =
    BEGIN
    -- Retrieve byteCount bytes from stream to output
    block: Env.Block;
    bytesMoved: CARDINAL ← 0;
    start: CARDINAL ← 0;

    LMin: PROC [l: LONG CARDINAL, c: CARDINAL] RETURNS [r: CARDINAL] = {
      IF l > c THEN r ← c ELSE r ← Inline.LowHalf[l]};


    -- byte align
    ByteAlign[];
    -- flush out current bit buffer if not word aligned
    IF bitPos # 0 THEN {currWrdPtr↑ ← bitBuf; start ← 1} ELSE start ← 0;
    -- kluge to avoid overflow in case byteCount is LAST[LONG CARDINAL]
    -- If so, it is assumed to mean retrieve til EOF.
    IF byteCount = LAST[LONG CARDINAL] THEN byteCount ← byteCount - 1;
    DO
      status: Stream.CompletionCode;
      endIndexPlusOne: CARDINAL;
      block.blockPointer ← LOOPHOLE[currWrdPtr];
      block.startIndex ← start;
      endIndexPlusOne ←
        Inline.LowHalf[(endWrdPtr - currWrdPtr)] * Env.bytesPerWord;
      block.stopIndexPlusOne ← LMin[byteCount + block.startIndex, endIndexPlusOne];
      [bytesMoved, status] ← Stream.GetBlock[inputSH, block];
      byteCount ← byteCount - bytesMoved;
      currWrdPtr ← currWrdPtr + (block.startIndex + bytesMoved) / Env.bytesPerWord;
      -- if output buffer is full, get a new one
      IF currWrdPtr = endWrdPtr THEN {GetNextWindow[]; bitBuf ← 0; bitPos ← 0};
      IF (status = endOfStream) OR byteCount = 0 THEN EXIT;
      start ← 0;
      ENDLOOP;  -- while bytes left
    -- if one byte is left over, write it out to buffer
    bitPos ←
      ((block.startIndex + bytesMoved) MOD Env.bytesPerWord) * Env.bitsPerByte;
    IF bitPos # 0 THEN bitBuf ← Inline.BITAND[currWrdPtr↑, 177400B]
    ELSE bitBuf ← 0;
    END;  -- Retrieve

  -- =====    
  SetPosition: PUBLIC PROCEDURE [pos: Position] =
    BEGIN
    -- Set the output to the specified position
    --
    -- Make sure current bit buffer is written out
    IF bitPos # 0 THEN currWrdPtr↑ ← Inline.BITAND[currWrdPtr↑, 377B] + bitBuf;
    -- see if we have enough pages allocated to the file
    IF pos.page + bufSize > fileSize THEN
      fileSize ← GrowFileSize[fileSize, growSize];  -- grow file
    -- change mapping
    [bufAddr, endWrdPtr, currWindow] ← BringNewWindow[[pos.page, bufSize]];
    currWrdPtr ← bufAddr + (pos.offset / Env.bytesPerWord);
    -- see if at word boundary
    bitPos ← Inline.LowHalf[(pos.offset MOD Env.bytesPerWord) * Env.bitsPerByte];
    IF bitPos = 0 THEN bitBuf ← 0
    ELSE {bitBuf ← Inline.BITAND[currWrdPtr↑, 177400B]};
    END;  -- SetPosition

  -- Interpress output routines

  -- =====  
  IPIdentifier: PUBLIC PROCEDURE [s: LONG STRING] =
    BEGIN OPEN Inline;
    shortToken: shortSequence operatorOrSequence IP.Token ← [
      operatorOrSequence[
      shortSequence[
      type: sequenceIdentifier, length: LowByte[LowHalf[s.length]]]]];
    PutWord[shortToken];
    FOR i: CARDINAL IN [0..s.length) DO PutByte[LOOPHOLE[s[i], Env.Byte]]; ENDLOOP;
    END;  -- IPIdentifier

  -- =====
  IPInteger: PUBLIC PROCEDURE [n: LONG INTEGER, length: CARDINAL ← 0] =
    BEGIN
    IF (length = 1)
      OR
        (length = 0
          AND n IN [-IP.shortNumberBias..(LAST[INTEGER] - IP.shortNumberBias)])
      THEN AppendShortNumber[Inline.LowHalf[n]]
    ELSE {
      shortSeq: shortSequence operatorOrSequence IP.Token;
      IF length = 0 THEN
        length ← IF n IN [FIRST[INTEGER]..LAST[INTEGER]] THEN 2 ELSE 4;
      shortSeq ← [
        operatorOrSequence[shortSequence[type: sequenceInteger, length: length]]];
      PutWord[shortSeq];
      IF length = 4 THEN PutWord[Inline.HighHalf[n]];
      PutWord[Inline.LowHalf[n]];
      };
    END;  -- IPInteger

  -- =====
  IPSeqCompressedPixel: PUBLIC PROCEDURE [size: LONG CARDINAL] =
    BEGIN
    longSeq: longSequence operatorOrSequence IP.Token ← [
      operatorOrSequence[
      longSequence[
      type: sequenceCompressedPixelVector, lengthHigh: Inline.HighHalf[size],
      lengthLow: Inline.LowHalf[size]]]];
    ptrToToken: LONG POINTER ← @longSeq;
    ptr: LONG POINTER TO LONG CARDINAL ← LOOPHOLE[ptrToToken];
    PutWord[Inline.LowHalf[ptr↑]];
    PutWord[Inline.HighHalf[ptr↑]];
    END;  -- IPSeqCompressedPixel

  -- =====
  OpOnly: PUBLIC PROCEDURE [code: Interpress.LongOperator] =
    BEGIN
    IF code IN IP.ShortOperator THEN {
      sOp: shortOperator operatorOrSequence IP.Token ← [
        operatorOrSequence[shortOperator[operator: code]]];
      pOp: POINTER TO PACKED ARRAY [0..1] OF Environment.Byte ← LOOPHOLE[@sOp];
      PutByte[pOp[0]]}
    ELSE {
      lOp: longOperator operatorOrSequence IP.Token ← [
        operatorOrSequence[longOperator[operator: code]]];
      PutWord[lOp];
      };
    END;  -- OpOnly

  END...
  
LOG 
20Nov84 - Okamoto - Changed FreeOutput to CloseOutput and added EmptyOutput.
22Jan85 - Okamoto - Changed so that mapUnit is unmapped before set size in EmptyOutput and GrowFileSize call in GetNextWindow. 
10Jul85 - castillo - copyright notice.