"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const bignumber_js_1 = require("bignumber.js");
const contractWrapperBase_1 = require("../contractWrapperBase");
const contractWrapperFactory_1 = require("../contractWrapperFactory");
const iContractWrapperBase_1 = require("../iContractWrapperBase");
const loggingService_1 = require("../loggingService");
const utils_1 = require("../utils");
const web3EventService_1 = require("../web3EventService");
/**
 * Provides the services of any voting machine that implements the `IntVoteInterface`
 * Arc contract interface.  Also serves as the base class for all the specific
 * voting machine contract wrapper classes.
 */
class IntVoteInterfaceWrapper extends contractWrapperBase_1.ContractWrapperBase {
    constructor() {
        super(...arguments);
        this.factory = exports.IntVoteInterfaceFactory;
        this.name = "IntVoteInterface";
        this.friendlyName = "IntVoteInterface";
    }
    /**
     * Get or watch NewProposal events, filtering out proposals that are no longer votable.
     */
    get VotableProposals() {
        return this.web3EventService.createEventFetcherFactory(this.contract.NewProposal, (error, log) => {
            if (!error) {
                log = log.filter((event) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                    const proposalId = event.args._proposalId;
                    return yield this.isVotable({ proposalId });
                }));
            }
            return { error, log };
        });
    }
    /**
     * Register a new proposal with the given parameters. Every proposal is given a unique ID
     * which is a hash generated by calculating the keccak256 of a packing of an
     * incremented counter and the address of the voting machine.
     * @param options
     */
    propose(options) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.organizationAddress) {
                loggingService_1.LoggingService.warn(`IntVoteInterface.propose: organizationAddress is not set, will be set to msg.sender`);
            }
            const numChoiceBounds = yield this.getAllowedRangeOfChoices();
            if (!Number.isInteger(options.numOfChoices)) {
                throw new Error(`numOfChoices must be a number`);
            }
            if (options.numOfChoices < numChoiceBounds.minVote) {
                throw new Error(`numOfChoices cannot be less than ${numChoiceBounds.minVote}`);
            }
            if (options.numOfChoices > numChoiceBounds.maxVote) {
                throw new Error(`numOfChoices cannot be greater than ${numChoiceBounds.maxVote}`);
            }
            if (!options.proposalParameters) {
                throw new Error(`proposalParameters is not set`);
            }
            if (!options.proposerAddress) {
                options.proposerAddress = utils_1.Utils.NULL_ADDRESS;
            }
            this.logContractFunctionCall("IntVoteInterface.propose", options);
            const txResult = yield this.wrapTransactionInvocation("IntVoteInterface.propose", options, this.contract.propose, [options.numOfChoices,
                options.proposalParameters,
                options.proposerAddress]);
            return new iContractWrapperBase_1.ArcTransactionProposalResult(txResult.tx, this.contract, this);
        });
    }
    /**
     * Cancel the given proposal
     * @param options
     */
    cancelProposal(options) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.proposalId) {
                throw new Error(`proposalId is not defined`);
            }
            this.logContractFunctionCall("IntVoteInterface.cancelProposal", options);
            return this.wrapTransactionInvocation("IntVoteInterface.cancelProposal", options, this.contract.cancelProposal, [options.proposalId]);
        });
    }
    /**
     * Vote on behalf of the current account or the given voter.
     * @param options
     */
    vote(options) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.proposalId) {
                throw new Error(`proposalId is not defined`);
            }
            yield this._validateVote(options.vote, options.proposalId);
            this.logContractFunctionCall("IntVoteInterface.vote", options);
            return this.wrapTransactionInvocation("IntVoteInterface.vote", options, this.contract.vote, [options.proposalId, options.vote, options.voterAddress || 0]);
        });
    }
    /**
     * Vote specified reputation amount on behalf of the current account or the given voter.
     * @param options
     */
    voteWithSpecifiedAmounts(options) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.proposalId) {
                throw new Error(`proposalId is not defined`);
            }
            yield this._validateVote(options.vote, options.proposalId);
            this.logContractFunctionCall("IntVoteInterface.voteWithSpecifiedAmounts", options);
            return this.wrapTransactionInvocation("IntVoteInterface.voteWithSpecifiedAmounts", options, this.contract.voteWithSpecifiedAmounts, [options.proposalId,
                options.vote,
                options.reputation,
                new bignumber_js_1.BigNumber(0),
                options.voterAddress || 0]);
        });
    }
    /**
     * Cancel voting on the proposal.
     * @param options
     */
    cancelVote(options) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.proposalId) {
                throw new Error(`proposalId is not defined`);
            }
            this.logContractFunctionCall("IntVoteInterface.cancelVote", options);
            return this.wrapTransactionInvocation("IntVoteInterface.cancelVote", options, this.contract.cancelVote, [options.proposalId]);
        });
    }
    /**
     * Get the number of voting choices allowed by the proposal.
     * @param options
     */
    getNumberOfChoices(options) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.proposalId) {
                throw new Error(`proposalId is not defined`);
            }
            this.logContractFunctionCall("IntVoteInterface.getNumberOfChoices", options);
            return (yield this.contract.getNumberOfChoices(options.proposalId)).toNumber();
        });
    }
    /**
     * Get whether the proposal is in a state where it can be voted-upon.
     * @param proposalId
     */
    isVotable(options) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.proposalId) {
                throw new Error(`proposalId is not defined`);
            }
            this.logContractFunctionCall("IntVoteInterface.isVotable", options);
            return yield this.contract.isVotable(options.proposalId);
        });
    }
    /**
     * Get the number of votes currently cast on the given choice.
     * @param options
     */
    voteStatus(options) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.proposalId) {
                throw new Error(`proposalId is not defined`);
            }
            yield this._validateVote(options.vote, options.proposalId);
            this.logContractFunctionCall("IntVoteInterface.voteStatus", options);
            return yield this.contract.voteStatus(options.proposalId, options.vote);
        });
    }
    /**
     * get whether voters are allowed to cast an abstaining vote on these proposals.
     */
    isAbstainAllow() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            this.logContractFunctionCall("IntVoteInterface.isAbstainAllow");
            return yield this.contract.isAbstainAllow();
        });
    }
    /**
     * Attempt to execute the given proposal vote.
     * @param proposalId
     */
    execute(options) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!options.proposalId) {
                throw new Error(`proposalId is not defined`);
            }
            this.logContractFunctionCall("IntVoteInterface.execute", options);
            return this.wrapTransactionInvocation("IntVoteInterface.execute", options, this.contract.execute, [options.proposalId]);
        });
    }
    /**
     * Return an array of the current counts of each vote choice on the proposal.
     * For straight Abstain, Yes and No votes you can use the values of the
     * `BinaryVoteResult` enum to dereference the array.  The Abstain vote
     * (in the zeroeth position) is always given even if the voting machine
     * does not allow abstentions.
     *
     * @param proposalId
     */
    getCurrentVoteStatus(proposalId) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            let numChoices = yield this.getNumberOfChoices({ proposalId });
            const abstainAllowed = yield this.isAbstainAllow();
            // when abstaining is not allowed, numChoices doesn't include it, but we always return it here, even if always zero
            if (!abstainAllowed) {
                ++numChoices;
            }
            const voteTotals = new Array(numChoices);
            for (let choice = 0; choice < numChoices; ++choice) {
                const voteTotal = yield this.voteStatus({ vote: choice, proposalId });
                voteTotals[choice] = voteTotal;
            }
            return voteTotals;
        });
    }
    /**
     * Returns promise of the allowed range of choices for a voting machine.
     */
    getAllowedRangeOfChoices() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const result = yield this.contract.getAllowedRangeOfChoices();
            return {
                maxVote: result[1].toNumber(),
                minVote: result[0].toNumber(),
            };
        });
    }
    hydrated() {
        /* tslint:disable:max-line-length */
        this.NewProposal = this.web3EventService.createEventFetcherFactory(this.contract.NewProposal);
        this.CancelProposal = this.web3EventService.createEventFetcherFactory(this.contract.CancelProposal);
        this.ExecuteProposal = this.web3EventService.createEventFetcherFactory(this.contract.ExecuteProposal);
        this.VoteProposal = this.web3EventService.createEventFetcherFactory(this.contract.VoteProposal);
        this.CancelVoting = this.web3EventService.createEventFetcherFactory(this.contract.CancelVoting);
        /* tslint:enable:max-line-length */
    }
    _validateVote(vote, proposalId) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const numChoices = yield this.getNumberOfChoices({ proposalId });
            if (!Number.isInteger(vote) || (vote < 0) || (vote > numChoices)) {
                throw new Error("vote choice is not valid");
            }
            if ((typeof vote !== "number") || (vote < 0)) {
                throw new Error(`vote must be a number greater than or equal to zero and less than or equal to ${numChoices}`);
            }
        });
    }
    organizationIdFromProposalCreator(schemeAddress, creator) {
        return utils_1.Utils.keccak256(["address", "address"], [schemeAddress, creator]);
    }
}
exports.IntVoteInterfaceWrapper = IntVoteInterfaceWrapper;
class IntVoteInterfaceFactoryType extends contractWrapperFactory_1.ContractWrapperFactory {
    new() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            throw new Error("`new` is not supported on IntVoteInterface. Only `at` is supported.");
        });
    }
    deployed() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            throw new Error("`deployed` is not supported on IntVoteInterface. Only `at` is supported.");
        });
    }
}
exports.IntVoteInterfaceFactoryType = IntVoteInterfaceFactoryType;
exports.IntVoteInterfaceFactory = new IntVoteInterfaceFactoryType("IntVoteInterface", IntVoteInterfaceWrapper, new web3EventService_1.Web3EventService());
//# sourceMappingURL=intVoteInterface.js.map