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---*).
