--[[------------------------------------
RandomLua v0.3.1
Pure Lua Pseudo-Random Numbers Generator
Under the MIT license.
copyright(c) 2011 linux-man
--]]------------------------------------

local _M = {}
local mod = math.fmod
local floor = math.floor
local abs = math.abs

local function normalize(n) --keep numbers at (positive) 32 bits
    return n % 0x80000000
end

local function bit_and(a, b)
    local r = 0
    local m = 0
    for m = 0, 31 do
        if (a % 2 == 1) and (b % 2 == 1) then r = r + 2^m end
        if a % 2 ~= 0 then a = a - 1 end
        if b % 2 ~= 0 then b = b - 1 end
        a = a / 2 b = b / 2
    end
    return normalize(r)
end

local function bit_or(a, b)
    local r = 0
    local m = 0
    for m = 0, 31 do
        if (a % 2 == 1) or (b % 2 == 1) then r = r + 2^m end
        if a % 2 ~= 0 then a = a - 1 end
        if b % 2 ~= 0 then b = b - 1 end
        a = a / 2 b = b / 2
    end
    return normalize(r)
end

local function bit_xor(a, b)
    local r = 0
    local m = 0
    for m = 0, 31 do
        if a % 2 ~= b % 2 then r = r + 2^m end
        if a % 2 ~= 0 then a = a - 1 end
        if b % 2 ~= 0 then b = b - 1 end
        a = a / 2 b = b / 2
    end
    return normalize(r)
end

local function seed()
    --return normalize(tonumber(tostring(os.time()):reverse()))
    return normalize(os.time())
end

--Mersenne twister
mersenne_twister = {}
mersenne_twister.__index = mersenne_twister

function mersenne_twister:randomseed(s)
    if not s then s = seed() end
    self.mt[0] = normalize(s)
    for i = 1, 623 do
        self.mt[i] = normalize(0x6c078965 * bit_xor(self.mt[i-1], floor(self.mt[i-1] / 0x40000000)) + i)
    end
end

function mersenne_twister:random(a, b)
    local y
    if self.index == 0 then
        for i = 0, 623 do
            --y = bit_or(floor(self.mt[i] / 0x80000000) * 0x80000000, self.mt[(i + 1) % 624] % 0x80000000)
            y = self.mt[(i + 1) % 624] % 0x80000000
            self.mt[i] = bit_xor(self.mt[(i + 397) % 624], floor(y / 2))
            if y % 2 ~= 0 then self.mt[i] = bit_xor(self.mt[i], 0x9908b0df) end
        end
    end
    y = self.mt[self.index]
    y = bit_xor(y, floor(y / 0x800))
    y = bit_xor(y, bit_and(normalize(y * 0x80), 0x9d2c5680))
    y = bit_xor(y, bit_and(normalize(y * 0x8000), 0xefc60000))
    y = bit_xor(y, floor(y / 0x40000))
    self.index = (self.index + 1) % 624
    if not a then return y / 0x80000000
    elseif not b then
        if a == 0 then return y
        else return 1 + (y % a)
        end
    else
        return a + (y % (b - a + 1))
    end
end

function _M.twister(s)
    local temp = {}
    setmetatable(temp, mersenne_twister)
    temp.mt = {}
    temp.index = 0
    temp:randomseed(s)
    return temp
end

--Linear Congruential Generator
linear_congruential_generator = {}
linear_congruential_generator.__index = linear_congruential_generator

function linear_congruential_generator:random(a, b)
    local y = (self.a * self.x + self.c) % self.m
    self.x = y
    if not a then return y / 0x10000
    elseif not b then
        if a == 0 then return y
        else return 1 + (y % a) end
    else
        return a + (y % (b - a + 1))
    end
end

function linear_congruential_generator:randomseed(s)
    if not s then s = seed() end
    self.x = normalize(s)
end

