--  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.Elementary_Functions;

package body Nomo.Internal_Messages.Premises is

   function Get_Specificity_Log (This : in Internal_Premise) return Real_Accurately is
   begin
      return This.Specificity_Log;
   end Get_Specificity_Log;

   procedure Maximize_Time_Span (Actuator        : in Time_Interval;
                                 Fitting_Nbr     : in Count;
                                 Time_Span       : out Time_Interval;
                                 Specificity_Log : in out Real_Accurately;
                                 Squares_Sum     : in out Positive_Time;
                                 Tolerance       : in out Strictly_Positive_Real;
                                 Values_Sum      : in out Time) is
      use Numerics.Reals.Elementary_Functions;
      Temp : Positive_Time_Interval;
   begin
      Values_Sum := Values_Sum + Time (Actuator);
      Time_Span := Time_Interval(Values_Sum / Time (Fitting_Nbr));
      Squares_Sum := Squares_Sum + Time(Actuator ** 2);
      Temp := Time_Interval(Squares_Sum / Time (Fitting_Nbr)) - Time_Span ** 2;
      if Temp > 0 then
         Specificity_Log := Specificity_Log - Log_10 (Tolerance / 2.0);
         Tolerance := Strictly_Positive_Real (Temp * 2);
         Specificity_Log := Specificity_Log + Log_10 (Real (Temp));
      end if;
   end Maximize_Time_Span;

   procedure Maximize_Credibility (Actuator        : in Real_0_To_1;
                                   Fitting_Nbr     : in Count;
                                   Credibility     : out Real_0_To_1;
                                   Specificity_Log : in out Real_Accurately;
                                   Squares_Sum     : in out Long_Real;
                                   Tolerance       : in out Strictly_Positive_Real;
                                   Values_Sum      : in out Long_Real) is
      use Numerics.Reals.Elementary_Functions;
      Temp : Positive_Real;
   begin
      Values_Sum := Values_Sum + Long_Real (Actuator);
      Credibility := Real_0_To_1 (Values_Sum / Long_Real (Fitting_Nbr));
      Squares_Sum := Squares_Sum + Long_Real (Actuator ** 2);
      Temp := Positive_Real (Squares_Sum / Long_Real (Fitting_Nbr)) - Credibility ** 2;
      if Temp > Zero_Plus then
         Specificity_Log := Specificity_Log - Log_10 (Tolerance / 2.0);
         Tolerance := Temp * 2.0;
         Specificity_Log := Specificity_Log + Log_10 (Temp);
      end if;
   end Maximize_Credibility;

   procedure Maximize (This        : in out Internal_Premise;
                       Fitting_Nbr : in Count) is
      use Numerics.Reals.Elementary_Functions;
   begin
      case This.Tolerance is
         when IN_TN_CN .. II_TI_CI =>
            null;
         when II_TI_CP .. IN_TN_CP =>
            Maximize_Credibility (This.Actuator_Credibility,
                                  Fitting_Nbr,
                                  This.Credibility ,
                                  This.Specificity_Log,
                                  This.Credibility_Trend.Squares_Sum,
                                  This.Credibility_Tolerance,
                                  This.Credibility_Trend.Values_Sum);
         when II_TP_CP .. IN_TP_CP =>
            Maximize_Time_Span (This.Actuator_Time_Span,
                                Fitting_Nbr,
                                This.Time_Span,
                                This.Specificity_Log,
                                This.Time_Span_Trend.Squares_Sum,
                                This.Time_Span_Tolerance,
                                This.Time_Span_Trend.Values_Sum);
            Maximize_Credibility (This.Actuator_Credibility ,
                                  Fitting_Nbr,
                                  This.Credibility ,
                                  This.Specificity_Log,
                                  This.Credibility_Trend.Squares_Sum,
                                  This.Credibility_Tolerance,
                                  This.Credibility_Trend.Values_Sum);
         when II_TP_CN .. IN_TP_CI =>
            Maximize_Time_Span (This.Actuator_Time_Span,
                                Fitting_Nbr,
                                This.Time_Span,
                                This.Specificity_Log,
                                This.Time_Span_Trend.Squares_Sum,
                                This.Time_Span_Tolerance,
                                This.Time_Span_Trend.Values_Sum);
      end case;
   end Maximize;

   function Measure_Distance (This : in Internal_Premise;
                              Sign : in Internal_Message) return Positive_Real is
      Answer : Positive_Real := Positive_Infinity;
   begin
      case This.Tolerance is
         when IN_TN_CN =>
            if This.Information = Sign.Information and This.Time_Span = Sign.Time_Span and This.Credibility = Sign.Credibility then
               Answer := 0.0;
            end if;
         when IN_TN_CP =>
            if This.Information = Sign.Information and This.Time_Span = Sign.Time_Span then
               Answer := (This.Credibility - Sign.Credibility) ** 2 / This.Credibility_Tolerance;
            end if;
         when IN_TN_CI =>
            if This.Information = Sign.Information and This.Time_Span = Sign.Time_Span then
               Answer := 0.0;
            end if;
         when IN_TP_CN =>
            if This.Information = Sign.Information and This.Credibility = Sign.Credibility then
               Answer := Real ((This.Time_Span - Sign.Time_Span) ** 2) / This.Time_Span_Tolerance;
            end if;
         when IN_TP_CP =>
            if This.Information = Sign.Information then
               Answer := Real ((This.Time_Span - Sign.Time_Span) ** 2) / This.Time_Span_Tolerance
                 + (This.Credibility - Sign.Credibility) ** 2 / This.Credibility_Tolerance;
            end if;
         when IN_TP_CI =>
            if This.Information = Sign.Information then
               Answer := Real ((This.Time_Span - Sign.Time_Span) ** 2) / This.Time_Span_Tolerance;
            end if;
         when IN_TI_CN =>
            if This.Information = Sign.Information and This.Credibility = Sign.Credibility then
               Answer := 0.0;
            end if;
         when IN_TI_CP =>
            if This.Information = Sign.Information then
               Answer := (This.Credibility - Sign.Credibility) ** 2 / This.Credibility_Tolerance;
            end if;
         when IN_TI_CI =>
            if This.Information = Sign.Information then
               Answer := 0.0;
            end if;
         when II_TN_CN =>
            if This.Time_Span = Sign.Time_Span and This.Credibility = Sign.Credibility then
               Answer := 0.0;
            end if;
         when II_TN_CP =>
            if This.Time_Span = Sign.Time_Span then
               Answer := (This.Credibility - Sign.Credibility) ** 2 / This.Credibility_Tolerance;
            end if;
         when II_TN_CI =>
            if This.Time_Span = Sign.Time_Span then
               Answer := 0.0;
            end if;
         when II_TP_CN =>
            if This.Credibility = Sign.Credibility then
               Answer := Real ((This.Time_Span - Sign.Time_Span) ** 2) / This.Time_Span_Tolerance;
            end if;
         when II_TP_CP =>
            Answer := Real ((This.Time_Span - Sign.Time_Span) ** 2) / This.Time_Span_Tolerance
              + (This.Credibility - Sign.Credibility) ** 2 / This.Credibility_Tolerance;
         when II_TP_CI =>
            Answer := Real ((This.Time_Span - Sign.Time_Span) ** 2) / This.Time_Span_Tolerance;
         when II_TI_CN =>
            if This.Credibility = Sign.Credibility then
               Answer := 0.0;
            end if;
         when II_TI_CP =>
            Answer := (This.Credibility - Sign.Credibility) ** 2 / This.Credibility_Tolerance;
         when II_TI_CI =>
            Answer := 0.0;
      end case;
      return Answer;
   exception
      when CONSTRAINT_ERROR =>
         return Positive_Infinity;
   end Measure_Distance;

   procedure Note (This : out Internal_Premise;
                   Sign : in Internal_Message) is
   begin
      This.Actuator_Time_Span := Sign.Time_Span;
      This.Actuator_Credibility := Sign.Credibility;
   end Note;

end Nomo.Internal_Messages.Premises;
