--  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 Ada.Text_IO;

with Ada.Directories;

with Ada.Exceptions;

with Ada.Command_Line;

with Ada.Real_Time;

with Interfaces.C.Strings;

with Nomo_Interpreter;

procedure Worldsquare is

   package Benchmark is

      pragma Linker_Options ("-lworldsquare");

      procedure Initialize (Map_Directory        : in Interfaces.C.Strings.chars_ptr;
                            Agents_Number        : in Interfaces.C.short;
                            Log_Directory        : in Interfaces.C.Strings.chars_ptr;
                            Log_Directory_Length : in Interfaces.C.size_t);
      pragma Import (Convention => C, Entity => Initialize, External_Name => "benchmarkInitialize");


      procedure Update_Map;
      pragma Import (Convention => C, Entity => Update_Map, External_Name => "benchmarkUpdateMap");

      procedure Save_Map;
      pragma Import (Convention => C, Entity => Save_Map, External_Name => "benchmarkSaveMap");

      procedure Triggers;
      pragma Import (Convention => C, Entity => Triggers, External_Name => "benchmarkTriggers");

      procedure Triggers_With_Log (Log          : in Nomo_Interpreter.Log_Flag;
                                   Log_Position : in Nomo_Interpreter.Log_Position_Flag;
                                   File         : in Nomo_Interpreter.File_Flag);
      pragma Import (Convention => C, Entity => Triggers_With_Log, External_Name => "benchmarkTriggersWithLog");

      procedure Triggers_With_Save;
      pragma Import (Convention => C, Entity => Triggers_With_Save, External_Name => "benchmarkTriggersWithSave");

      procedure Triggers_With_Log_With_Save (Log          : in Nomo_Interpreter.Log_Flag;
                                             Log_Position : in Nomo_Interpreter.Log_Position_Flag;
                                             File         : in Nomo_Interpreter.File_Flag);
      pragma Import (Convention => C, Entity => Triggers_With_Log_With_Save, External_Name => "benchmarkTriggersWithLogWithSave");

      procedure Finalize (Finalization : in Nomo_Interpreter.Finalization_Flag);
      pragma Import (Convention => C, Entity => Finalize, External_Name => "benchmarkFinalize");

   end Benchmark;

   Item        : String (1..256);
   Last        : Integer;
   Period_Long : Ada.Real_Time.Time_Span;

   procedure Error (Message : in String);

   procedure Error (Message : in String) is
      use Ada.Text_IO;
      File_Name : constant String := "worldsquare.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);
      Close(Error_File);
   end Error;

   task Engine is
      entry Start (Max_Steps : in Positive;
                   Period    : in Ada.Real_Time.Time_Span);
      entry Start_With_Log (Log_Type          : in Nomo_Interpreter.Log_Flag;
                            Log_Position_Type : in Nomo_Interpreter.Log_Position_Flag;
                            File_Type         : in Nomo_Interpreter.File_Flag;
                            Max_Steps         : in Positive;
                            Period            : in Ada.Real_Time.Time_Span);
      entry Pause;
      entry Kill;
   end Engine;

   task body Engine is
      use Ada.Real_Time;
      Key          : Boolean;
      Steps        : Integer;
      Max          : Positive;
      Log          : Nomo_Interpreter.Log_Flag;
      Log_Position : Nomo_Interpreter.Log_Position_Flag;
      File         : Nomo_Interpreter.File_Flag;
      Next_Time    : Time;
      dt           : Time_Span;
   begin
      loop
         select
            accept Start (Max_Steps : in Positive;
                          Period    : in Ada.Real_Time.Time_Span) do
               Max := Max_Steps;
               Next_Time := Clock;
               dt := Period;
               Key := true;
               Steps := 0;
            end Start;
            loop
               select
                  accept Start (Max_Steps : in Positive;
                                Period    : in Ada.Real_Time.Time_Span) do
                     Max := Max_Steps;
                     Next_Time := Clock;
                     dt := Period;
                     Key := true;
                     Steps := 0;
                  end Start;
               or
                  accept Pause do
                     Key := not (Key);
                     if Key then
                        Benchmark.Update_Map;
                        Next_Time := Clock;
                     elsif dt = Time_Span_Zero then
                        Benchmark.Save_Map;
                     end if;
                  end Pause;
               or
                  accept Kill;
                  exit;
               or
                  delay until Next_Time;
                  if Key then
                     if dt = Time_Span_Zero then
                        Benchmark.Triggers;
                     else
                        Benchmark.Triggers_With_Save;
                     end if;
                     Steps := Steps + 1;
                     Ada.Text_IO.Put (Positive'Image (Steps));
                     Next_Time := Next_Time + dt;
                     if Steps = Max then
                        if dt = Time_Span_Zero then
                           Benchmark.Save_Map;
                        end if;
                        exit;
                     end if;
                  end if;
               end select;
            end loop;
         or
            accept Start_With_Log (Log_Type          : in Nomo_Interpreter.Log_Flag;
                                   Log_Position_Type : in Nomo_Interpreter.Log_Position_Flag;
                                   File_Type         : in Nomo_Interpreter.File_Flag;
                                   Max_Steps         : in Positive;
                                   Period            : in Ada.Real_Time.Time_Span) do
               Log := Log_Type;
               Log_Position := Log_Position_Type;
               File := File_Type;
               Max := Max_Steps;
               Next_Time := Clock;
               dt := Period;
               Key := true;
               Steps := 0;
            end Start_With_Log;
            loop
               select
                  accept Start_With_Log (Log_Type          : in Nomo_Interpreter.Log_Flag;
                                         Log_Position_Type : in Nomo_Interpreter.Log_Position_Flag;
                                         File_Type         : in Nomo_Interpreter.File_Flag;
                                         Max_Steps         : in Positive;
                                         Period            : in Ada.Real_Time.Time_Span) do
                     Log := Log_Type;
                     Log_Position := Log_Position_Type;
                     File := File_Type;
                     Max := Max_Steps;
                     Next_Time := Clock;
                     dt := Period;
                     Key := true;
                     Steps := 0;
                  end Start_With_Log;
               or
                  accept Pause do
                     Key := not (Key);
                     Next_Time := Clock;
                     if Key then
                        Benchmark.Update_Map;
                        Next_Time := Clock;
                     elsif dt = Time_Span_Zero then
                        Benchmark.Save_Map;
                     end if;
                  end Pause;
               or
                  accept Kill;
                  exit;
               or
                  delay until Next_Time;
                  if Key then
                     if dt = Time_Span_Zero then
                        Benchmark.Triggers_With_Log (Log, Log_Position, File);
                     else
                        Benchmark.Triggers_With_Log_With_Save (Log, Log_Position, File);
                     end if;
                     File := Nomo_Interpreter.No;
                     Steps := Steps + 1;
                     Ada.Text_IO.Put (Positive'Image (Steps));
                     Next_Time := Next_Time + dt;
                     if Steps = Max then
                        if dt = Time_Span_Zero then
                           Benchmark.Save_Map;
                        end if;
                        exit;
                     end if;
                  end if;
               end select;
            end loop;
         or
            accept Kill;
            exit;
         or
            accept pause;
         end select;
      end loop;
   exception
      when Event : others =>
         Error (Ada.Exceptions.Exception_Information (Event));
   end Engine;

begin
   Benchmark.Initialize (Interfaces.C.Strings.New_String (Ada.Command_Line.Argument (1)),
                         Interfaces.C.short'Value (Ada.Command_Line.Argument (2)),
                         Interfaces.C.Strings.New_String (Ada.Command_Line.Argument (3)),
                         Interfaces.C.size_t'Value (Ada.Command_Line.Argument (4)));
   Period_Long := Ada.Real_Time.Milliseconds (Integer'Value (Ada.Command_Line.Argument (5)));
   loop
      Ada.Text_IO.Get_Line (Item, Last);
      if Item (1) = 'a' then
         Benchmark.Update_Map;
         Benchmark.Triggers_With_Save;
         Ada.Text_IO.Put ("1");
      elsif Item (1) = 'b' then
         Benchmark.Update_Map;
         Benchmark.Triggers_With_Log_With_Save (Nomo_Interpreter.Log_Flag'Val (Natural'Value (Item (2..2))),
                        			Nomo_Interpreter.Log_Position_Flag'Val (Natural'Value (Item (3..3))),
                                                Nomo_Interpreter.File_Flag'Val (Natural'Value (Item (4..4))));
         Ada.Text_IO.Put ("1");
      elsif Item (1) = 'c' then
         Benchmark.Update_Map;
         Engine.Start (Positive'Value (Item (2..Last)), Period_Long);
      elsif Item (1) = 'd' then
         Benchmark.Update_Map;
         Engine.Start_With_Log (Nomo_Interpreter.Log_Flag'Val (Natural'Value (Item (2..2))),
                                Nomo_Interpreter.Log_Position_Flag'Val (Natural'Value (Item (3..3))),
                                Nomo_Interpreter.File_Flag'Val (Natural'Value (Item (4..4))),
                                Positive'Value (Item (5..Last)),
                                Period_Long);
      elsif Item (1) = 'e' then
         Benchmark.Update_Map;
         Engine.Start (Positive'Value (Item (2..Last)), Ada.Real_Time.Time_Span_Zero);
      elsif Item (1) = 'f' then
         Benchmark.Update_Map;
         Engine.Start_With_Log (Nomo_Interpreter.Log_Flag'Val (Natural'Value (Item (2..2))),
                                Nomo_Interpreter.Log_Position_Flag'Val (Natural'Value (Item (3..3))),
                                Nomo_Interpreter.File_Flag'Val (Natural'Value (Item (4..4))),
                                Positive'Value (Item (5..Last)),
                                Ada.Real_Time.Time_Span_Zero);
      elsif Item (1) = 'g' then
         Engine.Pause;
      elsif Item (1) = 'h' then
         Engine.Kill;
         if Last = 2 then
            Benchmark.Finalize (Nomo_Interpreter.Finalization_Flag'Val (Natural'Value (Item (2..2))));
         else
            Benchmark.Finalize (Nomo_Interpreter.Any_Modification);
         end if;
         exit;
      else
         exit;
      end if;
   end loop;
   if not Engine'Terminated then
      Engine.Kill;
   end if;

exception
   when Event : others =>
      Error (Ada.Exceptions.Exception_Information (Event));
end Worldsquare;
