PROGRAM RECIPE;
(*
**  PROGRAM TITLE       THE RECIPE SYSTEM
**                      Version PAS-1.2 translated from
**                      the BASIC version into Pascal.
**
**  WRITTEN BY:         Ray Penley
**  DATE WRITTEN:       23 FEB 1980 / last modified: 28 FEB 80
**  WRITTEN FOR:        Computer hobbyists
**
**  PROGRAM SUMMARY:
**
**  The recipe system stores recipes and retrives them
**  by means of a numeric key that represents the foods
**  used in the meal.  Foods are divided into four
**  categories according to their nutritional value.
**
**  INPUT AND OUTPUT FILES:
**      RCPDAT.XXX and RCPDAT.YYY
**                 - the DATA and the backup files
**      RECIPE.MST - the statistics file
**      DUMMY.$$$  - see Procedure InputRecipe for use.
**
**  ORIGINAL PROGRAM:
**      T.G.LEWIS, 'THE MIND APPLIANCE'
**      HAYDEN BOOK COMPANY
**)
CONST
 str_len = 73;         (* max length of all strings + one *)
 EOS     = '|';        (* End of String marker *)
 Master  = 'RECIPE.MST';
 Tab20   = 20 ;
 Tab15   = 15 ;
 on      = true;
 off     = false;

(* !!!!! IMPLEMENTATION DEPENDENCY !!!!! *)
  (*****   PASCAL/Z ver 2.0   *****)
         INPUT   = 0;

TYPE
 string   = packed array[1..str_len] of char;
 string2  = packed array[1..2] of char;
 string14 = packed array[1..14] of char;
 datatype = record
                MR,            (* MaxRecords   *)
                CR : integer;  (* Curr_Rcds    *)
                F1,            (* current_ID   *)
                F2,            (* backup_ID    *)
                date : string14(* last_update  *)
            end;
VAR
 a_RAY         : packed array[1..5] of string;
 data          :datatype;
 Bell,
 command       :char;
 Last_update   :string14;
 Curr_Rcds,    (* No. of current active records *)
 Hash,         (* Computed Index value of Recipe *)
 Last,
 MaxRecords,   (* Maximum records allowed *)
 TTY           (* width of terminal/CRT *)
               :integer;
 End_of_File,          (* End of File flag *)
 End_of_Text,          (* End of Text flag *)
 adding_recipies,      (* flag = true when adding recipies *)
 switch, error,
 done, yflag   : boolean;

       (* FID. File Identifier *)
 current_ID,                   (* Current file ID *)
 backup_ID     :string14;      (* Back up file ID *)

       (* FCB. File descriptors *)
 fa, fb        :TEXT;
 stats         :FILE of datatype;


(*----------------------------------------------*
*            INPUT/OUTPUT ROUTINES             *
*----------------------------------------------*)


(*----------------------------------------------*)
(*                DISK I/O                      *)
(*----------------------------------------------*)


Procedure OPEN_MASTER;
begin
 (* OPEN file RECIPE.MST for READ assign stats *)
         RESET(master, stats);
 READ(stats, data );
 with data do begin
   MaxRecords := MR;
   Curr_Rcds  := CR;
   current_ID  := F1;
   backup_ID   := F2;
   last_update := date
   end(* with *)
end;

Procedure UPDATE_MASTER;
begin
 (* OPEN file RECIPE.MST for WRITE assign stats *)
         REWRITE(master, stats);
 with data do begin
   MR := MaxRecords;
   CR := Curr_Rcds;
   F1 := current_ID ;
   F2 := backup_ID ;
   date := last_update
   end(* with *);
 WRITE(stats, data )
end;

Procedure GETLINE((* VAR fx : TEXT; *)
                    VAR INBUFF : string );
