"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const avatarService_1 = require("../avatarService");
const commonTypes_1 = require("../commonTypes");
const configService_1 = require("../configService");
const bignumber_js_1 = require("bignumber.js");
const contractWrapperFactory_1 = require("../contractWrapperFactory");
const iContractWrapperBase_1 = require("../iContractWrapperBase");
const proposalGeneratorBase_1 = require("../proposalGeneratorBase");
const transactionService_1 = require("../transactionService");
const utils_1 = require("../utils");
const web3EventService_1 = require("../web3EventService");
class ContributionRewardWrapper extends proposalGeneratorBase_1.ProposalGeneratorBase {
    constructor() {
        super(...arguments);
        this.name = "ContributionReward";
        this.friendlyName = "Contribution Reward";
        this.factory = exports.ContributionRewardFactory;
    }
    /* tslint:enable:max-line-length */
    /**
     * Submit a proposal to reward a beneficiary for contributions to the DAO
     * @param {ProposeContributionRewardParams} options
     */
    proposeContributionReward(options = {}) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const defaults = {
                descriptionIsHashed: false,
                ethReward: "0",
                externalToken: "",
                externalTokenReward: "0",
                nativeTokenReward: "0",
                reputationChange: "0",
            };
            options = Object.assign({}, defaults, options);
            if (!options.avatar) {
                throw new Error("avatar address is not defined");
            }
            if (!options.description) {
                throw new Error("description is not defined");
            }
            if (!Number.isInteger(options.numberOfPeriods) || (options.numberOfPeriods <= 0)) {
                throw new Error("numberOfPeriods must be greater than zero");
            }
            if (!Number.isInteger(options.periodLength) || (options.periodLength < 0)) {
                throw new Error("periodLength must be an integer greater than or equal to zero");
            }
            /**
             * will thrown Error if not valid numbers
             */
            const web3 = yield utils_1.Utils.getWeb3();
            const reputationChange = web3.toBigNumber(options.reputationChange);
            const nativeTokenReward = web3.toBigNumber(options.nativeTokenReward);
            const ethReward = web3.toBigNumber(options.ethReward);
            const externalTokenReward = web3.toBigNumber(options.externalTokenReward);
            if ((nativeTokenReward.lt(0) ||
                ethReward.lt(0) ||
                externalTokenReward.lt(0))) {
                throw new Error("rewards must be greater than or equal to zero");
            }
            if (!((!reputationChange.eq(0) ||
                nativeTokenReward.gt(0) ||
                reputationChange.gt(0) ||
                ethReward.gt(0) ||
                externalTokenReward.gt(0)))) {
                throw new Error("no reward amount was given");
            }
            if (externalTokenReward.gt(0) && !options.externalToken) {
                throw new Error("external token reward is proposed but externalToken is not defined");
            }
            if (!options.beneficiaryAddress) {
                throw new Error("beneficiaryAddress is not defined");
            }
            const orgNativeTokenFee = (yield this.getSchemeParameters(options.avatar)).orgNativeTokenFee;
            const autoApproveTransfer = configService_1.ConfigService.get("autoApproveTokenTransfers") && (orgNativeTokenFee.toNumber() > 0);
            const functionName = "ContributionReward.proposeContributionReward";
            const payload = transactionService_1.TransactionService.publishKickoffEvent(functionName, options, 1 + (autoApproveTransfer ? 1 : 0));
            const eventContext = transactionService_1.TransactionService.newTxEventContext(functionName, payload, options);
            if (autoApproveTransfer) {
                /**
                 * approve immediate transfer of native tokens from msg.sender to the avatar
                 */
                const avatarService = new avatarService_1.AvatarService(options.avatar);
                const token = yield avatarService.getNativeToken();
                const result = yield token.approve({
                    amount: orgNativeTokenFee,
                    spender: options.avatar,
                    txEventContext: eventContext,
                });
                yield result.watchForTxMined();
            }
            this.logContractFunctionCall("ContributionReward.proposeContributionReward", options);
            const tx = yield this.sendTransaction(eventContext, this.contract.proposeContributionReward, [options.avatar,
                options.descriptionIsHashed ? options.description : utils_1.Utils.SHA3(options.description),
                reputationChange,
                [nativeTokenReward, ethReward, externalTokenReward, options.periodLength, options.numberOfPeriods],
                options.externalToken,
                options.beneficiaryAddress]);
            if (tx) {
                transactionService_1.TransactionService.publishTxLifecycleEvents(eventContext, tx, this.contract);
            }
            return new iContractWrapperBase_1.ArcTransactionProposalResult(tx, this.contract, yield this.getVotingMachine(options.avatar));
        });
    }
    /**
     * Redeem the specified rewards for the beneficiary of the proposal
     * @param {ContributionRewardRedeemParams} opts
     */
    redeemContributionReward(options = {}) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const defaults = {
                ethers: false,
                externalTokens: false,
                nativeTokens: false,
                reputation: false,
            };
            options = Object.assign({}, defaults, options);
            if (!options.proposalId) {
                throw new Error("proposalId is not defined");
            }
            if (!options.avatar) {
                throw new Error("avatar address is not defined");
            }
            this.logContractFunctionCall("ContributionReward.redeem", options);
            return this.wrapTransactionInvocation("ContributionReward.redeemContributionReward", options, this.contract.redeem, [options.proposalId,
                options.avatar,
                [options.reputation, options.nativeTokens, options.ethers, options.externalTokens]]);
        });
    }
    /**
     * Redeem external token reward for the beneficiary of the proposal
     * @param {ContributionRewardSpecifiedRedemptionParams} options
     */
    redeemExternalToken(options = {}) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.proposalId) {
                throw new Error("proposalId is not defined");
            }
            if (!options.avatar) {
                throw new Error("avatar address is not defined");
            }
            this.logContractFunctionCall("ContributionReward.redeemExternalToken", options);
            return this.wrapTransactionInvocation("ContributionReward.redeemExternalToken", options, this.contract.redeemExternalToken, [options.proposalId,
                options.avatar]);
        });
    }
    /**
     * Redeem reputation reward for the beneficiary of the proposal
     * @param {ContributionRewardSpecifiedRedemptionParams} options
     */
    redeemReputation(options = {}) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.proposalId) {
                throw new Error("proposalId is not defined");
            }
            if (!options.avatar) {
                throw new Error("avatar address is not defined");
            }
            this.logContractFunctionCall("ContributionReward.redeemReputation", options);
            return this.wrapTransactionInvocation("ContributionReward.redeemReputation", options, this.contract.redeemReputation, [options.proposalId,
                options.avatar]);
        });
    }
    /**
     * Redeem native token reward for the beneficiary of the proposal
     * @param {ContributionRewardSpecifiedRedemptionParams} options
     */
    redeemNativeToken(options = {}) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.proposalId) {
                throw new Error("proposalId is not defined");
            }
            if (!options.avatar) {
                throw new Error("avatar address is not defined");
            }
            this.logContractFunctionCall("ContributionReward.redeemNativeToken", options);
            return this.wrapTransactionInvocation("ContributionReward.redeemNativeToken", options, this.contract.redeemNativeToken, [options.proposalId,
                options.avatar]);
        });
    }
    /**
     * Redeem ether reward for the beneficiary of the proposal
     * @param {ContributionRewardSpecifiedRedemptionParams} options
     */
    redeemEther(options = {}) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.proposalId) {
                throw new Error("proposalId is not defined");
            }
            if (!options.avatar) {
                throw new Error("avatar address is not defined");
            }
            this.logContractFunctionCall("ContributionReward.redeemEther", options);
            return this.wrapTransactionInvocation("ContributionReward.redeemEther", options, this.contract.redeemEther, [options.proposalId,
                options.avatar]);
        });
    }
    /**
     * EntityFetcherFactory for votable ContributionProposals.
     * @param avatarAddress
     */
    getVotableProposals(avatarAddress) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!avatarAddress) {
                throw new Error("avatarAddress is not set");
            }
            return this.proposalService.getProposalEvents({
                baseArgFilter: { _avatar: avatarAddress },
                proposalsEventFetcher: this.NewContributionProposal,
                transformEventCallback: (event) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                    return this.getProposal(event.args._avatar, event.args._proposalId);
                }),
                votableOnly: true,
                votingMachine: yield this.getVotingMachine(avatarAddress),
            });
        });
    }
    /**
     * EntityFetcherFactory for executed ContributionProposals.
     * The Arc ContributionProposals contract retains the original proposal struct after execution.
     * @param avatarAddress
     */
    getExecutedProposals(avatarAddress) {
        return this.proposalService.getProposalEvents({
            baseArgFilter: { _avatar: avatarAddress },
            proposalsEventFetcher: this.ProposalExecuted,
            transformEventCallback: (event) => {
                return this.getProposal(avatarAddress, event.args._proposalId);
            },
        });
    }
    /**
     * Return a list of `ProposalRewards` for executed (passed by vote) proposals
     * that have rewards waiting to be redeemed by the given beneficiary.
     * `ProposalRewards` includes the total amount redeemable, the amount
     * yet-to-be redeemed and where applicable, the amount that the Dao has available
     * to pay out.  The latter is useful for anticipating whether an attempt
     * to redeem would succeed.
     * @param {GetBeneficiaryRewardsParams} options
     */
    getBeneficiaryRewards(options = {}) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.avatar) {
                throw new Error("avatar address is not defined");
            }
            if (!options.beneficiaryAddress) {
                throw new Error("beneficiaryAddress is not defined");
            }
            /**
             * Fetch from block 0 for the given avatar
             */
            const proposalsFetcher = this.ProposalExecuted(Object.assign({ _avatar: options.avatar }, options.proposalId ? { _proposalId: options.proposalId } : {}), { fromBlock: 0 });
            /**
             * get the proposals for the given beneficiary.
             * We don't use getExecutedProposals because we want to be able to work
             * across avatars.
             */
            const proposals = new Array();
            yield new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                proposalsFetcher.get((error, log) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                    if (error) {
                        return reject(error);
                    }
                    for (const event of log) {
                        const proposal = yield this.getProposal(options.avatar, event.args._proposalId);
                        if (proposal.beneficiaryAddress === options.beneficiaryAddress) {
                            proposals.push(proposal);
                        }
                    }
                    resolve();
                }));
            }));
            const rewardsArray = new Array();
            const avatarService = new avatarService_1.AvatarService(options.avatar);
            for (const proposal of proposals) {
                const proposalRewards = {};
                proposalRewards.proposalId = proposal.proposalId;
                yield this.computeRemainingReward(proposalRewards, proposal, "ethReward", options.avatar, RewardType.Eth);
                if (proposal.ethReward.gt("0")) {
                    proposalRewards.ethAvailableToReward = yield avatarService.getEthBalance();
                }
                yield this.computeRemainingReward(proposalRewards, proposal, "externalTokenReward", options.avatar, RewardType.ExternalToken);
                if (proposal.externalTokenReward.gt("0")) {
                    proposalRewards.externalTokensAvailableToReward = yield avatarService.getTokenBalance(proposal.externalToken);
                }
                yield this.computeRemainingReward(proposalRewards, proposal, "nativeTokenReward", options.avatar, RewardType.NativeToken);
                yield this.computeRemainingReward(proposalRewards, proposal, "reputationChange", options.avatar, RewardType.Reputation);
                rewardsArray.push(proposalRewards);
            }
            return rewardsArray;
        });
    }
    getParametersHash(params) {
        params = Object.assign({}, {
            orgNativeTokenFee: 0,
        }, params);
        return this._getParametersHash(params.orgNativeTokenFee, params.voteParametersHash, params.votingMachineAddress);
    }
    setParameters(params) {
        this.validateStandardSchemeParams(params);
        params = Object.assign({}, {
            orgNativeTokenFee: 0,
        }, params);
        const orgNativeTokenFee = new bignumber_js_1.BigNumber(params.orgNativeTokenFee);
        if (orgNativeTokenFee.lt(0)) {
            throw new Error("orgNativeTokenFee must be greater than or equal to 0");
        }
        return super._setParameters("ContributionReward.setParameters", params.txEventContext, orgNativeTokenFee, params.voteParametersHash, params.votingMachineAddress);
    }
    /**
     * Returns promise of a `ContributionProposal` for the given proposal id and avatar address.
     * @param avatarAddress
     * @param proposalId
     */
    getProposal(avatarAddress, proposalId) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const proposalParams = yield this.contract.organizationsProposals(avatarAddress, proposalId);
            return this.convertProposalPropsArrayToObject(proposalParams, proposalId);
        });
    }
    getDefaultPermissions() {
        return commonTypes_1.DefaultSchemePermissions.ContributionReward;
    }
    getSchemePermissions(avatarAddress) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return this._getSchemePermissions(avatarAddress);
        });
    }
    getSchemeParameters(avatarAddress) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return this._getSchemeParameters(avatarAddress);
        });
    }
    getParameters(paramsHash) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const params = yield this.getParametersArray(paramsHash);
            return {
                orgNativeTokenFee: params[0],
                voteParametersHash: params[1],
                votingMachineAddress: params[2],
            };
        });
    }
    hydrated() {
        /* tslint:disable:max-line-length */
        this.NewContributionProposal = this.createEventFetcherFactory(this.contract.NewContributionProposal);
        this.ProposalExecuted = this.createEventFetcherFactory(this.contract.ProposalExecuted);
        this.RedeemReputation = this.createEventFetcherFactory(this.contract.RedeemReputation);
        this.RedeemEther = this.createEventFetcherFactory(this.contract.RedeemEther);
        this.RedeemNativeToken = this.createEventFetcherFactory(this.contract.RedeemNativeToken);
        this.RedeemExternalToken = this.createEventFetcherFactory(this.contract.RedeemExternalToken);
        /* tslint:enable:max-line-length */
    }
    computeRemainingReward(proposalRewards, proposal, rewardName, avatarAddress, rewardType) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const amountToRedeemPerPeriod = proposal[rewardName];
            const countRedeemedPeriods = yield this.contract.getRedeemedPeriods(proposal.proposalId, avatarAddress, rewardType);
            const countRedeemablePeriods = yield this.contract.getPeriodsToPay(proposal.proposalId, avatarAddress, rewardType);
            const totalReward = amountToRedeemPerPeriod.mul(proposal.numberOfPeriods);
            const amountRewarded = amountToRedeemPerPeriod.mul(countRedeemedPeriods);
            const amountRedeemable = amountToRedeemPerPeriod.mul(countRedeemablePeriods);
            proposalRewards[rewardName] = totalReward;
            proposalRewards[`${rewardName}Unredeemed`] = totalReward.sub(amountRewarded);
            proposalRewards[`${rewardName}Redeemable`] = amountRedeemable;
        });
    }
    convertProposalPropsArrayToObject(propsArray, proposalId) {
        return {
            beneficiaryAddress: propsArray[5],
            ethReward: propsArray[2],
            executionTime: propsArray[8],
            externalToken: propsArray[3],
            externalTokenReward: propsArray[4],
            nativeTokenReward: propsArray[0],
            numberOfPeriods: propsArray[7],
            periodLength: propsArray[6],
            proposalId,
            reputationChange: propsArray[1],
        };
    }
}
exports.ContributionRewardWrapper = ContributionRewardWrapper;
var RewardType;
(function (RewardType) {
    RewardType[RewardType["Reputation"] = 0] = "Reputation";
    RewardType[RewardType["NativeToken"] = 1] = "NativeToken";
    RewardType[RewardType["Eth"] = 2] = "Eth";
    RewardType[RewardType["ExternalToken"] = 3] = "ExternalToken";
})(RewardType = exports.RewardType || (exports.RewardType = {}));
exports.ContributionRewardFactory = new contractWrapperFactory_1.ContractWrapperFactory("ContributionReward", ContributionRewardWrapper, new web3EventService_1.Web3EventService());
//# sourceMappingURL=contributionReward.js.map