with Ada.Exceptions;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Text_IO;           use Ada.Text_IO;
with Ada.Real_Time;         use Ada.Real_Time;

with Ada.Synchronous_Barriers;

with GNATCOLL.Atomic;       use GNATCOLL.Atomic;
with GNATCOLL.SQL;          use GNATCOLL.SQL;
with GNATCOLL.SQL.Exec;     use GNATCOLL.SQL.Exec;
with GNATCOLL.SQL.Exec.Tasking;
with GNATCOLL.Traces;       use GNATCOLL.Traces;

procedure Task_Cursor (Descr : Database_Description; Speed : Boolean) is
   use type Atomic_Counter;

   DB     : Database_Connection;
   Insert : Prepared_Statement :=
     Prepare ("insert into Test_Task_Cursor values ($1, $2, $3, $4)");
   Query  : array (Boolean) of Prepared_Statement;
   Sample : array (Boolean) of Unbounded_String;

   Stamp : Time;

   package ASB renames Ada.Synchronous_Barriers;

   B : ASB.Synchronous_Barrier (7);
   Printed : aliased Atomic_Counter := 0;
   Switch  : aliased Atomic_Counter := 0;

   procedure Fetch_Query
     (DB : Database_Connection;
      Query : Prepared_Statement;
      Output : out Unbounded_String);

   task type Parallel;

   --------------
   -- Parallel --
   --------------

   task body Parallel is
      Dummy : Boolean;
      DB    : Database_Connection;
      Idx   : constant Boolean :=
        Sync_Add_And_Fetch (Switch'Access, 1) rem 2 = 0;

      procedure Process (Idx : Boolean);

      -------------
      -- Process --
      -------------

      procedure Process (Idx : Boolean) is
         Data  : Unbounded_String;
      begin
         Fetch_Query (DB, Query (Idx), Data);

         if Data /= Sample (Idx) then
            if Sync_Add_And_Fetch (Printed'Access, 1) = 1 then
               Put_Line
               ("Differ" & Length (Sample (Idx))'Img & Length (Data)'Img
                  & ASCII.LF & To_String (Sample (Idx)) & ASCII.LF
                  & To_String (Data) & ASCII.LF
                  & "-------------------------------------------------------");
            end if;
         end if;
      end Process;

   begin
      ASB.Wait_For_Release (B, Dummy);
      DB := Tasking.Get_Task_Connection (Descr);

      Process (Idx);
      Process (not Idx);

      ASB.Wait_For_Release (B, Dummy);

   exception
      when E : others =>
         Put_Line ("Task " & Ada.Exceptions.Exception_Information (E));
         ASB.Wait_For_Release (B, Dummy);
   end Parallel;

   -----------------
   -- Fetch_Query --
   -----------------

   procedure Fetch_Query
     (DB : Database_Connection;
      Query : Prepared_Statement;
      Output : out Unbounded_String)
   is
      Result : Forward_Cursor;
      Do_Output : Boolean := not Speed or else Output = Null_Unbounded_String;
   begin
      if Do_Output then
         Output := Null_Unbounded_String;
      end if;

      Result.Fetch (DB, Query);

      if Do_Output then
         while Result.Has_Row loop
            for J in 0 .. Result.Field_Count - 1 loop
               Append
                 (Output, To_Unbounded_String (ASCII.HT & Result.Value (J)));
            end loop;

            Append
              (Output,
               Integer'Image (Result.Processed_Rows)
               & Integer'Image (Result.Current)
               & ASCII.HT & Boolean'Image (Result.Boolean_Value (3))
               & ASCII.LF);

            Result.Next;
         end loop;
      end if;

   end Fetch_Query;

   Test_Tasks : array (1 .. B.Release_Threshold - 1) of Parallel;

begin
   GNATCOLL.Traces.Parse_Config_File;
   DB := Descr.Build_Connection;

   Query (False) :=
     Prepare ("select * from Test_Task_Cursor order by 1 desc",
              Use_Cache => True, Name => "statement_desc");
   Query (True) :=
     Prepare ("select F_Text, F_Real, F_Int, F_Bool from Test_Task_Cursor"
              & " order by 1",
              Use_Cache => True, Name => "statement_asc");

   if not Check_Connection (DB) then
      Ada.Text_IO.Put_Line ("Check_Connection should return True");
   end if;

   DB.Execute
     ("create table Test_Task_Cursor"
      & " (F_Int Int, F_Real Real, F_Text Text, F_Bool Boolean)");

   if not DB.Success then
      --  Could be already exists
      DB.Rollback;
   end if;

   for J in 0 .. 9 loop
      DB.Execute
        (Insert,
         (1 => +J,
          2 => +(Float (J) * 10.0),
          3 => +(Float (J) + Float (J) * 1000.0),
          4 => +(J rem 2 = 0)));
   end loop;

   DB.Commit;

   Stamp := Clock;

   for J in 1 .. (if Speed then 1000_000 else 1) loop
      Fetch_Query (DB, Query (False), Sample (False));
      Fetch_Query (DB, Query (True), Sample (True));
   end loop;

   if Speed then
      Put_Line ("Time spend" & Duration'Image (To_Duration (Clock - Stamp)));
   end if;

   declare
      Dummy : Boolean;
   begin
      ASB.Wait_For_Release (B, Dummy);
      ASB.Wait_For_Release (B, Dummy);
   end;

   if Printed = 0 then
      Put_Line ("All equals");
      Put_Line (To_String (Sample (False)));
      Put_Line (To_String (Sample (True)));
   end if;

   DB.Execute ("drop table Test_Task_Cursor");

   DB.Commit;
end Task_Cursor;
