'use strict';

require('source-map-support/register');

var _chalk = require('chalk');

var _chalk2 = _interopRequireDefault(_chalk);

var _path = require('path');

var _path2 = _interopRequireDefault(_path);

var _fs = require('fs');

var _fs2 = _interopRequireDefault(_fs);

var _lodash = require('lodash');

var _lodash2 = _interopRequireDefault(_lodash);

var _cluster = require('cluster');

var _cluster2 = _interopRequireDefault(_cluster);

var _dotenv = require('dotenv');

var _dotenv2 = _interopRequireDefault(_dotenv);

var _ms = require('ms');

var _ms2 = _interopRequireDefault(_ms);

var _middlewares = require('./middlewares');

var _middlewares2 = _interopRequireDefault(_middlewares);

var _logger = require('./logger');

var _logger2 = _interopRequireDefault(_logger);

var _security = require('./security');

var _security2 = _interopRequireDefault(_security);

var _notifications = require('./notifications');

var _notifications2 = _interopRequireDefault(_notifications);

var _hear = require('./hear');

var _hear2 = _interopRequireDefault(_hear);

var _fallback = require('./fallback');

var _fallback2 = _interopRequireDefault(_fallback);

var _database = require('./database');

var _database2 = _interopRequireDefault(_database);

var _ghostContent = require('./ghost-content');

var _ghostContent2 = _interopRequireDefault(_ghostContent);

var _mediaManager = require('./media-manager');

var _mediaManager2 = _interopRequireDefault(_mediaManager);

var _licensing = require('./licensing');

var _licensing2 = _interopRequireDefault(_licensing);

var _about = require('./about');

var _about2 = _interopRequireDefault(_about);

var _modules = require('./modules');

var _modules2 = _interopRequireDefault(_modules);

var _cloud = require('./cloud');

var _cloud2 = _interopRequireDefault(_cloud);

var _renderers = require('./renderers');

var _renderers2 = _interopRequireDefault(_renderers);

var _users = require('./users');

var _users2 = _interopRequireDefault(_users);

var _service = require('./content/service');

var _service2 = _interopRequireDefault(_service);

var _getItemProviders = require('./content/getItemProviders');

var _getItemProviders2 = _interopRequireDefault(_getItemProviders);

var _helpers = require('./helpers');

var _helpers2 = _interopRequireDefault(_helpers);

var _janitor = require('./janitor');

var _janitor2 = _interopRequireDefault(_janitor);

var _stats = require('./stats');

var _stats2 = _interopRequireDefault(_stats);

var _bus = require('./bus');

var _bus2 = _interopRequireDefault(_bus);

var _configManager = require('./config-manager');

var _configManager2 = _interopRequireDefault(_configManager);

var _provider = require('./dialog/provider');

var _provider2 = _interopRequireDefault(_provider);

var _state = require('./dialog/state');

var _state2 = _interopRequireDefault(_state);

var _engine = require('./dialog/engine');

var _engine2 = _interopRequireDefault(_engine);

var _processors = require('./dialog/processors');

var _processors2 = _interopRequireDefault(_processors);

var _janitor3 = require('./dialog/janitor');

var _janitor4 = _interopRequireDefault(_janitor3);

var _skills = require('./skills');

var _skills2 = _interopRequireDefault(_skills);

var _memory = require('./queues/memory');

var _memory2 = _interopRequireDefault(_memory);

var _package = require('../package.json');

var _package2 = _interopRequireDefault(_package);

var _server = require('./server');

var _server2 = _interopRequireDefault(_server);

var _util = require('./util');

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

function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } /**
                                                                                                                                                                                                                                                                                                                                                                                                                                                                            * The global instance of Botpress, which is the main object
                                                                                                                                                                                                                                                                                                                                                                                                                                                                            * your bot will use to interact with Botpress.
                                                                                                                                                                                                                                                                                                                                                                                                                                                                            * @var {Botpress} bp
                                                                                                                                                                                                                                                                                                                                                                                                                                                                            * @example
                                                                                                                                                                                                                                                                                                                                                                                                                                                                            * // File: index.js
                                                                                                                                                                                                                                                                                                                                                                                                                                                                            * // All bots are passed an instance of `bp` upon start
                                                                                                                                                                                                                                                                                                                                                                                                                                                                            * // This is an example of an empty bot
                                                                                                                                                                                                                                                                                                                                                                                                                                                                            * module.exports = (bp) => { ... }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                            */

