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
   DB     : Database_Connection;
   Insert : constant 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;
   FV_Idx  : aliased Atomic_Counter := 0;

   Dummy : Boolean;

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

   task type Parallel;

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

   task body Parallel is
      DB    : Database_Connection;
      Idx   : constant Boolean :=
        Natural (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 Natural (Sync_Add_And_Fetch (Printed'Access, 1)) < 4 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));
         DB.Rollback;
         ASB.Wait_For_Release (B, Dummy);
   end Parallel;

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

   procedure Fetch_Query
     (DB : Database_Connection;
      Query : Prepared_Statement;
      Output : out Unbounded_String)
   is
      type Child_Cursor is new Direct_Cursor with null record;

      Result : Child_Cursor;
      FV : constant Integer := Integer (Sync_Add_And_Fetch (FV_Idx'Access, 1));
   begin
      Result.Fetch (DB, Query);

      while Result.Has_Row loop
         for J in 0 .. Result.Field_Count - 1 loop
            Append (Output, 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;

      Result.Find (5);

      if Result.Has_Row then
         Append (Output, "Found " & Result.Value (1) & ASCII.LF);
      else
         Put_Line ("Not found.");
      end if;

      Result.Find (FV);

      if Result.Has_Row xor FV in 0 .. 9 then
         Put_Line
           (FV'Img & ' ' & (if Result.Has_Row then "" else "not ") & "found.");
      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, Index_By => 0, 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, Index_By => 2, 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;

   ASB.Wait_For_Release (B, Dummy);
   ASB.Wait_For_Release (B, Dummy);

   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;
exception
   when E : others =>
      Put_Line ("Task " & Ada.Exceptions.Exception_Information (E));
      ASB.Wait_For_Release (B, Dummy);
      ASB.Wait_For_Release (B, Dummy);

      DB.Rollback;
      DB.Execute ("drop table Test_Task_Cursor");
      DB.Commit;

end Task_Cursor;