(**
Returns:
       End_of_Text = true if attempt is made to exceed
                   the input buffer length.
       End_of_File = true if EOF
       INBUFF    = input string
***)
VAR
CH   : CHAR;
ix, length : integer;
begin
 length := 0;
 End_of_Text := FALSE;
 WHILE NOT EOF(fa) AND (CH <> EOS) DO
   begin
   If length < str_len then
     begin
     READ(fa, CH );
     length := length +1;
     INBUFF [length] := CH
     end(* If *)
   ELSE        (***   error   ***)
     begin
     error := true;
     End_of_Text := TRUE
     end(* else *)
   end(* WHILE *);
   If length >= last then
     last:=length
   Else
     REPEAT
       INBUFF[ last ] := EOS;
       last := last -1
     UNTIL last=length;
       (*** !!! SET FLAG !!! ***)
 End_of_File := EOF(fa);
end(*---of GetLine---*);

Procedure PUTLINE((* VAR fx : TEXT; *)
                    VAR this :string );
VAR
 CH  : char;
 pos : integer;
begin
 pos := 0;
 REPEAT
   pos := pos +1;
   CH := this[ pos ];
   If CH <> EOS then Write(fb, CH)
 UNTIL (CH = EOS) OR (pos = str_len);
 Write(fb, EOS ) (* Mark the End of String *)
end(*---of PUTLINE---*);

Procedure PUT_RECORD((* VAR fx : TEXT; *)
                       VAR Index : integer );
VAR
 jx : integer;
begin
 Writeln(fb, Index:5);
 For jx:=1 to 5 do
   PUTLINE((* fb, *) a_RAY[jx] );
end(*---of PUT_RECORD---*);

Procedure GET_RECORD((* VAR fx : TEXT; *)
                       VAR Index : integer );
VAR
 JJ : integer;
begin
 READLN (fa, Index);
 FOR JJ := 1 to 5 DO
   GETLINE((* fa, *) a_RAY[JJ] );
end(*---of GET_RECORD---*);

(*----------------------------------------------*)
(*              CONSOLE I/O                     *)
(*----------------------------------------------*)

Procedure PRINT((* VAR fx : TEXT; *)
                  VAR this : string );
(*      Print the string 'this' until EOS       *)
VAR
 CH : CHAR;
 pos : integer;
begin
 pos := 0;
 REPEAT
   pos := pos +1;
   CH := this[ pos ];
   If CH <> EOS then Write(CH)
 UNTIL (CH = EOS) OR (pos = str_len);
 Writeln
end(*---of PRINT---*);

Procedure SCAN((* VAR fx : TEXT; *)
                VAR INBUFF : String ;
                     count : integer );
(*      SCAN Version 1.1                *
Enter with:
       count = maximum # chars allowed.
Returns:
       INBUFF = input string
       EOS    = End of string marker
Flags:
       error  = false - good input
              = true if buffer length exceeded
                     If invalid ASCII char detected.

       Valid Alphanumeric chars are:
       between the space - CHR(32) to the tilde - CHR(126)
GLOBAL
  str_len = << default for string length >>
  EOS   = '|';
  error  : boolean
  string  : packed array[1..str_len] of char
*)
VAR
 InChar : char;
 length : integer;
begin
 error := false;
 For length:=1 to str_len do INBUFF[ length ]:= EOS;
 length := 0;
 REPEAT
   If length < count then(* get valid inputs *)
     begin
     READ( InChar );
     If InChar IN [' ' .. '~'] then
       begin (* Increment length and store InChar *)
       length := length +1;
       INBUFF[length] := InChar
       end(* if *)
     ELSE
       begin
       Writeln(' Alphanumerics only -');
       error:=TRUE
       end(* else *)
     end(* If *)
   ELSE        (*   ERROR   *)
     begin (* RESET EndOfLine (EOLN) *)
     READLN(INBUFF[count]);
     Writeln('Maximum of', count:4, ' characters please!');
     error:=TRUE
     end(* ELSE *)
 UNTIL EOLN(INPUT) OR error;
end(*---of SCAN11---*);

(*----------------------------------------------*
*              UTILITY ROUTINES                *
*----------------------------------------------*)


Procedure QUIRY;
(*      YES/NO INPUT MODULE
Returns:
       yflag   =TRUE FOR ''Y' or 'y' INPUT
               =FALSE FOR 'N' or 'n' INPUT
GLOBAL
       yflag : boolean;
*)
VAR
 Ans : char;
 error : boolean;
