WITH Unchecked_Deallocation;
PACKAGE BODY Lists_Generic IS

  PROCEDURE Dispose IS 
    NEW Unchecked_Deallocation(Object => Node, Name => Position);
    
  FUNCTION Allocate (X: ElementType; P: Position) RETURN Position IS
    Result: Position;
  BEGIN
    Result := NEW Node'(Info => X, Link => P);
    RETURN Result;
  EXCEPTION
    WHEN Storage_Error =>
      RAISE OutOfSpace;
  END Allocate;
  
  PROCEDURE Deallocate (P: IN OUT Position) IS
  BEGIN
    Dispose (X => P);
  END Deallocate;

  PROCEDURE Initialize(L: IN OUT List) IS
    Previous: Position;
    Current : Position;
  BEGIN
    IF L.Head /= NULL THEN
      Current := L.Head;
      WHILE Current /= NULL LOOP
        Previous := Current;
        Current := Current.Link;
        Deallocate(Previous);
      END LOOP;
      L := (Head => NULL, Tail => NULL);
    END IF;
  END Initialize;

  PROCEDURE AddToFront(L: IN OUT List; X: ElementType) IS
  BEGIN
    L.Head := Allocate(X, L.Head);
    IF L.Tail = NULL THEN
      L.Tail := L.Head;
    END IF;
  END AddToFront;

  PROCEDURE AddToRear (L: IN OUT List; X: ElementType) IS
    P: Position;
  BEGIN
    P := Allocate(X, NULL);
    IF L.Head = NULL THEN
      L.Head := P;
      L.Tail := P;
    ELSE
      L.Tail.Link := P;
    END IF;
    L.Tail := P;
  END AddToRear;

  FUNCTION IsEmpty (L: List) RETURN Boolean IS
  BEGIN
    RETURN L.Head = NULL;
  END IsEmpty;
  
  FUNCTION IsFirst (L: List; P: Position) RETURN Boolean IS
  BEGIN
    RETURN (L.Head /= NULL) AND (P = L.Head);
  END IsFirst;

  FUNCTION IsLast (L: List; P: Position) RETURN Boolean IS
  BEGIN
    RETURN (L.Tail /= NULL) AND (P = L.Tail);
  END IsLast;
   
  FUNCTION IsPastEnd (L: List; P: Position) RETURN Boolean IS
  BEGIN
    RETURN P = NULL;
  END IsPastEnd;

  FUNCTION IsPastBegin (L: List; P: Position) RETURN Boolean IS
  BEGIN
    RETURN P = NULL;
  END IsPastBegin;

  FUNCTION First (L: List) RETURN Position IS
  BEGIN
    RETURN L.Head;
  END First;
  
  FUNCTION  Last (L: List) RETURN Position IS
  BEGIN
    RETURN L.Tail;
  END Last;
 
  FUNCTION Retrieve (L: IN List; P: IN Position) RETURN ElementType IS
  BEGIN
    IF IsEmpty(L) THEN
      RAISE EmptyList;
    ELSIF IsPastBegin(L, P) THEN
      RAISE PastBegin;
    ELSIF IsPastEnd(L, P) THEN
      RAISE PastEnd;
    ELSE
      RETURN P.Info;
    END IF;
  END Retrieve;
 
  PROCEDURE GoAhead (L: List; P: IN OUT Position) IS
  BEGIN
    IF IsEmpty(L) THEN
      RAISE EmptyList;
    ELSIF IsPastEnd(L, P) THEN
      RAISE PastEnd;
    ELSE
      P := P.Link;
    END IF;
  END GoAhead;
 
  PROCEDURE GoBack (L: List; P: IN OUT Position) IS
    Current: Position;
  BEGIN
    IF IsEmpty(L) THEN
      RAISE EmptyList;
    ELSIF IsPastBegin(L, P) THEN
      RAISE PastBegin;
    ELSIF IsFirst(L, P) THEN
      P := NULL;
    ELSE                    -- see whether P is in the list
      Current := L.Head;
      WHILE (Current /= NULL) AND THEN (Current.Link /= P) LOOP
        Current := Current.Link;
      END LOOP;

      IF Current = NULL THEN -- P was not in the list
        RAISE PastEnd;
      ELSE
        P := Current;        -- return predecessor pointer
      END IF;
    END IF;
  END GoBack;
 
  PROCEDURE Delete    (L: IN OUT List; P: Position) IS
    Previous: Position;
    Current : Position;
  BEGIN
    Current := P;
    IF IsEmpty(L) THEN
      RAISE EmptyList;
    ELSIF IsPastBegin(L, Current) THEN
      RAISE PastBegin;
    ELSIF IsFirst(L, Current) THEN  -- must adjust list header
      L.Head := Current.Link;
      IF L.Head = NULL THEN         -- deleted the only node
        L.Tail := NULL;
      END IF;
    ELSE                            -- "normal" situation
      Previous := Current;
      GoBack(L, Previous);
      Previous.Link := Current.Link;
      IF IsLast(L, Current) THEN     -- deleted the last node
        L.Tail := Previous;
      END IF;
    END IF;
    Deallocate(Current);
  END Delete;

  PROCEDURE Insert    (L: IN OUT List; X: ElementType; P: Position) IS
  BEGIN
    IF P = NULL THEN
      AddToRear(L, X);
    ELSE
      P.Link := Allocate(X, P.Link);
      IF (P = L.Tail) THEN
         L.Tail := P.Link;
      END IF;
    END IF;
  END Insert;

  PROCEDURE Replace   (L: IN OUT List; X: ElementType; P: Position) IS
  BEGIN
    IF P = NULL THEN
      RAISE PastEnd;
    ELSE
      P.Info := X;
    END IF;
  END Replace;
   
  PROCEDURE Copy (To: IN OUT List; From: IN List) IS
    Current: Position;
  BEGIN
    Initialize(To);
    Current := First(From);
    WHILE NOT IsPastEnd(From, Current) LOOP
      AddToRear(To, Retrieve(From, Current));
      GoAhead(From, Current);
    END LOOP;
  END Copy;   
  
END Lists_Generic;