text: 'use strict';
  const EventEmitter = require('events').EventEmitter;
  const STATUS_CODES = require('./util/status-codes');
  
  const Comparator = require('./sources/comparator');
  const Index = require('./sources/iindex');
  
  /**
   * A coordinating class that makes sure that Properties are resolved before
   * attempting to retrieve the Index. When the Index (or its underlying sources)
   * update, have Properties rebuild the view.
   *
   * @class Sources
   * @extends EventEmitter
   */
  class Sources extends EventEmitter {
    /**
     * Constructor
     * @param {Properties} properties
     */
    constructor(properties) {
      super();
  
      this.setMaxListeners(Sources.MAX_LISTENERS);
  
      this.properties = properties;
      this.indices = [];
  
      this.initialized = false;
      this.current = new Index([], null);
    }
  
    /**
     * Add an index document source
     *
     * @param  {Source} source
     */
    index(source) {
      this.indices.push(source);
    }
  
    /**
     * Initialize the Properties instance, then all of the index sources, then
     * trigger the first update, then subscribe to future update events.
     *
     * @return {Promise<Sources>}
     */
    initialize() {
      // Instance is already initialized
      if (this.initialized) {
        return Promise.resolve(this);
      }
  
      // Resource is currently initializing. Resolve on initialized
      if (this.initializing) {
        return new Promise((resolve) => {
          this.once('initialized', () => resolve(this));
        });
      }
  
      // Resource is not yet initialized. Start initializing
      this.initializing = true;
  
      return this.properties.initialize()
        .then(() => Promise.all(
          this.indices.map((source) => source.initialize())
        ))
        .then(() => this.update())
        .then(() => {
          // Subscribe to indices' update events
          this.indices.forEach((source) =>
            source.on('update', () => this.update()));
  
          // Subscribe to properties' build events once it's initialized
          this.properties.on('build', () => this.update());
  
          this.initializing = false;
          this.initialized = true;
          this.emit('initialized', this);
  
          return this;
        });
    }
  
    /**
     * Create a new Index, instantiate new sources, create and activate a new view,
     * then shutdown retired sources
     *
     * @return {Promise<Sources>}
     */
    update() {
      const updated = new Promise((resolve) => {
        this.once('_resolve_update', () => resolve(this));
      });
  
      if (this._updating) {
        return updated;
      }
  
      Log.log('INFO', 'Sources: Updating sources');
  
      // Block for a hold-down period to let multiple updates propagate
      this._updating = setTimeout(() => {
        let configs = [];
  
        // Aggregate Source configurations from index sources
        this.indices.forEach((source) => {
          if (!(source.sources instanceof Array)) {
            Log.log('WARN', `Sources: Index source ${source.name} does not have any sources. Ignoring.`);
  
            return;
          }
  
          configs = configs.concat(source.sources);
        });
  
        const next = new Index(configs, this.properties.persistent);
        const difference = Comparator.compare(this.current, next);
  
        this.current = difference.build(Sources.providers);
  
        // Nothing to see here. Don't trigger a view-update.
        if (!difference.changes) {
          delete this._updating;
  
          Log.log('INFO', 'Sources: Update successful, no changes.');
          this.emit('_resolve_update', this);
          this.emit('noupdate', this);
  
          return;
        }
  
        // Build and activate a new view from the new Index, then clean up old sources.
        this.properties.view(next.ordered()).activate()
          .then(() => {
            difference.cleanup();
            delete this._updating;
  
            Log.log('INFO', `Update successful, created ${difference.create.length} sources, ` +
                            `shutting down ${difference.destroy.length} sources`);
  
            this.emit('_resolve_update', this);
            this.emit('update', this);
          });
      }, Sources.UPDATE_HOLD_DOWN);
  
      return updated;
    }
  
    /**
     * Compute system status
     *
     * @return {Object}
     */
    health() {
      const object = {
        code: STATUS_CODES.OK,
        status: 'OK'
      };
  
      object.indices = this.indices.map((source) => {
        // TODO This logic is fairly ham-fisted right now. It'll work because nothing
        // is setting `state` to WARNING at the moment. When we do start supporting a
        // WARNING state, this will have to become aware that OK < WARING < ERROR.
        if (!source.ok) {
          object.code = STATUS_CODES.INTERNAL_SERVER_ERROR;
          object.status = source.state;
        }
  
        return source.status();
      });
  
      object.sources = this.properties.sources.map((source) => {
        if (!source.ok) {
          object.code = STATUS_CODES.INTERNAL_SERVER_ERROR;
          object.status = source.state;
        }
  
        return source.status();
      });
  
      return object;
    }
  }
  
  // Registered Source providers
  Sources.providers = {
    s3: require('./source/s3'),
    consul: require('./source/consul')
  };
  
  // Update hold-down timeout
  Sources.UPDATE_HOLD_DOWN = 1000; // eslint-disable-line rapid7/static-magic-numbers
  Sources.MAX_LISTENERS = 100; // eslint-disable-line rapid7/static-magic-numbers
  
  Sources.Comparator = Comparator;
  Sources.Index = Index;
  module.exports = Sources;
  