with Error_Reporting;
with String_Utilities;
with Set_Simple_Sequential_Bounded_Managed_Iterator;
with Bounded_String;
with System_Utilities;
with Map_Generic;
with Hash;
with Operator;
with Io;
procedure Force_Idle_Terminal (Maximum_Idle_Minutes : Natural := 30;
                               Monitor_Interval_Minutes : Natural := 1)
             -- default 30 minutes
              is

    Capability_Error : exception;

    package Sysu renames System_Utilities;
    package Bs renames Bounded_String;

    subtype Str is Bs.Variable_String (50);

    -- active session information record
    type Session_Info is
        record
            Userid : Str;
            Session_Name : Str;
            Input_Bytes : Long_Integer := 0;
            Idle_Minutes : Natural := 0;
        end record;

    Current_Active_Sessions_Iter : Sysu.Session_Iterator;

    --------------------------------------------------

    package Session_Set is new Set_Simple_Sequential_Bounded_Managed_Iterator
                                  (Item => Sysu.Session_Id);

    procedure Delete_From_Map
                 (The_Item : in Sysu.Session_Id; Continue : out Boolean);
    procedure Delete_Old_Sessions is new Session_Set.Iterate (Delete_From_Map);

    procedure Add_To_Map (The_Item : in Sysu.Session_Id;
                          Continue : out Boolean);
    procedure Add_New_Sessions is new Session_Set.Iterate (Add_To_Map);

    --------------------------------------------------

    function Session_Id_Hash (Key : Sysu.Session_Id) return Integer;
    package Active_Session_Db is
       new Map_Generic (Size => 10,
                        Domain_Type => Sysu.Session_Id,
                        Range_Type => Session_Info,
                        Hash => Session_Id_Hash);

    --------------------------------------------------

    -- update the session info in the db
    procedure Update_Db (Session : in Sysu.Session_Id);

    --------------------------------------------------

    -- information saved for each active session
    Session_Db : Active_Session_Db.Map;
    Db_Iter : Active_Session_Db.Iterator;

    Current_Active_Sessions, Save_Active_Sessions,
    Previous_Active_Sessions, No_Longer_Logged_In, Still_Logged_In :
       Session_Set.Set (50);

    --------------------------------------------------

    function Session_Id_Hash (Key : Sysu.Session_Id) return Integer is
    begin
        return Hash.Long_Integer_To_Integer (Key);
    end Session_Id_Hash;

    procedure Delete_From_Map (The_Item : in Sysu.Session_Id;
                               Continue : out Boolean) is
    begin
        Continue := True;
        Active_Session_Db.Undefine (The_Map => Session_Db, D => The_Item);
    end Delete_From_Map;

    procedure Add_To_Map (The_Item : in Sysu.Session_Id;
                          Continue : out Boolean) is
        Working : Session_Info;
    begin
        Continue := True;
        Bs.Copy (Working.Userid, Sysu.User_Name (The_Item));
        Bs.Copy (Working.Session_Name, Sysu.Session_Name (The_Item));
        Active_Session_Db.Define (Session_Db, The_Item, Working, True);
    end Add_To_Map;

    procedure Update_Db (Session : in Sysu.Session_Id) is
        Working : Session_Info := Active_Session_Db.Eval (Session_Db, Session);
    begin
        if Working.Input_Bytes = Sysu.Input_Count (Sysu.Terminal (Session)) then
            Working.Idle_Minutes := Working.Idle_Minutes +
                                       Monitor_Interval_Minutes;
        else
            Working.Idle_Minutes := 0;
            Working.Input_Bytes := Sysu.Input_Count (Sysu.Terminal (Session));
        end if;
        Active_Session_Db.Define (Session_Db, Session, Working, False);
    end Update_Db;
