with Unchecked_Deallocation;

package body Lists is


    procedure Free is
        new Unchecked_Deallocation (Element_Type, Element_Pointer);

    --------------------------------------------------------------------------

    procedure Attach (List1 : in out List; 
                      List2 : in List) is

        Endoflist1 : Element_Pointer;

        --| Attach List2 to List1.
        --| If List1 is null return List2
        --| If List1 equals List2 then raise CircularList
        --| Otherwise get the pointer to the last element of List1 and change
        --| its Next field to be List2.

    begin
        if List1.Head = null then
            List1 := List2;
        elsif List1 = List2 then
            raise Circularlist;
        else
            Endoflist1      := List1.Tail;
            Endoflist1.Next := List2.Head;
            List1.Tail      := List2.Tail;
        end if;
    end Attach;

    --------------------------------------------------------------------------

    procedure Attach (L : in out List; 
                      Element : in Itemtype) is

        Old_Tail, New_Element : Element_Pointer;

        --| Create a list containing Element and attach it to the end of L

    begin
        New_Element := new Element_Type'(Item => Element, Next => null);
        if L.Head = null then
          L.Head := New_Element;
          L.Tail := New_Element;
        else
          Old_Tail      := L.Tail;
          Old_Tail.Next := New_Element;
          L.Tail        := New_Element;
        end if;
      end Attach;

    --------------------------------------------------------------------------

    function Attach (Element1 : in Itemtype; 
                     Element2 : in Itemtype) return List is

        Newlist : List;

        --| Create a new list containing the information in Element1 and
        --| attach Element2 to that list.

    begin
        Newlist.Head := new Element_Type'(Item => Element1, Next => null);
        Newlist.Tail := NewList.Head;
        Attach (Newlist, Element2);
        return Newlist;
    end Attach;

    --------------------------------------------------------------------------

    procedure Attach (Element : in Itemtype; 
                      L : in out List) is

        --|  Create a new cell whose information is Element and whose Next
        --|  field is the list L.  This prepends Element to the List L.

        Old_Head, New_Head : Element_Pointer;

    begin
        if L.Head = null then
          L.Head := new Element_Type'(Item => Element, Next => null);
          L.Tail := L.Head;
        else
          Old_Head := L.Head;
          New_Head := new Element_Type'(Item => Element, Next => Old_Head);
          L.Head   := New_Head;
        end if;
    end Attach;

    --------------------------------------------------------------------------

    function Attach (List1 : in List; 
                     List2 : in List) return List is

        --| Attach List2 to List1.
        --| If List1 is null return List2
        --| If List1 equals List2 then raise CircularList
        --| Otherwise get the pointer to the last element of List1 and change
        --| its Next field to be List2.

        End_Of_List1 : Element_Pointer;
        New_List     : List;

    begin
        if List1.Head = null then
            return List2;
        elsif List1 = List2 then
            raise Circularlist;
        else
            End_Of_List1      := List1.Tail;
            End_Of_List1.Next := List2.Head;
            New_List.Head     := List1.Head;
            New_List.Tail     := List2.Tail;
            return New_List;
        end if;
    end Attach;

    -------------------------------------------------------------------------

    function Attach (L : in List; 
                     Element : in Itemtype) return List is

        New_Element : Element_Pointer;
        New_List    : List;
        End_Of_List : Element_Pointer;

        --| Create a list called New_List and attach it to the end of L.
        --| If L is null return New_List
        --| Otherwise get the last element in L and make its Next field
        --| New_List.

    begin
        New_Element := new Element_Type'(Item => Element, Next => null);
        if L.Head = null then
            New_List := (Head => New_Element, Tail => New_Element);
        else
            End_Of_List      := L.Tail;
            End_Of_List.Next := New_Element;
            New_List         := (Head => L.Head, Tail => New_Element);
        end if;
        return New_List;
    end Attach;

    --------------------------------------------------------------------------

    function Attach (Element : in Itemtype; 
                     L : in List) return List is

        New_Element : Element_Pointer;

    begin
        if L.Head = null then
          New_Element := new Element_Type'(Item => Element, Next => null);
          return (Head => New_Element, Tail => New_Element);
        else
          New_Element := new Element_Type'(Item => Element, Next => L.Head);
          return (Head => New_Element, Tail => L.Tail);
        end if;
    end Attach;

    --------------------------------------------------------------------------

    function Copy (L : in List) return List is

        --| If L is null return null
        --| Otherwise recursively copy the list by first copying the information
        --| at the head of the list and then making the Next field point to
        --| a copy of the tail of the list.

        Current_Element : Element_Pointer := L.Head;
        New_List        : List := (Head => null, Tail => null);

    begin
        while Current_Element /= null loop
            Attach (New_List, Current_Element.Item);
            Current_Element := Current_Element.Next;
        end loop;
        return New_List;
    end Copy;


    --------------------------------------------------------------------------

    --generic
    --  with function Copy (I : in Itemtype) return Itemtype;
    function Copydeep (L : in List) return List is

        --|  If L is null then return null.
        --|  Otherwise copy the first element of the list into the head of the
        --|  new list and copy the tail of the list recursively using CopyDeep.

        Current_Element : Element_Pointer := L.Head;
        New_List        : List := (Head => null, Tail => null);

    begin
        while Current_Element /= null loop
            Attach (New_List, Copy (Current_Element.Item));
            Current_Element := Current_Element.next;
        end loop;
        return New_List;
    end Copydeep;

    --------------------------------------------------------------------------

    function Create return List is

        --| Return the empty list.

    begin
        return (Head => null, Tail => null);
    end Create;

    --------------------------------------------------------------------------
    procedure Deletehead (L : in out List) is

        New_Head : Element_Pointer;

        --| Remove the element of the head of the list and return it to the heap.
        --| If L is null EmptyList.
        --| Otherwise save the Next field of the first element, remove the first
        --| element and then assign to L the Next field of the first element.

    begin
        if L.Head = null then
            raise Emptylist;
        else
            New_Head := L.Head.Next;
            Free (L.Head);
            L.Head := New_Head;
        end if;
    end Deletehead;

    --------------------------------------------------------------------------

    procedure Deleteitem (L : in out List; 
                          Element : in Itemtype) is


        --| Remove the first element in the list with the value Element.
        --| If the first element of the list is equal to element then
        --| remove it.  Otherwise, recurse on the tail of the list.

        Current_Element, Previous_Element : Element_Pointer;

    begin
        if L.Head = null then
            raise Itemnotpresent;
        elsif Equal (L.Head.Item, Element) then
            Deletehead (L);
        else
            Current_Element  := L.Head.Next;
            Previous_Element := L.Head;

            while Current_Element /= null and then 
                not Equal (Current_Element.Item, Element) loop

                Previous_Element := Current_Element;
                Current_Element  := Current_Element.Next;

            end loop;

            if Current_Element = null then
                raise Itemnotpresent;
            else

                if Current_Element = L.Tail then
                    L.Tail := Previous_Element;
                end if;

                Previous_Element.Next := Current_Element.Next;
                Free (Current_Element);

            end if;
        end if;
    end Deleteitem;

    --------------------------------------------------------------------------

    procedure Deleteitems (L : in out List; 
                           Element : in Itemtype) is

        Delete_List_Is_Empty  : Boolean := True;
        New_List_Is_Empty     : Boolean := True;
        Old_Tail              : Element_Pointer;
        Current_Element       : Element_Pointer := L.Head;
        New_List, Delete_List : List;

        procedure Append (Element : in out Element_Pointer;
                          To_List : in out List) is
	begin
            if To_List.Head = null then
                To_List.Head := Element;
                To_List.Tail := Element;
            else
                Old_Tail      := To_List.Tail;
                To_List.Tail  := Element;
                Old_Tail.Next := To_List.Tail;
	    end if;
	end Append;

    begin
        while Current_Element /= null loop
            if Equal (Current_Element.Item, Element) then
                Append (Element => Current_Element,
                        To_List => Delete_List);
                Delete_List_Is_Empty := False;
            else
                Append (Element => Current_Element,
                        To_List => New_List);
                New_List_Is_Empty := False;
            end if;

            Current_Element := Current_Element.Next;
        end loop;

        if Delete_List_Is_Empty then
            raise Itemnotpresent;
        else
            Delete_List.Tail.Next := null;
            Destroy (Delete_List);
        end if;

        if not New_List_Is_Empty then
            New_List.Tail.Next := null;
	end if;

        L := New_List;
    end Deleteitems;

    --------------------------------------------------------------------------

    procedure Destroy (L : in out List) is

        Current_Element   : Element_Pointer := L.Head;
        Element_To_Delete : Element_Pointer;

        --| Walk down the list removing all the elements and set the list to
        --| the empty list.

    begin
        if L.Head /= null then
            while Current_Element /= null loop
                Element_To_Delete := Current_Element;
                Current_Element   := Current_Element.Next;
                Free (Element_To_Delete);
            end loop;
            L := (Head => null, Tail => null);
        end if;
    end Destroy;

    --------------------------------------------------------------------------

    function Firstvalue (L : in List) return Itemtype is

        --| Return the first value in the list.

    begin
        if L.Head = null then
            raise Emptylist;
        else
            return L.Head.Item;
        end if;
    end Firstvalue;

    --------------------------------------------------------------------------

    function Isinlist (L : in List; 
                       Element : in Itemtype) return Boolean is

        Current_Element : Element_Pointer := L.Head;

        --| Check if Element is in L.  If it is return true otherwise return false.

    begin
        while Current_Element /= null and then
            not Equal (Current_Element.Item, Element) loop
                Current_Element := Current_Element.Next;
        end loop;
        return Current_Element /= null;
    end Isinlist;

    --------------------------------------------------------------------------

    function Isempty (L : in List) return Boolean is

        --| Is the list L empty.

    begin
        return L.Head = null;
    end Isempty;

    --------------------------------------------------------------------------

    function Lastvalue (L : in List) return Itemtype is


        --| Return the value of the last element of the list. Get the pointer
        --| to the last element of L and then return its information.

    begin
        if L.Head = null then
            raise Emptylist;
        else
            return L.Tail.Item;
        end if;
    end Lastvalue;

    --------------------------------------------------------------------------

    function Length (L : in List) return Integer is

        --| Recursively compute the length of L.  The length of a list is
        --| 0 if it is null or  1 + the length of the tail.

        Current_Element : Element_Pointer := L.Head;
        List_Length     : Natural := 0;

    begin
        while Current_Element /= null loop
            List_Length     := List_Length + 1;
            Current_Element := Current_Element.Next;
        end loop;

        return List_Length;
    end Length;

    --------------------------------------------------------------------------

    function Makelistiter (L : in List) return Listiter is

        --| Start an iteration operation on the list L.  Do a type conversion
        --| from List to ListIter.

    begin
        return Listiter (L);
    end Makelistiter;

    --------------------------------------------------------------------------

    function More (L : in Listiter) return Boolean is

        --| This is a test to see whether an iteration is complete.

    begin
        return L.Head /= null;
    end More;

    --------------------------------------------------------------------------

    procedure Next (Place : in out Listiter; 
                    Info : out Itemtype) is

        --| This procedure gets the information at the current place in the List
        --| and moves the ListIter to the next postion in the list.
        --| If we are at the end of a list then exception NoMore is raised.

        Next_Element : Element_Pointer := Place.Head;

    begin
        if Next_Element = null then
            raise Nomore;
        else
            Info       := Next_Element.Item;
            Place.Head := Next_Element.Next;
        end if;
    end Next;

    --------------------------------------------------------------------------

    procedure Replacehead (L : in out List; 
                           Info : in Itemtype) is

        --| This procedure replaces the information at the head of a list
        --| with the given information. If the list is empty the exception
        --| EmptyList is raised.

    begin
        if L.Head = null then
            raise Emptylist;
        else
            L.Head.Item := Info;
        end if;
    end Replacehead;

    --------------------------------------------------------------------------

    procedure Replacetail (L : in out List; 
                           Newtail : in List) is

        List_Head_Item : Itemtype;

        --| This destroys the tail of a list and replaces the tail with
        --| NewTail.  If L is empty EmptyList is raised.

    begin
        if L.Head = null then
            raise Emptylist;
        else
            List_Head_Item := L.Head.Item;
            Destroy (L);
            L := Attach (List_Head_Item, Newtail);
        end if;
    end Replacetail;

    --------------------------------------------------------------------------

    function Tail (L : in List) return List is

        --| This returns the list which is the tail of L.  If L is null Empty
        --| List is raised.

    begin
        if L.Head = null then
            raise Emptylist;
        else
            return (Head => L.Head.Next, Tail => L.Tail);
        end if;
    end Tail;

    --------------------------------------------------------------------------
    function Equal (List1 : in List; 
                    List2 : in List) return Boolean is

        Placeinlist1 : Element_Pointer := List1.Head;
        Placeinlist2 : Element_Pointer := List2.Head;

        --| This function tests to see if two lists are equal.  Two lists
        --| are equal if for all the elements of List1 the corresponding
        --| element of List2 has the same value.  Thus if the 1st elements
        --| are equal and the second elements are equal and so up to n.
        --|  Thus a necessary condition for two lists to be equal is that
        --| they have the same number of elements.

        --| This function walks over the two list and checks that the
        --| corresponding elements are equal.  As soon as we reach
        --| the end of a list (PlaceInList = null) we fall out of the loop.
        --| If both PlaceInList1 and PlaceInList2 are null after exiting the loop
        --| then the lists are equal.  If they both are not null the lists aren't
        --| equal.  Note that equality on elements is based on a user supplied
        --| function Equal which is used to test for item equality.

    begin
        while (Placeinlist1 /= null) and then (Placeinlist2 /= null) loop
            if not Equal (Placeinlist1.Item, Placeinlist2.Item) then
                return False;
            end if;
            Placeinlist1 := Placeinlist1.Next;
            Placeinlist2 := Placeinlist2.Next;
        end loop;
        return ((Placeinlist1 = null) and then (Placeinlist2 = null));
    end Equal;


end Lists;
