"use strict";

var randomBytes = require('randombytes');
var Charset = require('./charset.js');


function unsafeRandomBytes(length) {
  var stack = [];
  for (var i = 0; i < length; i++) {
    stack.push(Math.floor(Math.random() * 255));
  }

  return {
    length,
    readUInt8: function (index) {
      return stack[index];
    }
  };
}

function safeRandomBytes(length) {
  try {
    return randomBytes(length);
  } catch (e) {
    /* React/React Native Fix + Eternal loop removed */
    return unsafeRandomBytes(length);
  }
}

function processString(buf, initialString, chars, reqLen, maxByte) {
  var string = initialString;
  for (var i = 0; i < buf.length && string.length < reqLen; i++) {
    var randomByte = buf.readUInt8(i);
    if (randomByte < maxByte) {
      string += chars.charAt(randomByte % chars.length);
    }
  }
  return string;
}

function getAsyncString(string, chars, length, maxByte, cb) {
  randomBytes(length, function(err, buf) {
    if (err) {
      // Since it is waiting for entropy, errors are legit and we shouldn't just keep retrying
      cb(err);
    }
    var generatedString = processString(buf, string, chars, length, maxByte);
    if (generatedString.length < length) {
      getAsyncString(generatedString, chars, length, maxByte, cb);
    } else {
      cb(null, generatedString);
    }
  })
}

exports.generate = function(options, cb) {
  var charset = new Charset();

  var length, chars, capitalization, string = '';

  // Handle options
  if (typeof options === 'object') {
    length = typeof options.length === 'number' ? options.length : 32;

    if (options.charset) {
      charset.setType(options.charset);
    }
    else {
      charset.setType('alphanumeric');
    }

    if (options.capitalization) {
      charset.setcapitalization(options.capitalization);
    }

    if (options.readable) {
      charset.removeUnreadable();
    }

    charset.removeDuplicates();
  }
  else if (typeof options === 'number') {
    length = options;
    charset.setType('alphanumeric');
  }
  else {
    length = 32;
    charset.setType('alphanumeric');
  }

  // Generate the string
  var charsLen = charset.chars.length;
  var maxByte = 256 - (256 % charsLen);

  if (!cb) {
    while (string.length < length) {
      var buf = safeRandomBytes(Math.ceil(length * 256 / maxByte));
      string = processString(buf, string, charset.chars, length, maxByte);
    }

    return string;
  }

  getAsyncString(string, charset.chars, length, maxByte, cb);

};