begin
 error := true;
 yflag := false;
 REPEAT
   error := false;
   READ(Ans);
   If (Ans = 'Y') OR (Ans = 'y') then
     yflag := true
   Else
     If (Ans <> 'N') AND (Ans <> 'n') then
       begin
       Writeln(BELL, 'Please answer ''Y'' or ''N'' ');
       error := true
       end
 Until NOT error
end(*---of QUIRY---*);

Procedure CLEAR;
(* Device dependent procedure   *)
begin
 Write( CHR(26) );
end;

Procedure SKIP(L1 : integer);
VAR ix : integer;
begin
 FOR ix:=1 to L1 do Writeln;
end;

Procedure PAUSE;
VAR dummy : char;
begin
 skip(4);
 Write('Type return to continue:');
 READ(dummy);
end;

Procedure BREAK;
begin
 CLEAR;
 SKIP(5);
end;

Procedure Pstring(picture : string2; count : integer );
VAR ix : integer;
begin
 FOR ix:=1 to count DO Write( picture );
 Writeln;
end(*---of Pstring---*);

Procedure ShowRecipe;
VAR JJ : integer;
begin
 FOR JJ := 1 to 5 DO
   PRINT(a_RAY[JJ]) ;
 Writeln
end(*--of ShowRecipe--*);

Procedure Display_One(VAR Index : integer);
begin
 Writeln;
 Writeln( 'Recipe #', Index:5 );
 Writeln;
 Pstring( '- ', 20);
 Writeln;
 ShowRecipe;
 skip(4)
end;

(*----------------------------------------------*
*                 ADD MODULE                   *
*----------------------------------------------*)

Procedure InputFeatures(VAR I : integer);
(******************************************
*       Input Features of Recipe          *
*******************************************)
(*
RETURNS:
 Hash value computed for various choices
**)
CONST
 Msg1    = 'None of these' ;
VAR
  F, D, V, P :integer;

       Function QUIRY(X2 : integer) : integer;
       VAR ix : integer;
       begin
         REPEAT
           Writeln;
           Write('Enter Choice (1 to', X2:2, ') ');
           READ(ix);
         UNTIL (ix>=1) AND (ix<=X2) ;
         QUIRY := ix;
       end;
begin
 Writeln;
 Writeln( ' Enter number of choice :');
 Writeln;
 Writeln( ' ':Tab15, 'Fibre Foods' );
 Writeln;
 Writeln( ' ':Tab15, '1.  Bread (flour)     2.  Oats' );
 Writeln( ' ':Tab15, '3.  Rice              4.  Corn' );
 Writeln( ' ':Tab15, '5.  Macaroni          6.  Noodles' );
 Writeln( ' ':Tab15, '7.  Spaghetti         8.  ', Msg1 );
 F := quiry(8);
 Writeln;
 Writeln( ' ':Tab15, 'Protein' );
 Writeln;
 Writeln( ' ':Tab15, '1.  Beef              2.  Poultry' );
 Writeln( ' ':Tab15, '3.  Fish              4.  Eggs' );
 Writeln( ' ':Tab15, '5.  Beans             6.  Nuts' );
 Writeln( ' ':Tab15, '7.  ', Msg1 );
 P := quiry(7);
 BREAK;
 Writeln;
 Writeln( ' ':Tab15, 'Dairy' );
 Writeln;
 Writeln( ' ':Tab15, '1.  Milk                2.  Cheese' );
 Writeln( ' ':Tab15, '3.  Cottage Cheese      4.  Cream' );
 Writeln( ' ':Tab15, '5.  Sour Cream          6.  ', Msg1 );
 D := quiry(6);
 Writeln;
 Writeln( ' ':Tab15, 'Fruits and Vegetables' );
 Writeln;
 Writeln( ' ':Tab15, '1.  Citrus              2.  Melon' );
 Writeln( ' ':Tab15, '3.  Juices              4.  Greens' );
 Writeln( ' ':Tab15, '5.  Yellows & Reds' );
 Writeln( ' ':Tab15, '6.  ', Msg1 );
 V := quiry(6);

  (******************************************
  *  Compute the index value by assigning   *
  *  a weight to each digit in the set.     *
  *******************************************)

       I := 252*F + 36*P + 6*D + V -295
