with Ada.Numerics.Float_Random;
with Ada.Text_IO;

with Iterators.Std;
with Iterators.Text_IO;

procedure Iterators.Demo.JSA_20 is

   --  All necessary instantiations are in Iterators.Demo

   Rnd : Ada.Numerics.Float_Random.Generator;

   use Float_Iters.Linking;

   Data : aliased Float_Array_Iters.Element_Array :=
            (1 .. 1000 => Ada.Numerics.Float_Random.Random (Rnd));

   -------------
   -- Average --
   -------------

   procedure Average is
      Average : Float := 0.0;
   begin

      --  Manually compute an average

      for I in Data'Range loop
         Average := Average + Data (I);
      end loop;
      Average := Average / Float (Data'Length);

      --  same average using Reduce

      pragma Assert (Average =
                       (Float_Array_Iters.Const_Iter (Data)
                        & Float_Iters.Op.Reduce (0.0, "+"'Access))
                        / Float (Data'Length));
   end Average;

   -------------
   -- Top_Ten --
   -------------

   procedure Top_Ten is
      function Is_Candidate (Datum : Float) return Boolean is (Datum > 0.9);
      procedure Set_Winner (Datum : in out Float) is null;
   begin

      --  Plain loop

      for Element of Float_Iters.Iter -- Workaround for "of" bug -- see Iter decl
        (Float_Array_Iters.Iter (Data)
         & Float_Iters.Op.Filter (Is_Candidate'Access)
         & Float_Iters.Op.Take (10)).all
      loop
         Set_Winner (Element);
      end loop;

      --  Alternative: For_Each loop

      Float_Iters.Op.For_Each (Float_Array_Iters.Iter (Data)
                               & Float_Iters.Op.Filter (Is_Candidate'Access)
                               & Float_Iters.Op.Take (10),
                               Set_Winner'Unrestricted_Access);

   end Top_Ten;

   ---------------------------
   -- Print_Running_Average --
   ---------------------------

   procedure Print_Running_Average (Width : Positive := 3) is
      --  use Std.Strings.Linking;
      use Float2str.Linking;
      use Str2Float.Linking;
      use Float_Iters.Meta.Linking;

      use Float_Iters.Meta;
      use Float_Iters.Op;
      --  use Std.Strings.Op;

      use Float2str;
      use Str2float;

      function Div (F : Float) return Float is (F / Float (Width));
   begin
      Std.Strings.Op.For_Each
        --  (Just ("7") & "8" & "6" & "-1" & "12" & "15" & "-1" & "18"
        (Text_IO.Lines ("file_avg_demo.txt")  --  For each line
         & Map (Value'Access)                 --  Convert to Float
         & Filter (Is_Positive'Access)        --  Skip negative numbers
         & Window (Size => Width, Skip => 1)  --  Group into new iterator
         & Flat_Map (Scan (0.0, "+"'Access)   --     Sum subiterator
                     & Last)                  --     Take last (total sum)
         & Map (Div'Access)                   --  Divide by window size
         & Map (Image'Access),                --  Back to string
         GNAT.IO.Put_Line'Access);            --  Print running average
   end Print_Running_Average;

   --------------------------------------
   -- Print_Running_Average_Imperative --
   --------------------------------------
   --  Alternative using the Chain type instead of "&" concatenators
   procedure Print_Running_Average_Imperative (Width : Positive := 3) is
      S2F : Str2Float.Chain;
      Flt : Float_Iters.Operators.Chain;
      F2M : Float_Iters.Meta.Chain_To_Meta;
      M2F : Float_Iters.Meta.Chain_From_Meta;
      Sum : Float_Iters.Operators.Chain;
      Avg : Float_Iters.Operators.Chain;
      F2S : Float2Str.Chain;

      use Float_Iters.Op;

      function Div (F : Float) return Float is (F / Float (Width));
   begin
      S2F.Start (Text_IO.Lines ("file_avg_demo.txt"));
      S2F.Map (Value'Access);
      Flt.Resume (S2F);
      Flt.Filter (Is_Positive'Access);
      F2M.Resume (Flt);
      F2M.Window (Size => Width, Skip => 1);
      M2F.Resume (F2M);

      Sum.Start (Scan (0.0, "+"'Access));
      Sum.Continue (Last);
      M2F.Flat_Map (Sum.As_Iterator);

      Avg.Resume (M2F);
      Avg.Map (Div'Access);
      F2S.Resume (Avg);
      F2S.Map (Image'Access);
      F2S.For_Each (GNAT.IO.Put_Line'Access);
   end Print_Running_Average_Imperative;

   -------------------------------------
   -- Print_Running_Average_Classical --
   -------------------------------------
   --  Functionally equivalent classical implementation
   procedure Print_Running_Average_Classical (Width : Positive := 3) is
      use Ada.Text_IO;
      File : File_Type;
      Window : Float_Iters.Iterators.List;
      --  Any list-like type would do; we use this one that's already available
      --  instead of instantiating a new one.
   begin
      Open (File, In_File, "file_avg_demo.txt");

      while not End_Of_File (File) loop
         declare
            Line : constant String := Get_Line (File);
            Num  : constant Float  := Float'Value (Line);
         begin
            --  Skip negative numbers, used to signal invalid samples
            if Num >= 0.0 then
               --  Add new sample
               Window.Append (Num);
               --  Prune the window
               if Natural (Window.Length) > Width then
                  Window.Delete_First;
               end if;
               --  New averaged value?
               if Natural (Window.Length) = Width then
                  declare
                     Total : Float := 0.0;
                  begin
                     --  Compute total. This could be done more efficiently
                     --  by keeping a running total. However, that might have
                     --  numerical implications due to unneeded substractions,
                     --  would not be functionally equivalent to the Iterator
                     --  version, and would be less self-evident.
                     for Sample of Window loop
                        Total := Total + Sample;
                     end loop;
                     --  Write the new running average value
                     GNAT.IO.Put_Line (Float'Image (Total / Float (Width)));
                  end;
               end if;
            end if;
         end;
      end loop;

      Close (File);
   end Print_Running_Average_Classical;

   -----------------------------------
   -- Print_Running_Average_Ada202x --
   -----------------------------------

   procedure Print_Running_Average_Ada202x (Width : Positive := 3) is
      Window : Float_Iters.Iterators.List;
      --  Any list-like type would do; we use this one that's already available
      --  instead of instantiating a new one.
   begin
      for Line of Text_IO.Get_Lines ("file_avg_demo.txt") loop
         -- We would want to use a "when Float'Value (Line) >= 0" filter in the
         -- previous line, which is alas not yet implemented by GNAT.

         declare
            Num  : constant Float  := Float'Value (Line);
         begin
            --  Skip negative numbers, used to signal invalid samples
            if Num >= 0.0 then
               --  Add new sample
               Window.Append (Num);
               --  Prune the window
               if Natural (Window.Length) > Width then
                  Window.Delete_First;
               end if;
               --  New averaged value?
               if Natural (Window.Length) = Width then
                  declare
                     Total : Float := 0.0;
                  begin
                     --  Compute total. This could be done more efficiently
                     --  by keeping a running total. However, that might have
                     --  numerical implications due to unneeded substractions,
                     --  would not be functionally equivalent to the Iterator
                     --  version, and would be less self-evident.
                     for Sample of Window loop
                        Total := Total + Sample;
                     end loop;
                     --  Write the new running average value
                     GNAT.IO.Put_Line (Float'Image (Total / Float (Width)));
                  end;
               end if;
            end if;
         end;
      end loop;
   end Print_Running_Average_Ada202x;

begin
   Average;
   Top_Ten;

   GNAT.IO.Put_Line ("Iterators functional version");
   Print_Running_Average;

   GNAT.IO.Put_Line ("Iterators imperative version");
   Print_Running_Average_Imperative;

   GNAT.IO.Put_Line ("Classical no-iterators version");
   Print_Running_Average_Classical;

   GNAT.IO.Put_Line ("Ada 202x iterators version");
   Print_Running_Average_Ada202x;

end Iterators.Demo.JSA_20;
