'use strict';

var _lodash = require('lodash');

var _lodash2 = _interopRequireDefault(_lodash);

var _mware = require('mware');

var _mware2 = _interopRequireDefault(_mware);

var _path = require('path');

var _path2 = _interopRequireDefault(_path);

var _fs = require('fs');

var _fs2 = _interopRequireDefault(_fs);

var _bluebird = require('bluebird');

var _bluebird2 = _interopRequireDefault(_bluebird);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

const createMiddleware = (bp, middlewareName) => {
  const _use = (0, _mware2.default)();
  const _error = (0, _mware2.default)();

  const use = middleware => {
    if (typeof middleware !== 'function') {
      throw new TypeError('Expected all middleware arguments to be functions');
    }

    if (middleware.length === 2) {
      _use(middleware);
    } else if (middleware.length === 3) {
      _error(middleware);
    }
  };

  const dispatch = event => {
    if (!_lodash2.default.isPlainObject(event)) {
      throw new TypeError('Expected all dispatch arguments to be plain event objects');
    }

    const conformity = {
      type: value => typeof value === 'string',
      platform: value => typeof value === 'string',
      text: value => typeof value === 'string',
      raw: () => true
    };

    if (!_lodash2.default.conformsTo(event, conformity)) {
      throw new TypeError('Expected event to contain (type: string), ' + '(platform: string), (text: string), (raw: any)');
    }

    // Provide botpress to the event handlers
    event.bp = bp;

    _use.run(event, err => {
      if (err) {
        _error.run(err, event, () => {
          bp.logger.error(`[BOTPRESS] Unhandled error in middleware (${middlewareName}). Error: ${err.message}`);
        });
      }
    });

    return event._promise || _bluebird2.default.resolve();
  };

  return { use, dispatch };
}; /**
    * The middleware chain is in charge of pre-processing incoming and outgoing messages.
    * A middleware can for example translate a message on receiving and before sending.
    * Most middleware are registered by the modules. For example, the Analytics module
    * keeps track of the messages with the help of an incoming and an outgoing middleware.
    * @namespace Middleware
    */

module.exports = (bp, dataLocation, projectLocation, logger) => {
  const middlewaresFilePath = _path2.default.join(dataLocation, 'middlewares.json');

  const noopChain = arg => {
    let message = 'Middleware called before middlewares have been loaded. This is a no-op.' + ' Have you forgotten to call `bp.loadMiddlewares()` in your bot?';

    if (arg && typeof arg === 'object') {
      message += '\nCalled with: ' + JSON.stringify(arg, null, 2);
    }

    logger.warn(message);
  };

  const readCustomizations = () => {
    if (!_fs2.default.existsSync(middlewaresFilePath)) {
      _fs2.default.writeFileSync(middlewaresFilePath, '{}');
      return {};
    }
    return JSON.parse(_fs2.default.readFileSync(middlewaresFilePath));
  };

  let incoming = noopChain,
      outgoing = noopChain,
      customizations = readCustomizations();
  const middlewares = [];

  const writeCustomizations = () => {
    _fs2.default.writeFileSync(middlewaresFilePath, JSON.stringify(customizations));
  };

  const setCustomizations = middlewares => {
    _lodash2.default.each(middlewares, middleware => {
      const { name, order, enabled } = middleware;
      customizations[name] = { order, enabled };
    });
    writeCustomizations();
  };

  const resetCustomizations = () => {
    customizations = {};
    writeCustomizations();
  };

  /**
   * @typedef {Object} Event
   * @prop {String} platform
   * @prop {String} text
   * @prop {object} raw
   * @prop {String} type
   * @memberOf! Middleware
   */

  /**
   * @callback Handler
   * @memberOf! Middleware
   * @param {Object} event The incoming or outgoing event
   * @param {Function} next Call this function to make the event flow to the next middleware (see example)
   */

  /**
   * @typedef {Object} Middleware
   * @memberOf! Middleware
   * @property {String} name Unique name of the middleware
   * @property {Middleware.Handler} handler The handler function
   * @property {String} type Can be 'incoming' or 'outgoing'
   * @property {Number} order A positive number from 0 (before everything else) to 1000 (last middleware)
   * @property {Boolean} [enabled=true] Whether this middleware is enabled or not
   */

  /**
   * Registers a new middleware into the chain
   * @param  {Middleware.Middleware} middleware The middleware to register
   * @memberOf! Middleware
   */
  const register = middleware => {
    if (!middleware || !middleware.name) {
      logger.error('A unique middleware name is mandatory');
      return false;
    }

    if (!middleware.handler) {
      logger.error('A middleware handler is mandatory');
      return false;
    }

    if (!middleware.type || middleware.type !== 'incoming' && middleware.type !== 'outgoing') {
      logger.error('A middleware type (incoming or outgoing) is required');
      return false;
    }

    middleware.order = middleware.order || 0;
    middleware.enabled = typeof middleware.enabled === 'undefined' ? true : !!middleware.enabled;

    if (_lodash2.default.some(middlewares, m => m.name === middleware.name)) {
      logger.error('Another middleware with the same name has already been registered');
      return false;
    }

    middlewares.push(middleware);
  };

  const list = () => {
    return _lodash2.default.orderBy(middlewares.map(middleware => {
      const customization = customizations[middleware.name];
      if (customization) {
        return Object.assign({}, middleware, customization);
      }
      return middleware;
    }), 'order');
  };

  const load = () => {
    incoming = createMiddleware(bp, 'incoming');
    outgoing = createMiddleware(bp, 'outgoing');

    const { middleware: licenseMiddleware } = bp.licensing;
    incoming.use(licenseMiddleware);

    _lodash2.default.each(list(), m => {
      if (!m.enabled) {
        return logger.debug('SKIPPING middleware:', m.name, ' [Reason=disabled]');
      }

      logger.debug('Loading middleware:', m.name);

      if (m.type === 'incoming') {
        incoming.use(m.handler);
      } else {
        outgoing.use(m.handler);
      }
    });
  };

  const sendToMiddleware = type => event => {
    const mw = type === 'incoming' ? incoming : outgoing;
    return mw.dispatch ? mw.dispatch(event) : mw(event);
  };

  return {
    load,
    list,
    register,
    /**
     * Sends an incoming event (from the user to the bot)
     * @param  {Middleware.Event} event An event object
     * @memberOf! Middleware
     */
    sendIncoming: event => bp.messages.in.enqueue(event),

    /**
     * Sends an outgoing event (from the bot to the user)
     * @param  {Middleware.Event} event An event object
     * @memberOf! Middleware
     */
    sendOutgoing: event => bp.messages.out.enqueue(event),
    sendIncomingImmediately: sendToMiddleware('incoming'),
    sendOutgoingImmediately: sendToMiddleware('outgoing'),
    getCustomizations: () => customizations,
    setCustomizations,
    resetCustomizations
  };
};
//# sourceMappingURL=middlewares.js.map