end;


Procedure InputRecipe;

LABEL 2399; (*---EXIT---*)

VAR
 state : (absent, done, adding) ;
 ix, jx : integer;
 temp   : string14;
 Line   : string;

       Procedure Correct;
       begin
       REPEAT
         BREAK;
         Write(bell);
         Writeln(' ':(TTY DIV 2) -10, 'HERE IS YOUR RECIPE');
         Writeln;
         ShowRecipe;
         Writeln;
         Writeln('Are there any corrections to be made ');
         QUIRY;
         If yflag then
           begin
           BREAK;
           Writeln('Enter <cr> return if correct or Reenter the line');
           Writeln;
           For ix:=1 to 5 do
             begin
             PRINT(a_RAY[ix]);
             SCAN(Line, str_len -1);
             If Line[1] <> ' ' then a_RAY[ix] := Line
             end
           end(* If *)
       Until yflag=false;
       end(*---of Correct---*);

       Procedure QUEST;
       begin
         Pause;
         BREAK;
         Write('Do you want to ADD recipies? ' );
         QUIRY;
         CLEAR;
       end;

begin(*---InputRecipe---*)
 QUEST;
 If yflag=false then (* EXIT *) goto 2399;
 adding_recipies := true ;
 state := adding ;
 (* OPEN file backup_ID for WRITE assign fb *)
       REWRITE(backup_ID, fb);

 (* OPEN file current_ID for READ assign fa *)
       RESET(current_ID, fa);

 If NOT EOF(fa) then
   begin(* COPY current to back_up *)
   ix := 0 ;
   While ix < Curr_Rcds do
     begin
     ix := ix +1;
     GET_RECORD((* fa, *) HASH);
     PUT_RECORD((* fb, *) HASH);
     end(* while *)
   end(* IF *);

