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

package body Nomo.Interpreter.Internal_Events_Index.Registers.Plant is

   function Get_Before_Arrival_Time (This     : in Internal_Register;
                                     Position : in Internal_Event_Index) return Positive_Time is
      use Internal_Events;
      I     : Internal_Event_Index := Position;
      Model : Internal_Event renames This.Trace (Position);
   begin
      while This.Most_Ancient_Evidence /= I loop
         I := Internal_Event_Index (Integer (I - 1) mod Index_Modulo);
         if not This.Trace (I).Is_Equal (Model) then
            return This.Trace (I).Get_Arrival_Time;
         end if;
      end loop;
      return Positive_Time'Last;
   end Get_Before_Arrival_Time;

   function Get_Same_Arrival_Time (This     : in Internal_Register;
                                   Position : in Internal_Event_Index) return Positive_Time is
      use Internal_Events;
      I     : Internal_Event_Index := Position;
      Model : Internal_Event renames This.Trace (Position);
   begin
      while This.Most_Ancient_Evidence /= I loop
         I := Internal_Event_Index (Integer (I - 1) mod Index_Modulo);
         if This.Trace (I).Is_Equal (Model) then
            return This.Trace (I).Get_Arrival_Time;
         end if;
      end loop;
      return Positive_Time'Last;
   end Get_Same_Arrival_Time;

   function Get_First_Arrival_Time (This     : in Internal_Register;
                                    Position : in Internal_Event_Index) return Positive_Time is
   begin
      return This.Trace (Get_First_Position (This, Position)).Get_Arrival_Time;
   end Get_First_Arrival_Time;

   function Get_Last_Arrival_Time (This     : in Internal_Register;
                                   Position : in Internal_Event_Index) return Positive_Time is
   begin
      return This.Trace (Get_Last_Position (This, Position)).Get_Arrival_Time;
   end Get_Last_Arrival_Time;

   function Get_After_Arrival_Time (This     : in Internal_Register;
                                    Position : in Internal_Event_Index) return Positive_Time is
      use Internal_Events;
      I     : Internal_Event_Index := Position;
      Model : Internal_Event renames This.Trace (Position);
   begin
      while This.Current_Evidence /= I loop
         I := Internal_Event_Index (Natural (I + 1) mod Index_Modulo);
         if not This.Trace (I).Is_Equal (Model) then
            return This.Trace (I).Get_Arrival_Time;
         end if;
      end loop;
      return Positive_Time'Last;
   end Get_After_Arrival_Time;

   function Get_Before_Position (This     : in Internal_Register;
                                 Position : in Internal_Event_Index) return Internal_Event_Index is
      use Internal_Events;
      First : constant Internal_Event_Index := Get_First_Position (This, Position);
   begin
      if This.Most_Ancient_Evidence = First then
         return Position;
      else
         return Internal_Event_Index (Integer (First - 1) mod Index_Modulo);
      end if;
   end Get_Before_Position;

   function Get_First_Position (This     : in Internal_Register;
                                Position : in Internal_Event_Index) return Internal_Event_Index is
      use Internal_Events;
      I     : Internal_Event_Index := Position;
      Model : Internal_Event renames This.Trace (Position);
   begin
      while This.Most_Ancient_Evidence /= I loop
         I := Internal_Event_Index (Integer (I - 1) mod Index_Modulo);
         if not This.Trace (I).Is_Equal (Model) then
            I := Internal_Event_Index (Natural (I + 1) mod Index_Modulo);
            exit;
         end if;
      end loop;
      return I;
   end Get_First_Position;

   function Get_Same_Position (This     : in Internal_Register;
                               Position : in Internal_Event_Index) return Internal_Event_Index is
      use Internal_Events;
      I     : Internal_Event_Index := Position;
      Model : Internal_Event renames This.Trace (Position);
   begin
      while This.Most_Ancient_Evidence /= I loop
         I := Internal_Event_Index (Integer (I - 1) mod Index_Modulo);
         if This.Trace (I).Is_Equal (Model) then
            return I;
         end if;
      end loop;
      return Position;
   end Get_Same_Position;

   function Get_Last_Position (This     : in Internal_Register;
                               Position : in Internal_Event_Index) return Internal_Event_Index is
      use Internal_Events;
      I     : Internal_Event_Index := Position;
      Model : Internal_Event renames This.Trace (Position);
   begin
      while This.Current_Evidence /= I loop
         I := Internal_Event_Index (Natural (I + 1) mod Index_Modulo);
         if not This.Trace (I).Is_Equal (Model) then
            I := Internal_Event_Index (Integer (I - 1) mod Index_Modulo);
            exit;
         end if;
      end loop;
      return I;
   end Get_Last_Position;

   function Get_After_Position (This     : in Internal_Register;
                               Position : in Internal_Event_Index) return Internal_Event_Index is
      use Internal_Events;
      Last : constant Internal_Event_Index := Get_Last_Position (This, Position);
   begin
      if This.Current_Evidence = Last then
         return Position;
      else
         return Internal_Event_Index (Natural (Last + 1) mod Index_Modulo);
      end if;
   end Get_After_Position;

   procedure Get_Current_Event (This         : in Internal_Register;
                                Position     : out Internal_Event_Index;
                                Arrival_Time : out Positive_Time) is
   begin
      if  This.Current_Evidence /= Internal_Event_Index (Integer (This.Most_Ancient_Evidence - 1) mod Index_Modulo) then
         Position := This.Current_Evidence;
         Arrival_Time := This.Trace (This.Current_Evidence).Get_Arrival_Time;
      else
         Arrival_Time := Positive_Time'First;
      end if;
   end Get_Current_Event;

   procedure Get_Event (This          : in Internal_Register;
                        Arrival_Time  : in out Positive_Time;
                        Internal_Data : out Internal_Message)  is
      Low_Index    : Integer := Integer (Internal_Event_Index'First) + Integer (This.Current_Evidence) + 1;
      High_Index   : Integer := Integer (Internal_Event_Index'Last) + Integer (This.Current_Evidence) + 1;
      Median_Index : Integer;
      Final_Index  : Internal_Event_Index;
      Value        : Positive_Time;
   begin
      while Low_Index <= High_Index loop
         Median_Index := (Low_Index + High_Index) / 2;
         Value := This.Trace (Internal_Event_Index (Median_Index mod Integer (Index_Modulo))).Get_Arrival_Time;
         if Arrival_Time < Value then
            High_Index := Median_Index - 1;
         elsif Arrival_Time > Value then
           Low_Index := Median_Index + 1;
         else
            exit;
         end if;
      end loop;
      Final_Index := Internal_Event_Index ( (Median_Index mod Integer (Index_Modulo)));
      if This.Trace (Final_Index).Get_Arrival_Time > Arrival_Time then
        Final_Index := Internal_Event_Index ( ( (Median_Index - 1) mod Integer (Index_Modulo)));
      end if;
      Arrival_Time := This.Trace (Final_Index).Get_Arrival_Time;
      This.Trace (Final_Index).Get_Event (Arrival_Time, Internal_Data);
   end Get_Event;

   procedure Get_Event (This          : in Internal_Register;
                        Position      : in Internal_Event_Index;
                        Internal_Data : out Internal_Message) is
   begin
      This.Trace (Position).Get_Content (Internal_Data);
   end Get_Event;

   procedure Get_Event (This          : in Internal_Register;
                        Position      : in Internal_Event_Index;
                        Arrival_Time  : out Positive_Time;
                        Internal_Data : out Internal_Message) is
   begin
      This.Trace (Position).Get_Event (Arrival_Time, Internal_Data);
   end Get_Event;

end Nomo.Interpreter.Internal_Events_Index.Registers.Plant;
