
more_entropy = require 'more-entropy'
{ADRBG} = require './drbg'
{WordArray} = require './wordarray'
{XOR} = require './combine'
util = require './util'

_browser_rng_primitive = null

#===============================================

# A wrapper around the standard browser PRNG.
#
# @param {number} n The number of random bytes to grab.
# @return {Buffer} A buffer of `n` random bytes.
browser_rng = (n) ->
  v = new Uint8Array n
  _browser_rng_primitive v
  new Buffer v

#===============================================

# Also can use Microsoft's IE RNG gatherer
_browser_rng_primitive = if (m = window?.crypto?.getRandomValues)? then m.bind(window.crypto)
else if (m = window?.msCrypto?.getRandomValues)? then m.bind(window.msCrypto)
else null

#-----------

if _browser_rng_primitive? then _native_rng = browser_rng
else
  try
    # trick Browserify --- we don't want crypto on the browser!
    {rng} = require('cry' + 'pto')
    _native_rng = rng if rng?
  catch e
    # pass

#===============================================

native_rng = (x) ->
  # We need this, otherwise, we have huge problems.  So crash the
  # program if we don't have an RNG by now
  if not _native_rng?
    throw new Error 'No rng found; tried requiring "crypto" and window.crypto'
  return _native_rng x

#===============================================

# A high-level pseudo-random number generator, cryptographically-strong.
# Seed with the `more-entropy` module, and also with the standard
# provided PRNGS (either `crypto.rng`) on Node or (`window.crypto.getRandomValues`)
# on the browser.  Then, run an HMAC-DRBG to generate pseudo-random bytes.
class PRNG

  # Allocate a `more-entropy` generator and also an {ADRBG} to query into.
  constructor : () ->
    @meg = new more_entropy.Generator()
    @adrbg = new ADRBG ((n,cb) => @gen_seed n, cb), XOR.sign

  # @private
  # Convert the 64-bit float Now() to an 8-byte Buffer.
  now_to_buffer : () ->
    d = Date.now()
    ms = d % 1000
    s = Math.floor d / 1000
    buf = new Buffer 8
    buf.writeUInt32BE s, 0
    buf.writeUInt32BE ms, 4
    buf

  # @private
  #
  # Needed for {ADRBG}, called to generate a random seed periodically.
  # 
  # @param {number} nbits The number of seed bits needed.
  # @param {callback} cb The callback to call once the needed seed is
  #    reader.  Callback with a Buffer containing the random seed.
  gen_seed : (nbits, cb) ->

    nbytes = nbits / 8
    bufs = []
    bufs.push @now_to_buffer()
    await @meg.generate nbits, defer words
    bufs.push @now_to_buffer()
    bufs.push new Buffer words
    bufs.push native_rng nbytes
    bufs.push @now_to_buffer()
    cat = Buffer.concat bufs
    wa = WordArray.from_buffer cat
    util.scrub_buffer cat
    (util.scrub_buffer b for b in bufs)
    cb wa

  # @method generate
  #
  # Generate n random bytes of data, and callback when ready.  Pull
  # the bytes from the pseudo-random stream generated by HMAC-DRBG.
  #
  # @param {number} n The number of bytes that we need.
  # @param {callback} cb The callback to call when completed calls back with a {WordArray} of random data.
  #
  generate : (n, cb) -> @adrbg.generate n, cb

#===============================================

_prng = null

# @method generate
#
# Call into a global {PRNG} class.  Generate random bytes and callback
# with the given bytes.
#
# @param {number} n The number of bytes to generate.
# @param {callback} cb Callback with the random data when ready,
#   packaged as a {WordArray}.
#
generate = (n, cb) ->
  _prng = new PRNG() if not _prng?
  _prng.generate n, cb

#===============================================

exports.PRNG = PRNG
exports.generate = generate
exports.native_rng = native_rng

#===============================================

