'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _chalk = require('chalk');

var _chalk2 = _interopRequireDefault(_chalk);

var _lodash = require('lodash');

var _lodash2 = _interopRequireDefault(_lodash);

var _stackTrace = require('stack-trace');

var _stackTrace2 = _interopRequireDefault(_stackTrace);

var _watchr = require('watchr');

var _watchr2 = _interopRequireDefault(_watchr);

var _debug = require('../utils/debug');

var _debug2 = _interopRequireDefault(_debug);

var _print = require('./helpers/print');

var _printTools = require('../utils/print-tools');

var _dateUtils = require('../utils/date-utils');

var _socket = require('./helpers/socket');

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"); }); }; }

let pendingUpdates = 0;
const timer = new _dateUtils.Timer();
timer.getDuration = () => _chalk2.default.dim(`${timer.getDurationTime()} ms`);

const { debug } = (0, _debug2.default)('cmd-socket-watch');

class SocketWatch {
  constructor(context) {
    this.session = context.session;
    this.Socket = context.Socket;
    this.firstRun = true;
    this.traceTimers = [];
    this.scriptWatch = false;
    this.lastId = null;
  }

  static getFirstTrace(errorMsg) {
    const err = new Error(errorMsg);
    return _lodash2.default.find(_stackTrace2.default.parse(err), o => o.lineNumber !== null);
  }

  compile() {
    var _this = this;

    return _asyncToGenerator(function* () {
      try {
        yield _this.socket.compile();
      } catch (err) {
        (0, _print.printCompileError)(err);
      }
    })();
  }

  static printUpdateSuccessful(socketName, updateStatus) {
    debug('printUpdateSuccessful', socketName, updateStatus);
    const duration = timer.getDuration();
    const socketNameStr = _chalk2.default.cyan(`${socketName} socket`);

    if (updateStatus.status === 'ok') {
      const status = _chalk2.default.grey('files synced:');
      (0, _printTools.echo)(7)(`${status} ${(0, _dateUtils.currentTime)()} ${socketNameStr} ${duration}`);
    } else if (updateStatus.status === 'stopped') {
      const status = _chalk2.default.grey('files in sync:');
      (0, _printTools.echo)(6)(`${status} ${(0, _dateUtils.currentTime)()} ${socketNameStr} ${duration}`);
    } else if (updateStatus.status === 'error') {
      const errDetail = _chalk2.default.red(updateStatus.message.error);
      const status = _chalk2.default.red('files not synced:');
      (0, _printTools.echo)(3)(`${status} ${(0, _dateUtils.currentTime)()} ${socketNameStr} ${duration} ${errDetail}`);
    } else {
      const status = _chalk2.default.red('files not synced:');
      (0, _printTools.echo)(3)(`${status} ${(0, _dateUtils.currentTime)()} ${socketNameStr} ${duration}`);
    }
  }

  static printUpdateFailed(socketName, err) {
    const duration = timer.getDuration();
    const errDetail = JSON.parse(err).detail;
    (0, _printTools.echo)(3)(`${_chalk2.default.red('files not synced:')} ${(0, _dateUtils.currentTime)()} ${socketName} ${duration} ${errDetail}`);
  }

  doUpdate(config) {
    var _this2 = this;

    return _asyncToGenerator(function* () {
      debug('compilation start');
      timer.reset();
      // We have co count here updates
      pendingUpdates += 1;
      if (pendingUpdates > 1) {
        debug('not updating, update pending');
        return;
      }

      const socketName = _this2.socket.name;
      try {
        const updateStatus = yield _this2.socket.update({ config });
        SocketWatch.printUpdateSuccessful(socketName, updateStatus);
      } catch (err) {
        if (typeof err === 'object') return (0, _printTools.error)('Socket sync failed!');
        SocketWatch.printUpdateFailed(socketName, err);
      }

      // After update we have to understand if we should fire new one
      pendingUpdates -= 1;
      if (pendingUpdates > 0) {
        pendingUpdates = 0;
        _this2.doUpdate(config);
      }
    })();
  }

