------------------------------------------------------------------------------
--                                                                          --
--                         GNAT COMPILER COMPONENTS                         --
--                                                                          --
--                    G N A T . S O C K E T S . P O L L                     --
--                                                                          --
--                                 S p e c                                  --
--                                                                          --
--                       Copyright (C) 2020, AdaCore                        --
--                                                                          --
-- 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.      --
--                                                                          --
------------------------------------------------------------------------------

--  This package provides an interface to wait for one of a set of sockets to
--  become ready to perform I/O.

with System.OS_Constants;

package GNAT.Sockets.Poll is

   type Event_Type is (Input, Output, Error, Hang_Up, Invalid_Request);
   --  I/O events we can expect on socket.
   --  Input           - socket ready to read;
   --  Output          - socket available for write;
   --  Error           - socket is in error state;
   --  Hang_Up         - peer closed;
   --  Invalid_Request - invalid socket;

   type Event_Set is array (Event_Type) of Boolean;
   --  The type to get results on events waiting

   subtype Wait_Event_Type is Event_Type range Input .. Output;
   type Wait_Event_Set is array (Wait_Event_Type) of Boolean;
   --  The type to set events to wait. Note that Error event would be waited
   --  anyway.

   -------------------------------
   --  Predefined set of events --
   -------------------------------

   Input_Event  : constant Wait_Event_Set;
   --  Wait for input availability only

   Output_Event : constant Wait_Event_Set;
   --  Wait for output availability only

   Both_Events : constant Wait_Event_Set;
   --  Wait for Input and Output availability

   Error_Event : constant Wait_Event_Set;
   --  Wait only for error state on socket

   type Set (Size : Positive) is private;
   --  Set of sockets with I/O event set to wait on

   function Create (Size : Positive) return Set;
   --  Create empty socket set with defined size

   function To_Set
     (Socket : Socket_Type;
      Events : Wait_Event_Set;
      Size   : Positive := 1) return Set;
   --  Create socket set and put the Socket there at the first place.
   --  Events parameter is defining what state of the socket we are going to
   --  wait.

   procedure Append
     (Self   : in out Set;
      Socket : Socket_Type;
      Events : Wait_Event_Set);
   --  Add Socket and its I/O waiting state at the end of Self

   procedure Insert
     (Self       : in out Set;
      Socket     : Socket_Type;
      Events     : Wait_Event_Set;
      Index      : Positive;
      Keep_Order : Boolean := False);
   --  Insert Socket and its I/O waiting state at the Index position.
   --  If Keep_Order is True then all next elements moved to the next index up.
   --  Otherwise the old element from Index moved to the end of the Self set.

   procedure Remove
     (Self : in out Set; Index : Positive; Keep_Order : Boolean := False);
   --  Remove socket from Index. If Keep_Order is True then move all next
   --  elements after removed one to previous index. If Keep_Order is False
   --  then move the last element on place of the removed one.

   procedure Set_Event
     (Self  : in out Set;
      Index : Positive;
      Event : Wait_Event_Type;
      Value : Boolean);
   --  Set I/O waiting event to Value for the socket at Index position

   procedure Set_Events
     (Self   : in out Set;
      Index  : Positive;
      Events : Wait_Event_Set);
   --  Set I/O waiting events for the socket at Index position

   function Get_Events
     (Self : Set; Index : Positive) return Wait_Event_Set;
   --  Get I/O waiting events for the socket at Index position

   function Length (Self : Set) return Natural;
   --  Get the number of sockets currently in the Self set

   function Full (Self : Set) return Boolean;
   --  Return True if there is no more space in the Self set for new sockets

   procedure Wait (Self : in out Set; Timeout : Duration; Count : out Natural);
   --  Wait no longer than Timeout on the socket set for the I/O events.
   --  Count output parameter is the number of elements in the Self set are
   --  detected for I/O events. Zero Count mean timeout on wait.
   --  The iteration over activated elements in set could be done with routine
   --  Next. The kind of I/O events on element could be cheched with State or
   --  Status routines.

   procedure Next (Self : Set; Index : in out Natural);
   --  Iterate over set looking for the next index with active I/O event state.
   --  Put 0 initially into Index. Each iteration increments Index and then
   --  checks for state. End of iterations can be detected by 0 in the Index.

   procedure Copy (Source : Set; Target : out Set);
   --  Copy sockets and its I/O waiting events from Source set into Target

   function Resize (Self : Set; Size : Positive) return Set;
   --  Returns the copy of Source with modified Size

   function Growth (Self : Set) return Set;
   --  Returns the copy of Source with increased Size

   function Socket (Self : Set; Index : Positive) return Socket_Type;
   --  Returns the Socket from Index position

   function Status (Self : Set; Index : Positive) return Event_Set;
   --  Returns I/O events detected in previous Wait call at Index position

   procedure State
     (Self   : Set;
      Index  : Positive;
      Socket : out Socket_Type;
      Status : out Event_Set);
   --  Returns Socket and its I/O events detected in previous Wait call at
   --  Index position.

   function C_Status
     (Self : Set; Index : Positive) return Interfaces.C.unsigned;
   --  Return word with I/O events detected flags in previous Wait call at
   --  Index position. Possible flags are defined in System.OS_Constants names
   --  starting with POLL prefix.

private

   Input_Event  : constant Wait_Event_Set := (Input => True, Output => False);
   Output_Event : constant Wait_Event_Set := (Input => False, Output => True);
   Both_Events  : constant Wait_Event_Set := (others => True);
   Error_Event  : constant Wait_Event_Set := (others => False);

   package SOC renames System.OS_Constants;

   type nfds_t is mod 2 ** SOC.SIZEOF_nfds_t;
   for nfds_t'Size use SOC.SIZEOF_nfds_t;

   FD_Type_Bound : constant := 2 ** (SOC.SIZEOF_fd_type - 1);

   type FD_Type is range -FD_Type_Bound .. FD_Type_Bound - 1;
   for FD_Type'Size use SOC.SIZEOF_fd_type;

   type Events_Type is mod 2 ** SOC.SIZEOF_pollfd_events;
   for Events_Type'Size use SOC.SIZEOF_pollfd_events;

   type Pollfd is record
      Socket  : FD_Type;
      Events  : Events_Type := 0;
      REvents : Events_Type := 0;
   end record with Convention => C;

   type Poll_Set is array (Positive range <>) of Pollfd with Convention => C;

   type Set (Size : Positive) is record
      Length : Natural := 0;
      Max_FD : FD_Type := 0;
      Max_OK : Boolean;
      --  Is the Max_FD actual. It can became inactual after remove socket with
      --  Max_FD from set and became actual again after add socket with FD more
      --  than Max_FD.
      Fds    : Poll_Set (1 .. Size);
   end record;

   function Length (Self : Set) return Natural
   is (Self.Length);

   function Full (Self : Set) return Boolean
   is (Self.Size = Self.Length);

end GNAT.Sockets.Poll;
