------------------------------------------------------------------------------
--                                                                          --
--                      CHARLES CONTAINER LIBRARY                           --
--                                                                          --
--              Copyright (C) 2001-2003 Matthew J Heaney                    --
--                                                                          --
-- The Charles Container Library ("Charles") is free software; you can      --
-- redistribute it and/or modify it under terms of the GNU General Public   --
-- License as published by the Free Software Foundation; either version 2,  --
-- or (at your option) any later version.  Charles is distributed in the    --
-- hope that it will be useful, but WITHOUT ANY WARRANTY; without even the  --
-- implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. --
-- See the GNU General Public License for more details.  You should have    --
-- received a copy of the GNU General Public License distributed with       --
-- Charles;  see file COPYING.TXT.  If not, write to the Free Software      --
-- Foundation,  59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.    --
--                                                                          --
-- As a special exception, if other files instantiate generics from this    --
-- unit, or you link this unit with other files to produce an executable,   --
-- this unit does not by itself cause the resulting executable to be        --
-- covered by the GNU General Public License.  This exception does not      --
-- however invalidate any other reasons why the executable file might be    --
-- covered by the GNU Public License.                                       --
--                                                                          --
-- Charles is maintained by Matthew J Heaney.                               --
--                                                                          --
-- http://home.earthlink.net/~matthewjheaney/index.html                     --
-- mailto:matthewjheaney@earthlink.net                                      --
--                                                                          --
------------------------------------------------------------------------------

with Ada.Unchecked_Deallocation;
with System; use type System.Address;
with Charles.Algorithms.Generic_Lexicographical_Compare;