  updateSocket(files) {
    var _this3 = this;

    return _asyncToGenerator(function* () {
      debug('updateSocket');

      if (!_this3.socket.existRemotely) {
        return (0, _socket.askForConfigOptionsToInstall)(_this3.socket.spec.config).then(function (config) {
          return _this3.doUpdate(config);
        });
      } else if (_this3.socket.remote.status !== 'ok') {
        debug('socket exist, but status is not ok');
        return (0, _socket.askForConfigOptionsToSync)(_this3.socket).then(function (config) {
          return _this3.doUpdate(config);
        });
      }

      return _this3.doUpdate();
    })();
  }

  resolveCallback(cmd, stats) {
    var _this4 = this;

    return _asyncToGenerator(function* () {
      _this4.scriptWatch = true;
      const socketNameStr = _chalk2.default.cyan(`${_this4.socket.name} socket`);
      (0, _printTools.echo)(8)(`${_chalk2.default.grey('files built:')} ${(0, _dateUtils.currentTime)()} ${socketNameStr} ${timer.getDuration()}`);

      timer.reset();
      yield _this4.updateSocket().then(function () {
        if (_this4.firstRun) {
          (0, _printTools.echo)(3)(`${_chalk2.default.grey('watchdog started:')} ${(0, _dateUtils.currentTime)()} ${socketNameStr}`);
          (0, _printTools.echo)();
          (0, _printTools.echo)(21)(_chalk2.default.dim('(Hit Ctrl-C to stop)'));
          (0, _printTools.echo)();
          if (cmd.traces) {
            _this4.startCollectingTraces();
          }
        }
        _this4.firstRun = false;
      });
    })();
  }

  rejectCallback(errObj) {
    // if there is no endpoints watcher doesn't work
    if (errObj.errorType === 'noEndpointsError') {
      debug('nothing to watch, watcher is dead');
      this.scriptWatch = false;
    }
    // print error
    (0, _print.printCompileError)(errObj);

    if (this.firstRun) {
      (0, _printTools.echo)(3)(`${_chalk2.default.grey('watchdog started:')} ${(0, _dateUtils.currentTime)()} ${_chalk2.default.cyan(`${this.socket.name} socket`)}`);
      (0, _printTools.echo)(3)(_chalk2.default.dim('(Hit Ctrl-C to stop)'));
      (0, _printTools.echo)();
      if (this.cmd.traces) {
        this.startCollectingTraces();
      }
    }
    this.firstRun = false;
  }

  run([socketName, cmd]) {
    var _this5 = this;

    return _asyncToGenerator(function* () {
      _this5.socket = yield _this5.Socket.get(socketName);
      _this5.cmd = cmd;

      (0, _printTools.echo)();
      (0, _printTools.echo)(2)(`${_chalk2.default.grey('starting watchdog:')} ${(0, _dateUtils.currentTime)()} ${_chalk2.default.cyan(`${_this5.socket.name} socket`)}`);

      if (!_this5.socket.existLocally) {
        (0, _printTools.echo)();
        (0, _printTools.echo)(21)(_chalk2.default.dim('(Hit Ctrl-C to stop)'));
        if (cmd.traces) {
          return _this5.startCollectingTraces();
        }
      }
      _this5.runStalker(cmd);
    })();
  }

