'use strict';Object.defineProperty(exports, "__esModule", { value: true });exports.default = undefined;var _extends = Object.assign || function (target) {for (var i = 1; i < arguments.length; i++) {var source = arguments[i];for (var key in source) {if (Object.prototype.hasOwnProperty.call(source, key)) {target[key] = source[key];}}}return target;};var _slicedToArray = function () {function sliceIterator(arr, i) {var _arr = [];var _n = true;var _d = false;var _e = undefined;try {for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {_arr.push(_s.value);if (i && _arr.length === i) break;}} catch (err) {_d = true;_e = err;} finally {try {if (!_n && _i["return"]) _i["return"]();} finally {if (_d) throw _e;}}return _arr;}return function (arr, i) {if (Array.isArray(arr)) {return arr;} else if (Symbol.iterator in Object(arr)) {return sliceIterator(arr, i);} else {throw new TypeError("Invalid attempt to destructure non-iterable instance");}};}();var _createClass = function () {function defineProperties(target, props) {for (var i = 0; i < props.length; i++) {var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ("value" in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor);}}return function (Constructor, protoProps, staticProps) {if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor;};}(); // Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.

// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Parity.  If not, see <http://www.gnu.org/licenses/>.

var _lodash = require('lodash');

var _contract = require('@parity/api/lib/contract');var _contract2 = _interopRequireDefault(_contract);
var _format = require('@parity/api/lib/util/format');

var _config = require('../../../config');
var _contracts = require('../../../contracts');var _contracts2 = _interopRequireDefault(_contracts);
var _certifier = require('../../../contracts/abi/certifier.json');var _certifier2 = _interopRequireDefault(_certifier);

var _actions = require('./actions');
var _enhancedQuerier = require('./enhanced-querier');function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}function _classCallCheck(instance, Constructor) {if (!(instance instanceof Constructor)) {throw new TypeError("Cannot call a class as a function");}}

var log = (0, _config.getLogger)(_config.LOG_KEYS.CertificationsMiddleware);

var self = null;var

