--------------------------------
-- Ivan Levashew aka OCTAGRAM --
--         Barnaul, 2009-2010 --
--------------------------------

with System; use System;
with Interfaces; use Interfaces;
with System.Storage_Elements; use System.Storage_Elements;
with Turbo_Private.Bits; use Turbo_Private.Bits;

procedure Turbo_Private.Memory.Move_Fallback
   (Source, Target : Address; Amount : Unsigned_Addr)
is
   pragma Preelaborate;
   Buffer_Size : constant := 4096;

--   type Buffer_Type is new Storage_Array (0 .. Storage_Count (Amount - 1));
   type Buffer_Type is new Storage_Array (0 .. Buffer_Size - 1);

--   Source_Array : Buffer_Type;
--   pragma Import (C, Source_Array);
--   Target_Array : Buffer_Type;
--   pragma Import (C, Target_Array);

   Buffer : Buffer_Type;

   -- Amount > 0 !
   procedure Mini_Move (Source, Target : Address; Amount : Storage_Count) is
      Source_Array : Buffer_Type;
      for Source_Array'Address use Source;
      pragma Import (C, Source_Array);
      Target_Array : Buffer_Type;
      for Target_Array'Address use Target;
      pragma Import (C, Target_Array);      
   begin
      Buffer (0 .. Amount - 1) := Source_Array (0 .. Amount - 1);
      Target_Array (0 .. Amount - 1) := Buffer (0 .. Amount - 1);
   end Mini_Move;

   Amount_Div : constant Unsigned_Addr := Amount / Buffer_Size;
   Amount_Mod : constant Unsigned_Addr := Amount mod Buffer_Size;

   procedure Forward_Move is
   begin
      if Amount_Div >= 1 then
         for i in 0 .. Amount_Div - 1 loop
            Mini_Move (Source + i * Buffer_Size, Target + i * Buffer_Size, Buffer_Size);
         end loop;
      end if;
      if Amount_Mod >= 1 then
         Mini_Move (Source + Amount_Div * Buffer_Size,
                    Target + Amount_Div * Buffer_Size,
                    Storage_Count (Amount_Mod));
      end if;
   end Forward_Move;

   procedure Backward_Move is
   begin
      if Amount_Mod >= 1 then
         Mini_Move (Source + Amount_Div * Buffer_Size,
                    Target + Amount_Div * Buffer_Size,
                    Storage_Count (Amount_Mod));
      end if;
      if Amount_Div >= 1 then
         for i in reverse 0 .. Amount_Div - 1 loop
            Mini_Move (Source + i * Buffer_Size, Target + i * Buffer_Size, Buffer_Size);
         end loop;
      end if;
   end Backward_Move;

begin
   if Amount <= 0 then -- it can't be < 0 because Amount has Unsigned_Addr type
      return;
   elsif Source = Target then
      return;
   elsif Source + Amount <= Target or else
         Target + Amount <= Source then
      -- no intersection
      Forward_Move;
   elsif Source > Target then
      Forward_Move;
   else
      Backward_Move;
   end if;
end Turbo_Private.Memory.Move_Fallback;
