local require    = require
local ffi        = require "ffi"
local ffi_cdef   = ffi.cdef
local ffi_new    = ffi.new
local ffi_str    = ffi.string
local ffi_typeof = ffi.typeof
local C          = ffi.C
local type       = type
local random     = math.random
local randomseed = math.randomseed
local concat     = table.concat
local tostring   = tostring
local pcall      = pcall

ffi_cdef[[
typedef unsigned char u_char;
u_char * ngx_hex_dump(u_char *dst, const u_char *src, size_t len);
int RAND_bytes(u_char *buf, int num);
]]

local ok, new_tab = pcall(require, "table.new")
if not ok then
    new_tab = function () return {} end
end

local alnum  = {
    'A','B','C','D','E','F','G','H','I','J','K','L','M',
    'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
    'a','b','c','d','e','f','g','h','i','j','k','l','m',
    'n','o','p','q','r','s','t','u','v','w','x','y','z',
    '0','1','2','3','4','5','6','7','8','9'
}

local t = ffi_typeof "uint8_t[?]"

local function bytes(len, format)
    local s = ffi_new(t, len)
    C.RAND_bytes(s, len)
    if not s then return nil end
    if format == "hex" then
        local b = ffi_new(t, len * 2)
        C.ngx_hex_dump(b, s, len)
        return ffi_str(b, len * 2), true
    else
        return ffi_str(s, len), true
    end
end

local function seed()
    local a,b,c,d = bytes(4):byte(1, 4)
    return randomseed(a * 0x1000000 + b * 0x10000 + c * 0x100 + d)
end

local function number(min, max, reseed)
    if reseed then seed() end
    if min and max then return random(min, max)
    elseif min     then return random(min)
    else                return random() end
end

local function token(len, chars, sep)
    chars = chars or alnum
    local count
    local token = new_tab(len, 0)
    if type(chars) ~= "table" then
        chars = tostring(chars)
        count = #chars
        local n
        for i=1,len do
            n = number(1, count)
            token[i] = chars:sub(n, n)
        end
    else
        count = #chars
        for i=1,len do token[i] = chars[number(1, count)] end
    end
    return concat(token, sep)
end

seed()

return {
    bytes  = bytes,
    number = number,
    token  = token
}