import seedrandom from 'seedrandom';

function _defineProperties(target, props) {
  for (var i = 0; i < props.length; i++) {
    var descriptor = props[i];
    descriptor.enumerable = descriptor.enumerable || false;
    descriptor.configurable = true;
    if ("value" in descriptor) descriptor.writable = true;
    Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor);
  }
}
function _createClass(Constructor, protoProps, staticProps) {
  if (protoProps) _defineProperties(Constructor.prototype, protoProps);
  if (staticProps) _defineProperties(Constructor, staticProps);
  Object.defineProperty(Constructor, "prototype", {
    writable: false
  });
  return Constructor;
}
function _inheritsLoose(subClass, superClass) {
  subClass.prototype = Object.create(superClass.prototype);
  subClass.prototype.constructor = subClass;
  _setPrototypeOf(subClass, superClass);
}
function _setPrototypeOf(o, p) {
  _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) {
    o.__proto__ = p;
    return o;
  };
  return _setPrototypeOf(o, p);
}
function _toPrimitive(input, hint) {
  if (typeof input !== "object" || input === null) return input;
  var prim = input[Symbol.toPrimitive];
  if (prim !== undefined) {
    var res = prim.call(input, hint || "default");
    if (typeof res !== "object") return res;
    throw new TypeError("@@toPrimitive must return a primitive value.");
  }
  return (hint === "string" ? String : Number)(input);
}
function _toPropertyKey(arg) {
  var key = _toPrimitive(arg, "string");
  return typeof key === "symbol" ? key : String(key);
}

var RNG = /*#__PURE__*/function () {
  function RNG() {}
  var _proto = RNG.prototype;
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  _proto._seed = function _seed(seed, _opts) {
    // TODO: add entropy and stuff
    if (seed === (seed || 0)) {
      return seed;
    } else {
      var strSeed = '' + seed;
      var s = 0;
      for (var k = 0; k < strSeed.length; ++k) {
        s ^= strSeed.charCodeAt(k) | 0;
      }
      return s;
    }
  };
  return RNG;
}();

var RNGFunction = /*#__PURE__*/function (_RNG) {
  _inheritsLoose(RNGFunction, _RNG);
  function RNGFunction(thunk, opts) {
    var _this;
    _this = _RNG.call(this) || this;
    _this._rng = void 0;
    _this.seed(thunk, opts);
    return _this;
  }
  var _proto = RNGFunction.prototype;
  _proto.next = function next() {
    return this._rng();
  }
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  ;
  _proto.seed = function seed(thunk, _opts) {
    this._rng = thunk;
  };
  _proto.clone = function clone(_, opts) {
    return new RNGFunction(this._rng, opts);
  };
  _createClass(RNGFunction, [{
    key: "name",
    get: function get() {
      return 'function';
    }
  }]);
  return RNGFunction;
}(RNG);

/**
 * Construct an RNG with variable inputs. Used in calls to Random constructor
 * @param {...*} args - Distribution-specific arguments
 * @return RNG
 *
 * @example
 * new Random(RNGFactory(...args))
 */
var RNGFactory = (function () {
  var args = [].slice.call(arguments);
  var _args = args,
    _args$ = _args[0],
    arg0 = _args$ === void 0 ? 'default' : _args$;
  switch (typeof arg0) {
    case 'object':
      if (arg0 instanceof RNG) {
        return arg0;
      }
      break;
    case 'function':
      return new RNGFunction(arg0);
    case 'number':
    case 'string':
    default:
      return new RNGFunction(seedrandom.apply(void 0, args));
  }
  throw new Error("invalid RNG \"" + arg0 + "\"");
});

var uniform = (function (random, min, max) {
  if (min === void 0) {
    min = 0;
  }
  if (max === void 0) {
    max = 1;
  }
  return function () {
    return random.next() * (max - min) + min;
  };
});

function numberValidator(num) {
  return new NumberValidator(num);
}
var NumberValidator = function NumberValidator(num) {
  var _this = this;
  this.n = void 0;
  this.isInt = function () {
    if (Number.isInteger(_this.n)) {
      return _this;
    }
    throw new Error("Expected number to be an integer, got " + _this.n);
  };
  this.isPositive = function () {
    if (_this.n > 0) {
      return _this;
    }
    throw new Error("Expected number to be positive, got " + _this.n);
  };
  this.lessThan = function (v) {
    if (_this.n < v) {
      return _this;
    }
    throw new Error("Expected number to be less than " + v + ", got " + _this.n);
  };
  this.greaterThanOrEqual = function (v) {
    if (_this.n >= v) {
      return _this;
    }
    throw new Error("Expected number to be greater than or equal to " + v + ", got " + _this.n);
  };
  this.greaterThan = function (v) {
    if (_this.n > v) {
      return _this;
    }
    throw new Error("Expected number to be greater than " + v + ", got " + _this.n);
  };
  this.n = num;
};

