import _regeneratorRuntime from 'babel-runtime/regenerator';
import _Promise from 'babel-runtime/core-js/promise';
import _asyncToGenerator from 'babel-runtime/helpers/asyncToGenerator';
import _classCallCheck from 'babel-runtime/helpers/classCallCheck';

var _this2 = this;

import _ from 'lodash';

import PromiseQueue from '../utils/PromiseQueue';
import isRequired from '../utils/isRequired';
import * as errorConstants from '../constants/error';

/**
 * EventListener is used to watch events on the blockchain for a set of contracts.
 * Handlers for specific events can be added. When an event log is found EventListener
 * will fire all handlers registered for the contract.
 */

var EventListener =
/**
 * Listen for events in contract and handles callbacks with registered event handlers.
 * @param {object[]} _contractImplementations - Contract Implementation instances to fetch event logs for.
 */
function EventListener() {
  var _this = this;

  var _contractImplementations = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];

  _classCallCheck(this, EventListener);

  this.addContractImplementation = function (contractImplementationInstance) {
    _this.contractInstances.push(contractImplementationInstance);
    _this.contractEventHandlerMap[contractImplementationInstance.getContractAddress()] = {};
  };

  this.removeContractInstance = function () {
    var contractImplementationInstance = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : isRequired('contractImplementationInstance');

    var contractAddress = contractImplementationInstance.getContractAddress();
    // remove instance from this.contractInstances
    var removedInstance = _.remove(_this.contractInstances, function (instance) {
      return instance.getContractAddress() === contractAddress;
    });
    // if we didn't remove anything throw error
    if (removedInstance.length === 0) throw new Error(errorConstants.MISSING_CONTRACT_INSTANCE(contractAddress));
    // stop watching on these instances
    removedInstance.forEach(function (instance) {
      return _this.stopWatchingForEvents(instance);
    });

    // remove handlers for contract instance
    delete _this.contractEventHandlerMap[contractAddress];
  };

  this.addEventHandler = function () {
    var contractImplementationInstance = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : isRequired('contractAddress');
    var eventName = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : isRequired('eventName');
    var handler = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : isRequired('handler');

    var contractAddress = contractImplementationInstance.getContractAddress();
    if (!_this.contractEventHandlerMap[contractAddress][eventName]) _this.contractEventHandlerMap[contractAddress][eventName] = [];
    _this.contractEventHandlerMap[contractAddress][eventName].push(handler);
  };

  this.watchForEvents = function () {
    var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() {
      var fromBlock = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'latest';
      return _regeneratorRuntime.wrap(function _callee2$(_context2) {
        while (1) {
          switch (_context2.prev = _context2.next) {
            case 0:
              return _context2.abrupt('return', _Promise.all(_this.contractInstances.map(function () {
                var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(contractImplementation) {
                  var instance, newWatcherInstance;
                  return _regeneratorRuntime.wrap(function _callee$(_context) {
                    while (1) {
                      switch (_context.prev = _context.next) {
                        case 0:
                          _context.next = 2;
                          return contractImplementation.loadContract();

                        case 2:
                          instance = _context.sent;
                          newWatcherInstance = instance.allEvents({
                            fromBlock: fromBlock,
                            lastBlock: 'latest'
                          });

                          // NOTE: should we allow more than one listener per contract instance?

                          if (_this.watcherInstances[instance.address]) _this.watcherInstances[instance.address].stopWatching();

                          _this.watcherInstances[instance.address] = newWatcherInstance;
                          newWatcherInstance.watch(function (error, result) {
                            if (!error) {
                              var handlers = _this.contractEventHandlerMap[instance.address][result.event];
                              if (handlers) {
                                handlers.forEach(function (handler) {
                                  _this._queueEvent(handler, result);
                                });
                              }
                            }
                          });

                        case 7:
                        case 'end':
                          return _context.stop();
                      }
                    }
                  }, _callee, _this);
                }));

                return function (_x7) {
                  return _ref2.apply(this, arguments);
                };
              }())));

            case 1:
            case 'end':
              return _context2.stop();
          }
        }
      }, _callee2, _this);
    }));

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

  this.stopWatchingForEvents = function (contractImplementationInstance) {
    if (contractImplementationInstance) _this.watcherInstances[contractImplementationInstance.getContractAddress()].stopWatching();else _this.contractInstances.forEach(function (instance) {
      _this.watcherInstances[instance.getContractAddress()].stopWatching();
    });
  };

  this._queueEvent = function () {
    var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4(handler, event) {
      var eventTask;
      return _regeneratorRuntime.wrap(function _callee4$(_context4) {
        while (1) {
          switch (_context4.prev = _context4.next) {
            case 0:
              eventTask = function () {
                var _ref4 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3() {
                  return _regeneratorRuntime.wrap(function _callee3$(_context3) {
                    while (1) {
                      switch (_context3.prev = _context3.next) {
                        case 0:
                          _context3.next = 2;
                          return handler(event);

                        case 2:
                        case 'end':
                          return _context3.stop();
                      }
                    }
                  }, _callee3, _this);
                }));

                return function eventTask() {
                  return _ref4.apply(this, arguments);
                };
              }();

              _this.eventHandlerQueue.push(eventTask);

            case 2:
            case 'end':
              return _context4.stop();
          }
        }
      }, _callee4, _this);
    }));

    return function (_x8, _x9) {
      return _ref3.apply(this, arguments);
    };
  }();

  this.contractInstances = [];
  // map address -> { event: [handlers], ... }
  this.contractEventHandlerMap = {};
  // map address -> watcher instance
  this.watcherInstances = {};
  // event handler queue
  this.eventHandlerQueue = new PromiseQueue();
  // initialize class variables for new contract instances
  _contractImplementations.forEach(function (instance) {
    _this.addContractImplementation(instance);
  });
}

