-- BcdFileLookup.Mesa
-- Last edited by Satterthwaite on October 4, 1982 12:18 pm
-- Last edited by Lewis on 18-Mar-81 15:31:15

DIRECTORY
  Alloc: TYPE USING [AddNotify, Bounds, DropNotify, Handle, Notifier, Top, Words],
  BcdDefs: TYPE USING [
    FTIndex, FTNull, FTRecord, fttype, NameRecord, NullVersion, sstype, sttype],
  BcdFileDefs: TYPE USING [],
  BcdOps: TYPE USING [NameString],
  BcdUtilDefs: TYPE USING [NameForHti],
  File: TYPE USING [Capability, nullCapability],
  OSMiscOps: TYPE USING [FindFile, FileError],
  Strings: TYPE USING [
    AppendChar, AppendString, String, SubStringDescriptor, SubString],
  Symbols: TYPE USING [HTIndex, STIndex, STRecord],
  Table: TYPE USING [Base, Limit];

BcdFileLookup: PROGRAM
    IMPORTS Alloc, BcdUtilDefs, OSMiscOps, Strings
    EXPORTS BcdFileDefs = {

  maxFiles: NAT ~ Table.Limit/BcdDefs.FTRecord.SIZE;
  FileSequence: TYPE ~ RECORD [SEQUENCE length: [0..maxFiles] OF File.Capability];
  fileArray: LONG POINTER TO FileSequence;
  nullFile: File.Capability ~ File.nullCapability;


  table: Alloc.Handle;
  zone: UNCOUNTED ZONE;
  
  ftb, stb: Table.Base;

  Notifier: Alloc.Notifier ~ {ftb ← base[BcdDefs.fttype]; stb ← base[BcdDefs.sttype]};


  BuildFileTable: PUBLIC PROC [ownTable: Alloc.Handle, scratchZone: UNCOUNTED ZONE] ~ {
    OPEN Symbols;
    stLimit: STIndex;
    zone ← scratchZone;
    table ← ownTable; table.AddNotify[Notifier];
    stLimit ← table.Top[BcdDefs.sttype];
    FOR sti: STIndex ← STIndex.FIRST, sti+STRecord.SIZE UNTIL sti=stLimit DO
      WITH s~~stb[sti] SELECT FROM
        external =>
          WITH p~~s SELECT FROM
            file => IF p.fti = BcdDefs.FTNull THEN p.fti ← AddFile[s.hti];
            ENDCASE;
        ENDCASE;
      ENDLOOP;
    fileArray ← zone.NEW[FileSequence[table.Bounds[BcdDefs.fttype].size/BcdDefs.FTRecord.SIZE]];
    FOR i: NAT IN [0..fileArray.length) DO fileArray[i] ← nullFile ENDLOOP};

  AddFile: PROC [hti: Symbols.HTIndex] RETURNS [fti: BcdDefs.FTIndex] ~ {
    ftLimit: BcdDefs.FTIndex ~ table.Top[BcdDefs.fttype];
    name: BcdDefs.NameRecord ~ BcdUtilDefs.NameForHti[hti];
    FOR fti ← BcdDefs.FTIndex.FIRST, (fti + BcdDefs.FTRecord.SIZE) UNTIL fti = ftLimit DO
      IF ftb[fti].name = name THEN RETURN  ENDLOOP;
    fti ← table.Words[BcdDefs.fttype, BcdDefs.FTRecord.SIZE];
    ftb[fti] ← [name~name, version~BcdDefs.NullVersion];
    RETURN};

  EraseFileTable: PUBLIC PROC ~ {
    zone.FREE[@fileArray];
    table.DropNotify[Notifier]; table ← NIL; zone ← NIL};


  IndexForFti: PROC [fti: BcdDefs.FTIndex] RETURNS [CARDINAL] ~ INLINE {
    RETURN [LOOPHOLE[fti, CARDINAL]/BcdDefs.FTRecord.SIZE]};

  FtiForIndex: PROC [i: CARDINAL] RETURNS [BcdDefs.FTIndex] ~ INLINE {
    RETURN [BcdDefs.FTIndex.FIRST + i*BcdDefs.FTRecord.SIZE]};


  UnknownFile: PUBLIC ERROR [fti: BcdDefs.FTIndex] ~ CODE;

  CapabilityForFile: PUBLIC PROC [fti: BcdDefs.FTIndex] RETURNS [File.Capability] ~ {
    index: CARDINAL ~ IndexForFti[fti];
    IF index >= fileArray.length THEN ERROR UnknownFile[fti];
    IF fileArray[index] = nullFile THEN {
      ftb: Table.Base ← table.Bounds[BcdDefs.fttype].base;
      name: BcdDefs.NameRecord ~ ftb[fti].name;
      ssb: BcdOps.NameString ← table.Bounds[BcdDefs.sstype].base;
      ssd: Strings.SubStringDescriptor ← [
          base~@ssb.string, offset~name, length~MIN[ssb.size[name], 100]];
      nameStr: STRING ← [100];
      NormalizeFileName[in~@ssd, out~nameStr];
      fileArray[index] ← OSMiscOps.FindFile[nameStr, $read
          ! OSMiscOps.FileError => {CONTINUE}];
      IF fileArray[index] = nullFile THEN ERROR UnknownFile[fti]};
    RETURN [fileArray[index]]};

  NormalizeFileName: PROC [in: Strings.SubString, out: Strings.String] ~ {
    char: CHAR;
    dot: BOOL ← FALSE;
    out.length ← 0;
    FOR i: CARDINAL IN [in.offset .. in.offset+in.length) DO
      SELECT (char ← in.base[i]) FROM
	IN ['A..'Z] =>  char ← char + ('a-'A);
	'. =>  dot ← TRUE;
	ENDCASE;
      Strings.AppendChar[out, char];
      ENDLOOP;
    IF ~dot THEN Strings.AppendString[out, ".bcd"L]};
    
  }.