{--------------------------------------------------------------}
{                          ListMan                             }
{                                                              }
{    Mailing list manager demo using dynamic (heap) storage    }
{                                                              }
{                             by Jeff Duntemann                }
{                             Turbo Pascal V3.0                }
{                             Last update 2/1/86               }
{                                                              }
{    From the book, COMPLETE TURBO PASCAL, by Jeff Duntemann   }
{    Scott, Foresman & Co. (c) 1986,1987  ISBN 0-673-18600-8   }
{--------------------------------------------------------------}

PROGRAM ListMan;

TYPE
  String30 = String[30];       { Using derived string types }
  String6  = String[6];        { makes type NAPRec smaller }
  String3  = String[3];

  NAPPtr = ^NAPRec;
  NAPRec = RECORD
             Name    : String30;
             Address : String30;
             City    : String30;
             State   : String3;
             Zip     : String6;
             Next    : NAPPtr      { Points to next NAPRec }
           END;                    { in a linked list }

  NAPFile = FILE OF NAPRec;


VAR
  Ch       : Char;
  Root     : NAPPtr;
  Quit     : Boolean;



{$I YES.SRC }      { Contains Yes }


PROCEDURE ClearLines(First,Last : Integer);

VAR
  I : Integer;

BEGIN
  FOR I := First TO Last DO
    BEGIN
      GotoXY(1,I);
      ClrEOL
    END
END;



PROCEDURE ShowRecord(WorkRec : NAPRec);

VAR
  I : Integer;

BEGIN
  ClearLines(17,22);  { Clear away anything in that spot before }
  GotoXY(1,17);
  WITH WorkRec DO
    BEGIN
      Writeln('>>Name:     ',Name);
      Writeln('>>Address:  ',Address);
      Writeln('>>City:     ',City);
      Writeln('>>State:    ',State);
      Writeln('>>Zip:      ',Zip)
    END
END;


PROCEDURE CheckSpace;

VAR
  Space      : Integer;
  RealRoom   : Real;
  RecordRoom : Real;

BEGIN
  Space := MemAvail;    { MemAvail returns negative Integer for   }
                        { space larger than 32,767.  Convert }
                        { (to a real) by adding 65536 if negative }
  IF Space < 0 THEN RealRoom := 65536.0 + Space ELSE RealRoom := Space;

  RealRoom := RealRoom * 16;   { Delete this line for Z80 versions! }
                               { MemAvail for 8086 returns 16-byte  }
                               { paragraphs, not bytes!! }

  RecordRoom := RealRoom / SizeOf(NAPRec);
  ClearLines(2,3);
  Writeln('>>There is now room for ',RecordRoom:6:0,' records in your list.');
END;


PROCEDURE ListDispose(VAR Root : NAPPtr);

VAR
  Holder : NAPPtr;

BEGIN
  GotoXY(27,10); Write('>>Are you SURE? (Y/N): ');
  IF YES THEN
    IF Root <> Nil THEN
      REPEAT
        Holder := Root^.Next;    { First grab the next record...       }
        Dispose(Root);           { ...then dispose of the first one... }
        Root := Holder           { ...then make the next one the first }
      UNTIL Root = Nil;
  ClearLines(10,10);
  CheckSpace
END;


PROCEDURE AddRecords(VAR Root : NAPPtr);

VAR
  I       : Integer;
  Abandon : Boolean;
  WorkRec : NAPRec;
  Last    : NAPPtr;
  Current : NAPPtr;

