-- File: AltoFileOpsA.mesa
-- Last edited by Levin:  30-Apr-81 14:26:02

DIRECTORY
  AltoDefs USING [PageNumber],
  AltoFile USING [PageNumber],
  AltoFilePrivate USING [
    AltoPageNumber, FileHandle, FileObject, infinityPage, maxRuns,
    percentToIncrease, RunIndex, RunCount, RunTable, VdaRun],
  DiskIODefs USING [eofvDA, FID, fillInvDA, vDA],
  FileDefs USING [],
  Inline USING [COPY],
  VMStorage USING [shortTerm];

AltoFileOpsA: MONITOR LOCKS file.runTableLock USING file: AltoFilePrivate.FileHandle
  IMPORTS Inline, VMStorage
  EXPORTS AltoFile, AltoFilePrivate, FileDefs =

  BEGIN OPEN AltoFile, AltoFilePrivate, DiskIODefs;


  -- Miscellaneous Declarations --

  RunTableOverflow: ERROR = CODE;
  RunTableInconsistent: ERROR = CODE;
  ImpossiblevDA: ERROR = CODE;
  InvalidvDA: ERROR = CODE;


  -- Types Exported to FileDefs --

  FileObject: PUBLIC TYPE = AltoFilePrivate.FileObject;

  -- Procedures and Signals Exported to AltoFile --

  EnterDiskAddress: PUBLIC PROCEDURE [
    file: FileHandle, page: AltoDefs.PageNumber, vda: vDA] =
    LOOPHOLE[EnterPageAndvDA];

  MapPageToDiskAddress: PUBLIC PROCEDURE [
    file: FileHandle, page: PageNumber, successors: CARDINAL ← 0]
    RETURNS [nearPage: PageNumber, nearvda: vDA, knownPages: CARDINAL] =
    BEGIN
    diskPage: AltoDefs.PageNumber ← AltoFromFilePageNumber[page];
    entry: VdaRun;

    GetInfo: ENTRY PROCEDURE [file: FileHandle] = INLINE
      BEGIN
      slot: RunIndex ← FindPageInTable[file, diskPage].nearest;
      entry ← file.runTable[slot];
      IF entry.vda = fillInvDA THEN
	{diskPage ← entry.page - 1; entry ← file.runTable[slot - 1]; knownPages ← 1}
      ELSE
	BEGIN
	knownPagesLimit: CARDINAL = successors + 1;
	DO
	  e: VdaRun ← file.runTable[slot ← slot + 1];
	  known: CARDINAL ← e.page - diskPage;
	  IF known >= knownPagesLimit OR e.vda = fillInvDA THEN
	    {knownPages ← MIN[knownPagesLimit, known]; EXIT};
	  ENDLOOP;
	END;
      END;

    GetInfo[file];
    RETURN[
      FileFromAltoPageNumber[diskPage],
      vDA[VdaToCardinal[entry.vda] + diskPage - entry.page],
      knownPages]
    END;

  GetFileID: PUBLIC PROCEDURE [file: FileHandle] RETURNS [FID] =
    -- Note:  since the fileID never changes, no synchronization is used here.
    {RETURN[file.fileID]};

  FileToAltoPageNumber: PUBLIC PROCEDURE [page: PageNumber]
    RETURNS [AltoPageNumber] =
    {RETURN[AltoFromFilePageNumber[page]]};


  -- Procedures and Signals Exported to AltoFilePrivate --

  GetvDAForPage: PUBLIC PROCEDURE [file: FileHandle, page: AltoPageNumber]
    RETURNS [vDA] =
    BEGIN
    entry: VdaRun;

    GetEntry: ENTRY PROCEDURE [file: FileHandle] = INLINE
      {entry ← file.runTable[FindPageInTable[file, page].nearest]};

    GetEntry[file];
    SELECT entry.vda FROM
      eofvDA => ERROR ImpossiblevDA;
      fillInvDA => RETURN[entry.vda];
      ENDCASE => RETURN[vDA[VdaToCardinal[entry.vda] + page - entry.page]];
    END;

  EnterPageAndvDA: PUBLIC ENTRY PROCEDURE [
    file: FileHandle, page: AltoPageNumber, vda: vDA] =
    BEGIN
    match: BOOLEAN;
    slot: RunIndex;

    InsertRunSpace: PROCEDURE [where: RunIndex, slots: CARDINAL] =
      -- opens up file's runTable to have a gap of length 'slots' immediately after
      -- index 'where'.
      BEGIN
      oldLength: RunCount = file.nRuns;
      newLength: RunCount = oldLength + slots;
      oldSpace: RunCount = file.runTable.runSpace;
      table: POINTER TO RunTable ← file.runTable;
      IF newLength > oldSpace THEN
	BEGIN OPEN VMStorage;
	newSpace: RunCount = MIN[maxRuns, oldSpace + (oldSpace*percentToIncrease)/100];
	newTable: POINTER TO RunTable;
	IF oldSpace = maxRuns THEN ERROR RunTableOverflow;
	newTable ← shortTerm.NEW[RunTable[newSpace]];
	Inline.COPY[from: @table[0], to: @newTable[0], nwords: oldLength*SIZE[VdaRun]];
	shortTerm.FREE[@table];
	file.runTable ← table ← newTable;
	END;
      file.nRuns ← newLength;
      FOR i: CARDINAL DECREASING IN [where + 1..oldLength) DO
	table[i + slots] ← table[i];
	ENDLOOP;
      END;

    RemoveRunSpace: PROCEDURE [where: RunIndex, slots: CARDINAL] =
      -- eliminates slots numbered [where+1..where+slots] from file's runTable.
      BEGIN
      table: POINTER TO RunTable = file.runTable;
      firstToMove: CARDINAL = where + slots + 1;
      n: CARDINAL = file.nRuns - firstToMove;
      Inline.COPY[
	from: @table[firstToMove], to: @table[where + 1], nwords: n*SIZE[VdaRun]];
      file.nRuns ← file.nRuns - slots;
      END;

    MergeDown: PROCEDURE [where: RunIndex] RETURNS [BOOLEAN] =
      -- returns TRUE if [page, vda] can be merged from above into file.runTable[where],
      -- FALSE otherwise.  It is assumed that vda ~= fillInvDA.
      BEGIN
      entry: VdaRun = file.runTable[where];
      RETURN[
	IF entry.vda = fillInvDA THEN FALSE
	ELSE vDA[VdaToCardinal[entry.vda] + page - entry.page] = vda]
      END;

    MergeUp: PROCEDURE [where: RunIndex] RETURNS [BOOLEAN] = INLINE
      -- returns TRUE if [page, vda] can be merged from below into file.runTable[where],
      -- FALSE otherwise.  It is assumed that vda ~= fillInvDA.
      BEGIN
      entry: VdaRun = file.runTable[where];
      RETURN[
	IF entry.page = page + 1 THEN
	IF entry.vda = fillInvDA THEN FALSE
	ELSE vDA[VdaToCardinal[vda] + 1] = entry.vda ELSE FALSE]
      END;

    CheckSlotForFillInvDA: PROCEDURE RETURNS [isFillIn: BOOLEAN] = INLINE
      -- checks that [page, vda] is consistent with the entry in file.runTable[slot].
      -- If so, the value returned indicates whether file.runTable[slot] = fillInvDA.
      BEGIN
      entry: VdaRun = file.runTable[slot];
      IF (isFillIn ← entry.vda = fillInvDA) THEN RETURN
      ELSE
	IF VdaToCardinal[vda] ~= VdaToCardinal[entry.vda] + page - entry.page THEN
	  ERROR RunTableInconsistent;
      END;

    SELECT vda FROM
      eofvDA => RETURN; -- eliminates eof checks in CompletionProcedures
      fillInvDA => ERROR InvalidvDA;
      ENDCASE;
    [match, slot] ← FindPageInTable[file, page];
    IF CheckSlotForFillInvDA[] THEN
      -- note:  file.runTable[0].vda ~= fillInvDA implies slot > 0, which is assumed.
      -- note:  in the following comments, 'p' and 'v' are the variables 'page' and
      --	'vda', x and y are unsuitable vdas,  m < p-1, p+1 < n, and ? is
      --	irrelevant information.
      IF MergeUp[slot + 1] THEN			-- <?, fillin>, <p+1, v+1>
	IF match THEN				-- <p, fillin>, <p+1, v+1>
	  IF MergeDown[slot - 1] THEN		-- <p-1, v-1>, <p, fillin>, <p+1, v+1>
	    RemoveRunSpace[slot - 1, 2]
	  ELSE					-- <m | x>, <p, fillin>, <p+1, v+1>
	    {RemoveRunSpace[slot, 1]; file.runTable[slot].vda ← vda}
	ELSE file.runTable[slot + 1] ← [page, vda]	-- <m, fillin>, <p+1, v+1>
      ELSE
	IF match THEN				-- <p, fillin>, <n | x>
	  IF MergeDown[slot - 1] THEN		-- <p-1, v-1>, <p, fillin>, <n | x>
	    IF file.runTable[slot + 1].page = page + 1 THEN
	      RemoveRunSpace[slot - 1, 1]	-- <p-1, v-1>, <p, fillin>, <p+1, x>
	    ELSE file.runTable[slot].page ← page + 1  -- <p-1, v-1>, <p, fillin>, <n, ?>
	  ELSE					-- <m | x>, <p, fillin>, <n | x>
	    BEGIN
	    file.runTable[slot].vda ← vda;
	    IF file.runTable[slot + 1].page ~= page + 1 THEN
	      BEGIN				-- <m | x>, <p, fillin>, <n, ?>
	      InsertRunSpace[slot, 1];
	      file.runTable[slot + 1] ← [page + 1, fillInvDA];
	      END;
	    END
	ELSE					-- <m, fillin>, <n | x>
	  BEGIN
	  IF file.runTable[slot + 1].page = page + 1 THEN -- <m, fillin>, <p+1, x>
	    InsertRunSpace[slot, 1]
	  ELSE					-- <m, fillin>, <n, ?>
	    {InsertRunSpace[slot, 2]; file.runTable[slot + 2] ← [page + 1, fillInvDA]};
	  file.runTable[slot + 1] ← [page, vda];
	  END;
    END;

  FindLastKnownPage: PUBLIC PROCEDURE [file: FileHandle]
    RETURNS [AltoPageNumber, vDA] =
    BEGIN
    lastPage: AltoPageNumber;
    entry: VdaRun;

    GetInfo: ENTRY PROCEDURE [file: FileHandle] = INLINE
      BEGIN
      slot: RunIndex ← file.nRuns - 3;
      lastPage ← file.runTable[slot + 1].page - 1;
      entry ← file.runTable[slot];
      END;

    GetInfo[file];
    SELECT entry.vda FROM
      eofvDA => ERROR ImpossiblevDA;
      fillInvDA => ERROR RunTableInconsistent;
      ENDCASE =>
	RETURN[lastPage, vDA[VdaToCardinal[entry.vda] + lastPage - entry.page]];
    END;

  TruncatevDATable: PUBLIC ENTRY PROCEDURE [file: FileHandle, page: AltoPageNumber] =
    BEGIN
    slot: RunIndex;
    slot ← FindPageInTable[file, page].nearest;
    SELECT file.runTable[slot].vda FROM
      eofvDA => ERROR ImpossiblevDA;
      fillInvDA => NULL;
      ENDCASE => file.runTable[(slot ← slot + 1)] ← [page + 1, fillInvDA];
    file.runTable[slot + 1] ← [infinityPage, eofvDA];
    file.nRuns ← slot + 1 + 1;
    END;


  -- Internal Procedures --

  FindPageInTable: PROCEDURE [file: FileHandle, page: AltoPageNumber]
    RETURNS [found: BOOLEAN, nearest: RunIndex] =
    -- searches file.runTable for 'page'.  If the page is found,  then 'nearest' is
    -- the index of 'page' in the table.  If the page is not found, then 'nearest' is
    -- the index of the largest smaller-numbered page.
    BEGIN
    low: RunIndex ← 0;
    high: RunIndex ← file.nRuns - 1;
    found ← TRUE;
    UNTIL low > high DO
      nearest ← (low + high)/2;
      SELECT file.runTable[nearest].page FROM
	page => RETURN;
	< page => low ← nearest + 1;
	> page => high ← nearest - 1;
	ENDCASE;
      ENDLOOP;
    RETURN[FALSE, low - 1]
    END;

  AltoFromFilePageNumber: PROCEDURE [fp: PageNumber] RETURNS [AltoPageNumber] = INLINE
    {RETURN[fp + 1]};

  FileFromAltoPageNumber: PROCEDURE [ap: AltoPageNumber] RETURNS [PageNumber] = INLINE
    {RETURN[ap - 1]};

  VdaToCardinal: PROCEDURE [vda: vDA] RETURNS [CARDINAL] = INLINE
    {RETURN[LOOPHOLE[vda]]};

  END.