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

with Nomo.Interpreter.Plant.Errors_Manager;

with Nomo.Interpreter.Types_Directory.Relations;

with Nomo.Internal_Messages.Plant;

with Nomo.Interpreter.Storages.Plant;

with Nomo.Interpreter.Plant.Operators;

package body Nomo.Interpreter.Plant.Rule_Buffer.Writing is

   Duration_Limit : constant Time_Interval := Reader.Parameters.Get_Time_Span_Limit;
   Step : constant Positive_Time_Interval := Time_Interval (Reader.Parameters.Get_Period);

   Arrival_Times  : array (1 .. Reader.Parameters.Get_Maximum_Of_Premises) of Positive_Time;
   Times_Span     : Storages.Plant.Premises_Time_Span;
   Properties     : Storages.Plant.Premises_Property;
   Index_Premises : array (1 .. Reader.Parameters.Get_Maximum_Of_Premises) of Type_Index;
   Premise_Last  : Natural := 0;

   Input_Premise_Present  : Boolean := False;
   Prediction_Created     : Type_Index := 0;
   Prediction_Id          : Natural := 0;
   Prediction_Id_Present  : Boolean := False;
   Landmark_Present       : Boolean := False;
   Perception_Id          : Natural := 0;
   Command_Id             : Natural := 0;
   Input_Arrival_Time     : Positive_Time := 0;
   First_Arrival_Time     : Positive_Time := Positive_Time'Last;
   Temporal_Reference     : Positive_Time := 0;
   Has_Temporal_Reference : Boolean := False;

   procedure Reset_Storage is
      use Storages.Plant;
   begin
      Reset (Buffers (1)'Access);
      Input_Premise_Present := False;
      Prediction_Id_Present := False;
      Landmark_Present := False;
      First_Arrival_Time := Positive_Time'Last;
      Has_Temporal_Reference := False;
      Premise_Last := 0;
      Buffer_Last := 1;
      Reading_Index  := 1;
   end Reset_Storage;

   procedure Set_Internal_Conclusion (Index               : in Internal_Type_Index;
                                      Internal_Conclusion : in Internal_Message;
                                      Arrival_Time        : in Positive_Time;
                                      Operator            : in Positive;
                                      Is_Reference        : in Boolean) is
      use Plant.Errors_Manager;
      use Internal_Messages.Plant;
      use Types_Directory.Relations;

      function Is_Valid (Time_Span     : in Time_Interval;
                         Premise_Index : in Type_Index) return Boolean;
      function Is_Valid (Time_Span     : in Time_Interval;
                         Premise_Index : in Type_Index) return Boolean is
         Result : Boolean := True;
      begin
         if Time_Span > Duration_Limit then
            Error (OVER_TIME_SPAN_FOR_INTERNAL_CONDITION);
            Result := False;
         elsif Time_Span < 0 and then not (Premise_Index in Prediction_Type_Index or Premise_Index in Conception_Type_Index) then
            Error (ILLEGAL_REFERENCE_FOR_INTERNAL_CONDITION);
            Result := False;
         end if;
         return Result;
      end Is_Valid;

      Time_Span : Time_Interval := 0;
   begin
      if Has_Temporal_Reference then
         Time_Span := Time_Interval (Temporal_Reference - Arrival_Time);
         if Operators.Internal_Operators_With_Parameters(Operator).Time_Span_Imposed then
            Time_Span := Operators.Internal_Operators_With_Parameters(Operator).Time_Span_Value;
         else
            if not Is_Reference then
               Time_Span := Time_Span + Step;
            end if;
            for I in 1 .. Premise_Last loop
               if Arrival_Times (I) = Arrival_Time and then not (Index_Premises (I) in Prediction_Type_Index or Index_Premises (I) in Conception_Type_Index) then
                  Error (SAME_TIME_CONDITION_CONCLUSION);
                  exit;
               end if;
            end loop;
         end if;
      else
         Temporal_Reference := Arrival_Time;
      end if;
      if Time_Span > 0 then
         Error (BAD_REFERENCE_TIME);
      else
         if Index in Perception_Type_Index then
            if Perception_Id >= Natural(Get_Information (Internal_Conclusion)) then
               Error (NO_UNIQUE_PERCEPTION_CONCLUSION);
            elsif Input_Premise_Present then
               if Time_Span /= 0 then
                  Error (ILLEGAL_REFERENCE_FOR_PERCEPTION_CONCLUSION);
               elsif Storages.Plant.Has_Input_Premise (Buffers (1)'Access, Index) then
                  Error (FAIL_LINKED_PERCEPTION);
               end if;
               if Is_Reference then
                  if Temporal_Reference - Input_Arrival_Time /= Positive_Time (Step) then
                     Error (ILLEGAL_REFERENCE_FOR_INPUT_CONDITION);
                  end if;
               else
                  if Temporal_Reference - Input_Arrival_Time /= 0 then
                     Error (ILLEGAL_REFERENCE_FOR_INPUT_CONDITION);
                  end if;
               end if;
            else
               Error (INPUT_PREMISE_MISSING);
            end if;
            Perception_Id := Natural (Get_Information (Internal_Conclusion));
         elsif Time_Span < 0 and then not (Index in Conception_Type_Index or Index in Prediction_Type_Index) then
            Error (ILLEGAL_REFERENCE_FOR_INTERNAL_CONCLUSION);
         elsif Index in Check_Type_Index then
            if Prediction_Created in Prediction_Type_Index then
               if Index = Get_Check_Type (Prediction_Created) then
                  if not Landmark_Present then
                     Error (MISSING_LANDMARK_RULE);
                  elsif not(Prediction_Id_Present) then
                     Error (FAIL_PREDICTION_ID_IN_CHECKING_RULE);
                  elsif Prediction_Id /= Natural (Get_Information (Internal_Conclusion)) then
                     Error (FAIL_LANDMARK_ID);
                  else
                     Prediction_Created := 0;
                  end if;
               else
                  Error (FAIL_PREDICTIVE_TYPE_IN_CHECKING_RULE);
               end if;
            else
               Error (MISSING_PREDICTIVE_RULE);
            end if;
         elsif Index in Landmark_Type_Index then
            if Has_Check_Type (Index) then
               if Prediction_Created in Prediction_Type_Index then
                  if Prediction_Created /= Get_Prediction_Type (Index) then
                     Error (FAIL_LANDMARK_TYPE);
                  elsif Prediction_Id /= Natural (Get_Information (Internal_Conclusion)) then
                     Error (FAIL_LANDMARK_ID);
                  end if;
               else
                  Error (MISSING_PREDICTIVE_RULE);
               end if;
            end if;
         elsif Index in Anomaly_Type_Index then
            Error (FORBIDDEN_REFLECTION_RETURN_IN_CONCLUSION);
         elsif Index in Command_Type_Index then
            if Command_Id >= Natural (Get_Information (Internal_Conclusion)) then
               Error (NO_UNIQUE_COMMAND_CONCLUSION);
            elsif Perception_Id /= Natural (Get_Information (Internal_Conclusion)) then
               Error (FAIL_PERCEPTION_OF_COMMAND_CONCLUSION);
            else
               Command_Id := Natural (Get_Information (Internal_Conclusion));
            end if;
         elsif Index in Prediction_Type_Index then
            if Prediction_Id >= Natural (Get_Information (Internal_Conclusion)) then
               Error (NO_UNIQUE_PREDICTION_CONCLUSION);
            else
               Prediction_Created := Index;
               Prediction_Id := Natural (Get_Information (Internal_Conclusion));
            end if;
         end if;
         if Has_Temporal_Reference then
            if Is_Reference then
               for I in 1 .. Premise_Last loop
                  if Arrival_Times (I) /= 0 then
                     Times_Span (I) := Time_Interval(Temporal_Reference - Arrival_Times (I)) - Step;
                     if not Is_Valid (Times_Span (I), Index_Premises (I)) then
                        exit;
                     end if;
                  end if;
               end loop;
            else
               for I in 1 .. Premise_Last loop
                  if Arrival_Times (I) /= 0 then
                     Times_Span (I) := Time_Interval(Temporal_Reference - Arrival_Times (I));
                     if not Is_Valid (Times_Span (I), Index_Premises (I)) then
                        exit;
                     end if;
                  end if;
               end loop;
            end if;
         else
            for I in 1 .. Premise_Last loop
               if Arrival_Times (I) /= 0 then
                  Times_Span (I) := Time_Interval(Arrival_Time - Arrival_Times (I)) - Step;
                  if not Is_Valid (Times_Span (I), Index_Premises (I)) then
                     exit;
                  end if;
               end if;
            end loop;
         end if;
         Buffer_Last := Buffer_Last + 1;
         Storages.Plant.Copy_Condition (Buffers (Buffer_Last)'Access, Buffers (1)'Access, Input_Premise_Present or Index in Command_Type_Index);
         Storages.Plant.Set_Internal_Conclusion (Buffers (Buffer_Last), Index, Internal_Conclusion, Time_Span, Operator, Times_Span, Properties);
      end if;
   end Set_Internal_Conclusion;

   procedure Set_Internal_Condition (Index              : in Internal_Type_Index;
                                     Internal_Condition : in Internal_Messages.Internal_Message;
                                     Arrival_Time       : in Positive_Time;
                                     Property           : in Premise_Property;
                                     Operator           : in Positive) is
      use Plant.Errors_Manager;
      use Internal_Messages.Plant;
      use Types_Directory.Relations;
   begin
      if Storages.Plant.Internal_Condition_Is_Full (Buffers (1)'Access) then
         Error (OVER_INTERNAL_PREMISES_NUMBER);
      else
         if Index in Prediction_Type_Index then
            if Prediction_Created = Index and then Prediction_Id = Natural (Get_Information (Internal_Condition)) then
               Prediction_Id_Present := True;
            end if;
         elsif Index in Landmark_Type_Index then
            if Prediction_Created = Get_Prediction_Type (Index) and then Prediction_Id = Natural (Get_Information (Internal_Condition)) then
               Landmark_Present := True;
            end if;
         end if;
         Premise_Last := Premise_Last + 1;
         Index_Premises (Premise_Last) := Index;
         if Operators.Internal_Operators_With_Parameters (Operator).Time_Span_Imposed then
            Arrival_Times (Premise_Last) := 0;
            Times_Span (Premise_Last) := Operators.Internal_Operators_With_Parameters (Operator).Time_Span_Value;
         else
            Arrival_Times (Premise_Last) := Arrival_Time;
         end if;
         Properties (Premise_Last) := Property;
         Storages.Plant.Set_Internal_Condition (Buffers (1)'Access,
                                                Index,
                                                Internal_Condition,
                                                Operator);
      end if;
   end Set_Internal_Condition;

   procedure Set_External_Condition (Index              : in Input_Type_Index;
                                     External_Condition : in External_Message;
                                     Arrival_Time       : in Positive_Time;
                                     Operator           : in Positive) is
      use Plant.Errors_Manager;
   begin
      Input_Arrival_Time := Arrival_Time;
      if Input_Premise_Present then
         Error (OVER_INPUT_PREMISE);
      else
         Input_Premise_Present := True;
         if First_Arrival_Time > Arrival_Time then
            First_Arrival_Time := Arrival_Time;
         end if;
         Storages.Plant.Set_External_Condition (Buffers (1)'Access, Index, External_Condition, Operator);
      end if;
   end Set_External_Condition;

   procedure Set_Temporal_Reference (Reference_Time : in Positive_Time) is
   begin
      Temporal_Reference := Reference_Time;
      Has_Temporal_Reference := True;
   end Set_Temporal_Reference;

end Nomo.Interpreter.Plant.Rule_Buffer.Writing;
