--  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.Plant.Operators;

with Nomo.Interpreter.Plant.Errors_Manager;

package body Nomo.Interpreter.Plant.Affectation_Manager is

   type Transition is (Free, Affected, Affected_And_Referenced);

   Chunks_Transition : array (Chunk_Index) of Transition := (others => Free);

   Affectations_Number         : Natural := 0;
   Affected_Chunk              : Natural := 0;
   Targeted_Internal_Chunk     : Natural := 0;
   Affected_Perception_Chunk   : Natural := 0;
   Reference_Chunk             : Natural := 0;
   Reference_And_Origin_Mod    : Boolean;
   type Complex_Affectation is (Required, Prohibed, Optional);
   Complex_Affectation_Sensory : Complex_Affectation := Prohibed;
   Perception_Is_Arrive        : Boolean := False;

   procedure New_Update is
   begin
      Affected_Chunk := 0;
      Targeted_Internal_Chunk := 0;
      Affectations_Number := 0;
      Complex_Affectation_Sensory := Prohibed;
      Perception_Is_Arrive := False;
   end New_Update;

   procedure New_Affectation is
   begin
      Affected_Perception_Chunk := 0;
      Reference_Chunk := 0;
      Chunks_Transition := (others => Free);
   end New_Affectation;

   procedure Set_Internal_Operation (I         : in Internal_Chunk_Index;
                                     Operation : in Positive) is
      use Errors_Manager;
      use Operators.Operators_Description;
   begin
      case Operator_Without_Parameter'Val(Operation) is
         when Create_Perception_Forward =>
            if Affected_Perception_Chunk = 0 then
               if Chunks_Transition (I) = Free then
                  Chunks_Transition (I) := Affected;
                  Affected_Chunk := Natural (I);
                  Affected_Perception_Chunk := Natural (I);
                  Perception_Is_Arrive := True;
                  if Chunks_Transition (1) = Free then
                     Chunks_Transition (1) := Affected;
                  end if;
                  Affectations_Number := Affectations_Number + 1;
               else
                  Error (ALREADY_AFFECTED_CHUNK);
               end if;
            else
               Error (ALREADY_CREATED_PERCEPTION);
            end if;
         when Transform_In_Temporal_Reference | Transform_In_Temporal_Reference_And_Origin =>
            if Reference_Chunk = 0 then
               Chunks_Transition (I) := Affected_And_Referenced;
               Reference_Chunk := Natural (I);
               Reference_And_Origin_Mod := Transform_In_Temporal_Reference_And_Origin = Operator_Without_Parameter'Val( Operation);
            else
               Error (ALREADY_DESIGNED_REFERENCE_TIME);
            end if;
         when Backward | Forward | Same_Time =>
               Chunks_Transition (I) := Affected;
               Affected_Chunk := Natural (I);
               Affectations_Number := Affectations_Number + 1;
         when Target =>
            if Targeted_Internal_Chunk = 0 then
               Targeted_Internal_Chunk := Natural (I);
               if Chunks_Transition (I) = Free then
                  Chunks_Transition (I) := Affected;
               end if;
            else
               Error (ALREADY_DESIGNED_TARGET);
            end if;
         when others =>
            if Chunks_Transition (I) = Free then
               Chunks_Transition (I) := Affected;
               Affected_Chunk := Natural (I);
               Affectations_Number := Affectations_Number + 1;
            else
               Error (ALREADY_AFFECTED_CHUNK);
            end if;
      end case;
   end Set_Internal_Operation;

   procedure Set_External_Operation (Operation : in Positive) is
      use Errors_Manager;
      use Operators.Operators_Description;
   begin
      case Operator_Without_Parameter'Val( Operation) is
         when Get_There =>
            if Chunks_Transition (1) = Free then
               Chunks_Transition (1) := Affected;
               Affected_Chunk := 1;
               Affectations_Number := Affectations_Number + 1;
            else
               Error (ALREADY_AFFECTED_CHUNK);
            end if;
            Complex_Affectation_Sensory := Optional;
         when Get_Before | Get_First | Get_Last | Get_After =>
            Complex_Affectation_Sensory := Required;
            if Chunks_Transition (1) = Free then
               Chunks_Transition (1) := Affected;
               Affected_Chunk := 1;
               Affectations_Number := Affectations_Number + 1;
            else
               Error (ALREADY_AFFECTED_CHUNK);
            end if;
         when Transform_In_Temporal_Reference | Transform_In_Temporal_Reference_And_Origin =>
            if Reference_Chunk = 0 then
               Chunks_Transition (1) := Affected_And_Referenced;
               Reference_Chunk := 1;
               Reference_And_Origin_Mod := Transform_In_Temporal_Reference_And_Origin = Operator_Without_Parameter'Val( Operation);
            else
               Error (ALREADY_DESIGNED_REFERENCE_TIME);
            end if;
         when Target =>
            Error (ILLEGAL_TARGET_IN_EXTERNAL_CHUNK);
         when others =>
            Error (ILLEGAL_CREATION_OPERATOR_IN_EXTERNAL_CHUNK);
      end case;
   end Set_External_Operation;

   function Has_Targeted_Internal return Boolean is
      use Errors_Manager;
   begin
      if Targeted_Internal_Chunk /= 0 then
         if Affectations_Number = 1 then
            return True;
         elsif Affectations_Number > 1 then
            Error (OVER_AFFECTATION_OPERATORS_FOR_TARGET);
         elsif Affectations_Number = 0 then
            Error (AFFECTATION_EXPIRED_OPERATOR_FOR_TARGET);
         end if;
      elsif Complex_Affectation_Sensory = Required then
         Error (AFFECTATION_EXPIRED_OPERATOR_FOR_TARGET);
      else
         return False;
      end if;
      return False;
   end Has_Targeted_Internal;

   function Get_Affected_Chunk return Chunk_Index is
   begin
      return Chunk_Index (Affected_Chunk);
   end Get_Affected_Chunk;

   function Get_Created_Perception return Internal_Chunk_Index is
   begin
      return Chunk_Index (Affected_Perception_Chunk);
   end Get_Created_Perception;

   function Get_Targeted_Internal_Chunk return Internal_Chunk_Index is
   begin
      return Chunk_Index (Targeted_Internal_Chunk);
   end Get_Targeted_Internal_Chunk;

   function Get_Reference_Chunk return Chunk_Index is
   begin
      return Chunk_Index (Reference_Chunk);
   end Get_Reference_Chunk;

   function Has_Reference_Chunk return Boolean is
   begin
      return Reference_Chunk /= 0;
   end Has_Reference_Chunk;

   function Is_Reference_And_Origin return Boolean is
   begin
      return Reference_And_Origin_Mod;
   end Is_Reference_And_Origin;

   function Perception_Is_Present return Boolean is
   begin
      return Perception_Is_Arrive;
   end Perception_Is_Present;

end Nomo.Interpreter.Plant.Affectation_Manager;