var uniformInt = (function (random, min, max) {
  if (min === void 0) {
    min = 0;
  }
  if (max === void 0) {
    max = 1;
  }
  if (max === undefined) {
    max = min === undefined ? 1 : min;
    min = 0;
  }
  numberValidator(min).isInt();
  numberValidator(max).isInt();
  return function () {
    return Math.floor(random.next() * (max - min + 1) + min);
  };
});

var uniformBoolean = (function (random) {
  return function () {
    return random.next() >= 0.5;
  };
});

var normal = (function (random, mu, sigma) {
  if (mu === void 0) {
    mu = 0;
  }
  if (sigma === void 0) {
    sigma = 1;
  }
  return function () {
    var x, y, r;
    do {
      x = random.next() * 2 - 1;
      y = random.next() * 2 - 1;
      r = x * x + y * y;
    } while (!r || r > 1);
    return mu + sigma * y * Math.sqrt(-2 * Math.log(r) / r);
  };
});

var logNormal = (function (random, mu, sigma) {
  if (mu === void 0) {
    mu = 0;
  }
  if (sigma === void 0) {
    sigma = 1;
  }
  var normal = random.normal(mu, sigma);
  return function () {
    return Math.exp(normal());
  };
});

var bernoulli = (function (random, p) {
  if (p === void 0) {
    p = 0.5;
  }
  numberValidator(p).greaterThanOrEqual(0).lessThan(1);
  return function () {
    return Math.floor(random.next() + p);
  };
});

var binomial = (function (random, n, p) {
  if (n === void 0) {
    n = 1;
  }
  if (p === void 0) {
    p = 0.5;
  }
  numberValidator(n).isInt().isPositive();
  numberValidator(p).greaterThanOrEqual(0).lessThan(1);
  return function () {
    var i = 0;
    var x = 0;
    while (i++ < n) {
      if (random.next() < p) {
        x++;
      }
    }
    return x;
  };
});

var geometric = (function (random, p) {
  if (p === void 0) {
    p = 0.5;
  }
  numberValidator(p).greaterThan(0).lessThan(1);
  var invLogP = 1.0 / Math.log(1.0 - p);
  return function () {
    return Math.floor(1 + Math.log(random.next()) * invLogP);
  };
});

var logFactorialTable = [0.0, 0.0, 0.69314718055994529, 1.791759469228055, 3.1780538303479458, 4.7874917427820458, 6.5792512120101012, 8.5251613610654147, 10.604602902745251, 12.801827480081469];
var logFactorial = function logFactorial(k) {
  return logFactorialTable[k];
};
var logSqrt2PI = 0.91893853320467267;
var poisson = (function (random, lambda) {
  if (lambda === void 0) {
    lambda = 1;
  }
  numberValidator(lambda).isPositive();
  if (lambda < 10) {
    // inversion method
    var expMean = Math.exp(-lambda);
    return function () {
      var p = expMean;
      var x = 0;
      var u = random.next();
      while (u > p) {
        u = u - p;
        p = lambda * p / ++x;
      }
      return x;
    };
  } else {
    // generative method
    var smu = Math.sqrt(lambda);
    var b = 0.931 + 2.53 * smu;
    var a = -0.059 + 0.02483 * b;
    var invAlpha = 1.1239 + 1.1328 / (b - 3.4);
    var vR = 0.9277 - 3.6224 / (b - 2);
    return function () {
      while (true) {
        var u = void 0;
        var v = random.next();
        if (v <= 0.86 * vR) {
          u = v / vR - 0.43;
          return Math.floor((2 * a / (0.5 - Math.abs(u)) + b) * u + lambda + 0.445);
        }
        if (v >= vR) {
          u = random.next() - 0.5;
        } else {
          u = v / vR - 0.93;
          u = (u < 0 ? -0.5 : 0.5) - u;
          v = random.next() * vR;
        }
        var us = 0.5 - Math.abs(u);
        if (us < 0.013 && v > us) {
          continue;
        }
        var k = Math.floor((2 * a / us + b) * u + lambda + 0.445);
        v = v * invAlpha / (a / (us * us) + b);
        if (k >= 10) {
          var t = (k + 0.5) * Math.log(lambda / k) - lambda - logSqrt2PI + k - (1 / 12.0 - (1 / 360.0 - 1 / (1260.0 * k * k)) / (k * k)) / k;
          if (Math.log(v * smu) <= t) {
            return k;
          }
        } else if (k >= 0) {
          var _logFactorial;
          var f = (_logFactorial = logFactorial(k)) != null ? _logFactorial : 0;
          if (Math.log(v) <= k * Math.log(lambda) - lambda - f) {
            return k;
          }
        }
      }
    };
  }
});

