"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const dopts = require("default-options");
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 utils_1 = require("../utils");
class ContributionRewardWrapper extends contractWrapperBase_1.ContractWrapperBase {
    constructor() {
        super(...arguments);
        this.name = "ContributionReward";
        this.frendlyName = "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} opts
     */
    proposeContributionReward(opts = {}) {
        return __awaiter(this, void 0, void 0, function* () {
            /**
             * Note that explicitly supplying any property with a value of undefined will prevent the property
             * from taking on its default value (weird behavior of default-options)
             */
            const defaults = {
                avatar: undefined,
                beneficiaryAddress: undefined,
                description: undefined,
                ethReward: 0,
                externalToken: null,
                externalTokenReward: 0,
                nativeTokenReward: 0,
                numberOfPeriods: undefined,
                periodLength: undefined,
                reputationChange: 0,
            };
            const options = dopts(opts, defaults, { allowUnknown: true });
            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 greater than zero");
            }
            /**
             * will thrown Error if not valid numbers
             */
            const web3 = 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 < 0) ||
                (ethReward < 0) ||
                (externalTokenReward < 0)) {
                throw new Error("rewards must be greater than or equal to zero");
            }
            if (!((reputationChange !== 0) ||
                (nativeTokenReward > 0) ||
                (reputationChange > 0) ||
                (ethReward > 0) ||
                (externalTokenReward > 0))) {
                throw new Error("no reward amount was given");
            }
            if ((externalTokenReward > 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;
            if (configService_1.ConfigService.get("autoApproveTokenTransfers") && (orgNativeTokenFee.toNumber() > 0)) {
                /**
                 * 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();
                yield token.approve(this.address, orgNativeTokenFee, { from: yield utils_1.Utils.getDefaultAccount() });
            }
            const 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);
            return new contractWrapperBase_1.ArcTransactionProposalResult(tx);
        });
    }
    /**
     * Redeem the specified rewards for the beneficiary of the proposal
     * @param {ContributionRewardRedeemParams} opts
     */
    redeemContributionReward(opts = {}) {
        return __awaiter(this, void 0, void 0, function* () {
            const defaults = {
                avatar: undefined,
                ethers: false,
                externalTokens: false,
                nativeTokens: false,
                proposalId: undefined,
                reputation: false,
            };
            const options = dopts(opts, defaults, { allowUnknown: true });
            if (!options.proposalId) {
                throw new Error("proposalId is not defined");
            }
            if (!options.avatar) {
                throw new Error("avatar address is not defined");
            }
            const tx = yield this.contract.redeem(options.proposalId, options.avatar, [options.reputation, options.nativeTokens, options.ethers, options.externalTokens]);
            return new contractWrapperBase_1.ArcTransactionResult(tx);
        });
    }
    /**
     * Redeem external token reward for the beneficiary of the proposal
     * @param {ContributionRewardSpecifiedRedemptionParams} opts
     */
    redeemExternalToken(opts = {}) {
        return __awaiter(this, void 0, void 0, function* () {
            const defaults = {
                avatar: undefined,
                proposalId: undefined,
            };
            const options = dopts(opts, defaults, { allowUnknown: true });
            if (!options.proposalId) {
                throw new Error("proposalId is not defined");
            }
            if (!options.avatar) {
                throw new Error("avatar address is not defined");
            }
            const tx = yield this.contract.redeemExternalToken(options.proposalId, options.avatar);
            return new contractWrapperBase_1.ArcTransactionResult(tx);
        });
    }
    /**
     * Redeem reputation reward for the beneficiary of the proposal
     * @param {ContributionRewardSpecifiedRedemptionParams} opts
     */
    redeemReputation(opts = {}) {
        return __awaiter(this, void 0, void 0, function* () {
            const defaults = {
                avatar: undefined,
                proposalId: undefined,
            };
            const options = dopts(opts, defaults, { allowUnknown: true });
            if (!options.proposalId) {
                throw new Error("proposalId is not defined");
            }
            if (!options.avatar) {
                throw new Error("avatar address is not defined");
            }
            const tx = yield this.contract.redeemReputation(options.proposalId, options.avatar);
            return new contractWrapperBase_1.ArcTransactionResult(tx);
        });
    }
    /**
     * Redeem native token reward for the beneficiary of the proposal
     * @param {ContributionRewardSpecifiedRedemptionParams} opts
     */
    redeemNativeToken(opts = {}) {
        return __awaiter(this, void 0, void 0, function* () {
            const defaults = {
                avatar: undefined,
                proposalId: undefined,
            };
            const options = dopts(opts, defaults, { allowUnknown: true });
            if (!options.proposalId) {
                throw new Error("proposalId is not defined");
            }
            if (!options.avatar) {
                throw new Error("avatar address is not defined");
            }
            const tx = yield this.contract.redeemNativeToken(options.proposalId, options.avatar);
            return new contractWrapperBase_1.ArcTransactionResult(tx);
        });
    }
    /**
     * Redeem ether reward for the beneficiary of the proposal
     * @param {ContributionRewardSpecifiedRedemptionParams} opts
     */
    redeemEther(opts = {}) {
        return __awaiter(this, void 0, void 0, function* () {
            const defaults = {
                avatar: undefined,
                proposalId: undefined,
            };
            const options = dopts(opts, defaults, { allowUnknown: true });
            if (!options.proposalId) {
                throw new Error("proposalId is not defined");
            }
            if (!options.avatar) {
                throw new Error("avatar address is not defined");
            }
            const tx = yield this.contract.redeemEther(options.proposalId, options.avatar);
            return new contractWrapperBase_1.ArcTransactionResult(tx);
        });
    }
    /**
     * Return all ContributionReward proposals ever created under the given avatar.
     * Filter by the optional proposalId.
     */
    getDaoProposals(opts = {}) {
        return __awaiter(this, void 0, void 0, function* () {
            const defaults = {
                avatar: undefined,
                proposalId: null,
            };
            const options = dopts(opts, defaults, { allowUnknown: true });
            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) => __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} opts
     */
    getBeneficiaryRewards(opts = {}) {
        return __awaiter(this, void 0, void 0, function* () {
            const defaults = {
                avatar: undefined,
                beneficiaryAddress: undefined,
                proposalId: null,
            };
            const options = dopts(opts, defaults, { allowUnknown: true });
            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 __awaiter(this, void 0, void 0, function* () {
            this.validateStandardSchemeParams(params);
            params = Object.assign({}, {
                orgNativeTokenFee: 0,
            }, params);
            return _super("setParameters").call(this, params.orgNativeTokenFee, params.voteParametersHash, params.votingMachineAddress);
        });
    }
    getDefaultPermissions(overrideValue) {
        // return overrideValue || Utils.numberToPermissionsString(DefaultSchemePermissions.ContributionReward);
        return (overrideValue || commonTypes_1.DefaultSchemePermissions.ContributionReward);
    }
    getSchemePermissions(avatarAddress) {
        return __awaiter(this, void 0, void 0, function* () {
            return this._getSchemePermissions(avatarAddress);
        });
    }
    getSchemeParameters(avatarAddress) {
        return __awaiter(this, void 0, void 0, function* () {
            return this._getSchemeParameters(avatarAddress);
        });
    }
    getParameters(paramsHash) {
        return __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 __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.default("ContributionReward", ContributionRewardWrapper);
//# sourceMappingURL=contributionreward.js.map