CertifiersMonitor = function () {
  function CertifiersMonitor(api, store) {var _this = this;_classCallCheck(this, CertifiersMonitor);
    this._api = api;
    this._name = 'Certifiers';
    this._store = store;

    this._contract = new _contract2.default(this.api, _certifier2.default);
    this._contractEvents = ['Confirmed', 'Revoked'].
    map(function (name) {return _this.contract.events.find(function (e) {return e.name === name;});});

    this.certifiers = {};
    this.fetchedAccounts = {};

    this.load();
  }_createClass(CertifiersMonitor, [{ key: 'checkFilters', value: function checkFilters()












































    {
      this.checkCertifiersFilter();
      this.checkRegistryFilter();
    } }, { key: 'checkCertifiersFilter', value: function checkCertifiersFilter()

    {var _this2 = this;
      if (!this.certifiersFilter) {
        return;
      }

      this.api.eth.
      getFilterChanges(this.certifiersFilter).
      then(function (logs) {
        if (logs.length === 0) {
          return;
        }

        var parsedLogs = _this2.contract.parseEventLogs(logs).filter(function (log) {return log.params;});

        log.debug('received certifiers logs', parsedLogs);

        var promises = parsedLogs.map(function (log) {
          var account = log.params.who.value;
          var certifier = Object.values(_this2.certifiers).find(function (c) {return c.address === log.address;});

          if (!certifier) {
            log.warn('could not find the certifier', { certifiers: _this2.certifiers, log: log });
            return Promise.resolve();
          }

          return _this2.fetchAccount(account, { ids: [certifier.id] });
        });

        return Promise.all(promises);
      }).
      catch(function (error) {
        console.error(error);
      });
    } }, { key: 'checkRegistryFilter', value: function checkRegistryFilter()

    {var _this3 = this;
      if (!this.registryFilter) {
        return;
      }

      this.api.eth.
      getFilterChanges(this.registryFilter).
      then(function (logs) {
        if (logs.length === 0) {
          return;
        }

        var parsedLogs = _this3.contract.parseEventLogs(logs).filter(function (log) {return log.params;});
        var indexes = parsedLogs.map(function (log) {return log.params && log.params.id.value.toNumber();});

        log.debug('received registry logs', parsedLogs);
        return _this3.fetchElements(indexes);
      }).
      catch(function (error) {
        console.error(error);
      });
    }

    /**
       * Initial load of the Monitor.
       * Fetch the contract from the Registry, and
       * load the elements addresses
       */ }, { key: 'load', value: function load()
    {var _this4 = this;
      var badgeReg = _contracts2.default.get(this._api).badgeReg;

      log.debug('loading the ' + this.name + ' monitor...');
      return badgeReg.
      getContract().
      then(function (registryContract) {
        _this4._registry = registryContract;
        _this4._registryEvents = ['Registered', 'Unregistered', 'MetaChanged', 'AddressChanged'].
        map(function (name) {return _this4.registry.events.find(function (e) {return e.name === name;});});

        return _this4.registry.instance.badgeCount.call({});
      }).
      then(function (count) {
        log.debug('found ' + count.toFormat() + ' registered contracts for ' + _this4.name);
        return _this4.fetchElements((0, _lodash.range)(count.toNumber()));
      }).
      then(function () {
        return _this4.setRegistryFilter();
      }).
      then(function () {
        // Listen for new blocks
        return _this4.api.subscribe('eth_blockNumber', function (err) {
          if (err) {
            return;
          }

          _this4.checkFilters();
        });
      }).
      then(function () {
        log.debug('loaded the ' + _this4.name + ' monitor!', _this4.certifiers);
      }).
      catch(function (error) {
        log.error(error);
      });
    }

    /**
       * Fetch the given registered element
       */ }, { key: 'fetchElements', value: function fetchElements(
    indexes) {var _this5 = this;
      var badgeReg = _contracts2.default.get(this._api).badgeReg;var
      instance = this.registry.instance;

      var sorted = indexes.sort();
      var from = sorted[0];
      var last = sorted[sorted.length - 1];
      var limit = last - from + 1;

      // Fetch the address, name and owner in one batch
      return (0, _enhancedQuerier.querier)(this.api, { address: instance.address, from: from, limit: limit }, instance.badge).
      then(function (results) {
        var certifiers = results.
        map(function (_ref, index) {var _ref2 = _slicedToArray(_ref, 3),address = _ref2[0],name = _ref2[1],owner = _ref2[2];return {
            address: address, owner: owner,
            id: index + from,
            name: (0, _format.hexToAscii)((0, _format.bytesToHex)(name).replace(/(00)+$/, '')) };}).

        reduce(function (certifiers, certifier) {var
          id = certifier.id;

          if (!/^(0x)?0+$/.test(certifier.address)) {
            certifiers[id] = certifier;
          } else if (certifiers[id]) {
            delete certifiers[id];
          }

          return certifiers;
        }, {});

        // Fetch the meta-data in serie
        return Object.values(certifiers).reduce(function (promise, certifier) {
          return promise.then(function () {return badgeReg.fetchMeta(certifier.id);}).
          then(function (meta) {
            _this5.certifiers[certifier.id] = _extends({}, certifier, meta);
          });
        }, Promise.resolve());
      }).
      then(function () {return log.debug('fetched certifiers', { certifiers: _this5.certifiers });})
      // Fetch the know accounts in case it's an update of the certifiers
      .then(function () {return _this5.fetchAccounts(Object.keys(_this5.fetchedAccounts), { ids: indexes, force: true });});
    } }, { key: 'fetchAccounts', value: function fetchAccounts(

    addresses) {var _this6 = this;var _ref3 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},_ref3$ids = _ref3.ids,ids = _ref3$ids === undefined ? null : _ref3$ids,_ref3$force = _ref3.force,force = _ref3$force === undefined ? false : _ref3$force;
      var newAddresses = force ?
      addresses :
      addresses.filter(function (address) {return !_this6.fetchedAccounts[address];});

      if (newAddresses.length === 0) {
        return Promise.resolve();
      }

      log.debug('fetching values for "' + addresses.join(' ; ') + '" in ' + this.name + '...');
      return newAddresses.
      reduce(function (promise, address) {
        return promise.then(function () {return _this6.fetchAccount(address, { ids: ids });});
      }, Promise.resolve()).
      then(function () {
        log.debug('fetched values for "' + addresses.join(' ; ') + '" in ' + _this6.name + '!');
      }).
      then(function () {return _this6.setCertifiersFilter();});
    } }, { key: 'fetchAccount', value: function fetchAccount(

    address) {var _this7 = this;var _ref4 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},_ref4$ids = _ref4.ids,ids = _ref4$ids === undefined ? null : _ref4$ids;
      var certifiers = Object.values(this.certifiers);

      // Only fetch values for the givens ids, if any
      if (ids) {
        certifiers = certifiers.filter(function (certifier) {return ids.includes(certifier.id);});
      }

      certifiers.
      reduce(function (promise, certifier) {
        return promise.
        then(function () {
          return _this7.contract.at(certifier.address).instance.certified.call({}, [address]);
        }).
        then(function (certified) {var
          id = certifier.id,title = certifier.title,icon = certifier.icon,name = certifier.name;

          if (!certified) {
            return _this7.store.dispatch((0, _actions.removeCertification)(address, id));
          }

          log.debug('seen as certified', { address: address, id: id, name: name, icon: icon });
          _this7.store.dispatch((0, _actions.addCertification)(address, id, name, title, icon));
        });
      }, Promise.resolve()).
      then(function () {
        _this7.fetchedAccounts[address] = true;
      });
    } }, { key: 'setCertifiersFilter', value: function setCertifiersFilter()

    {var _this8 = this;
      var accounts = Object.keys(this.fetchedAccounts);
      var addresses = Object.values(this.certifiers).map(function (c) {return c.address;});
      // The events have as first indexed data the account address
      var topics = [
      this.contractEvents.map(function (event) {return '0x' + event.signature;}),
      accounts];


      if (accounts.length === 0 || addresses.length === 0) {
        return;
      }

      var promise = this.certifiersFilter ?
      this.api.eth.uninstallFilter(this.certifiersFilter) :
      Promise.resolve();

      log.debug('setting up registry filter', { topics: topics, accounts: accounts, addresses: addresses });

      return promise.
      then(function () {return _this8.api.eth.newFilter({
          fromBlock: 'latest',
          toBlock: 'latest',
          address: addresses,
          topics: topics });}).

      then(function (filterId) {
        _this8.certifiersFilter = filterId;
      }).
      catch(function (error) {
        console.error(error);
      });
    } }, { key: 'setRegistryFilter', value: function setRegistryFilter()

    {var _this9 = this;var
      address = this.registry.instance.address;
      var topics = [this.registryEvents.map(function (event) {return '0x' + event.signature;})];

      log.debug('setting up registry filter', { topics: topics, address: address });

      return this.api.eth.
      newFilter({
        fromBlock: 'latest',
        toBlock: 'latest',
        address: address, topics: topics }).

      then(function (filterId) {
        _this9.registryFilter = filterId;
      }).
      catch(function (error) {
        console.error(error);
      });
    } }, { key: 'api', get: function get() {return this._api;} }, { key: 'contract', get: function get() {return this._contract;} }, { key: 'contractEvents', get: function get() {return this._contractEvents;} }, { key: 'name', get: function get() {return this._name;} }, { key: 'store', get: function get() {return this._store;} }, { key: 'registry', get: function get() {return this._registry;} }, { key: 'registryEvents', get: function get() {return this._registryEvents;} }], [{ key: 'get', value: function get() {if (self) {return self;}self = new CertifiersMonitor();return self;} }, { key: 'init', value: function init(api, store) {if (!self) {self = new CertifiersMonitor(api, store);}} }]);return CertifiersMonitor;}();exports.default = CertifiersMonitor;