import _Promise from 'babel-runtime/core-js/promise';
import _Object$setPrototypeOf from 'babel-runtime/core-js/object/set-prototype-of';
import _getIterator from 'babel-runtime/core-js/get-iterator';
import _regeneratorRuntime from 'babel-runtime/regenerator';
import _asyncToGenerator from 'babel-runtime/helpers/asyncToGenerator';
import _classCallCheck from 'babel-runtime/helpers/classCallCheck';
import contract from 'truffle-contract';
import _ from 'lodash';

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

/**
 * ContractImplementation is a parent class for on chain contracts. It loads the contract from the
 * blockchain and exposes the contract instance for use by the child.
 */

var ContractImplementation = function ContractImplementation() {
  var web3Provider = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : isRequired('web3Provider');
  var artifact = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : isRequired('artifact');
  var contractAddress = arguments[2];

  _classCallCheck(this, ContractImplementation);

  _initialiseProps.call(this);

  this.contractAddress = contractAddress;
  this.artifact = artifact;
  this.contractInstance = null;
  this._Web3Wrapper = new Web3Wrapper(web3Provider);
  // loading params
  this._contractLoadedResolver = null;
  this._contractLoadedRejecter = null;
  this._loadingContractInstance = null;
  this.isLoading = false;
}

/**
 * Load contract instance if not yet initialized. Returns loading promise
 * @returns {Promise} resolves to contractInstance
 */


/**
 * Get the web3 provider object from the initialized web3 instance
 * @returns {object} web3 provider object
 */


/**
 * MetaMask safe get block data by blockNumber
 * @param {Int} blockNumber - Block number
 * @returns {Promise} block object
 */


/**
 * Set a new contract instance
 * @param {string} contractAddress - The address of the contract
 * @param {object} artifact - Contract artifact to use to load contract
 * @returns {object} contractInstance object
 */


/**
 * Load an existing contract from the current artifact and address
 */


/**
 * Instantiate contract.
 * @private
 * @param {object} artifact - The contract artifact.
 * @param {string} address - The hex encoded contract Ethereum address
 * @returns {object} - The contract instance.
 */


/**
 * Create a new Promise to be used in loading the contract.
 * @returns {Promise} - Resolves to contract instance.
 */


/**
 * Get the contract address for the currently instantiated contract.
 * @returns {string} - The address of the contract.
 */
;