package body Charles.Deques.Unbounded is


   procedure Free is
      new Ada.Unchecked_Deallocation (Node_Type,
                                      Node_Access);

   procedure Free is
      new Ada.Unchecked_Deallocation (Node_Access_Array,
                                      Node_Access_Array_Access);



   procedure Adjust (Container : in out Container_Type) is

      Nodes  : constant Node_Access_Array_Access := Container.Nodes;
      Last   : constant Natural := Container.Last;
      Length : constant Natural := Container.Length;

   begin

      if Nodes = null then
         return;
      end if;

      if Nodes'Length = 0
        or else Container.First > Last
      then
         Container.Nodes := null;
         return;
      end if;

      --TODO: make sure we bias handling is ok

      Container.Last := 0;
      Container.Length := 0;

      Container.Nodes := new Node_Access_Array'(Nodes.all);

      Container.Nodes (Container.First) :=
        new Node_Type'(Nodes (Container.First).all);

      if Container.First = Last then

         Container.Last := Last;
         Container.Length := Length;

         return;

      end if;

      Container.Last := Container.First;
      Container.Length := Elements_Per_Node - Container.Bias;

      for I in Container.First + 1 .. Last - 1 loop

         Container.Nodes (I) := new Node_Type'(Nodes (I).all);

         Container.Last := I;
         Container.Length := Container.Length + Elements_Per_Node;

      end loop;

      Container.Nodes (Last) := new Node_Type'(Nodes (Last).all);

      Container.Last := Last;
      Container.Length := Length;

   end Adjust;


   procedure Finalize (Container : in out Container_Type) is
   begin
      Clear (Container);
      Free (Container.Nodes);
   end;


   function Empty_Deque return Container_Type is
   begin
      return (Controlled with Nodes  => null,
                              First  => 1,
                              Last   => 0,
                              Length => 0,
                              Bias   => 0);
   end Empty_Deque;


   procedure Get_Element_Node
     (Container     : in     Container_Type;
      Index         : in     Natural;
      Node_Index    :    out Positive;
      Element_Index :    out Element_Index_Subtype) is

      subtype Index_Subtype is
        Natural range 0 .. Container.Length - 1;

      I : constant Integer'Base := Container.Bias + Index_Subtype'(Index);

      NI : constant Integer'Base := Container.First + I / Elements_Per_Node;

      subtype Node_Index_Subtype is
        Positive range Container.First .. Container.Last;
   begin
      Node_Index := Node_Index_Subtype'(NI);
      Element_Index := I rem Elements_Per_Node;
   end;


   function "=" (Left, Right : Container_Type) return Boolean is

      L_NI : Positive;
      L_EI : Element_Index_Subtype;

      R_NI : Positive;
      R_EI : Element_Index_Subtype;

   begin

      if Left'Address = Right'Address then
         return True;
      end if;

      if Left.Length /= Right.Length then
         return False;
      end if;

      for I in Natural range 0 .. Left.Length - 1 loop

         Get_Element_Node (Left, I, L_NI, L_EI);
         Get_Element_Node (Right, I, R_NI, R_EI);

         if Left.Nodes (L_NI) (L_EI) /= Right.Nodes (R_NI) (R_EI) then
            return False;
         end if;

      end loop;

      return True;

   end "=";


   function Generic_Less (Left, Right : Container_Type) return Boolean is

      function Is_Less (LI, RI : Natural) return Boolean is

         L_NI : Positive;
         L_EI : Element_Index_Subtype;

         R_NI : Positive;
         R_EI : Element_Index_Subtype;

      begin

         Get_Element_Node (Left, LI, L_NI, L_EI);
         Get_Element_Node (Right, RI, R_NI, R_EI);

         declare
            LE : Element_Type renames Left.Nodes (L_NI) (L_EI);
            RE : Element_Type renames Right.Nodes (R_NI) (R_EI);
         begin
            return LE < RE;
         end;

      end Is_Less;

      function Lexicographical_Compare is
         new Algorithms.Generic_Lexicographical_Compare
              (Natural, Integer'Succ);

   begin -- Generic_Less

      if Left'Address = Right'Address then
         return False;
      end if;

      return Lexicographical_Compare
               (Left_First  => 0,
                Left_Back   => Left.Length,
                Right_First => 0,
                Right_Back  => Right.Length);

   end Generic_Less;


   function Length (Container : Container_Type) return Natural is
   begin
      return Container.Length;
   end;


   function Is_Empty (Container : Container_Type) return Boolean is
   begin
      return Container.Length = 0;
   end;


   procedure Clear (Container : in out Container_Type) is

      F : Integer renames Container.First;
      N : Integer renames Container.Length;
      B : Integer renames Container.Bias;

      X : Node_Access;

   begin

      if F > Container.Last then
         pragma Assert (N = 0);
         return;
      end if;

      if F < Container.Last then

         X := Container.Nodes (F);
         Container.Nodes (F) := null;

         F := F + 1;
         N := N - Positive'(Elements_Per_Node - B);
         B := 0;

         Free (X);

      end if;

      while F < Container.Last loop

         X := Container.Nodes (F);
         Container.Nodes (F) := null;

         F := F + 1;
         N := N - Elements_Per_Node;

         Free (X);

      end loop;

      X := Container.Nodes (F);
      Container.Nodes (F) := null;

      F := F + 1;
      N := 0;
      B := 0;

      Free (X);

   end Clear;


   procedure Swap (Left, Right : in out Container_Type) is

      L_Nodes : constant Node_Access_Array_Access := Left.Nodes;
      L_First : constant Positive := Left.First;
      L_Last : constant Natural := Left.Last;
      L_Length : constant Natural := Left.Length;
      L_Bias   : constant Natural := Left.Bias;

   begin

      Left.Nodes := Right.Nodes;
      Left.First := Right.First;
      Left.Last := Right.Last;
      Left.Length := Right.Length;
      Left.Bias := Right.Bias;

      Right.Nodes := L_Nodes;
      Right.First := L_First;
      Right.Last := L_Last;
      Right.Length := L_Length;
      Right.Bias := L_Bias;

   end Swap;


--     function To_Container
--       (Length : Natural) return Container_Type is

--        Map : Map_Type;

--        Node_Count : constant Natural :=
--           (Length + Elements_Per_Node - 1) / Elements_Per_Node;

--     begin

--        Map.Length := Length;

--        if Node_Count /= 0 then

--           Map.Nodes := new Node_Access_Array (1 .. Node_Count);

--           for I in Map.Nodes'Range loop
--              Map.Nodes (I) := new Node_Type;
--           end loop;

--           Map.First := Map.Nodes'First;

--           Map.Last := Map.Nodes'Last;

--           Map.Bias := 0;

--        end if;

--        return Container_Type'(Controlled with Map);

--     end To_Container;


--     function To_Container
--       (Length : Natural;
--        Item   : Element_Type) return Container_Type is

--        Map : Map_Type;

--        Node_Count : constant Natural :=
--           (Length + Elements_Per_Node - 1) / Elements_Per_Node;

--     begin

--        Map.Length := Length;

--        if Node_Count /= 0 then

--           Map.Nodes := new Node_Access_Array (1 .. Node_Count);

--           for I in Map.Nodes'Range loop

--              Map.Nodes (I) :=
--                 new Node_Type'(Elements => (others => Item));

--           end loop;

--           Map.First := Map.Nodes'First;

--           Map.Last := Map.Nodes'Last;

--           Map.Bias := 0;

--        end if;

--        return Container_Type'(Controlled with Map);

--     end To_Container;


--     procedure Assign
--       (Target : in out Container_Type;
--        Length : in     Natural) is
--     begin
--        Assign (Target.Map, Length);
--     end;


--     procedure Assign
--       (Target : in out Container_Type;
--        Length : in     Natural;
--        Item   : in     Element_Type) is

--        procedure Process (Element : in out Element_Type) is
--        begin
--           Element := Item;
--        end;

--        procedure Iterate is
--           new Generic_Modify_Elements;
--     begin
--        Assign (Target.Map, Length);
--        Iterate (Target, First (Target), Back (Target));
--     end;


--     procedure Assign
--       (Target : in out Container_Type;
--        Source : in     Container_Type) is

--        Index : Natural := 0;

--        procedure Process (Element : Element_Type) is
--        begin
--           Replace_Element (Target.Map, Index, Element);
--           Index := Index + 1;
--        end;

--        procedure Iterate is
--           new Generic_Select_Elements;
--     begin
--        Assign (Target, Length (Source));
--        Iterate (Source, First (Source), Back (Source));
--     end;



   procedure Prepend
     (Container     : in out Container_Type;
      Element_Index :    out Element_Index_Subtype) is

   begin

      if Container.Length = 0 then

         if Container.Last < Container.First then

            if Container.Nodes = null then
               Container.Nodes := new Node_Access_Array (1 .. 1); --?
            end if;

            declare
               I : constant Positive := 1 + Container.Nodes'Length / 2; --?
            begin
               Container.Nodes (I) := new Node_Type;
               Container.First := I;
            end;

         else

            pragma Assert (Container.First = Container.Last);
            pragma Assert (Container.Nodes (Container.First) /= null);

            declare
               I : constant Positive := 1 + Container.Nodes'Length / 2; --?
            begin
               Container.Nodes (I) := Container.Nodes (Container.First);
               Container.First := I;
            end;

         end if;

         Container.Last := Container.First;
         Container.Length := 1;

         --assume this prepend will be followed by more prepends:
         Container.Bias := Elements_Per_Node - 1;
         Element_Index := Container.Bias;

         return;

      end if;

      if Container.Bias > 0 then

         Container.Length := Container.Length + 1;

         Container.Bias := Container.Bias - 1;
         Element_Index := Container.Bias;

         return;

      end if;

      if Container.First > 1 then

         declare
            Length : constant Positive := Container.Length + 1;
            I : constant Positive := Container.First - 1;
         begin
            Container.Nodes (I) := new Node_Type;
            Container.First := I;
            Container.Length := Length;
         end;

         Container.Bias := Elements_Per_Node - 1;
         Element_Index := Container.Bias;

         return;

      end if;

      if Container.Last < Container.Nodes'Last then

         declare
            Length : constant Positive := Container.Length + 1;
            Node   : constant Node_Access := new Node_Type;

            N : constant Positive := Container.Nodes'Last - Container.Last;
            I : constant Positive := (N + 1) / 2;  --?
            J : constant Positive := I + Container.Last;
         begin
            Container.Nodes (I + 1 .. J) :=
              Container.Nodes (1 .. Container.Last);

            Container.Nodes (I) := Node;

            Container.First := I;
            Container.Last := J;
            Container.Length := Length;
         end;

         Container.Bias := Elements_Per_Node - 1;
         Element_Index := Container.Bias;

         return;

      end if;

      declare
         Length : constant Positive := Container.Length + 1;

         Nodes_Last : constant Positive := 2 * Container.Nodes'Length;

         Nodes : constant Node_Access_Array_Access :=
            new Node_Access_Array (1 .. Nodes_Last);

         N : constant Positive := Container.Nodes'Length;
         I : constant Positive := (N + 1) / 2;  --?
      begin
         Container.First := 1 + I;
         Container.Last := Container.Last + I;

         Nodes (Container.First .. Container.Last) :=
           Container.Nodes.all;

         Free (Container.Nodes);
         Container.Nodes := Nodes;

         Container.Nodes (I) := new Node_Type;
         Container.First := I;

         Container.Length := Length;
      end;

      Container.Bias := Elements_Per_Node - 1;
      Element_Index := Container.Bias;

   end Prepend;


   procedure Prepend (Container : in out Container_Type) is
      Index : Element_Index_Subtype;
   begin
      Prepend (Container, Index);
   end;


   procedure Prepend
     (Container : in out Container_Type;
      New_Item  : in     Element_Type) is

      Index : Element_Index_Subtype;
   begin
      Prepend (Container, Index);
      Container.Nodes (Container.First) (Index) := New_Item;
   end;


   procedure Append
     (Container     : in out Container_Type;
      Element_Index :    out Element_Index_Subtype) is

   begin

      if Container.Length = 0 then

         if Container.Last < Container.First then

            if Container.Nodes = null then
               Container.Nodes := new Node_Access_Array (1 .. 1); --?
            end if;

            declare
               I : constant Positive := 1 + Container.Nodes'Length / 2; --?
            begin
               Container.Nodes (I) := new Node_Type;
               Container.First := I;
            end;

         else

            pragma Assert (Container.First = Container.Last);
            pragma Assert (Container.Nodes (Container.First) /= null);

            declare
               I : constant Positive := 1 + Container.Nodes'Length / 2; --?
            begin
               Container.Nodes (I) := Container.Nodes (Container.First);
               Container.First := I;
            end;

         end if;

         Container.Last := Container.First;
         Container.Length := 1;

         --assume this append will be followed by more appends:
         Container.Bias := 0;
         Element_Index := 0;

         return;

      end if;

      declare
         Length : constant Positive := Container.Length + 1;

         --count only the nodes that precede the last node
         Node_Count : constant Natural :=
           Container.Last - Container.First;

         Elements_Count : constant Natural :=
           Node_Count * Elements_Per_Node;

         Biased_Length : constant Positive :=
           Container.Bias + Container.Length;

         Count : constant Positive :=
           Biased_Length - Elements_Count;
      begin

         if Count < Elements_Per_Node then

            Element_Index := Count;

         elsif Container.Last < Container.Nodes'Last then

            declare
               I : constant Positive := Container.Last + 1;
            begin
               Container.Nodes (I) := new Node_Type;
               Container.Last := I;
            end;

            Element_Index := 0;

         elsif Container.First > 1 then

            declare
               Node : constant Node_Access := new Node_Type;

               N : constant Positive := Container.First - 1;
               M : constant Positive := (N + 1) / 2; --?

               I : constant Positive := Container.First - M;
               J : constant Positive := Container.Last - M;
            begin
               Container.Nodes (I .. J) :=
                 Container.Nodes (Container.First .. Container.Last);

               Container.First := I;
               Container.Last := J + 1;

               Container.Nodes (Container.Last) := Node;
            end;

            Element_Index := 0;

         else

            declare
               Nodes_Last : constant Positive :=
                 2 * Container.Nodes'Length;

               Nodes : constant Node_Access_Array_Access :=
                  new Node_Access_Array (1 .. Nodes_Last);

               N : constant Positive := Container.Nodes'Length;
               M : constant Natural := N / 2; --?
            begin
               Container.First := 1 + M;
               Container.Last := Container.Last + M;

               Nodes (Container.First .. Container.Last) :=
                 Container.Nodes.all;

               Free (Container.Nodes);
               Container.Nodes := Nodes;

               declare
                  I : constant Positive := Container.Last + 1;
               begin
                  Container.Nodes (I) := new Node_Type;
                  Container.Last := I;
               end;
            end;

            Element_Index := 0;

         end if;

         Container.Length := Length;

      end;

   end Append;


   procedure Append (Container : in out Container_Type) is
      Index : Element_Index_Subtype;
   begin
      Append (Container, Index);
   end;


   procedure Append
     (Container : in out Container_Type;
      New_Item  : in     Element_Type) is

      Index : Element_Index_Subtype;
   begin
      Append (Container, Index);
      Container.Nodes (Container.Last) (Index) := New_Item;
   end;


   procedure Insert
     (Container : in out Container_Type;
      Before    : in     Index_Type'Base) is

      I : constant Integer'Base := Index_Type'Pos (Before);
      J : constant Integer'Base := Index_Type'Pos (Index_Type'First);

      subtype Offset_Subtype is Natural range 0 .. Container.Length;

      Offset : constant Offset_Subtype := I - J;

   begin

      if Offset = 0 then
         Prepend (Container);

      elsif Offset = Container.Length then
         Append (Container);

      else
         null; --TODO

      end if;

   end Insert;


   procedure Insert
     (Container : in out Container_Type;
      Before    : in     Index_Type'Base;
      New_Item  : in     Element_Type) is

      I : constant Integer'Base := Index_Type'Pos (Before);
      J : constant Integer'Base := Index_Type'Pos (Index_Type'First);

      subtype Offset_Subtype is Natural range 0 .. Container.Length;

      Offset : constant Offset_Subtype := I - J;

   begin

      if Offset = 0 then
         Prepend (Container, New_Item);

      elsif Offset = Container.Length then
         Append (Container, New_Item);

      else
         null; --TODO

      end if;

   end Insert;



--     procedure Insert_N
--       (Container : in out Container_Type;
--        Before    : in     Index_Type'Base;
--        Count     : in     Natural) is

--     begin

--        if Count = 0 then
--           return;
--        end if;

--        declare
--           I : constant Integer'Base := Index_Type'Pos (Before);
--           J : constant Integer'Base := Index_Type'Pos (Index_Type'First);

--           subtype Offset_Subtype is Natural range 0 .. Container.Length;

--           Offset : constant Offset_Subtype := I - J;
--        begin
--           null;  --TODO
--        end;

--     end Insert_N;


--     procedure Insert_N
--       (Container : in out Container_Type;
--        Before    : in     Index_Type'Base;
--        Count     : in     Natural;
--        New_Item  : in     Element_Type) is

--        procedure Process
--          (Node          : access Node_Type;
--           First_Element : Positive;
--           Last_Element  : Positive) is
--        begin
--           for I in First_Element .. Last_Element loop
--              Node.Elements (I) := New_Item;
--           end loop;
--        end;

--        procedure Iterate is
--           new Rep_Types.Generic_Iteration (Process);

--        Before_Index : constant Integer'Base :=
--          Index_Type'Pos (Before) - Index_Type'Pos (Index_Type'First);

--   begin

--        Insert_N
--          (Container.Map,
--           Before_Index,
--           Count);

--        Iterate
--          (Map   => Container.Map,
--           First => Before_Index,
--           Last  => Before_Index + Count - 1);

--        null;
--     end Insert_N;




--     procedure Delete_First (Container : in out Container_Type) is
--     begin
--        Delete_First (Container.Map);
--     end;


--     procedure Delete_First
--       (Container : in out Container_Type;
--        Item      : in     Element_Type) is
--     begin
--        Delete_First (Container.Map, Item);
--     end;


--     procedure Delete_First_N
--       (Container : in out Container_Type;
--        Count     : in     Natural) is
--     begin
--        Delete_First_N (Container.Map, Count);
--     end;


--     procedure Delete_First_N
--       (Container : in out Container_Type;
--        Count     : in     Natural;
--        Item      : in     Element_Type) is
--     begin
--        Delete_First_N (Container.Map, Count, Item);
--     end;


--     procedure Delete_Last (Container : in out Container_Type) is
--     begin
--        Delete_Last (Container.Map);
--     end;


--     procedure Delete_Last
--       (Container : in out Container_Type;
--        Item      : in     Element_Type) is
--     begin
--        Delete_Last (Container.Map, Item);
--     end;


--     procedure Delete_Last_N
--       (Container : in out Container_Type;
--        Count     : in     Natural) is
--     begin
--        Delete_Last_N (Container.Map, Count);
--     end;


--     procedure Delete_Last_N
--       (Container : in out Container_Type;
--        Count     : in     Natural;
--        Item      : in     Element_Type) is
--     begin
--        Delete_Last_N (Container.Map, Count, Item);
--     end;


--     procedure Delete
--       (Container : in out Container_Type;
--        Index     : in     Index_Type) is

--        Iterator_Index : constant Integer'Base :=
--           Index_Type'Pos (Index) - Index_Type'Pos (Index_Type'First);
--     begin
--        Delete (Container.Map, Iterator_Index);
--     end;


--     procedure Delete
--       (Container : in out Container_Type;
--        First     : in     Index_Type'Base;
--        Back      : in     Index_Type'Base) is

--        F : constant Integer'Base :=
--           Index_Type'Pos (First) -
--           Index_Type'Pos (Index_Type'First);

--        L : constant Integer'Base :=
--           Index_Type'Pos (Index_Type'Pred (Back)) -
--           Index_Type'Pos (Index_Type'First);
--     begin
--        Delete (Container.Map, First => F, Last => L);
--     end;


--     procedure Delete
--       (Container : in out Container_Type;
--        First     : in     Index_Type'Base;
--        Back      : in     Index_Type'Base;
--        Item      : in     Element_Type) is
--     begin
--        null;
--     end;


--     procedure Delete_N
--       (Container : in out Container_Type;
--        First     : in     Index_Type'Base;
--        Count     : in     Natural) is
--     begin
--        null;
--     end;


--     procedure Delete_N
--       (Container : in out Container_Type;
--        First     : in     Index_Type'Base;
--        Count     : in     Natural;
--        Item      : in     Element_Type) is
--     begin
--        null;
--     end;



   function Element
     (Container : Container_Type;
      Index     : Index_Type'Base) return Element_Type is

      I : constant Integer'Base := Index_Type'Pos (Index);
      J : constant Integer'Base := Index_Type'Pos (Index_Type'First);

      Offset : constant Natural := I - J;

      NI : Positive;
      EI : Element_Index_Subtype;

   begin

      Get_Element_Node (Container, Offset, NI, EI);

      return Container.Nodes (NI) (EI);

   end Element;


   function Generic_Element
     (Container : Container_Type;
      Index     : Index_Type'Base) return Element_Access is

      I : constant Integer'Base := Index_Type'Pos (Index);
      J : constant Integer'Base := Index_Type'Pos (Index_Type'First);

      Offset : constant Natural := I - J;

      NI : Positive;
      EI : Element_Index_Subtype;

   begin

      Get_Element_Node (Container, Offset, NI, EI);

      declare
         E : Element_Type renames Container.Nodes (NI) (EI);
      begin
         return E'Access;
      end;

   end Generic_Element;


   procedure Replace_Element
     (Container : Container_Type;
      Index     : Index_Type'Base;
      By        : Element_Type) is

      I : constant Integer'Base := Index_Type'Pos (Index);
      J : constant Integer'Base := Index_Type'Pos (Index_Type'First);

      Offset : constant Natural := I - J;

      NI : Positive;
      EI : Element_Index_Subtype;

   begin

      Get_Element_Node (Container, Offset, NI, EI);

      Container.Nodes (NI) (EI) := By;

   end Replace_Element;


   generic
      with procedure Process (NI : Positive; EI : Element_Index_Subtype);
   procedure Generic_Iteration_Internal (Container : in Container_Type);

   procedure Generic_Iteration_Internal (Container : in Container_Type) is
   begin

      if Container.Length = 0 then
         return;
      end if;

      if Container.First = Container.Last then

         declare
            I : constant Natural := Container.Bias;
            J : constant Natural := I + Container.Length - 1;
         begin
            for K in I .. J loop
               Process (NI => Container.First, EI => K);
            end loop;
         end;

         return;

      end if;

      for K in Container.Bias .. Elements_Per_Node - 1 loop
         Process (NI => Container.First, EI => K);
      end loop;

      for Index in Container.First + 1 .. Container.Last - 1 loop
         for K in Node_Type'Range loop
            Process (NI => Index, EI => K);
         end loop;
      end loop;

      declare
         I : constant Natural := Container.Bias + Container.Length - 1;
         J : constant Element_Index_Subtype := I rem Elements_Per_Node;
      begin
         for K in 0 .. J loop
            Process (NI => Container.Last, EI => K);
         end loop;
      end;

   end Generic_Iteration_Internal;


   procedure Generic_Constant_Iteration (Container : in Container_Type) is

      procedure Process (NI : Positive; EI : Element_Index_Subtype) is
         pragma Inline (Process);
      begin
         Process (Container.Nodes (NI) (EI));
      end;

      procedure Iterate is
         new Generic_Iteration_Internal (Process);
   begin
      Iterate (Container);
   end;


   procedure Generic_Iteration (Container : in Container_Type) is

      procedure Process (NI : Positive; EI : Element_Index_Subtype) is
         pragma Inline (Process);
      begin
         Process (Container.Nodes (NI) (EI));
      end;

      procedure Iterate is
         new Generic_Iteration_Internal (Process);
   begin
      Iterate (Container);
   end;



   generic
      with procedure Process (NI : Positive; EI : Element_Index_Subtype);
   procedure Generic_Reverse_Iteration_Internal (Container : in Container_Type);

   procedure Generic_Reverse_Iteration_Internal (Container : in Container_Type) is
   begin

      if Container.Length = 0 then
         return;
      end if;

      if Container.First = Container.Last then

         declare
            I : constant Natural := Container.Bias;
            J : constant Natural := I + Container.Length - 1;
         begin
            for K in reverse I .. J loop
               Process (NI => Container.First, EI => K);
            end loop;
         end;

         return;

      end if;

      declare
         I : constant Natural := Container.Bias + Container.Length - 1;
         J : constant Element_Index_Subtype := I rem Elements_Per_Node;
      begin
         for K in reverse 0 .. J loop
            Process (NI => Container.Last, EI => K);
         end loop;
      end;

      for Index in reverse Container.First + 1 .. Container.Last - 1 loop
         for K in reverse Node_Type'Range loop
            Process (NI => Index, EI => K);
         end loop;
      end loop;

      for K in reverse Container.Bias .. Elements_Per_Node - 1 loop
         Process (NI => Container.First, EI => K);
      end loop;

   end Generic_Reverse_Iteration_Internal;


   procedure Generic_Constant_Reverse_Iteration
     (Container : in Container_Type) is

      procedure Process (NI : Positive; EI : Element_Index_Subtype) is
         pragma Inline (Process);
      begin
         Process (Container.Nodes (NI) (EI));
      end;

      procedure Iterate is
         new Generic_Reverse_Iteration_Internal (Process);
   begin
      Iterate (Container);
   end;


   procedure Generic_Reverse_Iteration
     (Container : in Container_Type) is

      procedure Process (NI : Positive; EI : Element_Index_Subtype) is
         pragma Inline (Process);
      begin
         Process (Container.Nodes (NI) (EI));
      end;

      procedure Iterate is
         new Generic_Reverse_Iteration_Internal (Process);
   begin
      Iterate (Container);
   end;



   function First (Container : Container_Type)
      return Index_Type is

      pragma Warnings (Off, Container);
   begin
      return Index_Type'First;
   end;


   function First_Element (Container : Container_Type)
      return Element_Type is
   begin
      return Element (Container, Index => Index_Type'First);
   end;


   function Last (Container : Container_Type)
      return Index_Type'Base is

      I : constant Integer'Base := Index_Type'Pos (Index_Type'First);
      J : constant Integer'Base := I + Container.Length - 1;

      Result : constant Index_Type'Base := Index_Type'Val (J);
   begin
      return Result;
   end;


   function Last_Element (Container : Container_Type) return Element_Type is
   begin
      return Element (Container, Index => Last (Container));
   end;


   function Back (Container : Container_Type)
      return Index_Type'Base is

      I : constant Integer'Base := Index_Type'Pos (Index_Type'First);
      J : constant Integer'Base := I + Container.Length;

      Result : constant Index_Type'Base := Index_Type'Val (J);
   begin
      return Result;
   end;


   function Generic_Find
     (Container : Container_Type;
      Index     : Index_Type'Base := Index_Type'First)
        return Index_Type'Base is

      I : Integer'Base :=
        Natural'(Index_Type'Pos (Index) - Index_Type'Pos (Index_Type'First));

      NI    : Positive;
      First : Element_Index_Subtype;

   begin

      if I >= Container.Length then
         return Back (Container);
      end if;

      Get_Element_Node (Container, I, NI, First);

      if Container.First = Container.Last then
         pragma Assert (NI = Container.First);

         declare
            Last : constant Element_Index_Subtype :=
              Container.Bias + Container.Length - 1;

            Node : Node_Type renames
              Container.Nodes (NI).all;
         begin
            for J in First .. Last loop
               if Predicate (Node (J)) then
                  declare
                     Result : constant Integer'Base :=
                       Index_Type'Pos (Index_Type'First) + I;
                  begin
                     return Index_Type'Val (Result);
                  end;
               end if;

               I := I + 1;
            end loop;
         end;

         return Back (Container);

      end if;

      declare
         Node : Node_Type renames
           Container.Nodes (NI).all;
      begin
         for J in First .. Elements_Per_Node - 1 loop
            if Predicate (Node (J)) then
               declare
                  Result : constant Integer'Base :=
                    Index_Type'Pos (Index_Type'First) + I;
               begin
                  return Index_Type'Val (Result);
               end;
            end if;

            I := I + 1;
         end loop;
      end;

      for NI_Index in NI + 1 .. Container.Last - 1 loop

         declare
            Node : Node_Type renames
              Container.Nodes (NI_Index).all;
         begin
            for J in Node'Range loop
               if Predicate (Node (J)) then
                  declare
                     Result : constant Integer'Base :=
                       Index_Type'Pos (Index_Type'First) + I;
                  begin
                     return Index_Type'Val (Result);
                  end;
               end if;

               I := I + 1;
            end loop;
         end;

      end loop;

      declare
         Last_Offset : constant Natural :=
           Container.Bias + Container.Length - 1;

         Last : constant Element_Index_Subtype :=
           Last_Offset rem Elements_Per_Node;

         Node : Node_Type renames
           Container.Nodes (Container.Last).all;
      begin
         for J in 0 .. Last loop
            if Predicate (Node (J)) then
               declare
                  Result : constant Integer'Base :=
                    Index_Type'Pos (Index_Type'First) + I;
               begin
                  return Index_Type'Val (Result);
               end;
            end if;

            I := I + 1;
         end loop;
      end;

      return Back (Container);

   end Generic_Find;


   function Find
     (Container : Container_Type;
      Item      : Element_Type;
      Index     : Index_Type'Base := Index_Type'First) return Index_Type'Base is

      function Predicate (E : Element_Type) return Boolean is
         pragma Inline (Predicate);
      begin
         return Item = E;
      end;

      function Find is
         new Generic_Find (Predicate);
   begin
      return Find (Container, Index);
   end;


   function Is_In
     (Item      : Element_Type;
      Container : Container_Type) return Boolean is
   begin
      return Find (Container, Item) /= Back (Container);
   end;


   function Generic_Reverse_Find
     (Container : Container_Type;
      Index     : Index_Type'Base := Index_Type'Last)
        return Index_Type'Base is

      I : Integer'Base;

      NI   : Positive;
      Last : Element_Index_Subtype;

   begin

      if Container.Length = 0 then
         return Back (Container);
      end if;

      I := Index_Type'Pos (Index) - Index_Type'Pos (Index_Type'First);

      if I < 0 then
         return Back (Container);
      end if;

      if I >= Container.Length then
         I := Container.Length - 1;
      end if;

      Get_Element_Node (Container, I, NI, Last);

      if Container.First = Container.Last then
         pragma Assert (NI = Container.First);

         declare
            Node : Node_Type renames Container.Nodes (NI).all;
         begin
            for J in reverse Container.Bias .. Last loop
               if Predicate (Node (J)) then
                  declare
                     Result : constant Integer'Base :=
                       Index_Type'Pos (Index_Type'First) + I;
                  begin
                     return Index_Type'Val (Result);
                  end;
               end if;

               I := I - 1;
            end loop;
         end;

         return Back (Container);

      end if;

      declare
         Node : Node_Type renames Container.Nodes (NI).all;
      begin
         for J in reverse 0 .. Last loop
            if Predicate (Node (J)) then
               declare
                  Result : constant Integer'Base :=
                    Index_Type'Pos (Index_Type'First) + I;
               begin
                  return Index_Type'Val (Result);
               end;
            end if;

            I := I - 1;
         end loop;
      end;

      for NI_Index in reverse Container.First + 1 .. NI - 1 loop

         declare
            Node : Node_Type renames Container.Nodes (NI_Index).all;
         begin
            for J in reverse Node'Range loop
               if Predicate (Node (J)) then
                  declare
                     Result : constant Integer'Base :=
                       Index_Type'Pos (Index_Type'First) + I;
                  begin
                     return Index_Type'Val (Result);
                  end;
               end if;

               I := I - 1;
            end loop;
         end;

      end loop;

      declare
         Node : Node_Type renames Container.Nodes (Container.First).all;
      begin
         for J in reverse Container.Bias .. Elements_Per_Node - 1 loop
            if Predicate (Node (J)) then
               declare
                  Result : constant Integer'Base :=
                    Index_Type'Pos (Index_Type'First) + I;
               begin
                  return Index_Type'Val (Result);
               end;
            end if;

            I := I - 1;
         end loop;
      end;

      return Back (Container);

   end Generic_Reverse_Find;



   function Reverse_Find
     (Container : Container_Type;
      Item      : Element_Type;
      Index     : Index_Type'Base := Index_Type'Last) return Index_Type'Base is

      function Predicate (E : Element_Type) return Boolean is
         pragma Inline (Predicate);
      begin
         return Item = E;
      end;

      function Reverse_Find is
         new Generic_Reverse_Find (Predicate);
   begin
      return Reverse_Find (Container, Index);
   end;



end Charles.Deques.Unbounded;