  runStalker(cmd) {
    var _this6 = this;

    const resolveCallback = this.resolveCallback.bind(this, cmd);
    const rejectCallback = this.rejectCallback.bind(this);

    // Stalking files
    debug('watching:', this.socket.getSocketPath());
    this.stalker = _watchr2.default.create(this.socket.getSocketPath());
    this.stalker.on('change', (() => {
      var _ref = _asyncToGenerator(function* (changeType, fileName) {
        timer.reset();
        // If watcher is not up, we have to check if it is worth to start it
        if (!_this6.scriptWatch && _this6.socket.getEntryPoints()) {
          debug('starting script watcher');
          _this6.socket.watchScripts(resolveCallback, rejectCallback);
        }

        const socketFilePath = fileName.replace(_this6.socket.getSocketPath(), '');

        (0, _printTools.echo)(7)(`${_chalk2.default.grey('file changed:')} ${(0, _dateUtils.currentTime)()} ${_chalk2.default.cyan(socketFilePath)}`);

        if (!fileName.match(/\.js$/)) {
          _this6.updateSocket();
        }
      });

      return function (_x, _x2) {
        return _ref.apply(this, arguments);
      };
    })());

    this.stalker.setConfig({
      interval: 300,
      persistent: true,
      catchupDelay: 300,
      preferredMethods: ['watch', 'watchFile'],
      followLinks: true,
      ignoreHiddenFiles: true, // ignoring .bundles, .dist etc.
      ignoreCommonPatterns: true
    });

    // First start of the stalker
    this.stalker.watch(() => {});

    // We have to be sure there are endpoints to watch
    if (this.socket.getEntryPoints()) {
      debug('starting script watcher just afrer start');
      this.socket.watchScripts(resolveCallback, rejectCallback);
    }
  }

  startCollectingTraces() {
    var _this7 = this;

    return _asyncToGenerator(function* () {
      try {
        const response = yield _this7.socket.getTraces(_this7.lastId);
        const trace = response.data;
        _this7.lastId = response.data.id;

        _this7.printTrace(trace);
        _this7.startCollectingTraces();
      } catch (err) {
        if (err.code === 'ECONNABORTED') {
          _this7.startCollectingTraces();
        } else {
          (0, _printTools.error)(4)(err.message);
        }
      }
    })();
  }

  // Decide about how to print trace and which
  printTrace(trace) {
    var _this8 = this;

    return _asyncToGenerator(function* () {
      if (trace.payload.status === 'pending' || trace.payload.status === 'processing') return;

      const traceUrl = trace.payload.links.self;
      const fullTrace = yield _this8.Socket.getEndpointTraceByUrl(traceUrl);
      const { stdout, stderr, response } = fullTrace.result;

      if (trace.metadata.source === 'event_handler') {
        debug(`Event handler trace: ${trace.metadata.event_handler}`);
        const traceHandlerName = trace.metadata.event_handler;
        SocketWatch.printTraceTriggerLine(fullTrace, traceHandlerName);
        if (stdout) SocketWatch.printTraceStdout(stdout);
        if (stderr) {
          (0, _printTools.echo)(6)(_chalk2.default.grey('script stderr:'));
          const errorMsg = fullTrace.result.stderr;

          try {
            const errorString = errorMsg.match(/\n(.*)\n\s.*at/)[1];
            const traceData = SocketWatch.getFirstTrace(errorMsg);
            const prettyTrace = _this8.socket.getPrettyTrace(traceData, traceHandlerName);
            SocketWatch.printFullTrace({ prettyTrace, errorString });
          } catch (err) {
            SocketWatch.printSimpleError(errorMsg);
          }
        }
      }

      if (trace.metadata.source === 'endpoint') {
        const traceFullEndpointName = trace.metadata.endpoint;
        const traceEndpointName = traceFullEndpointName.split('/')[1];

        SocketWatch.printTraceLine(fullTrace, traceFullEndpointName);
        if (stdout) SocketWatch.printTraceStdout(fullTrace.result.stdout);
        if (response && response.content) SocketWatch.printTraceBody(fullTrace.result.response);
        if (stderr) {
          (0, _printTools.echo)(6)(_chalk2.default.grey('script stderr:'));
          const errorMsg = fullTrace.result.stderr;

          try {
            const errorString = errorMsg.match(/\n(.*)\n\s.*at/)[1];
            const traceData = SocketWatch.getFirstTrace(errorMsg);

            const prettyTrace = _this8.socket.getPrettyTrace(traceData, traceEndpointName);
            SocketWatch.printFullTrace({ prettyTrace, errorString });
          } catch (err) {
            SocketWatch.printSimpleError(errorMsg);
          }
        }
      }
    })();
  }