/**
 * @namespace Botpress
 * @property {DialogEngine}  dialogEngine APIs to create and manipulate conversation flows
 * @property {KVS}  kvs Convenient, high-level storage mechanism
 * @property {ContentManager}  contentManager APIs to manage the content programmatically
 * @property {ContentRenderer}  renderers Change the look and feel of the
 * Content Elements (messages) on the different channels
 * @property {Database}  db (Advanced) Access to the internal Botpress Database
 * @property {Users}  users Store and manipulate data about users
 * @property {DialogStateManager}  dialogEngine.stateManager APIs to manipulate conversation states
 * @property {Logger}  logger Logging utility
 * @property {Botfile}  botfile The current botfile of the running bot
 */

const RESTART_EXIT_CODE = 107;

const mkdirIfNeeded = (path, logger) => {
  if (!_fs2.default.existsSync(path)) {
    logger.info(`Creating data directory: ${path}`);

    try {
      _fs2.default.mkdirSync(path);
    } catch (err) {
      logger.error(`[FATAL] Error creating directory: ${err.message}`);
      process.exit(1);
    }
  }
};

const REQUIRED_PROPS = ['botUrl'];

const validateBotfile = botfile => {
  if ('disableFileLogs' in botfile || _lodash2.default.get(botfile, 'log.file')) {
    console.log(`
      You're using the old logs configuration format.
      Since v11 botpress has stopped storing logs in files and
      has moved them to the database.

      Please update your botfile.

      Old configuration format:
        /*
          By default logs are enabled and available in dataDir
        */
        disableFileLogs: false,
        log: {
          file: 'bot.log',
          maxSize: 1e6 // 1mb
        }

      New format:
        /*
          By default logs are enabled and stored in the DB for 30 days
        */
        logs: {
          enabled: true,
          keepDays: 30
        }
      `);
    throw new Error('Outdated botfile format');
  }

  for (const prop of REQUIRED_PROPS) {
    if (!(prop in botfile)) {
      throw new Error(`Missing required botpress setting: ${prop}`);
    }
  }
};

class botpress {
  constructor({ botfile, options = {} }) {
    this.start = () => {
      if (_cluster2.default.isMaster) {
        let firstWorkerHasStartedAlready = false;

        const quit = (code = 0) => {
          if (this.stopServer) {
            this.stopServer();
          }

          process.exit(code);
        };

        const receiveMessageFromWorker = message => {
          if (message && message.workerStatus === 'starting') {
            if (!firstWorkerHasStartedAlready) {
              firstWorkerHasStartedAlready = true;
            } else {
              (0, _util.print)('info', '*** restarted worker process ***');
              this.stats.track('bot', 'restarted');
            }
          } else if (message.type === 'exit') {
            quit();
          }
        };

        _cluster2.default.on('exit', (worker, code /* , signal */) => {
          if (code === RESTART_EXIT_CODE) {
            _cluster2.default.fork().on('message', receiveMessageFromWorker);
          } else {
            quit(code);
          }
        });

        _cluster2.default.fork().on('message', receiveMessageFromWorker);
      }

      if (_cluster2.default.isWorker) {
        process.send({ workerStatus: 'starting' });
        this._start().catch(err => {
          (0, _util.print)('error', 'Error starting botpress: ', err.message, err.stack);
        });
      }
    };

    this.version = (0, _util.getBotpressVersion)();
    /**
     * The project location, which is the folder where botfile.js located
     */
    this.projectLocation = _path2.default.dirname(botfile);

    /**
     * Setup env with dotenv *before* requiring the botfile config
     */
    this._setupEnv();

    /**
     * The botfile config object
     */
    this.botfile = require(botfile);
    validateBotfile(this.botfile);

    this.stats = (0, _stats2.default)(this.botfile);

    this.interval = null;

    /*
      Check --inspect flag
    */

    const opts = _lodash2.default.result(options, 'opts') || {};

    this.hasInspectMode = opts.inspect || opts.i;
  }

