"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const gasLimits_js_1 = require("../../gasLimits.js");
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 iContractWrapperBase_1 = require("../iContractWrapperBase");
const transactionService_1 = require("../transactionService");
const utils_1 = require("../utils");
const web3EventService_1 = require("../web3EventService");
const wrapperService_1 = require("../wrapperService");
class DaoCreatorWrapper extends contractWrapperBase_1.ContractWrapperBase {
    constructor() {
        super(...arguments);
        this.name = "DaoCreator";
        this.friendlyName = "Dao Creator";
        this.factory = exports.DaoCreatorFactory;
    }
    /**
     * Create a new DAO
     * @param {ForgeOrgConfig} options
     */
    forgeOrg(options = {}) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const web3 = yield utils_1.Utils.getWeb3();
            /**
             * See these properties in ForgeOrgConfig
             */
            const defaults = {
                tokenCap: web3.toBigNumber(0),
                universalController: true,
            };
            options = Object.assign({}, defaults, options);
            if (!options.name) {
                throw new Error("DAO name is not defined");
            }
            if (!options.tokenName) {
                throw new Error("DAO token name is not defined");
            }
            if (!options.tokenSymbol) {
                throw new Error("DAO token symbol is not defined");
            }
            if (!options.founders) {
                throw new Error("DAO must have at least one founder");
            }
            let controllerAddress;
            if (options.universalController) {
                const contract = yield utils_1.Utils.requireContract("UController");
                controllerAddress = (yield contract.deployed()).address;
            }
            else {
                controllerAddress = 0;
            }
            const totalGas = gasLimits_js_1.computeForgeOrgGasLimit(options.founders.length);
            this.logContractFunctionCall("DaoCreator.forgeOrg (options)", options);
            this.logContractFunctionCall("DaoCreator.forgeOrg", {
                controllerAddress,
                founderAddresses: options.founders.map((founder) => web3.toBigNumber(founder.address)),
                founderReputation: options.founders.map((founder) => web3.toBigNumber(founder.reputation)),
                founderTokens: options.founders.map((founder) => web3.toBigNumber(founder.tokens)),
                gas: { gas: totalGas },
                name: options.name,
                tokenCap: options.tokenCap,
                tokenName: options.tokenName,
                tokenSymbol: options.tokenSymbol,
            });
            return this.wrapTransactionInvocation("DaoCreator.forgeOrg", options, this.contract.forgeOrg, [options.name,
                options.tokenName,
                options.tokenSymbol,
                options.founders.map((founder) => web3.toBigNumber(founder.address)),
                options.founders.map((founder) => web3.toBigNumber(founder.tokens)),
                options.founders.map((founder) => web3.toBigNumber(founder.reputation)),
                controllerAddress,
                options.tokenCap], { gas: totalGas });
        });
    }
    /**
     * Register schemes with newly-created DAO.
     * Can only be invoked by the agent that created the DAO
     * via forgeOrg, and at that, can only be called one time.
     * @param {SetSchemesConfig} options
     */
    setSchemes(options = {}) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            /**
             * See SetSchemesConfig
             */
            const defaults = {
                /**
                 * avatar address
                 */
                schemes: [],
                votingMachineParams: {},
            };
            options = Object.assign({}, defaults, options);
            if (!options.avatar) {
                throw new Error("avatar address is not defined");
            }
            const functionName = "DaoCreator.setSchemes";
            const payload = transactionService_1.TransactionService.publishKickoffEvent(functionName, options, this.setSchemesTransactionsCount(options));
            /**
             * resend sub-events as DaoCreator.setSchemes
             */
            const eventContext = transactionService_1.TransactionService.newTxEventContext(functionName, payload, options);
            const votingMachineParamsHashes = new Map();
            const paramsHashCacheSet = (address, hash) => {
                let hashes = votingMachineParamsHashes.get(address);
                if (!hashes) {
                    hashes = new Set();
                }
                hashes.add(hash);
            };
            const paramsHashCacheHasHash = (address, hash) => {
                const hashes = votingMachineParamsHashes.get(address);
                return hashes && hashes.has(hash);
            };
            const avatarService = new avatarService_1.AvatarService(options.avatar);
            const reputationAddress = yield avatarService.getNativeReputationAddress();
            const configuredVotingMachineName = configService_1.ConfigService.get("defaultVotingMachine");
            const defaultVotingMachineParams = Object.assign({
                // voting machines can't supply reputation as a default -- they don't know what it is
                reputation: reputationAddress,
                votingMachineName: configuredVotingMachineName,
            }, options.votingMachineParams || {});
            const defaultVotingMachine = yield wrapperService_1.WrapperService.getContractWrapper(defaultVotingMachineParams.votingMachineName, defaultVotingMachineParams.votingMachineAddress);
            // in case it wasn't supplied in order to get the default
            defaultVotingMachineParams.votingMachineAddress = defaultVotingMachine.address;
            let tx;
            /**
             * each voting machine applies its own default values in setParameters
             */
            let txResult = yield defaultVotingMachine.setParameters(Object.assign(defaultVotingMachineParams, { txEventContext: eventContext }));
            const defaultVoteParametersHash = txResult.result;
            paramsHashCacheSet(defaultVotingMachine.address, defaultVoteParametersHash);
            // avoid nonce collisions
            yield txResult.watchForTxMined();
            const initialSchemesSchemes = [];
            const initialSchemesParams = [];
            const initialSchemesPermissions = [];
            for (const schemeOptions of options.schemes) {
                if (!schemeOptions.name) {
                    throw new Error("options.schemes[n].name is not defined");
                }
                const wrapperFactory = wrapperService_1.WrapperService.factories[schemeOptions.name];
                if (!wrapperFactory) {
                    /* tslint:disable-next-line:max-line-length */
                    throw new Error("Non-arc schemes are not currently supported here.  You can add them later in your workflow.");
                }
                /**
                 * scheme will be a contract wrapper
                 */
                const scheme = schemeOptions.address ?
                    yield wrapperFactory.at(schemeOptions.address) : wrapperService_1.WrapperService.wrappers[schemeOptions.name];
                let schemeVotingMachineParams = schemeOptions.votingMachineParams;
                let schemeVoteParametersHash;
                let schemeVotingMachine;
                if (schemeVotingMachineParams) {
                    Object.assign(schemeOptions.votingMachineParams, { txEventContext: eventContext });
                    const schemeVotingMachineName = schemeVotingMachineParams.votingMachineName;
                    const schemeVotingMachineAddress = schemeVotingMachineParams.votingMachineAddress;
                    /**
                     * get the voting machine contract
                     */
                    if (!schemeVotingMachineAddress &&
                        (!schemeVotingMachineName ||
                            (schemeVotingMachineName === defaultVotingMachineParams.votingMachineName))) {
                        /**
                         *  scheme is using the default voting machine
                         */
                        schemeVotingMachine = defaultVotingMachine;
                    }
                    else {
                        /**
                         * scheme has its own voting machine. Go get it.
                         */
                        if (!schemeVotingMachineName) {
                            schemeVotingMachineParams.votingMachineName = defaultVotingMachineParams.votingMachineName;
                        }
                        /**
                         * Note we are not supporting non-Arc voting machines here, and it must have a wrapper class.
                         */
                        schemeVotingMachine = yield wrapperService_1.WrapperService.getContractWrapper(schemeVotingMachineParams.votingMachineName, schemeVotingMachineParams.votingMachineAddress);
                        // in case it wasn't supplied in order to get the default
                        schemeVotingMachineParams.votingMachineAddress = schemeVotingMachine.address;
                    }
                    schemeVotingMachineParams = Object.assign({}, defaultVotingMachineParams, schemeVotingMachineParams);
                    /**
                     * get the voting machine parameters
                     */
                    schemeVoteParametersHash = yield schemeVotingMachine.getParametersHash(schemeVotingMachineParams);
                    // avoid nonce collisions and avoid unnecessary transactions
                    if (!paramsHashCacheHasHash(schemeVotingMachine.address, schemeVoteParametersHash)) {
                        txResult = yield schemeVotingMachine.setParameters(schemeVotingMachineParams);
                        // avoid nonce collisions
                        yield txResult.watchForTxMined();
                        paramsHashCacheSet(schemeVotingMachine.address, schemeVoteParametersHash);
                    }
                }
                else {
                    // using the defaults
                    schemeVotingMachineParams = Object.assign({}, defaultVotingMachineParams, { txEventContext: eventContext });
                    schemeVoteParametersHash = defaultVoteParametersHash;
                }
                const schemeParameters = Object.assign({
                    txEventContext: eventContext,
                    voteParametersHash: schemeVoteParametersHash,
                    votingMachineAddress: schemeVotingMachineParams.votingMachineAddress,
                }, schemeOptions);
                const schemeParamsHash = yield scheme.getParametersHash(schemeParameters);
                // avoid nonce collisions and avoid unnecessary transactions
                if (!paramsHashCacheHasHash(scheme.address, schemeParamsHash)) {
                    /**
                     * This is the set of all possible parameters from which the current scheme
                     * will choose just the ones it requires
                     */
                    txResult = yield scheme.setParameters(schemeParameters);
                    // avoid nonce collisions
                    yield txResult.watchForTxMined();
                    paramsHashCacheSet(scheme.address, schemeParamsHash);
                }
                initialSchemesSchemes.push(scheme.address);
                initialSchemesParams.push(schemeParamsHash);
                /**
                 * Make sure the scheme has at least its required permissions, regardless of what the caller
                 * passes in.
                 */
                const requiredPermissions = scheme.getDefaultPermissions();
                const additionalPermissions = schemeOptions.permissions;
                /* tslint:disable-next-line:no-bitwise */
                initialSchemesPermissions.push(commonTypes_1.SchemePermissions.toString(requiredPermissions | additionalPermissions));
            }
            this.logContractFunctionCall("DaoCreator.setSchemes (options)", options);
            this.logContractFunctionCall("DaoCreator.setSchemes", {
                avatar: options.avatar,
                initialSchemesParams,
                initialSchemesPermissions,
                initialSchemesSchemes,
            });
            // register the schemes with the dao
            tx = yield this.sendTransaction(eventContext, this.contract.setSchemes, [options.avatar,
                initialSchemesSchemes,
                initialSchemesParams,
                initialSchemesPermissions]);
            if (tx) {
                transactionService_1.TransactionService.publishTxLifecycleEvents(eventContext, tx, this.contract);
            }
            return new iContractWrapperBase_1.ArcTransactionResult(tx, this.contract);
        });
    }
    forgeOrgTransactionsCount(options) {
        return 1;
    }
    setSchemesTransactionsCount(options) {
        /**
         * one for setSchemes, one for the default votingMachine params,
         * one for each scheme's params, and one for each scheme that is not using the default votingMachine
         */
        const schemes = options.schemes || [];
        const numSchemes = schemes.length;
        const numSchemesWithDefaultParams = schemes.filter((s) => !!s.votingMachineParams).length;
        return 2 + numSchemes + numSchemesWithDefaultParams;
    }
    hydrated() {
        /* tslint:disable:max-line-length */
        this.NewOrg = this.createEventFetcherFactory(this.contract.NewOrg);
        this.InitialSchemesSet = this.createEventFetcherFactory(this.contract.InitialSchemesSet);
        /* tslint:enable:max-line-length */
    }
}
exports.DaoCreatorWrapper = DaoCreatorWrapper;
/**
 * defined just to add good type checking
 */
class DaoCreatorFactoryType extends contractWrapperFactory_1.ContractWrapperFactory {
    /**
     *
     * @param controllerCreatorAddress The ControllerCreator that Arc will use when migrating
     * a new non-universal controller in `forgeOrg`.
     * Typically is `ControllerCreator` from Arc.
     */
    new(controllerCreatorAddress) {
        const _super = name => super[name];
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!controllerCreatorAddress) {
                controllerCreatorAddress = (yield (yield utils_1.Utils.requireContract("ControllerCreator")).deployed()).address;
            }
            return _super("new").call(this, controllerCreatorAddress);
        });
    }
}
exports.DaoCreatorFactoryType = DaoCreatorFactoryType;
exports.DaoCreatorFactory = new DaoCreatorFactoryType("DaoCreator", DaoCreatorWrapper, new web3EventService_1.Web3EventService());
//# sourceMappingURL=daoCreator.js.map