-- elhoim is full object organizer with editor and command interpreter.
-- Elhoim is Copyright (C) 2023 Manuel De Girardi ; 
--
--   This program is free software; you can redistribute it and/or modify
--   it under the terms of the GNU General Public License as published by
--   the Free Software Foundation; either version 2 of the License, or
--   (at your option) any later version.
--
--   This program 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
--   along with this program; if not, write to the Free Software
--   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
--
-- Date := "2023-05-26 17:40:38"
-- Version := "0.6.6b"
with El.Strings;

with Text_Io;
with Gnat.Sha1;
with Gnat.Sockets;
use Gnat.Sockets;
with Ada.Task_Identification;
use Ada.Task_Identification;

use Gnat;

with Ada.Unchecked_Deallocation;

package body El.Server is
use Strings;
   
   use Engine;    
   use Engine.Users_Manager;
   
   task body Server_Process is
      End_Of_Task : Boolean := False;
   begin
      loop
	 select
	    accept Halt(End_Of_Program : out Boolean) do
	       End_Of_Task := True;
	       The_Server.Hander_Lock.Seize;
	       Text_Io.Put_Line("Server : Going to shutdow the Engine");
	       The_Server.Hander_Lock.Release;
	       The_Server.Engine_Proc.Halt(End_Of_Task);
	       End_Of_Program := End_Of_Task;
	       Text_Io.Put_Line("Halting Server");
	    end Halt;
	    exit;
	 or
	    accept Initialize do
	       The_Server.Engine_Proc.Initialize;
	    end Initialize;
	 or
	    delay 1.0;
	    null;
	 end select;
      end loop;      
      --Server.Hander_Lock.Seize;
      Text_Io.Put_Line("Server halted");
      --Server.Hander_Lock.Release;
   end Server_Process;
   
   
   type Socket_Access is access all Socket_Type;
   
   
   task body Server_Interface_Process is
      
      task type Thread_Server(Socket : Socket_Access) is
	 entry Get_Id(Id : out Task_Id);
      end Thread_Server;
   
      type Thread_server_Access is access Thread_server;
      
      type Thread_Server_Task is
	 record
	    Id : Task_Id := Null_Task_id;
	    Thread_server : Thread_server_access;
	 end record;
      type thread_server_Task_Access is access Thread_Server_Task;   
   
      task body Thread_Server is
	 
	 Channel         : constant Stream_Access := Stream(Socket.all);
	 Logname         : String_Access;
	 Passwd          : Sha1.Message_Digest;
	 Logged          : Boolean := False;
      begin
	 
	 accept Get_Id(Id : out Task_Id) do
	    Id := Current_Task;
	 end Get_Id;
	 
	 Logname := new String ' (String'Input(Channel));
	 Passwd :=  Sha1.Message_Digest'Input(Channel);	 
	 
	 if Users_Manager.Add_User(Logname.all, Passwd) then	    
	    Logged := True;
	 elsif not Users_Manager.Check_Passwd(Logname.all, Passwd) then	    
	    Logged := False;
	 else
	    Logged := True;
	 end if;
	 
	 Boolean'Output(Channel, Logged);
	 
	 if Logged then
	    begin
	       loop
		  
		  begin
		     null;
		  exception
		     when Socket_Exception : Socket_Error =>		  
			raise;
		     when Host_Exception : Host_Error =>
			raise;
		     when others =>		  
			raise;
		  end;
	       end loop;      
	       Gnat.Sockets.Close_Socket(Socket.all);
	    exception
	       
	       when Socket_Exception : Socket_Error =>		  
		  raise;
	       when Host_Exception : Host_Error =>
		  raise;
	       when others =>		  
		  raise;
	    end;	       
	 else
	    Gnat.Sockets.Close_Socket (Socket.all);
	 end if;
      exception
	 when Constraint_Error =>

	    Gnat.Sockets.Close_Socket(Socket.all);
      end Thread_Server;
      
      procedure Thread_Server_Free is
	 new Ada.Unchecked_Deallocation(Thread_Server,thread_server_Access);
      procedure Thread_Server_Task_free is
	 new Ada.Unchecked_Deallocation(Thread_Server_Task,thread_server_Task_Access);
      
      Max_Client : constant Positive := 500;
      
      Liste : array(1..Max_Client) of Socket_access;
      Liste_Thread_task : array(1..Max_Client) of Thread_Server_Task_Access;
      Bliste : array(1..Max_Client) of Boolean;
      Reads,Write : Socket_Set_Type;

      Selector : Selector_Type;
      Srv_Adr,Adr : Sock_Addr_Type;
      Srv_Socket : Socket_Type;
      Status : Selector_Status;

      Ptr : Integer := 0;
      
      function Chercher return Natural is
      begin
         for I in 1..Max_Client loop
            if Liste_Thread_Task(I) = null then
               return I;
            elsif not Is_callable(Liste_Thread_Task(I).Id) or Is_terminated(Liste_Thread_Task(I).Id) then
               Thread_Server_Free(Liste_Thread_Task(I).Thread_Server);
               Thread_Server_Task_Free(Liste_Thread_Task(I));
               Liste_Thread_Task(I) := null;
               Liste(I) := new Socket_Type;
               return I;
            end if;
         end loop;
         return 0;
      end Chercher;

      Thread_Suivant : Natural := 0;

      End_Of_Task : Boolean := False;
      Run : Boolean := False;
   begin
      
      accept Init;
      Text_Io.Put_Line("Server initialize sockets...");
      for I in 1..Max_Client loop
	 liste(I) := new Socket_Type;
      end loop;
      
      Bliste := (others => False);
      
      Srv_Adr.Addr := Any_Inet_Addr;
      Srv_Adr.Port := 7054;
      
      Create_Socket(Srv_Socket);
      Set_Socket_Option
	(Srv_Socket,
	 Socket_Level,
	 (Reuse_Address, True));
      delay 0.2;
      Bind_Socket(Srv_Socket,Srv_Adr);
      Listen_Socket(Srv_Socket);
      Create_Selector(Selector);
      
      Text_Io.Put_Line("Server sockets initialized.");
      loop	 
	 
	 Empty(Reads);

	 Set(Reads,Srv_Socket);

	 for I in 1..Max_Client loop
	    if Bliste(I) then
	       Set(Reads, Liste(I).all);
	    end if;
	 end loop;

	 Check_Selector(Selector, Reads, Write, Status);
	 
	 case Status is
	    when Completed =>
	       
	       if Is_Set(Reads,Srv_Socket) then
		  Thread_Suivant := Chercher;

		  if Thread_Suivant /= 0 then
		     Ptr := Thread_Suivant;
		     
		     Accept_Socket(Srv_Socket, Liste(Ptr).all, Adr);
		     --Text_Io.Put_Line("Server accepted");
		     --  Set_Socket_Option
		     --                                  (Liste(Ptr).all,
		     --                                   Socket_Level,
		     --                                   (Reuse_Address, True));
		     select
			accept Halt;	    
			
			exit;
		     or delay 2.0;
		     end select;
		     Bliste(Ptr) := True;
		  else
		     delay 1.0;
		     
		  end if;
	       else

		  for I in 1..Max_Client loop

		     if bliste(I) then

			if Is_Set(Reads,Liste(I).all) then
			   Liste_Thread_Task(I) := new Thread_Server_Task;

			   Liste_Thread_Task(I).Thread_Server := new Thread_Server(Liste(i));

			   Liste_Thread_Task(i).Thread_Server.Get_Id(Liste_Thread_Task(i).Id);

			   Bliste(i) := false;

			end if;
		     end if;
		  end loop;
	       end if;
	    when Expired =>
	       Text_Io.Put_Line("expired");
	    when Aborted =>
	       Text_Io.Put_Line("aborted");
	 end case;	
      end loop;
   end Server_Interface_Process;
   
   

end El.Server ;