/**
 * Fetch all logs from contractInstance in a block range.
 * @param {object} contractImplementationInstance - Contract Implementation instance.
 * @param {number} firstBlock - Lower bound of search range.
 * @param {number} lastBlock - Upper bound of search range.
 * @returns {Promise} All events in block range.
 */


/**
 * Fetch logs from contractInstance for a specific event in a block range.
 * @param {object} contractImplementationInstance - contract Implementation instance.
 * @param {string} eventName - Name of the event.
 * @param {number} firstBlock - Lower bound of search range.
 * @param {number} lastBlock - Upper bound of search range.
 * @returns {Promise} All events in block range.
 */


/**
 * Add a contract instance to watch for new event logs.
 * @param {object} contractImplementationInstance - Contract Implementation instance
 */


/**
 * Remove contract instance being watched. Will also remove all handlers.
 * @param {string} contractImplementationInstance - contract implementation instance
 */


/**
 * Add event handler that will be called when event log is found.
 * @param {string} contractImplementationInstance - Contract implementation instance
 * @param {string} eventName - Name of event.
 * @param {function} handler - Function to be called when event is consumed.
 */


/**
 * Watch for events on all contract instances. Call registered handlers when logs are found.
 * @param {number} fromBlock - A block number can be passed to catch up on missed logs
 * @returns {Promise} - Promise resolves when all watchers have been started
 */


/**
 * Stop listening on contract. If no contractAddress supplied it stops all listeners.
 * @param {string} contractImplementationInstance - Address of the contract to stop watching
 */


/**
 * Queues an event.
 * @param {function} handler - The handler.
 * @param {object} event - The event.
 */
;

EventListener.getAllEventLogs = function () {
  var _ref5 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee5() {
    var contractImplementationInstance = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : isRequired('contractImplementationInstance');
    var firstBlock = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
    var lastBlock = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'latest';
    return _regeneratorRuntime.wrap(function _callee5$(_context5) {
      while (1) {
        switch (_context5.prev = _context5.next) {
          case 0:
            _context5.t0 = _Promise;
            _context5.next = 3;
            return contractImplementationInstance.loadContract();

          case 3:
            _context5.t1 = {
              fromBlock: firstBlock,
              toBlock: lastBlock
            };

            _context5.t2 = function (error, result) {
              if (error) throw new Error(errorConstants.ERROR_FETCHING_EVENTS(error));

              return result;
            };

            _context5.t3 = _context5.sent.allEvents(_context5.t1).get(_context5.t2);
            return _context5.abrupt('return', _context5.t0.all.call(_context5.t0, _context5.t3));

          case 7:
          case 'end':
            return _context5.stop();
        }
      }
    }, _callee5, _this2);
  }));

  return function () {
    return _ref5.apply(this, arguments);
  };
}();

EventListener.getEventLogs = function () {
  var _ref6 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee6() {
    var contractImplementationInstance = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : isRequired('contractImplementationInstance');
    var eventName = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : isRequired('eventName');
    var firstBlock = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
    var lastBlock = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'latest';
    return _regeneratorRuntime.wrap(function _callee6$(_context6) {
      while (1) {
        switch (_context6.prev = _context6.next) {
          case 0:
            _context6.t0 = _Promise;
            _context6.next = 3;
            return contractImplementationInstance.loadContract();

          case 3:
            _context6.t1 = eventName;
            _context6.t2 = {
              fromBlock: firstBlock,
              toBlock: lastBlock
            };

            _context6.t3 = function (error, result) {
              if (error) throw new Error(errorConstants.ERROR_FETCHING_EVENTS(error));

              if (eventName === result.event) return result;
            };

            _context6.t4 = _context6.sent[_context6.t1](_context6.t2).get(_context6.t3);
            return _context6.abrupt('return', _context6.t0.all.call(_context6.t0, _context6.t4));

          case 8:
          case 'end':
            return _context6.stop();
        }
      }
    }, _callee6, _this2);
  }));

  return function () {
    return _ref6.apply(this, arguments);
  };
}();

export default EventListener;
//# sourceMappingURL=EventListener.js.map