function _M.lcg(s, r)
    local temp = {}
    setmetatable(temp, linear_congruential_generator)
    temp.a, temp.c, temp.m = 1103515245, 12345, 0x10000  --from Ansi C
    if r then
        if r == 'nr' then temp.a, temp.c, temp.m = 1664525, 1013904223, 0x10000 --from Numerical Recipes.
        elseif r == 'mvc' then temp.a, temp.c, temp.m = 214013, 2531011, 0x10000 end--from MVC
    end
    temp:randomseed(s)
    return temp
end

-- Multiply-with-carry
multiply_with_carry = {}
multiply_with_carry.__index = multiply_with_carry

function multiply_with_carry:random(a, b)
    local m = self.m
    local t = self.a * self.x + self.c
    local y = t % m
    self.x = y
    self.c = floor(t / m)
    if not a then return y / 0x10000
    elseif not b then
        if a == 0 then return y
        else return 1 + (y % a) end
    else
    local diff = 0
    if a == b then return a end
    if a < 0 then
      diff = abs(a)
      a = a + diff
      b = b + diff
    end
        return a + (y % (b - a + 1)) - diff
    end
end

function multiply_with_carry:randomseed(s)
    if not s then s = seed() end
    self.c = self.ic
    self.x = normalize(s)
end

function _M.mwc(s, r)
    local temp = {}
    setmetatable(temp, multiply_with_carry)
    temp.a, temp.c, temp.m = 1103515245, 12345, 0x10000  --from Ansi C
    if r then
        if r == 'nr' then temp.a, temp.c, temp.m = 1664525, 1013904223, 0x10000 --from Numerical Recipes.
        elseif r == 'mvc' then temp.a, temp.c, temp.m = 214013, 2531011, 0x10000 end--from MVC
    end
    temp.ic = temp.c
    temp:randomseed(s)
    return temp
end

function _M.mwvc(s)
  return _M.mwc(s, 'mvc')
end

local B =  0x10000

-- rough adaptation of Knuth float generator
function _M.krandom( seedobj, fVal1, fVal2 )
  local ma = seedobj.ma
  local seed = seedobj.seed
  local mj, mk
  if seed < 0 or not ma then
    ma = {}
    seedobj.ma = ma
    mj = normalize( seed )
    mj = mod( mj, B )
    ma[55] = mj
    mk = 1
    for i = 1, 54 do
      local ii = mod( 21 * i,  55 )
      ma[ii] = mk
      mk = mj - mk
      if mk < 0 then mk = mk + B end
      mj = ma[ii]
    end
    for k = 1, 4 do
      for i = 1, 55 do
        ma[i] = ma[i] - ma[ 1 + mod( i + 30,  55) ]
        if ma[i] < 0 then ma[i] = ma[i] + B end
      end
    end
    seedobj.inext = 0
    seedobj.inextp = 31
    seedobj.seed = 1
  end -- if
  local inext = seedobj.inext
  local inextp = seedobj.inextp
  inext = inext + 1
  if inext == 56 then inext = 1 end
  seedobj.inext = inext
  inextp = inextp + 1
  if inextp == 56 then inextp = 1 end
  seedobj.inextp = inextp
  mj = ma[ inext ] - ma[ inextp ]
  if mj < 0 then mj = mj + B end
  ma[ inext ] = mj
  local temp_rand = mj / B
  if fVal2 then
    return floor( fVal1 + 0.5 + temp_rand * ( fVal2 - fVal1 ) )
  elseif fVal1 then
    return floor( temp_rand * fVal1 ) + 1
  else
    return temp_rand
  end
end

-- Sys rand
sys_rand = {}
sys_rand.__index = sys_rand
function sys_rand:random(a, b)
  local diff = 0
  if a and b and a == b then math.random(); return a end
  if a and b then
    if a < 0 then
      diff = abs(a)
      a = a + diff
      b = b + diff
    end
    return math.random(a, b) - diff
  end
  if a and a == 0 then return floor(math.random() * 0x10000) end
  if a then return math.random(a) end
  return math.random()
end

function sys_rand:randomseed(s)
   -- ignore
   return
end

function _M.sys_rand(s)
    local temp = {}
    setmetatable(temp, sys_rand)
    return temp
end

return _M