"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 contractWrapperBase_1 = require("../contractWrapperBase");
const contractWrapperFactory_1 = require("../contractWrapperFactory");
const transactionService_1 = require("../transactionService");
const utils_1 = require("../utils");
class ContributionRewardWrapper extends contractWrapperBase_1.ContractWrapperBase {
    constructor() {
        super(...arguments);
        this.name = "ContributionReward";
        this.friendlyName = "Contribution Reward";
        this.factory = exports.ContributionRewardFactory;
        /**
         * Events
         */
        /* tslint:disable:max-line-length */
        this.NewContributionProposal = this.createEventFetcherFactory("NewContributionProposal");
        this.ProposalExecuted = this.createEventFetcherFactory("ProposalExecuted");
        this.ProposalDeleted = this.createEventFetcherFactory("ProposalDeleted");
        this.RedeemReputation = this.createEventFetcherFactory("RedeemReputation");
        this.RedeemEther = this.createEventFetcherFactory("RedeemEther");
        this.RedeemNativeToken = this.createEventFetcherFactory("RedeemNativeToken");
        this.RedeemExternalToken = this.createEventFetcherFactory("RedeemExternalToken");
    }
    /* tslint:enable:max-line-length */
    /**
     * Submit a proposal for a reward for a contribution
     * @param {ProposeContributionRewardParams} options
     */
    proposeContributionReward(options = {}) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const defaults = {
                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 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 eventTopic = "txReceipts.ContributionReward.proposeContributionReward";
            const txReceiptEventPayload = transactionService_1.TransactionService.publishKickoffEvent(eventTopic, options, 1 + (autoApproveTransfer ? 1 : 0));
            let tx;
            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();
                tx = yield token.approve(this.address, orgNativeTokenFee, { from: yield utils_1.Utils.getDefaultAccount() });
                transactionService_1.TransactionService.publishTxEvent(eventTopic, txReceiptEventPayload, tx);
            }
            this.logContractFunctionCall("ContributionReward.proposeContributionReward", options);
            tx = yield this.contract.proposeContributionReward(options.avatar, utils_1.Utils.SHA3(options.description), reputationChange, [nativeTokenReward, ethReward, externalTokenReward, options.periodLength, options.numberOfPeriods], options.externalToken, options.beneficiaryAddress);
            transactionService_1.TransactionService.publishTxEvent(eventTopic, txReceiptEventPayload, tx);
            return new contractWrapperBase_1.ArcTransactionProposalResult(tx);
        });
    }
    /**
     * 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, () => {
                return 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, () => {
                return 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, () => {
                return 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, () => {
                return 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, () => {
                return this.contract.redeemEther(options.proposalId, options.avatar);
            });
        });
    }
    /**
     * Return all ContributionReward proposals ever created under the given avatar.
     * Filter by the optional proposalId.
     */
    getDaoProposals(options = {}) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const defaults = {
                proposalId: null,
            };
            options = Object.assign({}, defaults, options);
            if (!options.avatar) {
                throw new Error("avatar address is not defined");
            }
            const proposals = new Array();
            if (options.proposalId) {
                const orgProposal = yield this.contract.organizationsProposals(options.avatar, options.proposalId);
                const proposal = this.orgProposalToContributionProposal(orgProposal, options.proposalId);
                proposals.push(proposal);
            }
            else {
                const eventFetcher = this.NewContributionProposal({ _avatar: options.avatar }, { fromBlock: 0 });
                yield new Promise((resolve) => {
                    eventFetcher.get((err, log) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                        for (const event of log) {
                            const proposalId = event.args._proposalId;
                            const orgProposal = yield this.contract.organizationsProposals(options.avatar, proposalId);
                            const proposal = this.orgProposalToContributionProposal(orgProposal, proposalId);
                            proposals.push(proposal);
                        }
                        resolve();
                    }));
                });
            }
            return proposals;
        });
    }
    /**
     * Return a list of `ProposalRewards` for executed (passed by vote) proposals
     * that have rewards waiting to be redeemed by the given beneficiary.
     * `ProposalRewards` includes both the total amount redeemable and the amount
     * yet-to-be redeemed.
     * @param {GetBeneficiaryRewardsParams} options
     */
    getBeneficiaryRewards(options = {}) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const defaults = {
                proposalId: null,
            };
            options = Object.assign({}, defaults, options);
            if (!options.avatar) {
                throw new Error("avatar address is not defined");
            }
            if (!options.beneficiaryAddress) {
                throw new Error("beneficiaryAddress is not defined");
            }
            const proposals = yield this.getDaoProposals(options);
            const rewardsArray = new Array();
            for (const proposal of proposals) {
                const proposalRewards = {};
                proposalRewards.proposalId = proposal.proposalId;
                yield this.computeRemainingReward(proposalRewards, proposal, "ethReward", options.avatar, RewardType.Eth);
                yield this.computeRemainingReward(proposalRewards, proposal, "externalTokenReward", options.avatar, RewardType.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;
        });
    }
    setParameters(params) {
        const _super = name => super[name];
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            this.validateStandardSchemeParams(params);
            params = Object.assign({}, {
                orgNativeTokenFee: 0,
            }, params);
            return _super("_setParameters").call(this, "ContributionReward.setParameters", params.orgNativeTokenFee, params.voteParametersHash, params.votingMachineAddress);
        });
    }
    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],
            };
        });
    }
    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;
        });
    }
    orgProposalToContributionProposal(orgProposal, proposalId) {
        return {
            beneficiaryAddress: orgProposal[6],
            contributionDescriptionHash: orgProposal[9],
            ethReward: orgProposal[3],
            executionTime: orgProposal[9],
            externalToken: orgProposal[4],
            externalTokenReward: orgProposal[5],
            nativeTokenReward: orgProposal[1],
            numberOfPeriods: orgProposal[8],
            periodLength: orgProposal[7],
            proposalId,
            reputationChange: orgProposal[2],
        };
    }
}
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);
//# sourceMappingURL=contributionReward.js.map