-------------------------------------------------------------------------------
-- (C) Altran Praxis Limited
-------------------------------------------------------------------------------
--
-- The SPARK toolset 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 3, or (at your option) any later
-- version. The SPARK toolset 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 the SPARK toolset; see file
-- COPYING3. If not, go to http://www.gnu.org/licenses for a complete copy of
-- the license.
--
--=============================================================================

with Ada.Strings;
with Ada.Strings.Fixed;
with Ada.Text_IO;

with GNAT.Heap_Sort_G;

with CMD;
with Utility;

package body Work_Manager is

   package body Jobs is
      type Count_Per_Status is array (JobStatus) of Job_Index;

      Next_Job : Job_Index := 1;

      Job_Counts : Count_Per_Status := Count_Per_Status'(others => 0);

      procedure Check_Invariant is
         Total : Job_Index := 0;
      begin
         for I in JobStatus loop
            Total := Total + Job_Counts (I);
         end loop;
         if Total /= Work_Table.Last then
            Utility.Debug ("Invariant failure in Work_Manager");
            for I in JobStatus loop
               Utility.Debug (Natural'Image (Job_Counts (I)));
            end loop;
            Utility.Debug (Natural'Image (Work_Table.Last));
         end if;
      end Check_Invariant;

      procedure Add_Work_Package (File    : in String;
                                  JobType : in AnalysisType) is
      begin
         Work_Table.Increment_Last;

         Job_Counts (Pending) := Job_Counts (Pending) + 1;

         Work_Table.Table (Work_Table.Last) :=
           Work_Package'
           (File_Name => new String'(File),
            File_Size => Utility.Get_File_Size (File),
            Analysis  => JobType,
            Status    => Pending,
            Worker    => 0,
            WhyFailed => NullErrorString);

         Check_Invariant;
      end Add_Work_Package;

      procedure GetNextJob (Job_ID : out Job_Index) is
      begin
         --  Get the next job.
         Job_ID := Next_Job;

         --  Set the status of the job, and the worker.
         Work_Table.Table (Next_Job).Status := InProgress;

         Job_Counts (InProgress) := Job_Counts (InProgress) + 1;
         Job_Counts (Pending)    := Job_Counts (Pending) - 1;

         Next_Job := Next_Job + 1;

         Check_Invariant;
      end GetNextJob;

      function Get_File_Name (Job : in Job_Index) return String is
      begin
         return Work_Table.Table (Job).File_Name.all;
      end Get_File_Name;

      function Get_Simple_File_Name (Job : in Job_Index) return String is
         On_File : constant String := Get_File_Name (Job);

         --  Find the first directory separator from the right hand
         --  end of File_Name, so we can split into the directory,
         --  and the plain file name
         Dir_Index : constant Natural :=
           Ada.Strings.Fixed.Index (On_File, Utility.String_1'(1 => GNAT.OS_Lib.Directory_Separator), Ada.Strings.Backward);

         --  Simple file name of file to be processed, with 4-char suffix
         --  (e.g. ".vcg" or ".dpc") removed
         SF : constant String := On_File (Dir_Index + 1 .. (On_File'Last - 4));
      begin
         return SF;
      end Get_Simple_File_Name;

      function Get_Analysis_Type (Job : in Job_Index) return AnalysisType is
      begin
         return Work_Table.Table (Job).Analysis;
      end Get_Analysis_Type;

      function Get_HasFailed (Job : in Job_Index) return Boolean is
      begin
         return Work_Table.Table (Job).Status = Failed;
      end Get_HasFailed;

      function Get_WhyFailed (Job : in Job_Index) return ErrorString is
      begin
         return Work_Table.Table (Job).WhyFailed;
      end Get_WhyFailed;

      procedure JobFinished (Job : in Job_Index) is
      begin
         --  Mark the job as completed.
         Work_Table.Table (Job).Status := Finished;
         Job_Counts (Finished)         := Job_Counts (Finished) + 1;
         Job_Counts (InProgress)       := Job_Counts (InProgress) - 1;

         Check_Invariant;
      end JobFinished;

      --  Signal that a job has failed and record the reason.
      procedure JobFailed (Job        : in Job_Index;
                           FailReason : in ErrorString) is
      begin
         --  Mark the job as failed.
         Work_Table.Table (Job).Status    := Failed;
         Work_Table.Table (Job).WhyFailed := FailReason;
         Work_Manager.AnyFailed           := True;

         Job_Counts (Failed)     := Job_Counts (Failed) + 1;
         Job_Counts (InProgress) := Job_Counts (InProgress) - 1;

         Check_Invariant;
      end JobFailed;

      procedure Sort_Files_By_Size is
         use Ada.Streams.Stream_IO;

         Heap_Sort_Temp : Work_Package;

         procedure Move_Work_Package (From : in Natural;
                                      To   : in Natural) is
         begin
            if From = 0 then
               Work_Table.Table (To) := Heap_Sort_Temp;
            elsif To = 0 then
               Heap_Sort_Temp := Work_Table.Table (From);
            else
               Work_Table.Table (To) := Work_Table.Table (From);
            end if;
         end Move_Work_Package;

         function Lt_Work_Package (Op1 : in Natural;
                                   Op2 : in Natural) return Boolean is
            File_Size_Op1 : Ada.Streams.Stream_IO.Count;
            File_Size_Op2 : Ada.Streams.Stream_IO.Count;
         begin
            if Op1 = 0 then
               File_Size_Op1 := Heap_Sort_Temp.File_Size;
            else
               File_Size_Op1 := Work_Table.Table (Op1).File_Size;
            end if;
            if Op2 = 0 then
               File_Size_Op2 := Heap_Sort_Temp.File_Size;
            else
               File_Size_Op2 := Work_Table.Table (Op2).File_Size;
            end if;
            return File_Size_Op1 < File_Size_Op2;
         end Lt_Work_Package;

         function Lt_Work_Package_Reversed (Op1 : in Natural;
                                            Op2 : in Natural) return Boolean is
         begin
            return not Lt_Work_Package (Op1, Op2);
         end Lt_Work_Package_Reversed;

         package WP_Heap_Sort_Asc is new GNAT.Heap_Sort_G (Move_Work_Package, Lt_Work_Package);
         package WP_Heap_Sort_Dsc is new GNAT.Heap_Sort_G (Move_Work_Package, Lt_Work_Package_Reversed);

      begin

         if CMD.Sort_VCGs then

            if CMD.Reverse_Order then
               WP_Heap_Sort_Asc.Sort (Work_Table.Last);
            else
               WP_Heap_Sort_Dsc.Sort (Work_Table.Last);
            end if;

         end if;
      end Sort_Files_By_Size;

      procedure Display_Status_Banner is
      begin
         Ada.Text_IO.Put_Line ("Job-ID Status     Filename");
         Ada.Text_IO.Put_Line ("====== ======     ========");
      end Display_Status_Banner;

      function Total_Number_Of_Files return Job_Index is
      begin
         return Work_Table.Last;
      end Total_Number_Of_Files;

      function Number_Of_Files (Of_Status : in JobStatus) return Job_Index is
      begin
         return Job_Counts (Of_Status);
      end Number_Of_Files;

      --  Display the list of jobs to do, to the screen.
      procedure List_Jobs is
      begin

         if Total_Number_Of_Files = 0 then
            Ada.Text_IO.Put_Line ("No VCG or DPC files were found that require processing");
         else
            Ada.Text_IO.Put_Line ("Files to be simplified are:");
            for I in Natural range 1 .. Total_Number_Of_Files loop
               Ada.Text_IO.Put_Line
                 (Work_Table.Table (I).File_Name.all &
                    "," &
                    Ada.Streams.Stream_IO.Count'Image (Work_Table.Table (I).File_Size) &
                    " bytes");
            end loop;
            Ada.Text_IO.New_Line;
            Ada.Text_IO.Put_Line (Natural'Image (Total_Number_Of_Files) & " files require processing");
         end if;
         Ada.Text_IO.New_Line;
      end List_Jobs;

      procedure Clear is
      begin
         Work_Table.Set_Last (0);
      end Clear;
   end Jobs;

end Work_Manager;