var exponential = (function (random, lambda) {
  if (lambda === void 0) {
    lambda = 1;
  }
  numberValidator(lambda).isPositive();
  return function () {
    return -Math.log(1 - random.next()) / lambda;
  };
});

var irwinHall = (function (random, n) {
  if (n === void 0) {
    n = 1;
  }
  numberValidator(n).isInt().greaterThanOrEqual(0);
  return function () {
    var sum = 0;
    for (var i = 0; i < n; ++i) {
      sum += random.next();
    }
    return sum;
  };
});

var bates = (function (random, n) {
  if (n === void 0) {
    n = 1;
  }
  numberValidator(n).isInt().isPositive();
  var irwinHall = random.irwinHall(n);
  return function () {
    return irwinHall() / n;
  };
});

var pareto = (function (random, alpha) {
  if (alpha === void 0) {
    alpha = 1;
  }
  numberValidator(alpha).greaterThanOrEqual(0);
  var invAlpha = 1.0 / alpha;
  return function () {
    return 1.0 / Math.pow(1.0 - random.next(), invAlpha);
  };
});

var RNGMathRandom = /*#__PURE__*/function (_RNG) {
  _inheritsLoose(RNGMathRandom, _RNG);
  function RNGMathRandom() {
    return _RNG.apply(this, arguments) || this;
  }
  var _proto = RNGMathRandom.prototype;
  _proto.next = function next() {
    return Math.random();
  }
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  ;
  _proto.seed = function seed(_seed, _opts) {
    // intentionally empty
  };
  _proto.clone = function clone() {
    return new RNGMathRandom();
  };
  _createClass(RNGMathRandom, [{
    key: "name",
    get: function get() {
      return 'default';
    }
  }]);
  return RNGMathRandom;
}(RNG);

/**
 * Seedable random number generator supporting many common distributions.
 *
 * Defaults to Math.random as its underlying pseudorandom number generator.
 *
 * @name Random
 * @class
 *
 * @param {RNG|function} [rng=Math.random] - Underlying pseudorandom number generator.
 */