BEGIN
  GotoXY(27,7); Write('<<Adding Records>>');
  REPEAT               { Until user answers 'N' to "MORE?" question... }
    ClearLines(24,24);
    FillChar(WorkRec,SizeOf(WorkRec),CHR(0));  { Zero the record }
    ClearLines(9,15);
    GotoXY(1,9);
    WITH WorkRec DO          { Fill the record with good data }
      BEGIN
        Write('>>Name:     '); Readln(Name);
        Write('>>Address:  '); Readln(Address);
        Write('>>City:     '); Readln(City);
        Write('>>State:    '); Readln(State);
        Write('>>Zip:      '); Readln(Zip)
      END;
    Abandon := False;
                        { Here we traverse list to spot duplicates: }

    IF Root = Nil THEN      { If list is empty point Root to record }
      BEGIN
        New(Root);
        WorkRec.Next := Nil;  { Make sure list is terminated by Nil }
        Root^ := WorkRec;
      END
    ELSE                      { ...if there's something in list already   }
      BEGIN
        Current := Root;      { Start traverse at Root of list }
        REPEAT
          IF Current^.Name = WorkRec.Name THEN { If duplicate found }
            BEGIN
              ShowRecord(Current^);
              GotoXY(1,15);
              Write
('>>The record below duplicates the above entry''s Name.  Toss entry? (Y/N): ');
              IF Yes THEN Abandon := True ELSE Abandon := False;
              ClearLines(15,22)
            END;
          Last := Current;
          Current := Current^.Next
        UNTIL (Current = Nil) OR Abandon OR (Current^.Name > WorkRec.Name);

        IF NOT Abandon THEN            { Add WorkRec to the linked list  }
          IF Root^.Name > WorkRec.Name THEN  { New Root item!     }
            BEGIN
              New(Root);               { Create a new dynamic NAPRec  }
              WorkRec.Next := Last;    { Point new record at old Root }
              Root^ := WorkRec         { Point new Root at WorkRec    }
            END
          ELSE
            BEGIN
              NEW(Last^.Next);         { Create a new dynamic NAPRec, }
              WorkRec.Next := Current; { Points its Next to Current  }
              Last^.Next^ := WorkRec;  { and assign WorkRec to it    }
              CheckSpace               { Display remaining heapspace }
            END;
      END;
    GotoXY(1,24); Write('>>Add another record to the list? (Y/N): ');
  UNTIL NOT Yes;
END;


PROCEDURE LoadList(VAR Root : NAPPtr);

VAR
  WorkName : String30;
  WorkFile : NAPFile;
  Current  : NAPPtr;
  I        : Integer;
  OK       : Boolean;

BEGIN
  Quit := False;
  REPEAT
    ClearLines(10,10);
    Write('>>Enter the Name of the file you wish to load: ');
    Readln(WorkName);
    IF Length(WorkName) = 0 THEN   { Hit (CR) only to abort LOAD }
      BEGIN
        ClearLines(10,12);
        Quit := True
      END
    ELSE
      BEGIN
        Assign(WorkFile,WorkName);
        {$I-} Reset(WorkFile); {$I+}
        IF IOResult <> 0 THEN          { 0 = OK; 255 = File Not Found }
          BEGIN
            GotoXY(1,12);
            Write('>>That file does not exist.  Please enter another.');
            OK := False
          END
        ELSE OK := True                { OK means File Is open }
      END
    UNTIL OK OR Quit;
  IF NOT Quit THEN
    BEGIN
      ClearLines(10,12);
      Current := Root;
      IF Root = Nil THEN               { If list is currently empty }
        BEGIN
          NEW(Root);                   { Load first record to Root^ }
          Read(WorkFile,Root^);
          Current := Root
        END                            { If list is not empty, find the end: }
      ELSE WHILE Current^.Next <> Nil DO Current := Current^.Next;
      IF Root^.Next <> Nil THEN { If file contains more than 1 record }
      REPEAT
        NEW(Current^.Next);          { Read and add records to list }
        Current := Current^.Next;    { until a record's Next field  }
        Read(WorkFile,Current^)      { comes up Nil   }
      UNTIL Current^.Next = Nil;
      CheckSpace;
      Close(WorkFile)
    END
END;


PROCEDURE ViewList(Root : NAPPtr);

VAR
  I        : Integer;
  WorkFile : NAPFile;
  Current  : NAPPtr;

BEGIN
  IF Root = Nil THEN                 { Nothing is now in the list }
    BEGIN
      GotoXY(27,18);
      Writeln('<<Your list is empty!>>');
      GotoXY(26,20);
      Write('>>Press (CR) to continue: ');
      Readln
    END
  ELSE
    BEGIN
      GotoXY(31,7); Write('<<Viewing Records>>');
      Current := Root;
      WHILE Current <> Nil DO   { Traverse and display until Nil found }
        BEGIN
          ShowRecord(Current^);
          GotoXY(1,23);
          Write('>>Press (CR) to view Next record in the list: ');
          Readln;
          Current := Current^.Next
        END;
      ClearLines(19,22)
    END
END;


PROCEDURE SaveList(Root : NAPPtr);

VAR
  WorkName : String30;
  WorkFile : NAPFile;
  Current  : NAPPtr;
  I        : Integer;

BEGIN
  GotoXY(1,10);
  Write('>>Enter the filename for saving out your list: ');
  Readln(WorkName);
  Assign(WorkFile,WorkName);   { Open the file for write access }
  Rewrite(WorkFile);
  Current := Root;
  WHILE Current <> Nil DO      { Traverse and write }
    BEGIN
      Write(WorkFile,Current^);
      Current := Current^.Next
    END;
  Close(WorkFile)
END;



BEGIN       { MAIN }
  ClrScr;
  GotoXY(28,1); Write('<<Linked List Maker>>');
  CheckSpace;
  GotoXY(17,8);  Write('--------------------------------------------');
  Root := Nil; Quit := False;
  REPEAT
    ClearLines(5,7);
    ClearLines(9,24);
    GotoXY(1,5);
    Write
    ('>>[L]oad, [A]dd record, [V]iew, [S]ave, [C]lear list, or [Q]uit: ');
    Readln(Ch);                    { Get a command }
    CASE Ch OF
     'A','a' : AddRecords(Root);  { Parse the command & perform it }
     'C','c' : ListDispose(Root);
     'L','l' : LoadList(Root);
     'S','s' : SaveList(Root);
     'V','v' : ViewList(Root);
     'Q','q' : Quit := True;
    END; { CASE }
  UNTIL Quit
END.