begin

    Operator.Enable_Privileges (Enable => True);
    if not Operator.Privileged_Mode then
        raise Capability_Error;
    end if;
    Active_Session_Db.Initialize (Session_Db);
    Session_Set.Clear (Current_Active_Sessions);
    Session_Set.Clear (Previous_Active_Sessions);
    Session_Set.Clear (Save_Active_Sessions);
    Session_Set.Clear (No_Longer_Logged_In);
    Session_Set.Clear (Still_Logged_In);

    -- monitor idle sessions forever
    loop

        --------------------------------------------------
        -- Read current active sessions
        --------------------------------------------------

        -- Process all currently active sessions and
        -- add to current set
        Sysu.Init (Current_Active_Sessions_Iter);

        while not Sysu.Done (Current_Active_Sessions_Iter) loop
            Session_Set.Add (The_Item => Sysu.Value
                                            (Current_Active_Sessions_Iter),
                             To_The_Set => Current_Active_Sessions);
            Sysu.Next (Current_Active_Sessions_Iter);
        end loop;

        -- save for next iteration
        Session_Set.Copy (Current_Active_Sessions, Save_Active_Sessions);

        --------------------------------------------------
        -- Delete sessions that are no longer logged in
        --------------------------------------------------
        -- Alg. for no longer logged in are
        --  no_longer := prev_logged_in (prev_logged_in ^ curr_logged_in)
        --    ('^' us intersection)
        Session_Set.Intersection (Previous_Active_Sessions,
                                  Current_Active_Sessions, Still_Logged_In);
        Session_Set.Difference (Previous_Active_Sessions,
                                Still_Logged_In, No_Longer_Logged_In);
        Delete_Old_Sessions (Over_The_Set => No_Longer_Logged_In);

        --------------------------------------------------
        -- Now iterate thru the session db.
        -- For each session in db
        --   if still logged in
        --   then
        --      update info in db
        --      remove from currently active sessions
        --   end if
        -- end loop
        --------------------------------------------------
        Active_Session_Db.Init (Db_Iter, Session_Db);
        while not Active_Session_Db.Done (Db_Iter) loop
            if Session_Set.Is_A_Member (Active_Session_Db.Value (Db_Iter),
                                        Current_Active_Sessions) then
                Update_Db (Active_Session_Db.Value (Db_Iter));
                Session_Set.Remove (Active_Session_Db.Value (Db_Iter),
                                    Current_Active_Sessions);
            end if;
            Active_Session_Db.Next (Db_Iter);
        end loop;

        --------------------------------------------------
        -- Add new active sessions to data base (map).
        --------------------------------------------------
        Add_New_Sessions (Over_The_Set => Current_Active_Sessions);


        --------------------------------------------------
        -- Finally, iterate over the map and
        -- force any sessions that are over the limit.
        -- (Note: Are not deleted from map because
        --   they will be noticed missing the next iteration
        --   thru the loop
        --------------------------------------------------
        Active_Session_Db.Init (Db_Iter, Session_Db);
        while not Active_Session_Db.Done (Db_Iter) loop
            if Active_Session_Db.Eval
                  (Session_Db, Active_Session_Db.Value (Db_Iter)).
               Idle_Minutes >= Maximum_Idle_Minutes then

                --------------------------------------------------
                -- take appropriate action here.
                -- sending a msg for testing
                --------------------------------------------------
                declare
                    Ses_Id : Sysu.Session_Id :=
                       Active_Session_Db.Value (Db_Iter);

                begin
                    Operator.Force_Logoff
                       (Physical_Line => Sysu.Terminal
                                            (Active_Session_Db.Value (Db_Iter)),
                        Commit_Buffers => True,
                        Response => "<PROFILE>");

                    Error_Reporting.Report_Error
                       ("Force_Idle_Terminal_" &
                        String_Utilities.Number_To_String
                           (Sysu.Terminal (Ses_Id)),
                        Error_Reporting.Create_Condition_Name
                           ("Logging Off", Error_Reporting.Normal),
                        Sysu.User_Name (Ses_Id) & "." &
                           Sysu.Session_Name (Ses_Id));


                exception
                    -- don't blow up if unable or if user
                    -- is already gone
                    when others =>
                        null;
                end;
            end if;
            Active_Session_Db.Next (Db_Iter);
        end loop;

        Session_Set.Clear (Current_Active_Sessions);
        Session_Set.Clear (Previous_Active_Sessions);
        Session_Set.Clear (No_Longer_Logged_In);
        Session_Set.Clear (Still_Logged_In);
        Session_Set.Copy (From_The_Set => Save_Active_Sessions,
                          To_The_Set => Previous_Active_Sessions);
        Session_Set.Clear (Save_Active_Sessions);

        -- Wait the monitor period
        delay Monitor_Interval_Minutes * 60.0;

    end loop;
exception
    when Capability_Error =>
        Io.Echo ("ERROR - Insufficient Privileges");
end Force_Idle_Terminal;