  /**
   * Start the bot instance
   *
   * It will do the following initiation steps:
   *
   * 1. setup logger
   * 2. resolve paths (dataLocation)
   * 3. inject security functions
   * 4. load modules
   * @private
   */
  _start() {
    var _this = this;

    return _asyncToGenerator(function* () {
      _this.stats.track('bot', 'started');

      if (!_this.interval) {
        _this.interval = setInterval(function () {
          _this.stats.track('bot', 'running');
        }, 30 * 1000);
      }

      _this.botpressPath = _path2.default.join(__dirname, '../');

      const { projectLocation, botfile } = _this;

      const isFirstRun = _fs2.default.existsSync(_path2.default.join(projectLocation, '.welcome'));
      const dataLocation = (0, _util.getDataLocation)(botfile.dataDir, projectLocation);
      const configLocation = (0, _util.getDataLocation)(botfile.modulesConfigDir, projectLocation);
      const dbLocation = _path2.default.join(dataLocation, 'db.sqlite');
      const version = _package2.default.version;

      const logger = (0, _logger2.default)(botfile.logs);
      mkdirIfNeeded(dataLocation, logger);
      mkdirIfNeeded(configLocation, logger);

      const db = (0, _database2.default)({
        sqlite: { location: dbLocation },
        postgres: botfile.postgres,
        logger,
        botpressPath: _this.botpressPath
      });

      yield db.get(); // Running migrations

      const janitor = (0, _janitor2.default)({ db, logger });

      logger.enableDbStorageIfNeeded({ db, janitor });
      logger.info(`Starting botpress version ${version}`);

      janitor.start();

      const kvs = db._kvs;

      const cloud = yield (0, _cloud2.default)({ projectLocation, botfile, logger });

      if (!!botfile.login.useCloud && (yield cloud.isPaired())) {
        setInterval(function () {
          return cloud.updateRemoteEnv();
        }, (0, _ms2.default)('10m'));
        cloud.updateRemoteEnv(); // async on purpose
      }

      const configManager = new _configManager2.default({ configLocation, botfile, logger });

      const security = yield (0, _security2.default)({
        dataLocation,
        securityConfig: botfile.login,
        projectLocation,
        db,
        cloud,
        logger
      });

      const modules = (0, _modules2.default)(logger, projectLocation, dataLocation, configManager);

      const moduleDefinitions = modules._scan();

      const events = new _bus2.default();

      const notifications = (0, _notifications2.default)({
        knex: yield db.get(),
        modules: moduleDefinitions,
        logger,
        events
      });
      const about = (0, _about2.default)(projectLocation);
      const licensing = (0, _licensing2.default)({
        logger,
        projectLocation,
        version,
        db,
        botfile,
        bp: _this
      });
      const middlewares = (0, _middlewares2.default)(_this, dataLocation, projectLocation, logger);
      const { hear, middleware: hearMiddleware } = (0, _hear2.default)();
      const { middleware: fallbackMiddleware } = (0, _fallback2.default)(_this);

      const users = (0, _users2.default)({ db });
      const ghostManager = (0, _ghostContent2.default)({
        projectLocation,
        logger,
        db,
        enabled: !!_lodash2.default.get(botfile, 'ghostContent.enabled')
      });
      const contentManager = yield (0, _service2.default)({
        logger,
        projectLocation,
        botfile,
        ghostManager
      });
      const mediaManager = yield (0, _mediaManager2.default)({
        botfile,
        logger,
        ghostManager,
        projectLocation
      });

      // Register the built-in item providers such as "-random()"
      Object.keys(_getItemProviders2.default).forEach(function (provider) {
        contentManager.registerGetItemProvider(provider, _getItemProviders2.default[provider]);
      });

      const renderers = (0, _renderers2.default)({
        logger,
        middlewares,
        db,
        contentManager,
        botfile
      });

      const stateManager = (0, _state2.default)({ db });
      const flowProvider = new _provider2.default({ logger, projectLocation, botfile, ghostManager });
      const dialogJanitor = (0, _janitor4.default)({ db, middlewares, botfile });
      const dialogEngine = new _engine2.default({ flowProvider, stateManager, logger });

      const skillsManager = new _skills2.default({ logger });

      dialogEngine.onError(function ({ message }) {
        return notifications.create({ message: `DialogEngine: ${message}`, level: 'error', redirectUrl: '/logs' });
      });

      // Registers the default output processor, which sends messages to the user
      dialogEngine.registerOutputProcessor(_processors2.default['default']);
      dialogJanitor.install();

      const incomingQueue = new _memory2.default('Incoming', logger, {
        redis: botfile.redis
      });
      incomingQueue.subscribe(function (job) {
        return middlewares.sendIncomingImmediately(job.event);
      });

      const outgoingQueue = new _memory2.default('Outgoing', logger, {
        redis: botfile.redis
      });
      outgoingQueue.subscribe(function (job) {
        return middlewares.sendOutgoingImmediately(job.event);
      });

      const messages = {
        in: {
          enqueue: function (event) {
            return incomingQueue.enqueue({ event });
          },
          cancelAll: function (event) {
            return incomingQueue.cancelAll({ event });
          },
          peek: function (event) {
            return incomingQueue.peek({ event });
          }
        },
        out: {
          enqueue: function (event) {
            return outgoingQueue.enqueue({ event });
          },
          cancelAll: function (event) {
            return outgoingQueue.cancelAll({ event });
          },
          peek: function (event) {
            return outgoingQueue.peek({ event });
          }
        }
      };

      middlewares.register(renderers.incomingMiddleware);
      middlewares.register(hearMiddleware);
      middlewares.register(fallbackMiddleware);

      _lodash2.default.assign(_this, {
        dataLocation,
        isFirstRun,
        version,
        logger,
        security, // login, authenticate, getSecret
        events,
        notifications, // load, save, send
        about,
        middlewares,
        hear,
        licensing,
        modules,
        db,
        janitor,
        kvs,
        configManager,
        cloud,
        renderers,
        users,
        ghostManager,
        contentManager,
        mediaManager,
        dialogEngine,
        dialogJanitor,
        messages,
        skills: skillsManager
      });

      Object.defineProperty(_this, 'umm', {
        get() {
          logger.warn('DEPRECATION NOTICE – bp.umm is deprecated and will be removed – Please see bp.renderers instead.');
          return renderers;
        }
      });

      const loadedModules = yield modules._load(moduleDefinitions, _this);

      _this.stats.track('bot', 'modules', 'loaded', loadedModules.length);

      _lodash2.default.assign(_this, {
        _loadedModules: loadedModules
      });

      skillsManager.registerSkillsFromModules(_lodash2.default.values(loadedModules));
      yield contentManager.init();

      notifications._bindEvents();

      const server = (0, _server2.default)(_this);
      server.start().then(function (srv) {
        _this.stopServer = srv && srv.stop;

        if (_this.hasInspectMode) {
          const serverPID = process.pid;
          const inspectSignal = 'SIGUSR1';

          if (process.platform === 'win32') {
            process._debugProcess(serverPID);
          } else {
            process.kill(serverPID, inspectSignal);
          }
        }

        events.emit('ready');
        for (const mod of _lodash2.default.values(loadedModules)) {
          mod.handlers.ready && mod.handlers.ready(_this, mod.configuration, _helpers2.default);
        }

        const { botUrl } = botfile;
        logger.info(_chalk2.default.green.bold(`Bot launched. Visit: ${botUrl}`));
      });

      const middlewareAutoLoading = _lodash2.default.get(botfile, 'middleware.autoLoading');
      if (!_lodash2.default.isNil(middlewareAutoLoading) && middlewareAutoLoading === false) {
        logger.debug('Middleware Auto Loading was disabled. Call bp.middlewares.load() manually.');
      } else {
        middlewares.load();
      }

      const projectEntry = require(projectLocation);
      if (typeof projectEntry === 'function') {
        projectEntry.call(projectEntry, _this);
      } else {
        logger.error('[FATAL] The bot entry point must be a function that takes an instance of bp');
        process.exit(1);
      }

      process.on('uncaughtException', function (err) {
        logger.error('[FATAL] An unhandled exception occurred in your bot', err);
        if (_util.isDeveloping) {
          logger.error(err.stack);
        }

        _this.stats.trackException(err.message);
        process.exit(1);
      });

      process.on('unhandledRejection', function (reason, p) {
        logger.error('Unhandled Rejection in Promise: ', p, 'Reason:', reason);

        _this.stats.trackException(reason);
        if (_util.isDeveloping && reason && reason.stack) {
          logger.error(reason.stack);
        }
      });
    })();
  }

  restart(interval = 0) {
    setTimeout(() => {
      process.exit(RESTART_EXIT_CODE);
    }, interval);
  }

  _setupEnv() {
    const envPath = _path2.default.resolve(this.projectLocation, '.env');
    if (_fs2.default.existsSync(envPath)) {
      const envConfig = _dotenv2.default.parse(_fs2.default.readFileSync(envPath));
      for (const k in envConfig) {
        if (_lodash2.default.isNil(process.env[k]) || process.env.ENV_OVERLOAD) {
          process.env[k] = envConfig[k];
        }
      }
    }
  }
}

module.exports = botpress;
//# sourceMappingURL=botpress.js.map