--  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.Interpreter.Beginning.Parameters;
pragma Elaborate_All (Nomo.Interpreter.Beginning.Parameters);

package body Nomo.Interpreter.Activations_Memories is

   procedure Set_Activation_Last (This          : in out Activations_Memory;
                                  Selected_Rule : not null Rule_Ptr;
                                  T             : in Positive_Time) is
      Activation_Last : constant Activation_Index := (This.Activation_Last + 1) mod Index_Modulo;
   begin
      This.Activation_Last := Activation_Last;
      This.Trace (Activation_Last).Selected_Rule := Selected_Rule;
      This.Trace (Activation_Last).Time := T;
   end Set_Activation_Last;

   function Search_Succ (This     : in Activations_Memory;
                         T        : in Positive_Time;
                         Landmark : in Positive_Time_Interval) return Integer;
   pragma Precondition (Landmark > 0);
   pragma Postcondition (Search_Succ'Result in Activation_Index or Search_Succ'Result = Failure);

   function Search_Succ (This     : in Activations_Memory;
                         T        : in Positive_Time;
                         Landmark : in Positive_Time_Interval) return Integer is
      Low_Index     : Integer := Activation_Index'First + This.Activation_Last;
      High_Index    : Integer := Activation_Index'Last + This.Activation_Last;
      Median_Index  : Natural;
      Value         : Positive_Time;
      Trace         : Activations_Trace renames This.Trace;
      Landmark_Time : constant Positive_Time := T - Positive_Time(Landmark);
   begin
      while Low_Index <= High_Index loop
         Median_Index := (Low_Index + High_Index) / 2;
         Value := Trace (Median_Index mod Index_Modulo).Time;
         if Landmark_Time = Value then
            return Median_Index mod Index_Modulo;
         elsif Landmark_Time < Value then
            High_Index := Median_Index - 1;
         else
            Low_Index := Median_Index + 1;
         end if;
      end loop;
      if Low_Index /= Activation_Index'Last + This.Activation_Last + 1 then
         return Median_Index mod Index_Modulo;
      else
         return Failure;
      end if;
   end Search_Succ;

   function Search_Pred (This     : in Activations_Memory;
                         T        : in Positive_Time;
                         Landmark : in Positive_Time_Interval) return Integer;
   pragma Precondition (Landmark > 0);
   pragma Postcondition (Search_Pred'Result in Activation_Index or Search_Pred'Result = Failure);

   function Search_Pred (This     : in Activations_Memory;
                         T        : in Positive_Time;
                         Landmark : in Positive_Time_Interval) return Integer is
      Median_Index  : Natural;
      Trace         : Activations_Trace renames This.Trace;
      Landmark_Time : constant Positive_Time := T - Positive_Time (Landmark); -- + Beginning.Parameters.Get_Period;
   begin
      Median_Index := Search_Succ (This, T, Landmark);
      if Median_Index = Failure then
         Median_Index := This.Activation_Last;
         if T - This.Trace (Median_Index).Time < Duration_Limit then
            return Median_Index;
         else
            return Failure;
         end if;
      else
         if Landmark_Time = Trace (Median_Index).Time then
            return Median_Index;
         elsif T - This.Trace ((Median_Index - 1) mod Index_Modulo).Time < Duration_Limit then
            return (Median_Index - 1) mod Index_Modulo;
         else
            return Failure;
         end if;
      end if;
   end Search_Pred;

   procedure Reinforce_Zone (This           : in Activations_Memory;
                             T              : in Positive_Time;
                             First_Landmark : in Positive_Time_Interval;
                             Last_Landmark  : in Positive_Time_Interval;
                             Quantity       : in Real_Accurately_0_To_1);
   pragma Precondition (First_Landmark >= Last_Landmark);
   pragma Precondition (First_Landmark > 0 and Last_Landmark > 0);

   procedure Reinforce_Zone (This           : in Activations_Memory;
                             T              : in Positive_Time;
                             First_Landmark : in Positive_Time_Interval;
                             Last_Landmark  : in Positive_Time_Interval;
                             Quantity       : in Real_Accurately_0_To_1) is
      I : Integer := Search_Succ (This, T, First_Landmark);
   begin
      if I /= Failure then
         declare
            Last : constant Integer := Search_Pred (This, T, Last_Landmark);
         begin
            if Last /= Failure then
               loop
                  Reinforce (This.Trace (I).Selected_Rule.all, Quantity);
                  exit when I = Last;
                  I := (I + 1) mod Index_Modulo;
               end loop;
            end if;
         end;
      end if;
   end Reinforce_Zone;

   procedure Reinforce (This            : in Activations_Memory;
                        T               : in Positive_Time;
                        First_Landmark  : in Positive_Time_Interval;
                        Second_Landmark : in Positive_Time_Interval;
                        Quantity        : in Real_Accurately_0_To_1) is
   begin
      if First_Landmark = 0 and Second_Landmark = 0 then
         if T - This.Trace (This.Activation_Last).Time < Duration_Limit then
            Reinforce (This.Trace (This.Activation_Last).Selected_Rule.all, Quantity);
         end if;
      elsif First_Landmark = 0 then
         declare
            Index : constant Integer := Search_Succ (This, T, Second_Landmark);
         begin
            if Index /= Failure then
               Reinforce (This.Trace (Index).Selected_Rule.all, Quantity);
            end if;
         end;
      elsif Second_Landmark = 0 then
         declare
            Index : constant Integer := Search_Succ (This, T, First_Landmark);
         begin
            if Index /= Failure then
               Reinforce (This.Trace (Index).Selected_Rule.all, Quantity);
            end if;
         end;
      else
         Reinforce_Zone (This,
                         T,
                         Positive_Time_Interval'Max(First_Landmark, Second_Landmark),
                         Positive_Time_Interval'Min(First_Landmark, Second_Landmark),
                         Quantity);
      end if;
   end Reinforce;

end Nomo.Interpreter.Activations_Memories;
