------------------------------------------------------------------------------
--                                                                          --
--                         GNAT RUN-TIME COMPONENTS                         --
--                                                                          --
--                         S Y S T E M . C _ T I M E                        --
--                                                                          --
--                                  B o d y                                 --
--                                                                          --
--          Copyright (C) 2025-2026, Free Software Foundation, Inc.         --
--                                                                          --
-- GNAT is free software; you can  redistribute it  and/or modify it under --
-- terms of the  GNU General Public License as published  by the Free Soft- --
-- ware  Foundation;  either version 3,  or (at your option) any later ver- --
-- sion.  GNAT is distributed in the hope that it will be useful, but WITH- --
-- OUT ANY WARRANTY;  without even the  implied warranty of MERCHANTABILITY --
-- or FITNESS FOR A PARTICULAR PURPOSE.                                     --
--                                                                          --
-- As a special exception under Section 7 of GPL version 3, you are granted --
-- additional permissions described in the GCC Runtime Library Exception,   --
-- version 3.1, as published by the Free Software Foundation.               --
--                                                                          --
-- You should have received a copy of the GNU General Public License and    --
-- a copy of the GCC Runtime Library Exception along with this program;     --
-- see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see    --
-- <http://www.gnu.org/licenses/>.                                          --
--                                                                          --
-- GNAT was originally developed  by the GNAT team at  New York University. --
-- Extensive contributions were provided by Ada Core Technologies, Inc.     --
--                                                                          --
------------------------------------------------------------------------------

package body System.C_Time is

   --  Two Duration representations are described in targparm.ads:
   --   Size      Small       Last = (2**(Size - 1) - 1) * Small
   --    32    0.02                   42_949_672.94
   --    64    0.000_000_001       9_223_372_036.854_775_807

   Recip : constant := (if Duration'Size = 32 then 50 else 1_000_000_000);
   --  The reciprocal of the Small used to write "* Small" as "/ Recip"

   Milli : constant := 1_000;
   Micro : constant := 1_000_000;
   Nano  : constant := 1_000_000_000;
   --  The standard divisors

   pragma Unsuppress (Overflow_Check);
   --  Overflow may occur during the various conversions

   -------------------------
   -- In_Timeval_Duration --
   -------------------------

   --  Immediate : constant Duration := 0.0;

   --  Forever   : constant Duration :=
   --    Duration'Min (Duration'Last, 1.0 * OS_Constants.MAX_tv_sec);

   --  subtype Timeval_Duration is Duration range Immediate .. Forever;

   function In_Timeval_Duration (T : timeval) return Boolean is
      Max_Dur  : constant := 2**(Duration'Size - 1) - 1;
      Max_Sec  : constant := Max_Dur / Recip;
      Max_Usec : constant := (Max_Dur mod Recip) * Micro / Recip;

      --  When Duration'Size = 64 and time_t'Size = 32, the compiler
      --  complains that Max_Sec does not fit in time_t, hence cannot
      --  be compared with T.tv_sec.
      Safe_Max_Sec : constant :=
        (if Max_Sec > time_t'Last then time_t'Last else Max_Sec);
      Safe_Max_Usec : constant :=
        (if Max_Sec > time_t'Last then usec_t'Last else Max_Usec);

   begin
      pragma Warnings (Off, "condition is always");
      return T.tv_sec >= 0
        and then (T.tv_sec > 0 or else T.tv_usec >= 0)
        and then T.tv_sec <= Safe_Max_Sec
        and then (T.tv_sec < Safe_Max_Sec or else T.tv_usec <= Safe_Max_Usec)
        and then T.tv_sec <= OS_Constants.MAX_tv_sec
        and then (T.tv_sec < OS_Constants.MAX_tv_sec or else T.tv_usec = 0);
      pragma Warnings (On, "condition is always");
   end In_Timeval_Duration;

   -----------------------------
   -- Milliseconds_To_Timeval --
   -----------------------------

   function Milliseconds_To_Timeval (M : Interfaces.C.int) return timeval is
      use Interfaces.C;
      Q : constant int := M  /  Milli;
      R : constant int := M rem Milli;

   begin
      return (tv_sec => time_t (Q), tv_usec => usec_t (R) * (Micro / Milli));
   end Milliseconds_To_Timeval;

   -----------------------------
   -- Nanoseconds_To_Timespec --
   -----------------------------

   function Nanoseconds_To_Timespec (N : Interfaces.C.int) return timespec is
      use Interfaces.C;
      Q : constant int := N  /  Nano;
      R : constant int := N rem Nano;

   begin
      return (tv_sec => time_t (Q), tv_nsec => nsec_t (R));
   end Nanoseconds_To_Timespec;

   -----------------
   -- To_Duration --
   -----------------

   --  Duration (tv_usec) is OK even when Duration'Size = 32, see above

   function To_Duration (T : timeval) return Duration is
   begin
      return Duration (T.tv_sec) + Duration (T.tv_usec) / Micro;
   end To_Duration;

   --  Duration (tv_nsec) overflows when Duration'Size = 32, see above.
   --  Scale down nanoseconds by the value of the Small in nanoseconds.

   function To_Duration (T : timespec) return Duration is
      S : constant := Nano / Recip;

   begin
      return Duration (T.tv_sec) + Duration (T.tv_nsec / S) / (Nano / S);
   end To_Duration;

   -----------------
   -- To_Timespec --
   -----------------

   function To_Timespec (T : timeval) return timespec is
   begin
      return (tv_sec => T.tv_sec, tv_nsec => nsec_t (T.tv_usec) * Milli);
   end To_Timespec;

   function To_Timespec (D : Duration) return timespec is
      tv_sec  : time_t;
      tv_nsec : nsec_t;

   begin
      if D = 0.0 then
         tv_sec  := 0;
         tv_nsec := 0;

      elsif D < 0.0 then
         tv_sec := time_t (D + 0.5);
         if D = Duration (tv_sec) then
            tv_nsec := 0;
         else
            tv_nsec := nsec_t ((D - Duration (tv_sec)) * Nano + 0.5);
         end if;

      else
         tv_sec := time_t (D - 0.5);
         if D = Duration (tv_sec) then
            tv_nsec := 0;
         else
            tv_nsec := nsec_t ((D - Duration (tv_sec)) * Nano - 0.5);
         end if;
      end if;

      return (tv_sec, tv_nsec);
   end To_Timespec;

   -----------------
   -- To_Timeval --
   -----------------

   function To_Timeval (D : Duration) return timeval is
      tv_sec  : time_t;
      tv_usec : usec_t;

   begin
      if D = 0.0 then
         tv_sec  := 0;
         tv_usec := 0;

      elsif D < 0.0 then
         tv_sec := time_t (D + 0.5);
         if D = Duration (tv_sec) then
            tv_usec := 0;
         else
            tv_usec := usec_t ((D - Duration (tv_sec)) * Micro + 0.5);
         end if;

      else
         tv_sec := time_t (D - 0.5);
         if D = Duration (tv_sec) then
            tv_usec := 0;
         else
            tv_usec := usec_t ((D - Duration (tv_sec)) * Micro - 0.5);
         end if;
      end if;

      return (tv_sec, tv_usec);
   end To_Timeval;

end System.C_Time;
