"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
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 paramsArray = [
                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
            ];
            /**
             * The default gas limit will be insufficient when using non-universal controller,
             * so we do a proper estimate here.  We're estimating even when universal since
             * the value will be optimial for the user in any case.
             */
            const totalGas = yield this.estimateGas(this.contract.forgeOrg, paramsArray);
            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, paramsArray, { 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 configuredVotingMachineName = configService_1.ConfigService.get("defaultVotingMachine");
            const defaultVotingMachineParams = Object.assign({
                votingMachineName: configuredVotingMachineName,
            }, options.votingMachineParams || {});
            let tx;
            let defaultVotingMachine;
            let defaultVoteParametersHash;
            /**
             * The default voting machine name can only be missing if the DAO is going to have
             * no universal schemes or each universal scheme has specified its voting machine.
             */
            const hasDefaultVotingMachine = !!defaultVotingMachineParams.votingMachineName;
            if (hasDefaultVotingMachine) {
                /**
                 * At this time, if you're going to supply a default voting machine, it has to be an Arc contract,
                 * and it must be wrapped.
                 */
                defaultVotingMachine = (yield wrapperService_1.WrapperService.getContractWrapper(defaultVotingMachineParams.votingMachineName, defaultVotingMachineParams.votingMachineAddress));
                if (!defaultVotingMachine) {
                    throw new Error(`voting machine ${defaultVotingMachineParams.votingMachineName} was not found`);
                }
                // in case the address wasn't supplied, in order to get the default
                defaultVotingMachineParams.votingMachineAddress = defaultVotingMachine.address;
                /**
                 * each wrapped voting machine applies its own default values in `setParameters`
                 */
                paramsHashCacheSet(defaultVotingMachine.address, defaultVoteParametersHash);
                const txResult = yield defaultVotingMachine.setParameters(Object.assign(defaultVotingMachineParams, { txEventContext: eventContext }));
                defaultVoteParametersHash = txResult.result;
                // avoid nonce collisions
                yield txResult.watchForTxMined();
            }
            const initialSchemesSchemes = [];
            const initialSchemesParams = [];
            const initialSchemesPermissions = [];
            /**
             * enumerate all the schemes
             */
            for (const schemeOptions of options.schemes) {
                let wrapperFactory;
                let schemeWrapper;
                let truffleContract;
                let contractAddress;
                if (schemeOptions.name) {
                    wrapperFactory = wrapperService_1.WrapperService.factories[schemeOptions.name];
                }
                if (wrapperFactory) {
                    schemeWrapper = schemeOptions.address ?
                        yield wrapperFactory.at(schemeOptions.address) :
                        wrapperService_1.WrapperService.wrappers[schemeOptions.name];
                    if (!schemeWrapper && schemeOptions.address) {
                        throw new Error(`An instance of '${schemeOptions.name}' could not be found at ${schemeOptions.address}`);
                    }
                    /**
                     * Else there is a factory but no address was given and no deployed scheme was found,
                     * so the scheme is wrapped but hasn't been deployed by Arc.js.
                     * So it must be a wrapped non-universal scheme -- these aren't deployed.
                     */
                    if (schemeWrapper) {
                        truffleContract = schemeWrapper.contract;
                        contractAddress = schemeWrapper.address;
                    } // else is a wrapped non-universal scheme -- these aren't deployed
                }
                /**
                 * If no factory (not wrapped) or couldn't get it from the factory (not deployed),
                 * then get the scheme from Truffle.
                 */
                if (!schemeWrapper) {
                    if (!schemeOptions.address) {
                        throw new Error(`A scheme that has no contract wrapper or has not been deployed by Arc.js must supply an address`);
                    }
                    if (schemeOptions.name) {
                        const artifactContract = yield utils_1.Utils.requireContract(schemeOptions.name);
                        truffleContract = (yield artifactContract.at(schemeOptions.address));
                        if (!truffleContract) {
                            throw new Error(`An instance of '${schemeOptions.name}' could not be found at ${schemeOptions.address}`);
                        }
                        contractAddress = truffleContract.address;
                    }
                    else {
                        /**
                         * No name is given, so it is assumed to be a non-Arc scheme;
                         * we have not even a truffleContract to work with.
                         */
                        contractAddress = schemeOptions.address;
                    }
                }
                if (!contractAddress) {
                    throw new Error("internal error: no contract address");
                }
                const isUniversal = schemeWrapper &&
                    !!schemeWrapper.contract.getParametersHash &&
                    !!schemeWrapper.contract.setParameters;
                let schemeVotingMachineParams = schemeOptions.votingMachineParams;
                let schemeVoteParametersHash;
                let schemeVotingMachine;
                let schemeParamsHash = schemeOptions.parametersHash;
                if (isUniversal && !schemeParamsHash && schemeWrapper) {
                    /**
                     * proceed to set the scheme's parameters, starting with optional voting machine params.
                     * Note this requires a wrapper on a universal contract, and that a params hash has not been given.
                     */
                    if (schemeVotingMachineParams) {
                        Object.assign(schemeOptions.votingMachineParams, { txEventContext: eventContext });
                        const schemeVotingMachineName = schemeVotingMachineParams.votingMachineName;
                        const schemeVotingMachineAddress = schemeVotingMachineParams.votingMachineAddress;
                        if (!schemeVotingMachineAddress && !schemeVotingMachineName && !hasDefaultVotingMachine) {
                            throw new Error("supplied voting machine parameters are not sufficient");
                        }
                        /**
                         * 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));
                            if (!schemeVotingMachine) {
                                throw new Error(`wrapped voting machine '${schemeVotingMachineParams.votingMachineName}' was not found`);
                            }
                            // 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)) {
                            paramsHashCacheSet(schemeVotingMachine.address, schemeVoteParametersHash);
                            const txResult = yield schemeVotingMachine.setParameters(schemeVotingMachineParams);
                            // avoid nonce collisions
                            yield txResult.watchForTxMined();
                        }
                    }
                    else {
                        // use the default voting machine params
                        schemeVotingMachineParams = Object.assign({}, defaultVotingMachineParams, { txEventContext: eventContext });
                        schemeVoteParametersHash = defaultVoteParametersHash;
                    }
                    /**
                     * Set the schemes parameters, merging in any voting machine params we obtained above.
                     * Note the scheme may ignore all the voting machine parameters if it doesn't use a voting machine
                     */
                    const schemeParameters = Object.assign({
                        txEventContext: eventContext,
                        voteParametersHash: schemeVoteParametersHash,
                        votingMachineAddress: schemeVotingMachineParams.votingMachineAddress,
                    }, schemeOptions);
                    schemeParamsHash = yield schemeWrapper.getParametersHash(schemeParameters);
                    // avoid nonce collisions and avoid unnecessary transactions
                    if (!paramsHashCacheHasHash(schemeWrapper.address, schemeParamsHash)) {
                        paramsHashCacheSet(schemeWrapper.address, schemeParamsHash);
                        /**
                         * This is the set of all possible parameters from which the current scheme
                         * will choose just the ones it requires
                         */
                        const txResult = yield schemeWrapper.setParameters(schemeParameters);
                        // avoid nonce collisions
                        yield txResult.watchForTxMined();
                    }
                }
                else {
                    /**
                     * scheme is not universal || schemeParamsHash is given || there is no wrapper
                     *
                     * schemeParamsHash can be supplied for non-wrapped schemes, particularly non-Arc schemes.
                     * Otherwise NULL_HASH is used, appropriate for non-universal schemes that don't have
                     * parameters to be registered against a controller.
                     */
                    schemeParamsHash = schemeParamsHash || utils_1.Utils.NULL_HASH;
                }
                initialSchemesSchemes.push(contractAddress);
                initialSchemesParams.push(schemeParamsHash);
                const defaultPermissions = schemeWrapper ? schemeWrapper.getDefaultPermissions() : commonTypes_1.SchemePermissions.None;
                /* tslint:disable-next-line:no-bitwise */
                initialSchemesPermissions.push(commonTypes_1.SchemePermissions.toString(defaultPermissions | schemeOptions.permissions));
            }
            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