(*
    Title:    M2Files - Implementation
    LastEdit: "Fri Nov  2 11:45:31 1984"
    Author:   Mick Jordan
    Cambridge University Computer Laboratory
*)

(*
    NS16032 Panos version
    No hashing or archiving.
    Note 10 char filename limitation - from ADFS
*)

IMPLEMENTATION MODULE M2Files;


FROM Storage IMPORT ALLOCATE, DEALLOCATE;
FROM Streams IMPORT 
    Stream, ErrorCode, StreamProcs, Get, GetN, Create, Delete;
IMPORT BadStream;
FROM SysStreams IMPORT sysOut;
FROM TextIO IMPORT WriteChars, NewLine, ReadChars, WhiteSpace, EndOfLine;
FROM WriteF IMPORT WriteF1, H;
IMPORT HashStrings; 
FROM HashStrings IMPORT HashTable, HashId,
    EnterString, EnterChars, LookupChars, Assoc, Retrieve;
IMPORT Strings; FROM Strings IMPORT String;
IMPORT FileStream; 


TYPE FProc = PROCEDURE(ARRAY OF CHAR): Stream;


CONST MaxNameSize = 10; (*  max filename size  (ADFS, ok for NFS as well *)


TYPE FileData = POINTER TO RECORD
(*  archives not supported but left here for future development *)
        CASE inarchive: BOOLEAN OF
          TRUE:
        (*
              archiveid: ArchiveId;
              memberid: MemberId;
              archivename: String;
        *)
        | FALSE:
              directory: String;
        END (* case *);
    END (* record *);


VAR 
    dirsep, extsep: ARRAY [0..1] OF CHAR;
    m2Path: ARRAY [0..1023] OF CHAR;
    m2PathFound: BOOLEAN;
    notify: BOOLEAN;
    extht, ht: HashTable;


TYPE Mode = (In, Out, InOut);


PROCEDURE Open(m: Mode; name: ARRAY OF CHAR; ext: Extension): Stream;

VAR s: Stream;
  extsuffix: ARRAY [0 .. 4] OF CHAR;
  directoryPrefix: ARRAY [0..255] OF CHAR;
  fproc: FProc;
  filedata: FileData;
  hashid: HashId;
  directory: String;

BEGIN
  (* have we hashed this extension type? *)
  IF (m = Out) OR LookupChars(extht, ext, hashid) THEN

    Strings.CopyCC(name, Filename);
    Filename[MaxNameSize] := 0C; (*  truncate *)
    ext[3] := 0C; (*  just in case *)
    Strings.ConcatCC(extsep, ext, extsuffix);
    Strings.ConcatCC(Filename, extsuffix, Filename);

    CASE m OF
      In:   fproc := FileStream.CreateInput;
    | Out:  fproc := FileStream.CreateOutput;
    | InOut: fproc := FileStream.CreateInOutput;
    END;
    
    IF m # Out THEN
 
      IF LookupChars(ht, Filename, hashid) THEN
        filedata := FileData( Retrieve(ht, hashid) );
        WITH filedata^ DO
          IF inarchive THEN
          (*
            s := CreateFastInput(archiveid, memberid);
            Strings.CopySC(archivename, Filename);
            RETURN s;
          *)
            HALT
          ELSE Strings.CopySC(directory, directoryPrefix);
            Strings.ConcatCC(directoryPrefix, dirsep, directoryPrefix);
            Strings.ConcatCC(directoryPrefix, Filename, Filename);
            RETURN Try(Filename, fproc);
          END;
        END;
      ELSE RETURN BadStream.CreateInOutput();
      END;
 
    ELSE RETURN Try(Filename, fproc);
    END;

  ELSE RETURN SlowOpen(m, name, ext);
  END;
END Open;


PROCEDURE Try(name: ARRAY OF CHAR; fproc: FProc): Stream;
BEGIN
  IF notify THEN
    WriteChars(sysOut, "Trying ");
    WriteChars(sysOut, name); NewLine(sysOut);
  END;
  RETURN fproc(name);
END Try;


PROCEDURE CreateInput(name: ARRAY OF CHAR; ext: Extension): Stream;
BEGIN
  RETURN Open(In, name, ext);
END CreateInput;


PROCEDURE CreateOutput(name: ARRAY OF CHAR; ext: Extension): Stream;
BEGIN
  RETURN Open(Out, name, ext);
END CreateOutput;


PROCEDURE CreateInOutput(name: ARRAY OF CHAR; ext: Extension): Stream;
BEGIN
  RETURN Open(InOut, name, ext);
END CreateInOutput;


PROCEDURE HashExt(ext: Extension);
BEGIN
END HashExt;


PROCEDURE SetM2PathFromFile;
  VAR s: Stream;
BEGIN
  (* This should probably set up an environment variable rather than use a
     global. Think about it anyone who reads this.
  *)
  s := FileStream.CreateInput("m2Path");
  IF s^.status # Success THEN
    Strings.CopyCC("|$.m2.lib", m2Path);
  ELSE ReadChars(s, m2Path, EndOfLine);
  END;
  Delete(s); 
  DISPOSE(s);
  m2PathFound := TRUE;
END SetM2PathFromFile;
    

PROCEDURE SlowOpen(m: Mode; name: ARRAY OF CHAR; ext: Extension): Stream;

VAR firstindex, lastindex: CARDINAL;
  s: Stream;
  tFilename: ARRAY [0 .. 79] OF CHAR;
  extsuffix: ARRAY [0 ..  4] OF CHAR;
  m2dir: ARRAY [0 .. 79] OF CHAR;
  nullm2dir, endstring: BOOLEAN;
  fproc: FProc;

BEGIN
  firstindex := 0; lastindex := 0;

  (*  append extension *)
  Strings.CopyCC(name, Filename);
  Filename[MaxNameSize] := 0C; (*  truncate *)
  ext[3] := 0C;                       (*  just in case *)
  Strings.ConcatCC(extsep, ext, extsuffix);
    
  IF NOT m2PathFound THEN SetM2PathFromFile END;
          
  Strings.ConcatCC(Filename, extsuffix, Filename);
  Strings.CopyCC(Filename, tFilename); (*  save *)

  CASE m OF
    In:   fproc := FileStream.CreateInput;
  | Out:  fproc := FileStream.CreateOutput;
  | InOut: fproc := FileStream.CreateInOutput;
  END;
    
  LOOP
    endstring := NOT Strings.FindC(m2Path, '|', lastindex);

    (* extract first directory to search *)
    nullm2dir := lastindex = firstindex;
    IF NOT nullm2dir THEN
      Strings.ExtractCC(m2Path, firstindex, lastindex, m2dir);
    END;
    INC(lastindex);
    firstindex :=  lastindex;
       
    Strings.CopyCC(tFilename, Filename); (*  recover *)
    IF NOT nullm2dir THEN
      Strings.ConcatCC(m2dir, dirsep,  m2dir);
      Strings.ConcatCC(m2dir, Filename, Filename);
    END;

    s := Try(Filename, fproc);
    IF s^.status = Success THEN RETURN s END;
    IF endstring THEN RETURN BadStream.CreateInOutput() END;
  END;
END SlowOpen;


BEGIN
  (* construct single character strings ! *)
  dirsep[0] := '.'; dirsep[1] := 0C;
  extsep[0] := '-'; extsep[1] := 0C;
  notify := FALSE;
  m2PathFound := FALSE;
  ht := HashStrings.New(128); extht := HashStrings.New(9);
END M2Files.
