import _Promise from 'babel-runtime/core-js/promise';
import _slicedToArray from 'babel-runtime/helpers/slicedToArray';
import _extends from 'babel-runtime/helpers/extends';
import _regeneratorRuntime from 'babel-runtime/regenerator';
import _asyncToGenerator from 'babel-runtime/helpers/asyncToGenerator';
import _classCallCheck from 'babel-runtime/helpers/classCallCheck';
import _ from 'lodash';

import * as arbitratorConstants from '../constants/arbitrator';
import * as disputeConstants from '../constants/dispute';
import isRequired from '../utils/isRequired';

/**
 * Disputes API. Provides cross arbitrator and arbitrable contracts functionality.
 * Requires Store Provider to be set.
 */

var Disputes = function Disputes() {
  var arbitratorInstance = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : isRequired('arbitratorInstance');
  var arbitrableInstance = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : isRequired('arbitrableInstance');
  var storeProviderInstance = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : isRequired('storeProviderInstance');

  _classCallCheck(this, Disputes);

  _initialiseProps.call(this);

  this._ArbitratorInstance = arbitratorInstance;
  this._ArbitrableInstance = arbitrableInstance;
  this._StoreProviderInstance = storeProviderInstance;
  this.disputeCache = {};
}
/**
 * Set arbitrator instance.
 * @param {object} arbitratorInstance - instance of an arbitrator contract.
 */

/**
 * Set arbitrable instance.
 * @param {object} arbitrableInstance - instance of an arbitrable contract.
 */

/**
 * Set store provider instance.
 * @param {object} storeProviderInstance - instance of store provider wrapper.
 */


// **************************** //
// *         Events           * //
// **************************** //

/**
 * Method to register all dispute handlers to an EventListener.
 * @param {string} account - The address of the user.
 * @param {object} eventListener - The EventListener instance. See utils/EventListener.js.
 */


/**
 * Event listener handler that stores dispute in store upon creation
 * @param {string} event - The event log.
 * @param {string} account - Account of user to update store data for
 */


// **************************** //
// *        Internal          * //
// **************************** //

/**
 * Add new data to the cache
 * @param {number} disputeId - The index of the dispute. Used as the key in cache
 * @param {object} newCacheData - Freeform data to cache. Will overwrite data with the same keys.
 */


/**
 * Get the block at which a dispute was created. Used to find timestamps for dispute.
 * The start block is cached after it has been found once as it will never change.
 * @param {number} disputeId - The index of the dispute.
 * @param {string} account - The address of the user.
 * @returns {number} The block number that the dispute was created.
 */


// **************************** //
// *          Public          * //
// **************************** //
/**
 * Fetch the shared dispute data from the store.
 * @param {string} account - The users account.
 * @param {string} disputeId - The index of the dispute.
 * @returns {Promise} The dispute data in the store.
 */


/**
 * Get the dispute deadline for the appeal.
 * @param {number} disputeId - The index of the dispute.
 * @param {number} appeal - The appeal number. 0 if there have been no appeals.
 * @returns {number} timestamp of the appeal
 */


/**
 * Get the timestamp on when the dispute's ruling was finalized.
 * @param {number} disputeId - The index of the dispute.
 * @param {number} appeal - The appeal number. 0 if there have been no appeals.
 * @returns {number} timestamp of the appeal
 */


/**
 * Get the timestamp on when the dispute's appeal was created
 * @param {number} disputeId - The index of the dispute.
 * @param {string} account - The users address.
 * @param {number} appeal - The appeal number. 0 if there have been no appeals.
 * @returns {number} timestamp of the appeal
 */


/**
 * Get data for a dispute. This method provides data from the store as well as both
 * arbitrator and arbitrable contracts. Used to get all relevant data on a dispute.
 * @param {number} disputeId - The dispute's ID.
 * @param {string} account - The juror's address.
 * @returns {object} - Data object for the dispute that uses data from the contract and the store.
 */
