require "random/secure"

# (c) Bob Jenkins, March 1996, Public Domain
# You may use this code in any way you wish, and it is free.  No warrantee.
# http://burtleburtle.net/bob/rand/isaacafa.html

class Random::ISAAC
  include Random

  private getter counter
  private getter aa
  private getter bb
  private getter cc

  private def random_seeds
    Random::Secure.rand(StaticArray(UInt32, 8))
  end

  def initialize(seeds = random_seeds)
    @rsl = StaticArray(UInt32, 256).new { 0_u32 }
    @mm = StaticArray(UInt32, 256).new { 0_u32 }
    @counter = 0
    @aa = @bb = @cc = 0_u32
    init_by_array(seeds)
  end

  def new_seed(seeds = random_seeds)
    @aa = @bb = @cc = 0_u32
    init_by_array(seeds)
  end

  def next_u : UInt32
    if (@counter -= 1) == -1
      isaac
      @counter = 255
    end
    @rsl[counter]
  end

  private def isaac
    @cc &+= 1
    @bb &+= cc

    256.times do |i|
      @aa ^= case i % 4
             when 0 then aa << 13
             when 1 then aa >> 6
             when 2 then aa << 2
             else        aa >> 16
             end
      x = @mm[i]
      @aa = @mm[(i + 128) % 256] &+ aa
      @mm[i] = y = @mm[(x >> 2) % 256] &+ aa &+ bb
      @rsl[i] = @bb = @mm[(y >> 10) % 256] &+ x
    end
  end

  private def init_by_array(seeds)
    seed_len = seeds.size
    256.times { |i| @rsl[i] = i < seed_len ? seeds[i].to_u32 : 0_u32 }

    a = b = c = d = e = f = g = h = 0x9e3779b9_u32

    mix = -> {
      a ^= b << 11; d &+= a; b &+= c
      b ^= c >> 2; e &+= b; c &+= d
      c ^= d << 8; f &+= c; d &+= e
      d ^= e >> 16; g &+= d; e &+= f
      e ^= f << 10; h &+= e; f &+= g
      f ^= g >> 4; a &+= f; g &+= h
      g ^= h << 8; b &+= g; h &+= a
      h ^= a >> 9; c &+= h; a &+= b
    }
    4.times(&mix)

    scramble = ->(seed : StaticArray(UInt32, 256)) {
      0.step(to: 255, by: 8) do |i|
        a &+= seed[i]; b &+= seed[i + 1]; c &+= seed[i + 2]; d &+= seed[i + 3]
        e &+= seed[i + 4]; f &+= seed[i + 5]; g &+= seed[i + 6]; h &+= seed[i + 7]
        mix.call
        @mm[i] = a; @mm[i + 1] = b; @mm[i + 2] = c; @mm[i + 3] = d
        @mm[i + 4] = e; @mm[i + 5] = f; @mm[i + 6] = g; @mm[i + 7] = h
      end
    }

    scramble.call(@rsl)
    scramble.call(@mm)

    isaac
    @counter = 256
  end
end
