text: 'use strict';
  
  const EventEmitter = require('events');
  
  const CoreKeyGenerator = require('./core-key-generator');
  const clock = require('./clock');
  
  const delay = ms => new Promise(resolve => setTimeout(resolve, ms).unref());
  function createBackoff() {
  	let current = 100;
  	const max = current * (2 ** 8);
  	return () => {
  		const tmp = current;
  		if (current < max) {
  			current *= 2;
  		}
  		return delay(tmp);
  	};
  }
  
  const DEFAULT_SIGNING_KEY_USE = 60 * 60;
  const MINIMUM_SIGNING_KEY_USE = 60 * 60;
  const MAXIMUM_SIGNING_KEY_USE = 23 * 60 * 60;
  
  const DEFAULT_MAX_TOKEN_LIFETIME = 5 * 60;
  const MINIMUM_MAX_TOKEN_LIFETIME = 5 * 60;
  
  class KeyGenerator extends EventEmitter {
  
  	constructor(opts) {
  		if (typeof opts !== 'object') {
  			throw new TypeError(`"opts" should be an Object. Got "${typeof opts}".`);
  		}
  
  		super();
  
  		this._core = new CoreKeyGenerator(opts);
  
  		this._keyUseLifetime = DEFAULT_SIGNING_KEY_USE;
  		this._tokenLifetime = DEFAULT_MAX_TOKEN_LIFETIME;
  
  		if (typeof opts.lifetimes !== 'undefined') {
  			const lifetimes = opts.lifetimes;
  
  			if (typeof lifetimes !== 'object') {
  				throw new TypeError(`"opts.lifetimes" should be an object. Got "${lifetimes}" (${typeof lifetimes}).`);
  			}
  
  			if (typeof lifetimes.keyUse !== 'undefined') {
  				const keyUse = opts.keyUse;
  
  				if (typeof keyUse !== 'number' || keyUse !== Math.round(keyUse)) {
  					throw new TypeError(`"opts.lifetimes.keyUse" should be an integer. Got "${keyUse}" (${typeof keyUse}).`);
  				}
  
  				if (keyUse < MINIMUM_SIGNING_KEY_USE || MAXIMUM_SIGNING_KEY_USE < keyUse) {
  					throw new Error(`"opts.lifetimes.keyUse" must be between ${MINIMUM_SIGNING_KEY_USE} and ${MAXIMUM_SIGNING_KEY_USE}. Got "${keyUse}".`);
  				}
  
  				this._keyUseLifetime = keyUse;
  			}
  
  			if (typeof lifetimes.token !== 'undefined') {
  				const token = lifetimes.token;
  
  				if (typeof token !== 'number' || token !== Math.round(token)) {
  					throw new TypeError(`"opts.lifetimes.token" should be an integer. Got "${token}" (${typeof token}).`);
  				}
  
  				if (token < MINIMUM_MAX_TOKEN_LIFETIME) {
  					throw new Error(`"opts.lifetimes.token" must be at least ${MINIMUM_MAX_TOKEN_LIFETIME}. Got "${token}".`);
  				}
  
  				this._tokenLifetime = token;
  			}
  		}
  
  		this._current = null;
  		this._next = this._ensureGenerated(this._keyUseLifetime);
  		this._rotate();
  	}
  
  	async _rotate() {
  		this._current = this._next;
  
  		const { keyUseExpiry } = await this._current;
  
  		this._next = this._ensureGenerated(this._keyUseLifetime * 2);
  
  		await this._next;
  
  		setTimeout(() => this._rotate(), (keyUseExpiry - clock()) * 1000).unref();
  	}
  
  	async _ensureGenerated(keyUseLifetime) {
  		const backoff = createBackoff();
  		for (;;) {
  			try {
  				const exp = clock() + keyUseLifetime + this._tokenLifetime;
  				const keyUseExpiry = exp - this._tokenLifetime;
  
  				const key = await this._core.generateNewKey(exp);
  
  				return { key, keyUseExpiry };
  			} catch (err) {
  				this.emit('error', err);
  				await backoff();
  			}
  		}
  	}
  
  	async getCurrentPrivateKey() {
  		const { key, keyUseExpiry } = await this._current;
  
  		if (clock() > keyUseExpiry) {
  			throw new Error('Failed to generate a key before it expired');
  		}
  
  		return key;
  	}
  
  }
  
  module.exports = KeyGenerator;
  