--  Copyright (2008-2013) Cédric Coussinet (cedric.coussinet@nomoseed.net)
--
--  This program is free software: you can redistribute it and/or modify
--  it under the terms of the GNU Affero General Public License as published
--  by the Free Software Foundation, either version 3 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 Affero General Public License for more details.

--  You should have received a copy of the GNU Affero General Public License
--  along with this program. If not, see <http://www.gnu.org/licenses/>

with Nomo.Numerics.Reals.Long;

with Nomo.Numerics.Reals.Elementary_Functions;

with Nomo.Numerics.Accurately.Constants;

package body Nomo.Interpreter.External_Messages.Premises is

   function Get_Specificity_Log (This : in External_Premise) return Real_Accurately is
   begin
      return This.Specificity_Log / 2.0;
   end Get_Specificity_Log;

   use Numerics.Reals.Long;

   procedure Maximize_Information (Actuator        : in Real;
                                   Fitting_Nbr     : in Count;
                                   Information     : out Real;
                                   Tolerance       : out Strictly_Positive_Real;
                                   Specificity_Log : in out Real_Accurately;
                                   Squares_Sum     : in out Positive_Long_Real;
                                   Values_Sum      : in out Long_Real);
   pragma precondition (Squares_Sum > 0.0);
   pragma Inline (Maximize_Information);

   procedure Maximize_Information (Actuator        : in Real;
                                   Fitting_Nbr     : in Count;
                                   Information     : out Real;
                                   Tolerance       : out Strictly_Positive_Real;
                                   Specificity_Log : in out Real_Accurately;
                                   Squares_Sum     : in out Positive_Long_Real;
                                   Values_Sum      : in out Long_Real) is
      use Numerics.Reals.Elementary_Functions;
      Variance : Strictly_Positive_Real;
   begin
      Values_Sum := Values_Sum + Long_Real (Actuator);
      Squares_Sum := Squares_Sum + Positive_Long_Real (Actuator ** 2);
      Information := Real (Values_Sum / Positive_Long_Real (Fitting_Nbr));
      Variance := Real (Squares_Sum  / Positive_Long_Real (Fitting_Nbr)) -  Information ** 2;
      Specificity_Log := Specificity_Log + Log_10 (Variance);
      Tolerance := 2.0 * Variance;
   end Maximize_Information;

   procedure Maximize (This        : in out External_Premise;
                       Fitting_Nbr : in Count) is
      use Numerics.Accurately.Constants;
      use Numerics.Reals.Elementary_Functions;
      use Integrations_Arrays.Integrations_Arrays_Instance;
      Information : External_Message renames This.Information;
      Tolerance   : Tolerances_Array renames This.Tolerance;
      Actuator    : External_Message renames This.Actuator;
      Trend       : Integrations_Array renames This.Trend;
   begin
      This.Specificity_Log := 0.0;
      for I in 1 .. This.Size loop
         if Tolerance (I) = Zero_Plus then
            This.Specificity_Log := This.Specificity_Log + Zero_Plus_Log;
         elsif Tolerance (I) = Positive_Infinity then
            This.Specificity_Log := This.Specificity_Log + Positive_Infinity_Log;
         else
            Maximize_Information (Actuator (I),
                                  Fitting_Nbr,
                                  Information (I),
                                  Tolerance (I),
                                  This.Specificity_Log,
                                  Trend (I).Squares_Sum,
                                  Trend (I).Values_Sum);
         end if;
      end loop;
   end Maximize;

   function Measure_Distance (This : in External_Premise;
                              Sign : in External_Message) return Positive_Real is
      use Integrations_Arrays.Integrations_Arrays_Instance;
      Information : External_Message renames This.Information;
      Tolerance   : Tolerances_Array renames This.Tolerance;
      Distance    : Positive_Real := 0.0;
   begin
      for I in 1 .. This.Size loop
         if Tolerance (I) = Zero_Plus then
            if Information (I) /= Sign (I) then
               return Positive_Infinity;
            end if;
         elsif Tolerance (I) /= Positive_Infinity then
            Distance := Distance + (Information (I) - Sign (I)) ** 2 / Tolerance (I);
         end if;
      end loop;
      return Distance;
   exception
      when Constraint_Error =>
         return Positive_Infinity;
   end Measure_Distance;

   procedure Note (This : in out External_Premise;
                   Sign : in External_Message) is
   begin
      This.Actuator (1 .. This.Size) := Sign (1 .. This.Size);
   end Note;

end Nomo.Interpreter.External_Messages.Premises;