(*---Input/Enter additional recipies until done---*)
(*---or curr_records > Max_Records allowed     ---*)

 REPEAT
 If Curr_Rcds > MaxRecords then
   state := done
 Else(* we can add more date *)
   begin
     Writeln( 'Identify Recipe with features. First ');
     InputFeatures(HASH);
     BREAK;
     Writeln( 'Now Enter 5 lines of the recipe');
     Writeln;
     For jx := 1 to 5 DO
       begin
       Write('>');
       SCAN( a_RAY[jx], str_len -1 );
       end;(* For *)
     Correct(* if required *);
     Curr_Rcds := Curr_Rcds +1;
     PUT_RECORD((* fb, *) HASH);
     QUEST;
     If yflag=false then state := done;
   end;(* else *)
 UNTIL state<>adding;

 (*---------------------------------------*
  *         ***   trick   ***             *
  *  close previous file ID assigned      *
  *  FCB fb and fix CP/M directory entry  *
  *---------------------------------------*)

       REWRITE('DUMMY.$$$', fb);

 (*    SWAP file ID`s                          *)
 (*    Back Up file is now the Current file    *)
 temp := backup_ID;
 backup_ID := current_ID;
 current_ID := temp;

 UPDATE_MASTER;(*--status file--*)

2399: (* EXIT *);
end(*--of InputRecipe--*);


(*--------------------------------------*)
(*            DUMP/FIND MODULE          *)
(*--------------------------------------*)

Procedure File_Scan ;
(*
GLOBAL
 MaxRecords = maximum allowed records
 Curr_Rcds = # of recipes in file
*)
VAR
 state : (absent, found, searching) ;
 ix, index : integer;

       Procedure DUMP;
       (*********************************
       *  OUTPUT all Recipes from file  *
       **********************************)
       begin
         REPEAT
           If ix > Curr_Rcds then
             state := absent
           Else
             begin
               ix := ix +1;
               GET_RECORD((* fa, *) HASH);
               Display_One(HASH);
               Pause
             end(* else *)
         UNTIL state<>searching;
       end(*--of DUMP--*);

       Procedure FIND;
       (*************************************
       *       Lookup recipes from file     *
       **************************************)
       VAR
         Index : integer;
       begin
         CLEAR;
         InputFeatures(Index);
         REPEAT
           If ix > Curr_Rcds then
             state := absent
           Else
             begin
               GET_RECORD((* fa, *) HASH);
               If HASH=Index then
                 state := found
               Else
                 ix := ix +1
             end(* else *);
         Until state<>searching;
         If state=found then
           begin
           CLEAR;
           Display_One(HASH);
           end;
       end(*--of Lookup--*);

begin(*---File_Scan---*)
 Pause;
 state := absent;
 If adding_recipies then
   (* read new stats *) OPEN_MASTER;
 (* OPEN file current_ID for READ assign fa *)
         RESET(current_ID, fa);

 If NOT EOF(fa) then
   begin
   state := searching ;
   ix := 1 ;
   If Curr_rcds=0 then
     state := absent
   Else
     begin
       CASE command of
         'O', 'o':     DUMP;
         'F', 'f':     FIND
       end(* case *)
     end(* else *)
   end(* IF *);
 If state=absent then
   begin
   BREAK;
   Writeln('That''s all the Recipes on File');
   end;
 Pause;
end(*---of File_Scan---*);

(*--------------------------------------*)
(*            INITIALIZATION            *)
(*--------------------------------------*)


Procedure INIT1;
(* byte count/record = (chars/line + overhead/line) times No. of lines *)
begin
 BELL          := CHR(7) ;
 TTY           :=  72 ;
 last          := str_len ;
 MaxRecords    := 50 ;(* 360 times 50 = 18000 bytes *)
 Curr_Rcds     :=  0 ;
 Last_Update   := 'YY/MM/DD      ';
 current_ID    := 'RCPDAT.XXX    ';
 backup_ID     := 'RCPDAT.YYY    ';
 adding_recipies := false
end;

Procedure INIT2;
begin
  (* OPEN file `RECIPE.MST` for READ assign stats *)
         RESET(master, stats);

 If EOF(stats) then(* not found *)
   (* OPEN file `RECIPE.MST` for WRITE assign stats *)
         UPDATE_MASTER
 Else begin(* READ in data record *)
   READ(stats, data );
   with data do begin
     MaxRecords := MR;
     Curr_Rcds  := CR;
     current_ID := F1;
     backup_ID  := F2;
     last_update := date
     end(* with *)
   end;
 SKIP(5);
 Writeln('Last update of Recipe data file was ', last_update);
 Writeln('File currently consists of ', Curr_Rcds:4, ' Recipies');
 Writeln;
 Write('Please enter todays date <YY/MM/DD>  ');
 READLN(last_update)
end;

(*----------------------------------------------*
*              MAIN PROGRAM                    *
*----------------------------------------------*)

BEGIN
 INIT1;
 CLEAR;
 Pstring( '**', (TTY DIV 2));
 Writeln;
 Writeln( ' ':22, 'The Recipe System');
 Writeln;
 Pstring( '**', (TTY DIV 2));
 INIT2;
 done := false;
 WHILE NOT(done) DO
   begin
   CLEAR;
   Pstring( '**', (TTY DIV 2));
   skip(3);
   Writeln( ' ':Tab15, 'Select One of the following:');
   Writeln;
   Writeln( ' ':Tab20, 'I(nput Recipes');
   Writeln( ' ':Tab20, 'O(utput all Recipes');
   Writeln( ' ':Tab20, 'F(ind a Recipe');
   Writeln( ' ':Tab20, 'S(top');
   switch := on;
   WHILE switch(* is on *
) do
     begin
     switch := off;
     Writeln;
     Write(' ':(Tab15), 'Enter choice   ' );
     READ( command );
       CASE command of
         'I', 'i':     InputRecipe;
         'O', 'o',
         'F', 'f':     File_Scan;
         'S', 's':     done := true;
        ELSE:          begin
                       Write(BELL);
                       switch := on
                       end
       end(* case *)
     end(* while switch is on *)
   end(* while not done *)
end(*---of Program Recipe---*).