;

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

  this.setArbitratorInstance = function (arbitratorInstance) {
    _this._ArbitratorInstance = arbitratorInstance;
  };

  this.setArbitrableInstance = function (arbitrableInstance) {
    _this._ArbitrableInstance = arbitrableInstance;
  };

  this.setStoreProviderInstance = function (storeProviderInstance) {
    _this._StoreProviderInstance = storeProviderInstance;
  };

  this.registerStoreUpdateEventListeners = function () {
    var account = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : isRequired('account');
    var eventListener = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : isRequired('eventListener');

    var eventHandlerMap = {
      DisputeCreation: [_this._storeNewDisputeHandler]
    };

    var _loop = function _loop(event) {
      if (eventHandlerMap.hasOwnProperty(event)) {
        eventHandlerMap[event].forEach(function (handler) {
          eventListener.addEventHandler(_this._ArbitratorInstance, event, function (args) {
            return handler(args, account);
          });
        });
      }
    };

    for (var event in eventHandlerMap) {
      _loop(event);
    }
  };

  this._storeNewDisputeHandler = function () {
    var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(event, account) {
      var disputeId, arbitratorAddress, disputeData, arbitrableContractData;
      return _regeneratorRuntime.wrap(function _callee$(_context) {
        while (1) {
          switch (_context.prev = _context.next) {
            case 0:
              // There is no need to handle this event if we are not using the store
              disputeId = event.args._disputeID.toNumber();
              arbitratorAddress = _this._ArbitratorInstance.getContractAddress();
              // arbitrator data

              _context.next = 4;
              return _this._ArbitratorInstance.getDispute(disputeId);

            case 4:
              disputeData = _context.sent;
              _context.next = 7;
              return _this._ArbitrableInstance.setContractInstance(disputeData.arbitrableContractAddress);

            case 7:
              _context.next = 9;
              return _this._ArbitrableInstance.getData(account);

            case 9:
              arbitrableContractData = _context.sent;

              if (!(account === arbitrableContractData.partyA || account === arbitrableContractData.partyB)) {
                _context.next = 13;
                break;
              }

              _context.next = 13;
              return _this._StoreProviderInstance.updateDisputeProfile(account, arbitratorAddress, disputeId, {
                contractAddress: disputeData.arbitrableContractAddress,
                partyA: arbitrableContractData.partyA,
                partyB: arbitrableContractData.partyB,
                blockNumber: event.blockNumber
              });

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

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

  this._updateDisputeCache = function (disputeId) {
    var newCacheData = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

    _this.disputeCache[disputeId] = _extends({}, _this.disputeCache[disputeId], newCacheData);
  };

  this._getDisputeStartBlock = function () {
    var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(disputeId, account) {
      var cachedDispute, arbitratorAddress, blockNumber, userData, disputeCreationEvent;
      return _regeneratorRuntime.wrap(function _callee2$(_context2) {
        while (1) {
          switch (_context2.prev = _context2.next) {
            case 0:
              cachedDispute = _this.disputeCache[disputeId];

              if (!(cachedDispute && cachedDispute.startBlock)) {
                _context2.next = 3;
                break;
              }

              return _context2.abrupt('return', cachedDispute.startBlock);

            case 3:
              arbitratorAddress = _this._ArbitratorInstance.getContractAddress();
              blockNumber = void 0;
              _context2.prev = 5;
              _context2.next = 8;
              return _this._StoreProviderInstance.getDispute(account, arbitratorAddress, disputeId);

            case 8:
              userData = _context2.sent;

              blockNumber = userData.blockNumber;
              // eslint-disable-next-line no-unused-vars
              _context2.next = 14;
              break;

            case 12:
              _context2.prev = 12;
              _context2.t0 = _context2['catch'](5);

            case 14:
              if (blockNumber) {
                _context2.next = 19;
                break;
              }

              _context2.next = 17;
              return _this._ArbitratorInstance.getDisputeCreationEvent(disputeId);

            case 17:
              disputeCreationEvent = _context2.sent;

              if (disputeCreationEvent) {
                blockNumber = disputeCreationEvent.blockNumber;
              }

            case 19:

              // cache start block for dispute
              _this._updateDisputeCache(disputeId, { startBlock: blockNumber });
              return _context2.abrupt('return', blockNumber);

            case 21:
            case 'end':
              return _context2.stop();
          }
        }
      }, _callee2, _this, [[5, 12]]);
    }));

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

  this.getDisputeFromStore = function () {
    var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3(account, disputeId) {
      var arbitratorAddress;
      return _regeneratorRuntime.wrap(function _callee3$(_context3) {
        while (1) {
          switch (_context3.prev = _context3.next) {
            case 0:
              arbitratorAddress = _this._ArbitratorInstance.getContractAddress();
              return _context3.abrupt('return', _this._StoreProviderInstance.getDispute(account, arbitratorAddress, disputeId));

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

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

  this.getDisputeDeadline = function () {
    var _ref4 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4(disputeId) {
      var appeal = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
      var cachedDispute, dispute, deadlineTimestamp, currentDeadlines;
      return _regeneratorRuntime.wrap(function _callee4$(_context4) {
        while (1) {
          switch (_context4.prev = _context4.next) {
            case 0:
              cachedDispute = _this.disputeCache[disputeId] || {};

              if (!(cachedDispute.appealDeadlines && cachedDispute.appealDeadlines[appeal])) {
                _context4.next = 3;
                break;
              }

              return _context4.abrupt('return', cachedDispute.appealDeadlines[appeal]);

            case 3:
              _context4.next = 5;
              return _this._ArbitratorInstance.getDispute(disputeId);

            case 5:
              dispute = _context4.sent;
              _context4.next = 8;
              return _this._ArbitratorInstance.getDisputeDeadlineTimestamp(dispute.firstSession + appeal);

            case 8:
              deadlineTimestamp = _context4.sent;


              if (deadlineTimestamp) {
                currentDeadlines = cachedDispute.appealDeadlines || [];

                currentDeadlines[appeal] = deadlineTimestamp;
                // cache the deadline for the appeal
                _this._updateDisputeCache(disputeId, {
                  appealDeadlines: currentDeadlines
                });
              }

              return _context4.abrupt('return', deadlineTimestamp);

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

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

  this.getAppealRuledAt = function () {
    var _ref5 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee5(disputeId) {
      var appeal = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
      var cachedDispute, dispute, appealRuledAtTimestamp, currentRuledAt;
      return _regeneratorRuntime.wrap(function _callee5$(_context5) {
        while (1) {
          switch (_context5.prev = _context5.next) {
            case 0:
              cachedDispute = _this.disputeCache[disputeId];

              if (!(cachedDispute && cachedDispute.appealRuledAt && cachedDispute.appealRuledAt[appeal])) {
                _context5.next = 3;
                break;
              }

              return _context5.abrupt('return', cachedDispute.appealRuledAt[appeal]);

            case 3:
              _context5.next = 5;
              return _this._ArbitratorInstance.getDispute(disputeId);

            case 5:
              dispute = _context5.sent;
              _context5.next = 8;
              return _this._ArbitratorInstance.getAppealRuledAtTimestamp(dispute.firstSession + appeal);

            case 8:
              appealRuledAtTimestamp = _context5.sent;


              // cache the deadline for the appeal
              if (appealRuledAtTimestamp) {
                currentRuledAt = cachedDispute.appealRuledAt || [];

                currentRuledAt[appeal] = appealRuledAtTimestamp;
                _this._updateDisputeCache(disputeId, {
                  appealRuledAt: currentRuledAt
                });
              }

              return _context5.abrupt('return', appealRuledAtTimestamp);

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

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

  this.getAppealCreatedAt = function () {
    var _ref6 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee6(disputeId, account) {
      var appeal = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
      var cachedDispute, dispute, appealCreatedAtTimestamp, creationBlock, timestampSeconds, currentCreatedAt;
      return _regeneratorRuntime.wrap(function _callee6$(_context6) {
        while (1) {
          switch (_context6.prev = _context6.next) {
            case 0:
              cachedDispute = _this.disputeCache[disputeId];

              if (!(cachedDispute && cachedDispute.appealCreatedAt && cachedDispute.appealCreatedAt[appeal])) {
                _context6.next = 3;
                break;
              }

              return _context6.abrupt('return', cachedDispute.appealCreatedAt[appeal]);

            case 3:
              _context6.next = 5;
              return _this._ArbitratorInstance.getDispute(disputeId);

            case 5:
              dispute = _context6.sent;
              appealCreatedAtTimestamp = null;

              if (!(appeal === 0)) {
                _context6.next = 18;
                break;
              }

              _context6.next = 10;
              return _this._getDisputeStartBlock(disputeId, account);

            case 10:
              creationBlock = _context6.sent;

              if (!creationBlock) {
                _context6.next = 16;
                break;
              }

              _context6.next = 14;
              return _this._ArbitratorInstance._getTimestampForBlock(creationBlock);

            case 14:
              timestampSeconds = _context6.sent;


              appealCreatedAtTimestamp = timestampSeconds * 1000;

            case 16:
              _context6.next = 22;
              break;

            case 18:
              _context6.next = 20;
              return _this._ArbitratorInstance.getAppealCreationTimestamp(dispute.firstSession + (appeal - 1) // appeal was created during previous session
              );

            case 20:
              appealCreatedAtTimestamp = _context6.sent;


              // cache the deadline for the appeal
              if (appealCreatedAtTimestamp) {
                currentCreatedAt = cachedDispute.appealCreatedAt || [];

                currentCreatedAt[appeal] = appealCreatedAtTimestamp;
                _this._updateDisputeCache(disputeId, {
                  appealCreatedAt: currentCreatedAt
                });
              }

            case 22:
              return _context6.abrupt('return', appealCreatedAtTimestamp);

            case 23:
            case 'end':
              return _context6.stop();
          }
        }
      }, _callee6, _this);
    }));

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

  this.getDataForDispute = function () {
    var _ref7 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee7(disputeId, account) {
      var arbitratorAddress, _ref8, _ref9, dispute, period, session, arbitrableContractAddress, _ref10, _ref11, arbitrableContractData, evidence, contractStoreData, appealDraws, userData, netPNK, lastSession, appealJuror, appealRulings, appeal, isLastAppeal, draws, canRule, canRepartition, canExecute, ruling, rulingPromises, _ref12, _ref13, appealCreatedAt, appealDeadline, appealRuledAt;

      return _regeneratorRuntime.wrap(function _callee7$(_context7) {
        while (1) {
          switch (_context7.prev = _context7.next) {
            case 0:
              arbitratorAddress = _this._ArbitratorInstance.getContractAddress();
              // Get dispute data from contract. Also get the current session and period.

              _context7.next = 3;
              return _Promise.all([_this._ArbitratorInstance.getDispute(disputeId), _this._ArbitratorInstance.getPeriod(), _this._ArbitratorInstance.getSession()]);

            case 3:
              _ref8 = _context7.sent;
              _ref9 = _slicedToArray(_ref8, 3);
              dispute = _ref9[0];
              period = _ref9[1];
              session = _ref9[2];


              // Get arbitrable contract data and evidence
              arbitrableContractAddress = dispute.arbitrableContractAddress;
              _context7.next = 11;
              return _this._ArbitrableInstance.setContractInstance(arbitrableContractAddress);

            case 11:
              _context7.next = 13;
              return _Promise.all([_this._ArbitrableInstance.getContractData(), _this._ArbitrableInstance.getEvidence()]);

            case 13:
              _ref10 = _context7.sent;
              _ref11 = _slicedToArray(_ref10, 2);
              arbitrableContractData = _ref11[0];
              evidence = _ref11[1];
              _context7.next = 19;
              return _this._StoreProviderInstance.getContractByAddress(arbitrableContractData.partyA, arbitrableContractAddress);

            case 19:
              contractStoreData = _context7.sent;


              // Get dispute data from the store
              appealDraws = [];

              // get draws if they have been added to store.

              _context7.prev = 21;
              _context7.next = 24;
              return _this._StoreProviderInstance.getDispute(account, arbitratorAddress, disputeId);

            case 24:
              userData = _context7.sent;

              if (userData.appealDraws) appealDraws = userData.appealDraws || [];
              // eslint-disable-next-line no-unused-vars
              _context7.next = 31;
              break;

            case 28:
              _context7.prev = 28;
              _context7.t0 = _context7['catch'](21);

              // Dispute exists on chain but not in store. We have lost draws for past disputes.
              console.error('Dispute does not exist in store.');

            case 31:
              _context7.next = 33;
              return _this._ArbitratorInstance.getNetTokensForDispute(disputeId, account);

            case 33:
              netPNK = _context7.sent;


              // Build juror info and ruling arrays, indexed by appeal number
              lastSession = dispute.firstSession + dispute.numberOfAppeals;
              appealJuror = [];
              appealRulings = [];
              appeal = 0;

            case 38:
              if (!(appeal <= dispute.numberOfAppeals)) {
                _context7.next = 68;
                break;
              }

              isLastAppeal = dispute.firstSession + appeal === lastSession;

              // Get appeal data

              draws = appealDraws[appeal] || [];
              canRule = false;
              canRepartition = false;
              canExecute = false;
              ruling = void 0;
              rulingPromises = [_this._ArbitratorInstance.currentRulingForDispute(disputeId, appeal)];

              // Extra info for the last appeal

              if (isLastAppeal) {
                if (draws.length > 0) rulingPromises.push(_this._ArbitratorInstance.canRuleDispute(disputeId, draws, account));

                if (session && period) canRepartition = lastSession <= session && // Not appealed to the next session
                period === arbitratorConstants.PERIOD.EXECUTE && // Executable period
                dispute.state === disputeConstants.STATE.OPEN; // Open dispute
                canExecute = dispute.state === disputeConstants.STATE.EXECUTABLE; // Executable state
              }

              // Wait for parallel requests to complete
              ;_context7.next = 50;
              return _Promise.all(rulingPromises);

            case 50:
              _ref12 = _context7.sent;
              _ref13 = _slicedToArray(_ref12, 2);
              ruling = _ref13[0];
              canRule = _ref13[1];
              _context7.next = 56;
              return _this.getAppealCreatedAt(dispute.disputeId, account, appeal);

            case 56:
              appealCreatedAt = _context7.sent;
              _context7.next = 59;
              return _this.getDisputeDeadline(dispute.disputeId, appeal);

            case 59:
              appealDeadline = _context7.sent;
              _context7.next = 62;
              return _this.getAppealRuledAt(dispute.disputeId, appeal);

            case 62:
              appealRuledAt = _context7.sent;


              appealJuror[appeal] = {
                createdAt: appealCreatedAt,
                fee: dispute.arbitrationFeePerJuror * draws.length,
                draws: draws,
                canRule: canRule
              };
              appealRulings[appeal] = {
                voteCounter: dispute.voteCounters[appeal],
                deadline: appealDeadline,
                ruledAt: appealRuledAt,
                ruling: ruling,
                canRepartition: canRepartition,
                canExecute: canExecute
              };

            case 65:
              appeal++;
              _context7.next = 38;
              break;

            case 68:
              return _context7.abrupt('return', {
                // Arbitrable Contract Data
                arbitrableContractAddress: arbitrableContractAddress,
                arbitratorAddress: arbitratorAddress,
                partyA: arbitrableContractData.partyA,
                partyB: arbitrableContractData.partyB,

                // Dispute Data
                disputeId: disputeId,
                firstSession: dispute.firstSession,
                lastSession: lastSession,
                numberOfAppeals: dispute.numberOfAppeals,
                disputeState: dispute.state,
                disputeStatus: dispute.status,
                appealJuror: appealJuror,
                appealRulings: appealRulings,
                netPNK: netPNK,

                // Store Data
                title: contractStoreData ? contractStoreData.title : undefined,
                description: contractStoreData ? contractStoreData.description : undefined,
                email: contractStoreData ? contractStoreData.email : undefined,
                evidence: evidence,
                metaEvidence: arbitrableContractData.metaEvidence
              });

            case 69:
            case 'end':
              return _context7.stop();
          }
        }
      }, _callee7, _this, [[21, 28]]);
    }));

    return function (_x20, _x21) {
      return _ref7.apply(this, arguments);
    };
  }();
};

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