"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const es6_promisify_1 = require("es6-promisify");
const configService_1 = require("./configService");
const loggingService_1 = require("./loggingService");
const utils_1 = require("./utils");
const utilsInternal_1 = require("./utilsInternal");
/**
 * Generic class factory for all of the contract wrapper classes.
 */
class ContractWrapperFactory {
    /**
     * Connstructor to create a contract wrapper factory for the given
     * Arc contract name and wrapper class.
     * @param solidityContract Name of the contract
     * @param wrapper - Class of the contract
     */
    constructor(solidityContractName, wrapper, web3EventService) {
        this.solidityContractName = solidityContractName;
        this.wrapper = wrapper;
        this.web3EventService = web3EventService;
    }
    static setConfigService(configService) {
        this.configService = configService;
    }
    static clearContractCache() {
        this.contractCache.clear();
    }
    /**
     * Deploy a new instance of the contract and return a wrapper around it.
     * @param rest Optional arguments to the Arc contracts constructor.
     */
    new(...rest) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            yield this.ensureSolidityContract();
            let gas;
            if (configService_1.ConfigService.get("estimateGas") && (!rest || !rest.length || (!rest[rest.length - 1].gas))) {
                gas = yield this.estimateConstructorGas(...rest);
                loggingService_1.LoggingService.debug(`Instantiating ${this.solidityContractName} with gas: ${gas}`);
            }
            if (gas) {
                rest = [...rest, { gas }];
            }
            const hydratedWrapper = yield new this.wrapper(this.solidityContract, this.web3EventService).hydrateFromNew(...rest);
            if (hydratedWrapper && ContractWrapperFactory.configService.get("cacheContractWrappers")) {
                this.setCachedContract(this.solidityContractName, hydratedWrapper);
            }
            return hydratedWrapper;
        });
    }
    /**
     * Return a wrapper around the contract, hydrated from the given address.
     * Returns undefined if not found.
     * @param address
     */
    at(address) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            yield this.ensureSolidityContract();
            const getWrapper = () => {
                return new this.wrapper(this.solidityContract, this.web3EventService).hydrateFromAt(address);
            };
            return this.getHydratedWrapper(getWrapper, address);
        });
    }
    /**
     * Return a wrapper around the contract as deployed by the current version of Arc.js.
     * Note this is usually not needed as the WrapperService provides these
     * wrappers already hydrated.
     * Returns undefined if not found.
     */
    deployed() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            /**
             * use deployed address if supplied for this contract
             */
            const externallyDeployedAddress = utils_1.Utils.getDeployedAddress(this.solidityContractName);
            if (!externallyDeployedAddress) {
                throw new Error("ContractWrapperFactory: No deployed contract address has been supplied.");
            }
            return this.at(externallyDeployedAddress);
        });
    }
    ensureSolidityContract() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            /**
             * requireContract caches and uncaches the contract appropriately
             */
            return utils_1.Utils.requireContract(this.solidityContractName)
                .then((contract) => this.solidityContract = contract);
        });
    }
    estimateConstructorGas(...params) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const web3 = yield utils_1.Utils.getWeb3();
            yield this.ensureSolidityContract();
            const callData = web3.eth.contract(this.solidityContract.abi).new.getData(...params, {
                data: this.solidityContract.bytecode,
            });
            const currentNetwork = yield utils_1.Utils.getNetworkName();
            const maxGasLimit = yield utilsInternal_1.UtilsInternal.computeMaxGasLimit();
            // note that Ganache is identified specifically as the one instantiated by arc.js (by the networkId)
            if (currentNetwork === "Ganache") {
                return maxGasLimit; // because who cares with ganache and we can't get good estimates from it
            }
            const gas = yield es6_promisify_1.promisify((callback) => web3.eth.estimateGas({ data: callData }, callback))();
            return Math.max(Math.min(gas, maxGasLimit), 21000);
        });
    }
    getHydratedWrapper(getWrapper, address) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            let hydratedWrapper;
            if (ContractWrapperFactory.configService.get("cacheContractWrappers")) {
                if (!address) {
                    try {
                        address = this.solidityContract.address;
                    }
                    catch (_a) {
                        // the contract has not been deployed, so can't get it's address
                    }
                }
                if (address) {
                    hydratedWrapper = this.getCachedContract(this.solidityContractName, address);
                    if (hydratedWrapper) {
                        loggingService_1.LoggingService.debug(`ContractWrapperFactory: obtained wrapper from cache: ${hydratedWrapper.address}`);
                    }
                }
                if (!hydratedWrapper) {
                    hydratedWrapper = yield getWrapper();
                    if (hydratedWrapper) {
                        this.setCachedContract(this.solidityContractName, hydratedWrapper);
                    }
                }
            }
            else {
                hydratedWrapper = yield getWrapper();
            }
            return hydratedWrapper;
        });
    }
    getCachedContract(name, at) {
        if (!at) {
            return undefined;
        }
        const addressMap = ContractWrapperFactory.contractCache.get(name);
        if (!addressMap) {
            return undefined;
        }
        return addressMap.get(at);
    }
    setCachedContract(name, wrapper) {
        if (wrapper) {
            let addressMap = ContractWrapperFactory.contractCache.get(name);
            if (!addressMap) {
                addressMap = new Map();
                ContractWrapperFactory.contractCache.set(name, addressMap);
            }
            addressMap.set(wrapper.address, wrapper);
        }
    }
}
/**
 * this is a Map keyed by contract name of a Map keyed by address to an `IContractWrapper`
 */
ContractWrapperFactory.contractCache = new Map();
exports.ContractWrapperFactory = ContractWrapperFactory;
//# sourceMappingURL=contractWrapperFactory.js.map