-------------------------------------------------------------------------------
-- (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.Calendar;

with Date_Time;
with CommandLineData;
with E_Strings;
with ScreenEcho;
with Version;

package body File_Utils is

   procedure Print_A_Header (File        : in SPARK_IO.File_Type;
                             Header_Line : in String;
                             File_Type   : in File_Types) is

      Len_Date_Time : constant Integer := 23;

      subtype Typ_Date_Time_Range is Integer range 1 .. Len_Date_Time;
      subtype Typ_Date_Time is String (Typ_Date_Time_Range);

      FDL_Comment_Open  : constant Character := '{';
      FDL_Comment_Close : constant Character := '}';

      Extended_Date_String : E_Strings.T;
      Date_String          : Typ_Date_Time;
      Star_Line            : E_Strings.T;

      procedure Get_Current_Date_Time (Date_String : out Typ_Date_Time)
      --# derives Date_String from ;
      is
         --# hide Get_Current_Date_Time;
         --
         -- FUNCTION :
         --
         -- Returns a string indicating the current date and time as a 22 character
         -- string in the format 24-12-1988 12:00:00.00
         --
         -- OPERATION :
         --
         -- Uses the standard package Calendar.
         --

         Hours_In_Day         : constant := Date_Time.Hours_T'Last + 1;
         Minutes_In_Hour      : constant := Date_Time.Minutes_T'Last + 1;
         Seconds_In_Minute    : constant := Date_Time.Seconds_T'Last + 1;
         Hundredths_In_Second : constant := 100;

         subtype Hundredths_Type is Natural range 0 .. Hundredths_In_Second - 1;

         subtype Digit is Natural range 0 .. 9;

         Year       : Date_Time.Years_T;
         Month      : Date_Time.Months_T;
         Day        : Date_Time.Days_T;
         Hours      : Date_Time.Hours_T;
         Minutes    : Date_Time.Minutes_T;
         Seconds    : Date_Time.Seconds_T;
         Hundredths : Hundredths_Type;

         procedure Decode_Time
           (Time       : in     Ada.Calendar.Time;
            Year       :    out Date_Time.Years_T;
            Month      :    out Date_Time.Months_T;
            Day        :    out Date_Time.Days_T;
            Hours      :    out Date_Time.Hours_T;
            Minutes    :    out Date_Time.Minutes_T;
            Seconds    :    out Date_Time.Seconds_T;
            Hundredths :    out Hundredths_Type)
         is

            System_Time : Duration;

            procedure Decode_Duration
              (Time       : in     Duration;
               Hours      :    out Date_Time.Hours_T;
               Minutes    :    out Date_Time.Minutes_T;
               Seconds    :    out Date_Time.Seconds_T;
               Hundredths :    out Hundredths_Type)
            is

               -- 0 .. 8_640_000, to ensure we cover the full range of Ada.Calendar.Day_Duration in subsequent calculations.
               type Time_Of_Day is range 0 .. Hours_In_Day * Minutes_In_Hour * Seconds_In_Minute * Hundredths_In_Second;

               System_Time : Time_Of_Day;
            begin
               System_Time := (Time_Of_Day (Time * Duration'(100.0))) mod Time_Of_Day'Last; -- limit result to 8_639_999
               Hours       := Date_Time.Hours_T (System_Time / (Minutes_In_Hour * Seconds_In_Minute * Hundredths_In_Second));
               Minutes     := Date_Time.Minutes_T (System_Time / (Seconds_In_Minute * Hundredths_In_Second) mod Minutes_In_Hour);
               Seconds     := Date_Time.Seconds_T (System_Time / Hundredths_In_Second mod Seconds_In_Minute);
               Hundredths  := Hundredths_Type (System_Time mod Hundredths_In_Second);
            end Decode_Duration;

         begin
            Ada.Calendar.Split
              (Date    => Time,
               Year    => Ada.Calendar.Year_Number (Year),
               Month   => Ada.Calendar.Month_Number (Month),
               Day     => Ada.Calendar.Day_Number (Day),
               Seconds => System_Time);
            Decode_Duration
              (Time       => System_Time,
               Hours      => Hours,
               Minutes    => Minutes,
               Seconds    => Seconds,
               Hundredths => Hundredths);
         end Decode_Time;

         --  Convert a single digit into a character.
         function Digit_Image (Value : Digit) return Character is
            type Digit_Images is array (Digit) of Character;
            Image : constant Digit_Images := Digit_Images'('0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
         begin
            return Image (Value);
         end Digit_Image;

         --  Format the given value in a zero-padded field of the
         --  given width.
         function Format_Number (Value : Natural;
                                 Width : Natural) return String is
            Num_Digits : constant Natural := Width;

            subtype Digit_Number is Positive range 1 .. Num_Digits;
            subtype Result_Type is String (Digit_Number);

            Result : Result_Type;

            function Get_Digit (Pos : Digit_Number) return Digit is
            begin
               return Digit ((Value / 10 ** (Digit_Number'Last - Pos)) mod 10);
            end Get_Digit;
         begin
            for I in Digit_Number loop
               Result (I) := Digit_Image (Get_Digit (I));
            end loop;
            return Result;
         end Format_Number;

         function Date_Image
           (Day   : Date_Time.Days_T;
            Month : Date_Time.Months_T;
            Year  : Date_Time.Years_T)
           return  String
         is
         begin
            return Format_Number (Day, 2) & '-' & Date_Time.Month_Names (Month) & '-' & Format_Number (Year, 4);
         end Date_Image;

         function Time_Image
           (Hours      : Date_Time.Hours_T;
            Minutes    : Date_Time.Minutes_T;
            Seconds    : Date_Time.Seconds_T;
            Hundredths : Hundredths_Type)
           return       String
         is
         begin
            return Format_Number (Hours, 2) &
              ':' &
              Format_Number (Minutes, 2) &
              ':' &
              Format_Number (Seconds, 2) &
              '.' &
              Format_Number (Hundredths, 2);
         end Time_Image;

      begin
         Decode_Time
           (Time       => Ada.Calendar.Clock,
            Year       => Year,
            Month      => Month,
            Day        => Day,
            Hours      => Hours,
            Minutes    => Minutes,
            Seconds    => Seconds,
            Hundredths => Hundredths);
         Date_String :=
           Date_Image (Day   => Day,
                       Month => Month,
                       Year  => Year) &
           ' ' &
           Time_Image (Hours      => Hours,
                       Minutes    => Minutes,
                       Seconds    => Seconds,
                       Hundredths => Hundredths);
      end Get_Current_Date_Time;

      procedure Set_Col (File : in SPARK_IO.File_Type;
                         Posn : in Positive)
      --# global in out SPARK_IO.File_Sys;
      --# derives SPARK_IO.File_Sys from *,
      --#                                File,
      --#                                Posn;
      is
      begin
         if File = SPARK_IO.Standard_Output then
            ScreenEcho.Set_Col (Posn);
         else
            SPARK_IO.Set_Col (File => File,
                              Posn => Posn);
         end if;
      end Set_Col;

      procedure Put_Char (File : in SPARK_IO.File_Type;
                          Item : in Character)
      --# global in out SPARK_IO.File_Sys;
      --# derives SPARK_IO.File_Sys from *,
      --#                                File,
      --#                                Item;
      is
      begin
         if File = SPARK_IO.Standard_Output then
            ScreenEcho.Put_Char (Item);
         else
            SPARK_IO.Put_Char (File => File,
                               Item => Item);
         end if;
      end Put_Char;

      procedure Put_String (File : in SPARK_IO.File_Type;
                            Item : in String;
                            Stop : in Natural)
      --# global in out SPARK_IO.File_Sys;
      --# derives SPARK_IO.File_Sys from *,
      --#                                File,
      --#                                Item,
      --#                                Stop;
      is
      begin
         if File = SPARK_IO.Standard_Output then
            ScreenEcho.Put_String (Item);
         else
            SPARK_IO.Put_String (File => File,
                                 Item => Item,
                                 Stop => Stop);
         end if;
      end Put_String;

      procedure New_Line (File    : in SPARK_IO.File_Type;
                          Spacing : in Positive)
      --# global in out SPARK_IO.File_Sys;
      --# derives SPARK_IO.File_Sys from *,
      --#                                File,
      --#                                Spacing;
      is
      begin
         if File = SPARK_IO.Standard_Output then
            ScreenEcho.New_Line (Spacing);
         else
            SPARK_IO.New_Line (File    => File,
                               Spacing => Spacing);
         end if;
      end New_Line;

      procedure Center (File  : in SPARK_IO.File_Type;
                        E_Str : in E_Strings.T)
      --# global in     File_Type;
      --#        in out SPARK_IO.File_Sys;
      --# derives SPARK_IO.File_Sys from *,
      --#                                E_Str,
      --#                                File,
      --#                                File_Type;
      is
         Page_Width : constant Natural := 80;
      begin
         Set_Col (File => File,
                  Posn => (Page_Width - E_Strings.Get_Length (E_Str => E_Str)) / 2);
         if File_Type = Dec_File then
            Put_Char (File => File,
                      Item => FDL_Comment_Open);
            E_Strings.Put_String (File  => File,
                                  E_Str => E_Str);
            Put_Char (File => File,
                      Item => FDL_Comment_Close);
            New_Line (File    => File,
                      Spacing => 1);

         elsif File_Type = Rule_File then
            Put_String (File => File,
                        Item => "/*",
                        Stop => 0);
            E_Strings.Put_String (File  => File,
                                  E_Str => E_Str);
            Put_String (File => File,
                        Item => "*/",
                        Stop => 0);
            New_Line (File    => File,
                      Spacing => 1);
         else
            E_Strings.Put_String (File  => File,
                                  E_Str => E_Str);
            New_Line (File    => File,
                      Spacing => 1);
         end if;
      end Center;

      procedure Output_Version_Line (File : SPARK_IO.File_Type)
      --# global in     CommandLineData.Content;
      --#        in     File_Type;
      --#        in out SPARK_IO.File_Sys;
      --# derives SPARK_IO.File_Sys from *,
      --#                                CommandLineData.Content,
      --#                                File,
      --#                                File_Type;
      is
         Version_Line : E_Strings.T;
      begin
         if CommandLineData.Content.Plain_Output then
            Version_Line := E_Strings.Copy_String (Str => "Examiner " & Version.Toolset_Distribution & " Edition");
         else
            Version_Line := E_Strings.Copy_String (Str => "Examiner " & Version.Toolset_Banner_Line);
         end if;

         Center (File  => File,
                 E_Str => Version_Line);

      end Output_Version_Line;

   begin
      Extended_Date_String := E_Strings.Copy_String (Str => "DATE : ");
      Star_Line            := E_Strings.Copy_String (Str => "*******************************************************");

      Center (File  => File,
              E_Str => Star_Line);
      if Header_Line'Length /= 0 then
         Center (File  => File,
                 E_Str => E_Strings.Copy_String (Str => Header_Line));
      end if;

      Output_Version_Line (File => File);

      -- Display the copyright line if not in plain mode.
      -- Otherwise, display a blank line.
      if not CommandLineData.Content.Plain_Output then
         Center (File  => File,
                 E_Str => E_Strings.Copy_String (Str => Version.Toolset_Copyright));
      else
         New_Line (File    => File,
                   Spacing => 1);
      end if;

      Center (File  => File,
              E_Str => Star_Line);
      New_Line (File    => File,
                Spacing => 2);

      if not CommandLineData.Content.Plain_Output then
         Get_Current_Date_Time (Date_String => Date_String);
         E_Strings.Append_String (E_Str => Extended_Date_String,
                                  Str   => Date_String);
         Center (File  => File,
                 E_Str => Extended_Date_String);
         New_Line (File    => File,
                   Spacing => 1);
      end if;
   end Print_A_Header;

end File_Utils;
