'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const bignumber_js_1 = require("bignumber.js");
const schemeWrapperBase_1 = require("../schemeWrapperBase");
const utilsInternal_1 = require("../utilsInternal");
const web3EventService_1 = require("../web3EventService");
class Locking4ReputationWrapper extends schemeWrapperBase_1.SchemeWrapperBase {
    constructor() {
        super(...arguments);
        this.name = 'Locking4Reputation';
        this.friendlyName = 'Locking For Reputation';
        this.factory = null;
    }
    /**
     * Redeem reputation
     * @param options
     * @returns null ArcTransactionResult if there is nothing to redeem due to locker having no score
     */
    redeem(options) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.lockerAddress) {
                throw new Error('lockerAddress is not defined');
            }
            const hasLocked = yield this.lockerHasLocked(options.lockerAddress);
            if (!hasLocked) {
                // because the Arc contract will revert in this case
                return Promise.resolve(null);
            }
            const errMsg = yield this.getRedeemBlocker(options.lockerAddress);
            if (errMsg) {
                throw new Error(errMsg);
            }
            this.logContractFunctionCall('Locking4Reputation.redeem', options);
            return this.wrapTransactionInvocation('Locking4Reputation.redeem', options, this.contract.redeem, [options.lockerAddress]);
        });
    }
    /**
     * Returns reason why can't redeem, or else null if can redeem
     * @param lockerAddress
     */
    getRedeemBlocker(lockerAddress) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const lockingEndTime = yield this.getLockingEndTime();
            const now = yield utilsInternal_1.UtilsInternal.lastBlockDate();
            if (now <= lockingEndTime) {
                return 'the locking period has not ended';
            }
            const redeemEnableTime = yield this.getRedeemEnableTime();
            if (now <= redeemEnableTime) {
                throw new Error(`nothing can be redeemed until after ${redeemEnableTime}`);
            }
            return null;
        });
    }
    /**
     * Returns error message else null if can release
     * @param lockerAddress
     * @param lockId
     */
    getReleaseBlocker(lockerAddress, lockId) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const lockInfo = yield this.getLockInfo(lockerAddress, lockId);
            const now = yield utilsInternal_1.UtilsInternal.lastBlockDate();
            const amount = new bignumber_js_1.default(lockInfo.amount);
            if (amount.lte(0)) {
                return 'current locked amount must be greater than zero';
            }
            if (now <= lockInfo.releaseTime) {
                return 'the lock period has not ended';
            }
            if (lockInfo.released) {
                return 'lock is already released';
            }
            return null;
        });
    }
    /**
     * Returns reason why can't lock, else null if can lock
     */
    getLockBlocker(options) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.lockerAddress) {
                return 'lockerAddress is not defined';
            }
            const amount = new bignumber_js_1.default(options.amount);
            if (amount.isNaN()) {
                return 'amount does not represent a number';
            }
            if (amount.lte(0)) {
                return 'amount to lock must be greater than zero';
            }
            if (!Number.isInteger(options.period)) {
                return 'period does not represent a number';
            }
            if (options.period <= 0) {
                return 'period must be greater than zero';
            }
            const now = yield utilsInternal_1.UtilsInternal.lastBlockDate();
            const maxLockingPeriod = yield this.getMaxLockingPeriod();
            if (options.period > maxLockingPeriod) {
                return 'the locking period exceeds the maximum locking period';
            }
            const lockingStartTime = yield this.getLockingStartTime();
            const lockingEndTime = yield this.getLockingEndTime();
            if ((now < lockingStartTime) || (now > lockingEndTime)) {
                return 'the locking period has not started or has expired';
            }
            return null;
        });
    }
    getUserEarnedReputation(options) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            let rep = new bignumber_js_1.default(0);
            if (!options.lockerAddress) {
                throw new Error('lockerAddress is not defined');
            }
            const hasLocked = yield this.lockerHasLocked(options.lockerAddress);
            if (hasLocked) {
                const errMsg = yield this.getRedeemBlocker(options.lockerAddress);
                if (errMsg) {
                    throw new Error(errMsg);
                }
                this.logContractFunctionCall('Locking4Reputation.redeem.call', options);
                rep = yield this.contract.redeem.call(options.lockerAddress);
            }
            else {
                /**
                 * see if it is 0 by result of the reputation being redeemed
                 */
                const events = yield this.Redeem({ _beneficiary: options.lockerAddress }, { fromBlock: 0 }).get();
                if (events.length) {
                    if (events.length > 1) {
                        throw new Error('unexpectedly received more than one Redeem event for the account');
                    }
                    rep = events[0].args._amount;
                }
            }
            return rep;
        });
    }
    getLockerScore(lockerAddress) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!lockerAddress) {
                throw new Error('lockerAddress is not defined');
            }
            const lockerInfo = yield this.getLockerInfo(lockerAddress);
            return lockerInfo ? lockerInfo.score : new bignumber_js_1.default(0);
        });
    }
    lockerHasLocked(lockerAddress) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!lockerAddress) {
                throw new Error('lockerAddress is not defined');
            }
            return (yield this.getLockerScore(lockerAddress)).gt(0);
        });
    }
    /**
     * Get a promise of the first date/time when anything can be redeemed
     */
    getRedeemEnableTime() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            this.logContractFunctionCall('Locking4Reputation.redeemEnableTime');
            const seconds = yield this.contract.redeemEnableTime();
            return new Date(seconds.toNumber() * 1000);
        });
    }
    getTotalLocked() {
        this.logContractFunctionCall('Locking4Reputation.totalLocked');
        return this.contract.totalLocked();
    }
    getTotalLockedLeft() {
        this.logContractFunctionCall('Locking4Reputation.totalLockedLeft');
        return this.contract.totalLockedLeft();
    }
    getTotalScore() {
        this.logContractFunctionCall('Locking4Reputation.totalScore');
        return this.contract.totalScore();
    }
    /**
     * get total number of locks
     */
    getLockCount() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            this.logContractFunctionCall('Locking4Reputation.lockingsCounter');
            return (yield this.contract.lockingsCounter()).toNumber();
        });
    }
    /**
     * get the total reputation this contract will reward
     */
    getReputationReward() {
        this.logContractFunctionCall('Locking4Reputation.reputationReward');
        return this.contract.reputationReward();
    }
    /**
     * get the total reputation this contract has not yet rewarded
     */
    getReputationRewardLeft() {
        this.logContractFunctionCall('Locking4Reputation.reputationRewardLeft');
        return this.contract.reputationRewardLeft();
    }
    getLockingEndTime() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            this.logContractFunctionCall('Locking4Reputation.lockingEndTime');
            const dt = yield this.contract.lockingEndTime();
            return new Date(dt.toNumber() * 1000);
        });
    }
    getLockingStartTime() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            this.logContractFunctionCall('Locking4Reputation.lockingStartTime');
            const dt = yield this.contract.lockingStartTime();
            return new Date(dt.toNumber() * 1000);
        });
    }
    getMaxLockingPeriod() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            this.logContractFunctionCall('Locking4Reputation.maxLockingPeriod');
            // returns seconds
            return (yield this.contract.maxLockingPeriod()).toNumber();
        });
    }
    getAvatar() {
        this.logContractFunctionCall('Locking4Reputation.avatar');
        return this.contract.avatar();
    }
    /**
     * Returns promise of information about a locked amount for the given locker and lockerId.
     * @param lockerAddress
     * @param lockId
     */
    getLockInfo(lockerAddress, lockId) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            this.logContractFunctionCall('Locking4Reputation.lockers', { lockerAddress, lockId });
            const lockInfo = yield this.contract.lockers(lockerAddress, lockId);
            let amount = lockInfo[0];
            let released = false;
            if (amount.eq(0)) {
                amount = yield this.getReleasedAmount(lockerAddress, lockId);
                released = amount.gt(0); // should always be true!
            }
            return {
                amount,
                lockId,
                lockerAddress,
                releaseTime: new Date(lockInfo[1].toNumber() * 1000),
                released,
            };
        });
    }
    /**
     * Returns the amount originally locked (which one can't obtain other than via
     * Release events once the lock is released).  Returns 0 if not released or event otherwise
     * not found.
     * @param lockerAddress
     * @param lockId
     */
    getReleasedAmount(lockerAddress, lockId) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            let amount = new bignumber_js_1.default(0);
            const releasesFetcher = this.getReleases();
            const releases = yield releasesFetcher({ _beneficiary: lockerAddress, _lockingId: lockId }, { fromBlock: 0 }).get();
            if (releases.length) {
                amount = releases[0].amount;
            }
            return amount;
        });
    }
    /**
     * Returns promise of information about the given locker, including the locker's score.
     * Score determines the proportion of total reputation that can be redeemed by the locker.
     *
     * @param lockerAddress
     */
    getLockerInfo(lockerAddress) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            this.logContractFunctionCall('Locking4Reputation.scores', { lockerAddress });
            const score = yield this.contract.scores(lockerAddress);
            return {
                lockerAddress,
                score,
            };
        });
    }
    /**
     * Returns EntityEventFetcher that returns `LockInfo` for each `Lock` event.
     * Note this includes released locks.
     */
    getLocks() {
        const web3EventService = new web3EventService_1.Web3EventService();
        return web3EventService.createEntityFetcherFactory(this.Lock, (event) => tslib_1.__awaiter(this, void 0, void 0, function* () {
            const lockInfo = (yield this.getLockInfo(event.args._locker, event.args._lockingId));
            lockInfo.transationHash = event.transactionHash;
            return lockInfo;
        }));
    }
    /**
     * Returns EntityEventFetcher that returns `ReleaseInfo` for each `Release` event.
     */
    getReleases() {
        const web3EventService = new web3EventService_1.Web3EventService();
        return web3EventService.createEntityFetcherFactory(this.Release, (event) => tslib_1.__awaiter(this, void 0, void 0, function* () {
            return Promise.resolve({
                amount: event.args._amount,
                lockId: event.args._lockingId,
                lockerAddress: event.args._beneficiary,
            });
        }));
    }
    /**
     * Returns EntityEventFetcher that returns `LockerInfo` for each account that has created a lock.
     * It is fired for an account whenever a `Lock`, `Redeem` or `Release` event is emitted.
     */
    getLockers(options = {}) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const filter = {};
            if (options.lockerAddress) {
                filter._locker = options.lockerAddress;
            }
            if (options.lockingId) {
                filter._lockingId = options.lockingId;
            }
            const fetcher = this.getLocks()(filter, options.filterObject);
            const lockInfos = yield fetcher.get();
            const foundAddresses = new Set();
            const lockers = new Array();
            for (const lockInfo of lockInfos) {
                if (!foundAddresses.has(lockInfo.lockerAddress)) {
                    foundAddresses.add(lockInfo.lockerAddress);
                    const lockerInfo = yield this.getLockerInfo(lockInfo.lockerAddress);
                    lockers.push(lockerInfo);
                }
            }
            return lockers;
        });
    }
    _initialize(options, checkmaxLockingPeriod = true) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const avatar = yield this.getAvatar();
            if (!utilsInternal_1.UtilsInternal.isNullAddress(avatar)) {
                throw new Error('you can only call initialize once');
            }
            if (!options.avatarAddress) {
                throw new Error('avatarAddress is not defined');
            }
            if (!options.lockingEndTime) {
                throw new Error('lockingEndTime is not defined');
            }
            if (!options.lockingStartTime) {
                throw new Error('lockingStartTime is not defined');
            }
            if (!options.redeemEnableTime) {
                throw new Error('redeemEnableTime is not defined');
            }
            if (options.lockingEndTime <= options.lockingStartTime) {
                throw new Error('lockingEndTime must be greater than lockingStartTime');
            }
            if (checkmaxLockingPeriod) {
                if (!Number.isInteger(options.maxLockingPeriod)) {
                    throw new Error('maxLockingPeriod is not an integer');
                }
                if (options.maxLockingPeriod <= 0) {
                    throw new Error('maxLockingPeriod must be greater than zero');
                }
            }
            if (!options.reputationReward) {
                throw new Error('reputationReward is not defined');
            }
            const reputationReward = new bignumber_js_1.default(options.reputationReward);
            if (reputationReward.lte(0)) {
                throw new Error('reputationReward must be greater than zero');
            }
        });
    }
    _release(options) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.lockerAddress) {
                throw new Error('lockerAddress is not defined');
            }
            if (!options.lockId) {
                throw new Error('lockId is not defined');
            }
            const errMsg = yield this.getReleaseBlocker(options.lockerAddress, options.lockId);
            if (errMsg) {
                throw new Error(errMsg);
            }
        });
    }
    hydrated() {
        super.hydrated();
        /* tslint:disable:max-line-length */
        this.Redeem = this.createEventFetcherFactory(this.contract.Redeem);
        this.Release = this.createEventFetcherFactory(this.contract.Release);
        this.Lock = this.createEventFetcherFactory(this.contract.Lock);
        /* tslint:enable:max-line-length */
    }
}
exports.Locking4ReputationWrapper = Locking4ReputationWrapper;
//# sourceMappingURL=locking4Reputation.js.map