var Random = /*#__PURE__*/function () {
  function Random(rng) {
    var _this = this;
    this._rng = void 0;
    this._patch = void 0;
    this._cache = {};
    this.next = function () {
      return _this._rng.next();
    };
    this["float"] = function (min, max) {
      return _this.uniform(min, max)();
    };
    this["int"] = function (min, max) {
      return _this.uniformInt(min, max)();
    };
    this.integer = function (min, max) {
      return _this.uniformInt(min, max)();
    };
    this.bool = function () {
      return _this.uniformBoolean()();
    };
    this["boolean"] = function () {
      return _this.uniformBoolean()();
    };
    this.uniform = function (min, max) {
      return _this._memoize('uniform', uniform, min, max);
    };
    this.uniformInt = function (min, max) {
      return _this._memoize('uniformInt', uniformInt, min, max);
    };
    this.uniformBoolean = function () {
      return _this._memoize('uniformBoolean', uniformBoolean);
    };
    this.normal = function (mu, sigma) {
      return normal(_this, mu, sigma);
    };
    this.logNormal = function (mu, sigma) {
      return logNormal(_this, mu, sigma);
    };
    this.bernoulli = function (p) {
      return bernoulli(_this, p);
    };
    this.binomial = function (n, p) {
      return binomial(_this, n, p);
    };
    this.geometric = function (p) {
      return geometric(_this, p);
    };
    this.poisson = function (lambda) {
      return poisson(_this, lambda);
    };
    this.exponential = function (lambda) {
      return exponential(_this, lambda);
    };
    this.irwinHall = function (n) {
      return irwinHall(_this, n);
    };
    this.bates = function (n) {
      return bates(_this, n);
    };
    this.pareto = function (alpha) {
      return pareto(_this, alpha);
    };
    if (rng && rng instanceof RNG) {
      this.use(rng);
    } else {
      this.use(new RNGMathRandom());
    }
    this._cache = {};
  }
  /**
   * @member {RNG} Underlying pseudo-random number generator
   */
  var _proto = Random.prototype;
  /**
   * Creates a new `Random` instance, optionally specifying parameters to
   * set a new seed.
   *
   * @see RNG.clone
   *
   * @param {string} [seed] - Optional seed for new RNG.
   * @param {object} [opts] - Optional config for new RNG options.
   * @return {Random}
   */
  _proto.clone = function clone() {
    var args = [].slice.call(arguments);
    if (args.length) {
      return new Random(RNGFactory.apply(void 0, args));
    } else {
      return new Random(this.rng.clone());
    }
  }
  /**
   * Sets the underlying pseudorandom number generator used via
   * either an instance of `seedrandom`, a custom instance of RNG
   * (for PRNG plugins), or a string specifying the PRNG to use
   * along with an optional `seed` and `opts` to initialize the
   * RNG.
   *
   * @example
   * import random from 'random'
   *
   * random.use('example_seedrandom_string')
   * // or
   * random.use(seedrandom('kittens'))
   * // or
   * random.use(Math.random)
   *
   * @param {...*} args
   */;
  _proto.use = function use() {
    this._rng = RNGFactory.apply(void 0, [].slice.call(arguments));
  }
  /**
   * Patches `Math.random` with this Random instance's PRNG.
   */;
  _proto.patch = function patch() {
    if (this._patch) {
      throw new Error('Math.random already patched');
    }
    this._patch = Math.random;
    Math.random = this.uniform();
  }
  /**
   * Restores a previously patched `Math.random` to its original value.
   */;
  _proto.unpatch = function unpatch() {
    if (this._patch) {
      Math.random = this._patch;
      delete this._patch;
    }
  }
  // --------------------------------------------------------------------------
  // Uniform utility functions
  // --------------------------------------------------------------------------
  /**
   * Convenience wrapper around `this.rng.next()`
   *
   * Returns a floating point number in [0, 1).
   *
   * @return {number}
   */;
  /**
   * Returns an item chosen uniformly at trandom from the given array.
   *
   * Convence wrapper around `random.uniformInt()`
   *
   * @param {Array<T>} [array] - Lower bound (integer, inclusive)
   * @return {T | undefined}
   */
  _proto.choice = function choice(array) {
    if (!Array.isArray(array)) {
      throw new Error("Random.choice expected input to be an array, got " + typeof array);
    }
    var length = array == null ? void 0 : array.length;
    if (length > 0) {
      var index = this.uniformInt(0, length - 1)();
      return array[index];
    } else {
      return undefined;
    }
  }
  // --------------------------------------------------------------------------
  // Uniform distributions
  // --------------------------------------------------------------------------
  /**
   * Generates a [Continuous uniform distribution](https://en.wikipedia.org/wiki/Uniform_distribution_(continuous)).
   *
   * @param {number} [min=0] - Lower bound (float, inclusive)
   * @param {number} [max=1] - Upper bound (float, exclusive)
   * @return {function}
   */;
  // --------------------------------------------------------------------------
  // Internal
  // --------------------------------------------------------------------------
  /**
   * Memoizes distributions to ensure they're only created when necessary.
   *
   * Returns a thunk which that returns independent, identically distributed
   * samples from the specified distribution.
   *
   * @private
   *
   * @param {string} label - Name of distribution
   * @param {function} getter - Function which generates a new distribution
   * @param {...*} args - Distribution-specific arguments
   *
   * @return {function}
   */
  _proto._memoize = function _memoize(label, getter) {
    var args = [].slice.call(arguments, 2);
    var key = "" + args.join(';');
    var value = this._cache[label];
    if (value === undefined || value.key !== key) {
      value = {
        key: key,
        distribution: getter.apply(void 0, [this].concat(args))
      };
      this._cache[label] = value;
    }
    return value.distribution;
  };
  _createClass(Random, [{
    key: "rng",
    get: function get() {
      return this._rng;
    }
  }]);
  return Random;
}();
// defaults to Math.random as its RNG
var random = new Random();

export { RNG, RNGFactory, Random, random as default };
//# sourceMappingURL=random.module.js.map
