-- This program is the last attempt of artificial intelligency with Ada.
-- Elhoim is Copyright (C) 2023 Manuel ; 
--
--   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-01 20:03:01"
-- Version := "1.0.0a"
with PragmARC.Math.Functions;

with Ada.Text_Io;
use Ada;

package body Lib.Neural is
   
      package body Neuro_Process is   
      
      package Real_Io is new Text_Io.Float_Io(Real);
      use Real_Io;
      
      package Real_Math is new PragmARC.Math.Functions (Supplied_Real => Real);
      
      procedure Train     (Samples    : access Sample_Set_Type;
			   Max_Epochs : in     Positive;
			   Converged  : in     Normal_Type;		
			   Reuse      : in     Boolean;
			   RMS_Error  :    out Normal_Type) is	 
	 procedure Get_Input(Pattern : in     Positive;
			     Input   :    out Node_Set;
			     Desired :    out Node_Set) is
	 begin
	    for I in Input'Range loop
	       Input(I) := Samples(Pattern).Input(I);
	    end loop;
	    for I in Desired'Range loop
	       Desired(I) := Samples(Pattern).Output(I);
	    end loop;
	 end Get_Input;
	 
	 package NN is new REM_NN
	   (
	    Num_Input_Nodes => Num_Inputs,
	    Num_Hidden_Nodes => Num_hiddens,
	    Num_Output_Nodes => Num_outputs,
	    Num_Patterns => Max_samples,
	    New_Random_Weights => not Reuse,
	    Input_To_Output_Connections => False,
	    Weight_File_Name => Filename,
	    Get_Input => Get_input
	   );
	 
	 use NN;
	 
	 
	 Desired, Response : Node_Set(1..Num_Outputs) := (others => 0.0);
	 Error     : Real := 0.0;      
	 Current_Epoch : Natural := 0;
      begin	 
	 
	 --Text_Io.Put_Line("Network filename : " & Filename);	 
	 RMS_Error := 0.0;
	 loop
	    for Pattern in 1..Max_Samples loop
	       NN.Train;
	       NN.Respond (pattern, Response);
	       for I in Desired'Range loop
		  Desired(I) := Samples(Pattern).Output(I);
	       end loop;
	       for I in Response'Range loop
		  Error :=
		    Error + (Desired(I) - Response(i) );
	       end loop;
	       
	       RMS_Error := RMS_Error + ((Error/Real(Response'Length)) ** 2);
	       Error := 0.0;
	    end loop;
	    RMS_Error :=
	      Real_Math.Sqrt(RMS_Error / Real (Max_samples)) ;
	    Current_Epoch := Current_Epoch + 1;
	    Text_Io.Put(Ascii.Cr & "   RMS_Error:");
	    Real_Io.Put(RMS_Error);
	    Text_Io.Put("Epoch:" & Integer'Image(Current_Epoch));
	    exit when Current_Epoch = Max_Epochs or (Current_Epoch > 3 and Converged >= RMS_Error);
	    
	    NN.Save_Weights;
	 end loop;
	 Text_Io.New_Line;
	 NN.Save_Weights;
      end Train;
      
      
      task body Request_NN is
	 
      begin
	 accept Respond (Input       : in     Input_Type;
			 Response    :    out Output_Type) do
	    declare
	       procedure Get_Input(Pattern : in     Positive;
				   Input   :    out Node_Set;
				   Desired :    out Node_Set) is
	       begin
		  for I in Input'Range loop
		     Input(I) := Respond.Input(I);
		  end loop;
		  for I in Desired'Range loop
		     Desired(I) := 0.0;
		  end loop;
		  
	       end Get_Input;
	       
	       
	       package NN is new REM_NN
		 (
		  Num_Input_Nodes => Num_Inputs,
		  Num_Hidden_Nodes => Num_hiddens,
		  Num_Output_Nodes => Num_outputs,
		  Num_Patterns => 1,
		  New_Random_Weights => False,
		  Input_To_Output_Connections => False,
		  Weight_File_Name => Filename,
		  Get_Input => Get_input
		 );
	       
	       
	       
	       Output : NN.Output_set := (others => 0.0);
	    begin
	       Response := (others => 0.0);
	       
	       NN.Respond (1, output);   
	       
	       for I in output'Range loop	    
		  
		  if Output(I) >= 0.5 then
		     Response(I) := 1.0;
		  else
		     Response(I) := 0.0;
		  end if;
		  
	       end loop;
	       
	    end;
	 end Respond;
	       
	       
      end Request_NN;
	 
      
      
   end Neuro_Process;

end Lib.Neural ;