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

with Nomo.Interpreter.Types_Index.Ranges;

with Nomo.Interpreter.Reception_Channel;

with Nomo.Interpreter.Log_Manager.Writing;

with Nomo.Interpreter.Beginning.Parameters;
pragma Elaborate_All (Nomo.Interpreter.Beginning.Parameters);

with Nomo.Interpreter.Event_Memory.Internal.Updating;
pragma Elaborate_All (Nomo.Interpreter.Event_Memory.Internal.Updating);

with Nomo.Interpreter.Event_Memory.External.Updating;
pragma Elaborate_All (Nomo.Interpreter.Event_Memory.External.Updating);

with Nomo.Interpreter.Epistemic_Memory.Interpretation;
pragma Elaborate_All (Nomo.Interpreter.Epistemic_Memory.Interpretation);

with Nomo.Interpreter.Epistemic_Memory.Logging;
pragma Elaborate_All (Nomo.Interpreter.Epistemic_Memory.Logging);

with Nomo.Interpreter.Epistemic_Memory.Loading;
pragma Elaborate_All (Nomo.Interpreter.Epistemic_Memory.Loading);

with Nomo.Interpreter.Logger;

with Nomo.Reader.Finalization;

with Nomo.Interpreter.Epistemic_Memory.Initialization;

with Nomo.Interpreter.Log_Manager;

with Nomo.Interpreter.Plant.Engine;
pragma Elaborate_All (Nomo.Interpreter.Plant.Engine);

with Nomo.Interpreter.Plant.Rule_Buffer.Reading;

with Ada.Exceptions;

with Ada.Text_IO;

with Ada.Directories;

package body Nomo.Interpreter.Kernel is

   procedure Input (Id   : in Interfaces.C.short;
                    Data : in System.Address) is
      use Types_Index.Ranges;
   begin
      Reception_Channel.This.Put (Input_Type_Index (Id), Data);
   end Input;

   use Numerics.Times;

   Period : constant Positive_Time := Beginning.Parameters.Get_Period;

   procedure Error (Message : in String);

   protected Engine is

      procedure Initialize_Log_Directory (Directory : in Interfaces.C.Strings.chars_ptr;
                                          Length    : in Interfaces.C.size_t);

      procedure Cycle;

      procedure Log (Context : in File_Flag);

   private
      Time : Positive_Time := 0;
   end Engine;

   procedure Initialize (Callbacks : in System.Address;
                         Directory : in Interfaces.C.Strings.chars_ptr;
                         Length    : in Interfaces.C.size_t) is
      procedure Adainit;
      pragma Import (Convention => C, Entity => Adainit, External_Name => "nomointerpreterinit");
      use Interfaces.C.Strings;
   begin
      Adainit;
      Epistemic_Memory.Initialization.Initialize (Callbacks);
      if Directory /= Null_Ptr then
        Engine.Initialize_Log_Directory (Directory, Length);
      end if;
   exception
      when Event : others =>
         Error (Ada.Exceptions.Exception_Information (Event));
   end Initialize;

   protected body Engine is

      procedure Initialize_Log_Directory (Directory : in Interfaces.C.Strings.chars_ptr;
                                          Length    : in Interfaces.C.size_t) is
         use Interfaces.C.Strings;
      begin
         if Directory /= Null_Ptr then
            Logger.Initialize (Value (Directory, Length));
         end if;
      end Initialize_Log_Directory;

      procedure Cycle is
      begin
         Event_Memory.External.Updating.Update (Time);
         Plant.Engine.Assume (Time);
         while Plant.Rule_Buffer.Reading.Has_New_Rule loop
            if Epistemic_Memory.Loading.Has_Space (Plant.Rule_Buffer.Reading.Get_Type_Index) then
               Epistemic_Memory.Loading.Load (Plant.Rule_Buffer.Reading.Get_Type_Index,
                                              Plant.Rule_Buffer.Reading.Get_Buffer);
            else
               Plant.Rule_Buffer.Reading.Send_Error;
            end if;
         end loop;
         Epistemic_Memory.Interpretation.Interpret;
         Time := Time + Period;
         Epistemic_Memory.Interpretation.Assume (Time);
         Event_Memory.Internal.Updating.Update (Time);
      end Cycle;

      procedure Log (Context : in File_Flag) is
      begin
         Epistemic_Memory.Logging.Log (Time, Context);
      end Log;

   end Engine;

   procedure Triggers is
   begin
      Engine.Cycle;
   exception
      when Event : others =>
         Error (Ada.Exceptions.Exception_Information (Event));
   end Triggers;

   procedure Triggers (Flags    : in System.Address;
                       Position : in Log_Position;
                       File     : in File_Flag) is
      use Log_Manager;
   begin
      if System."/=" (System.Null_Address, Flags) then
         declare
            Log : Log_Flags;
            for Log'Address use Flags;
         begin
            Log_Manager.Writing.Set (Log);
            if Position = Before_Assume then
               Engine.Log (File);
            end if;
            Engine.Cycle;
            if Position = After_Assume then
               Engine.Log (File);
            end if;
         end;
      else
         Engine.Cycle;
      end if;
   exception
      when Event : others =>
         Error (Ada.Exceptions.Exception_Information (Event));
   end Triggers;

   procedure Finalize (Flag : in Finalization_Flag) is
      procedure Adafinal;
      pragma Import (Convention => C, Entity => Adafinal, External_Name => "nomointerpreterfinal");
      use Log_Manager;
   begin
      if Flag /= Any_Modification then
         Log_Manager.Writing.Set (Log_All);
         Engine.Log (Temp);
      end if;
      Logger.Finalize;
      case Flag is
         when Any_Modification =>
            Reader.Finalization.Finalize;
         when Save_Seed =>
            Reader.Finalization.Finalize_And_Save (Logger.Get_Directory);
         when Update_Seed =>
            Reader.Finalization.Finalize_And_Update (Logger.Get_Directory);
      end case;
      Adafinal;
   exception
      when Event : others =>
         Error (Ada.Exceptions.Exception_Information (Event));
   end Finalize;

   procedure Error (Message : in String) is
      use Ada.Text_IO;
      File_Name : constant String := "nomointerpreter.err";
      Error_File : File_Type;
   begin
      if Ada.Directories.Exists (File_Name) then
         Open (Error_File , Append_File, File_Name);
      else
         Create (Error_File , Append_File, File_Name);
      end if;
      Put_Line (Error_File, Message);
   end Error;

end Nomo.Interpreter.Kernel;