var _initialiseProps = function _initialiseProps() {
  var _this = this;

  this.loadContract = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
    var newLoadingPromise;
    return _regeneratorRuntime.wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            if (!_this.isLoading) {
              _context.next = 2;
              break;
            }

            return _context.abrupt('return', _this._loadingContractInstance);

          case 2:
            if (!_this.contractInstance) {
              _context.next = 4;
              break;
            }

            return _context.abrupt('return', _this.contractInstance);

          case 4:
            if (!(!_this.contractAddress || !_this.artifact)) {
              _context.next = 6;
              break;
            }

            throw new Error(errorConstants.CONTRACT_INSTANCE_NOT_SET);

          case 6:
            newLoadingPromise = _this._newLoadingPromise();

            _this._loadingContractInstance = newLoadingPromise;
            _this._load();
            return _context.abrupt('return', newLoadingPromise);

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

  this.getWeb3Provider = function () {
    return _this._Web3Wrapper.getProvider();
  };

  this.getBlock = function () {
    var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(blockNumber) {
      return _regeneratorRuntime.wrap(function _callee2$(_context2) {
        while (1) {
          switch (_context2.prev = _context2.next) {
            case 0:
              return _context2.abrupt('return', _this._Web3Wrapper.getBlock(blockNumber));

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

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

  this.setContractInstance = function () {
    var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3() {
      var contractAddress = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _this.contractAddress;
      var artifact = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _this.artifact;
      return _regeneratorRuntime.wrap(function _callee3$(_context3) {
        while (1) {
          switch (_context3.prev = _context3.next) {
            case 0:
              _this.contractAddress = contractAddress;
              _this.artifact = artifact;
              _this.contractInstance = null;
              return _context3.abrupt('return', _this.loadContract());

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

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

  this._load = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4() {
    return _regeneratorRuntime.wrap(function _callee4$(_context4) {
      while (1) {
        switch (_context4.prev = _context4.next) {
          case 0:
            _this.isLoading = true;
            _context4.prev = 1;
            _context4.next = 4;
            return _this._instantiateContractIfExistsAsync(_this.artifact, _this.contractAddress);

          case 4:
            _this.contractInstance = _context4.sent;


            _this.isLoading = false;
            _this._contractLoadedResolver(_this.contractInstance);
            _context4.next = 13;
            break;

          case 9:
            _context4.prev = 9;
            _context4.t0 = _context4['catch'](1);

            _this.isLoading = false;
            _this._contractLoadedRejecter(_context4.t0);

          case 13:
          case 'end':
            return _context4.stop();
        }
      }
    }, _callee4, _this, [[1, 9]]);
  }));

  this._instantiateContractIfExistsAsync = function () {
    var _ref5 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee6(artifact, address) {
      var c, contractInstance, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, funcABI;

      return _regeneratorRuntime.wrap(function _callee6$(_context6) {
        while (1) {
          switch (_context6.prev = _context6.next) {
            case 0:
              _context6.prev = 0;
              _context6.next = 3;
              return contract(artifact);

            case 3:
              c = _context6.sent;
              _context6.t0 = c;
              _context6.next = 7;
              return _this._Web3Wrapper.getProvider();

            case 7:
              _context6.t1 = _context6.sent;
              _context6.next = 10;
              return _context6.t0.setProvider.call(_context6.t0, _context6.t1);

            case 10:
              if (!_.isUndefined(address)) {
                _context6.next = 16;
                break;
              }

              _context6.next = 13;
              return c.deployed();

            case 13:
              _context6.t2 = _context6.sent;
              _context6.next = 19;
              break;

            case 16:
              _context6.next = 18;
              return c.at(address);

            case 18:
              _context6.t2 = _context6.sent;

            case 19:
              contractInstance = _context6.t2;


              // Estimate gas before sending transactions
              _iteratorNormalCompletion = true;
              _didIteratorError = false;
              _iteratorError = undefined;
              _context6.prev = 23;
              for (_iterator = _getIterator(contractInstance.abi); !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
                funcABI = _step.value;

                // Check for non-constant functions
                if (funcABI.type === 'function' && funcABI.constant === false) {
                  (function () {
                    var func = contractInstance[funcABI.name];

                    // eslint-disable-next-line no-loop-func
                    contractInstance[funcABI.name] = function () {
                      var _ref6 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee5() {
                        var _args5 = arguments;
                        return _regeneratorRuntime.wrap(function _callee5$(_context5) {
                          while (1) {
                            switch (_context5.prev = _context5.next) {
                              case 0:
                                _context5.next = 2;
                                return func.estimateGas.apply(func, _args5);

                              case 2:
                                return _context5.abrupt('return', func.apply(undefined, _args5));

                              case 3:
                              case 'end':
                                return _context5.stop();
                            }
                          }
                        }, _callee5, _this);
                      }));

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

                    // Keep reference to the original function for special cases
                    contractInstance[funcABI.name].original = func;

                    // Forward other accessors to the original function
                    _Object$setPrototypeOf(contractInstance[funcABI.name], func);
                  })();
                }
              }

              _context6.next = 31;
              break;

            case 27:
              _context6.prev = 27;
              _context6.t3 = _context6['catch'](23);
              _didIteratorError = true;
              _iteratorError = _context6.t3;

            case 31:
              _context6.prev = 31;
              _context6.prev = 32;

              if (!_iteratorNormalCompletion && _iterator.return) {
                _iterator.return();
              }

            case 34:
              _context6.prev = 34;

              if (!_didIteratorError) {
                _context6.next = 37;
                break;
              }

              throw _iteratorError;

            case 37:
              return _context6.finish(34);

            case 38:
              return _context6.finish(31);

            case 39:
              return _context6.abrupt('return', contractInstance);

            case 42:
              _context6.prev = 42;
              _context6.t4 = _context6['catch'](0);

              console.error(_context6.t4);

              if (!_.includes(_context6.t4.message, 'not been deployed to detected network')) {
                _context6.next = 47;
                break;
              }

              throw new Error(errorConstants.CONTRACT_NOT_DEPLOYED);

            case 47:
              throw new Error(errorConstants.UNABLE_TO_LOAD_CONTRACT);

            case 48:
            case 'end':
              return _context6.stop();
          }
        }
      }, _callee6, _this, [[0, 42], [23, 27, 31, 39], [32,, 34, 38]]);
    }));

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

  this._newLoadingPromise = function () {
    return new _Promise(function (resolve, reject) {
      _this._contractLoadedResolver = resolve;
      _this._contractLoadedRejecter = reject;
    });
  };

  this.getContractAddress = function () {
    return _this.contractAddress;
  };
};

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