--  Copyright (C) 2015  Daniel Otte <bg@nerilex.org>
--
--  This program is free software: you can redistribute it and/or modify
--  it under the terms of the GNU 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 General Public License for more details.
--
--  You should have received a copy of the GNU General Public License
--  along with this program.  If not, see <http://www.gnu.org/licenses/>.

package body Spritz is

   procedure Initialize (ctx : out Context) is
   begin
      ctx.i := 0;
      ctx.j := 0;
      ctx.k := 0;
      ctx.z := 0;
      ctx.a := 0;
      ctx.w := 1;
      for i in ctx.S'Range loop
         ctx.S(i) := u8(i);
      end loop;
   end Initialize;

   procedure Output (ctx : in out Context; z : out u8) is
   begin
      ctx.z := ctx.S(ctx.j + ctx.S(ctx.i + ctx.S(ctx.z + ctx.k)));
      z := ctx.z;
   end Output;

   procedure Update (ctx : in out Context) is
   begin
      ctx.i := ctx.i + ctx.w;
      ctx.j := ctx.k + ctx.S(ctx.j + ctx.S(ctx.i));
      ctx.k := ctx.i + ctx.k + ctx.S(ctx.j);
      Swap(ctx.S(ctx.i), ctx.S(ctx.j));
   end Update;

   procedure Crush (ctx : in out Context) is
   begin
      for v in u8 range 0 .. u8(N / 2 - 1) loop
         if ctx.S(v) > ctx.S(u8(N - 1) - v) then
            Swap(ctx.S(v), ctx.S(u8(N - 1) - v));
         end if;
      end loop;
   end Crush;

   procedure Whip (ctx : in out Context) is
   begin
      for i in 1 .. 2 * N loop
         Update(ctx);
      end loop;
      ctx.w := ctx.w + 2;
   end Whip;

   procedure Shuffle (ctx : in out Context) is
   begin
      Whip(ctx);
      Crush(ctx);
      Whip(ctx);
      Crush(ctx);
      Whip(ctx);
      ctx.a := 0;
   end Shuffle;

   procedure Drip (ctx : in out Context; z : out u8) is
   begin
      if ctx.a > 0 then
         Shuffle(ctx);
      end if;
      Update(ctx);
      Output(ctx, z);
   end Drip;

   procedure Squeeze (ctx : in out Context; P : out u8_Array) is
      z : u8;
   begin
      for i in P'Range loop
         Drip(ctx, z);
         P(i) := z;
      end loop;
   end Squeeze;

   procedure AbsorbStop (ctx : in out Context) is
   begin
      if ctx.a = u8(N / 2) then
         Shuffle(ctx);
      end if;
      ctx.a := ctx.a + 1;
   end AbsorbStop;

   procedure AbsorbNibble (ctx : in out Context; x : in u8) is
   begin
      if ctx.a = u8(N / 2) then
         Shuffle(ctx);
      end if;
      Swap(ctx.S(ctx.a), ctx.S(u8(N / 2) + x));
      ctx.a := ctx.a + 1;
   end AbsorbNibble;

   procedure Absorb (ctx : in out Context; x : in u8) is
   begin
      AbsorbNibble(ctx, x and 15);
      AbsorbNibble(ctx, Shift_Right(x, 4));
   end Absorb;

   procedure Absorb (ctx : in out Context; x : in u8_Array) is
   begin
      for i in x'Range loop
         Absorb(ctx, x(i));
      end loop;
   end Absorb;

   procedure Absorb (ctx : in out Context; x : in String) is
   begin
      for i in x'Range loop
         Absorb(ctx, u8(Character'Pos(x(i))));
      end loop;
   end Absorb;

   procedure Absorb (ctx : in out Context; x : in Natural) is
      buf : u8_Array (1 .. Integer'Size / 8);
      c : Integer := 1 + Integer'Size / 8;
      t : Natural := x;
   begin
      while t > 0 loop
         c := c - 1;
         buf(c) := u8(t rem 256);
         t := t / 256;
      end loop;
      Absorb(ctx, buf(c .. buf'Last));
   end Absorb;

end Spritz;