  // Single line with date, id etc.
  static printTraceLine(trace, traceEndpointName) {
    const endpointCall = trace.status === 'success' ? _chalk2.default.grey('endpoint call:') : _chalk2.default.red('endpoint call:');
    const endpoint = _chalk2.default.cyan(traceEndpointName);
    const duration = _chalk2.default.dim(`${trace.duration} ms`);
    const remoteAddr = _chalk2.default.dim(`(${trace.meta.REMOTE_ADDR})`);
    const requestMethod = _chalk2.default.dim(trace.meta.REQUEST_METHOD);
    const exitCode = _chalk2.default.cyan(trace.result.response ? trace.result.response.status : '200');
    (0, _printTools.echo)(6)(`${endpointCall} ${(0, _dateUtils.currentTime)()} ${endpoint} ${requestMethod} ${exitCode} ${remoteAddr} ${duration}`);
  }

  // Single line with date, id etc.
  static printTraceTriggerLine(trace, eventName = 'event') {
    const endpointCall = trace.status === 'success' ? _chalk2.default.grey('handler call:') : _chalk2.default.red('handler call:');
    const event = _chalk2.default.cyan(eventName);
    const duration = _chalk2.default.dim(`${trace.duration} ms`);
    (0, _printTools.echo)(6)(`${endpointCall} ${(0, _dateUtils.currentTime)()} ${event} ${duration}`);
  }

  static printTraceStdout(stdout, title = 'script stdout:') {
    (0, _printTools.echo)(6)(_chalk2.default.grey(title));
    (0, _printTools.echo)();
    stdout.split('\n').forEach(line => {
      (0, _printTools.echo)(14)(_chalk2.default.dim(line));
    });
    (0, _printTools.echo)();
  }

  static printTraceBody(response) {
    (0, _printTools.echo)(8)(_chalk2.default.grey(`script body: (${response.content_type})`));
    (0, _printTools.echo)();
    let code = response.content;
    try {
      code = (0, _printTools.printSourceCode)(response.content_type, response.content);
    } catch (err) {
      debug(err);
    }
    code.split('\n').forEach(line => {
      (0, _printTools.echo)(21)(line);
    });
    (0, _printTools.echo)();
  }

  static printFileLine(params) {
    (0, _printTools.echo)(21)([`${_chalk2.default.dim('at')} ${params.origFilePath}`, `${_chalk2.default.dim('line:')} ${params.lineNumber},`, `${_chalk2.default.dim('column:')} ${params.columnNumber}`].join(' '));
    (0, _printTools.echo)();
  }

  static printTraceLines(params) {
    (0, _printTools.echo)();
    params.lines.forEach(line => {
      (0, _printTools.echo)(21)(_chalk2.default.dim(line));
    });
    (0, _printTools.echo)(21)(params.errorString);
  }

  static printFullTrace(params) {
    SocketWatch.printTraceLines({
      lines: params.prettyTrace.lines,
      errorString: params.errorString
    });

    SocketWatch.printFileLine({
      origFilePath: params.prettyTrace.origFilePath,
      lineNumber: params.prettyTrace.lineNumber,
      columnNumber: params.prettyTrace.columnNumber
    });
  }

  static printSimpleError(simpleError) {
    (0, _printTools.echo)();
    const err = simpleError.split('\n').join(`\n${(0, _printTools.p)(21)()}`);
    (0, _printTools.echo)(21)(_chalk2.default.dim.red(err));
    (0, _printTools.echo)();
  }
}
exports.default = SocketWatch;
module.exports = exports['default'];
//# sourceMappingURL=socket-watch.js.map