"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const avatarService_1 = require("./avatarService");
const commonTypes_1 = require("./commonTypes");
const loggingService_1 = require("./loggingService");
const transactionService_1 = require("./transactionService");
const utils_1 = require("./utils");
/**
 * Abstract base class for all Arc contract wrapper classes
 *
 * Example of how to define a wrapper:
 *
 * ```
 * import { ContractWrapperBase } from "../contractWrapperBase";
 * import { ContractWrapperFactory } from "../contractWrapperFactory";
 *
 * export class AbsoluteVoteWrapper extends ContractWrapperBase {
 *   [ wrapper properties and methods ]
 * }
 *
 * export const AbsoluteVote = new ContractWrapperFactory("AbsoluteVote", AbsoluteVoteWrapper);
 * ```
 */
class ContractWrapperBase {
    /**
     * ContractWrapperFactory constructs this
     * @param solidityContract The json contract truffle artifact
     */
    constructor(solidityContract) {
        this.solidityContract = solidityContract;
    }
    /**
     * The address of the contract
     */
    get address() { return this.contract.address; }
    /**
     * Initialize from a newly-migrated instance.
     * This will migrate a new instance of the contract to the net.
     * @returns this
     */
    hydrateFromNew(...rest) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            try {
                this.contract = yield this.solidityContract.new(...rest)
                    .then((contract) => contract, (error) => { throw error; });
            }
            catch (ex) {
                loggingService_1.LoggingService.error(`hydrateFromNew failing: ${ex}`);
                return undefined;
            }
            return this;
        });
    }
    /**
     * Initialize from a given address on the current network.
     * @param address of the deployed contract
     * @returns this
     */
    hydrateFromAt(address) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            try {
                this.contract = yield this.solidityContract.at(address)
                    .then((contract) => contract, (error) => { throw error; });
            }
            catch (ex) {
                loggingService_1.LoggingService.error(`hydrateFromAt failing: ${ex}`);
                return undefined;
            }
            return this;
        });
    }
    /**
     * Initialize as it was migrated by Arc.js on the current network.
     * @returns this
     */
    hydrateFromDeployed() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            try {
                this.contract = yield this.solidityContract.deployed()
                    .then((contract) => contract, (error) => { throw error; });
            }
            catch (ex) {
                loggingService_1.LoggingService.error(`hydrateFromDeployed failing: ${ex}`);
                return undefined;
            }
            return this;
        });
    }
    /**
     * Call setParameters on this.contract.
     * Returns promise of ArcTransactionDataResult<Hash> where Result is the parameters hash.
     *
     * @param {any} params -- parameters as the contract.setParameters function expects them.
     */
    setParameters(...params) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            throw new Error("setParameters has not been not implemented by the contract wrapper");
        });
    }
    /**
     * Given a hash, returns the associated parameters as an object.
     * @param paramsHash
     */
    getParameters(paramsHash) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            throw new Error("getParameters has not been not implemented by the contract wrapper");
        });
    }
    /**
     * Given an avatar address, returns the schemes parameters hash
     * @param avatarAddress
     */
    getSchemeParametersHash(avatarAddress) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const controller = yield this.getController(avatarAddress);
            return controller.getSchemeParameters(this.address, avatarAddress);
        });
    }
    /**
     * Given a hash, returns the associated parameters as an array, ordered by the order
     * in which the parameters appear in the contract's Parameters struct.
     * @param paramsHash
     */
    getParametersArray(paramsHash) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return this.contract.parameters(paramsHash);
        });
    }
    /**
     * Returns the controller associated with the given avatar
     * @param avatarAddress
     */
    getController(avatarAddress) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const avatarService = new avatarService_1.AvatarService(avatarAddress);
            return avatarService.getController();
        });
    }
    _setParameters(functionName, ...params) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const parametersHash = yield this.contract.getParametersHash(...params);
            const eventTopic = "txReceipts.ContractWrapperBase.setParameters";
            const txReceiptEventPayload = transactionService_1.TransactionService.publishKickoffEvent(eventTopic, params, 1);
            const txResult = yield this.wrapTransactionInvocation(functionName, 
            // typically this is supposed to be an object, but here it is an array
            params, () => {
                return this.contract.setParameters(...params);
            });
            transactionService_1.TransactionService.publishTxEvent(eventTopic, txReceiptEventPayload, txResult.tx);
            return new ArcTransactionDataResult(txResult.tx, parametersHash);
        });
    }
    /**
     * Returns this scheme's permissions.
     * @param avatarAddress
     */
    _getSchemePermissions(avatarAddress) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const permissions = yield (yield this.getController(avatarAddress))
                .getSchemePermissions(this.address, avatarAddress);
            return commonTypes_1.SchemePermissions.fromString(permissions);
        });
    }
    _getSchemeParameters(avatarAddress) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const paramsHash = yield this.getSchemeParametersHash(avatarAddress);
            return this.getParameters(paramsHash);
        });
    }
    /**
     * Returns a function that creates an EventFetcher<TArgs>.
     * For subclasses to use to create their event handlers.
     * This is identical to what you get with Truffle, except that
     * the result param of the callback is always guaranteed to be an array.
     *
     * Example:
     *
     *    public NewProposal = this.eventWrapperFactory<NewProposalEventResult>("NewProposal");
     *    const event = NewProposal(...);
     *    event.get(...).
     *
     * @type TArgs - name of the event args (EventResult) interface, like NewProposalEventResult
     * @param eventName - Name of the event like "NewProposal"
     */
    createEventFetcherFactory(eventName) {
        const that = this;
        /**
         * This is the function that returns the EventFetcher<TArgs>
         * @param argFilter
         * @param filterObject
         * @param callback
         */
        const eventFetcherFactory = (argFilter, filterObject, rootCallback) => {
            let baseEvent;
            let receivedEvents;
            if (!!filterObject.suppressDups) {
                receivedEvents = new Set();
            }
            const handleEvent = (error, log, callback) => {
                /**
                 * always provide an array
                 */
                if (!!error) {
                    log = [];
                }
                else if (!Array.isArray(log)) {
                    log = [log];
                }
                /**
                 * optionally prune duplicate events (see https://github.com/ethereum/web3.js/issues/398)
                 */
                if (receivedEvents && log.length) {
                    log = log.filter((evt) => {
                        if (!receivedEvents.has(evt.transactionHash)) {
                            receivedEvents.add(evt.transactionHash);
                            return true;
                        }
                        else {
                            return false;
                        }
                    });
                }
                callback(error, log);
            };
            const eventFetcher = {
                get(callback) {
                    baseEvent.get((error, log) => {
                        handleEvent(error, log, callback);
                    });
                },
                watch(callback) {
                    baseEvent.watch((error, log) => {
                        handleEvent(error, log, callback);
                    });
                },
                stopWatching() {
                    baseEvent.stopWatching();
                },
            };
            /**
             * if callback is set then this will start watching immediately,
             * otherwise caller must use `get` and `watch`
             */
            const wrapperRootCallback = rootCallback ?
                (error, log) => {
                    if (!!error) {
                        log = [];
                    }
                    else if (!Array.isArray(log)) {
                        log = [log];
                    }
                    rootCallback(error, log);
                } : undefined;
            baseEvent = that.contract[eventName](argFilter, filterObject, wrapperRootCallback);
            return eventFetcher;
        };
        return eventFetcherFactory;
    }
    validateStandardSchemeParams(params) {
        if (!params.voteParametersHash) {
            throw new Error(`voteParametersHash is not defined`);
        }
        if (!params.votingMachineAddress) {
            throw new Error(`votingMachineAddress is not defined`);
        }
    }
    /**
     * Wrap code that creates a transaction in the given transaction event. This is a helper
     * just for the common case of generating a single transaction.
     * @param functionName Should look like [contractName].[functionName]
     * @param options Options that will be passed to the contract function being invoked
     * @param generateTx Callback that will invoke the contract function
     */
    wrapTransactionInvocation(functionName, options, generateTx) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const topic = `txReceipts.${functionName}`;
            const txReceiptEventPayload = transactionService_1.TransactionService.publishKickoffEvent(topic, options, 1);
            const tx = yield generateTx();
            transactionService_1.TransactionService.publishTxEvent(topic, txReceiptEventPayload, tx);
            return new ArcTransactionResult(tx);
        });
    }
    logContractFunctionCall(functionName, params) {
        loggingService_1.LoggingService.debug(`calling ${functionName}${params ? ` with: ${loggingService_1.LoggingService.stringifyObject(params)}` : ""}`);
    }
}
exports.ContractWrapperBase = ContractWrapperBase;
class ArcTransactionResult {
    constructor(tx) {
        this.tx = tx;
    }
    /**
     * Returns a value from the transaction logs.
     * @param valueName - The name of the property whose value we wish to return
     * @param eventName - Name of the event in whose log we are to look for the value
     * @param index - Index of the log in which to look for the value, when eventName is not given.
     * Default is the index of the last log in the transaction.
     */
    getValueFromTx(valueName, eventName = null, index = 0) {
        return utils_1.Utils.getValueFromLogs(this.tx, valueName, eventName, index);
    }
}
exports.ArcTransactionResult = ArcTransactionResult;
/**
 * Base or actual type returned by all contract wrapper methods that generate a transaction and initiate a proposal.
 */
class ArcTransactionProposalResult extends ArcTransactionResult {
    constructor(tx) {
        super(tx);
        this.proposalId = utils_1.Utils.getValueFromLogs(tx, "_proposalId");
    }
}
exports.ArcTransactionProposalResult = ArcTransactionProposalResult;
/**
 * Base or actual type returned by all contract wrapper methods that generate a transaction and any other result.
 */
class ArcTransactionDataResult extends ArcTransactionResult {
    constructor(tx, result) {
        super(tx);
        this.result = result;
    }
}
exports.ArcTransactionDataResult = ArcTransactionDataResult;
//# sourceMappingURL=contractWrapperBase.js.map