"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const pubSubEventService_1 = require("./pubSubEventService");
const transactionService_1 = require("./transactionService");
const utilsInternal_1 = require("./utilsInternal");
/**
 * Support for working with events that originate from Arc contracts
 * and are served up by Web3.
 *
 * See [Arc Web3 Events in Arc.js](/Events#web3events).
 */
class Web3EventService {
    /**
     * Returns a function that creates an EventFetcher<TEventArgs>.
     * For subclasses to use to create their event handlers.
     * This is identical to what you get with Truffle, except with enhancements.
     *
     * Note that the callback parameter of `EventFetcher.get` is optional; you
     * may alternatively obtain the promise of a `Array<TEventArgs>` from the return value
     * of `get`.
     *
     * See [Arc Web3 Events in Arc.js](/Events#web3events).
     *
     * @param baseEvent - the event from the Truffle contract.
     * @param preProcessEvent - optionally supply this to modify the err and log arguments before they are
     * passed to the `get`/`watch` callback.
     * @param baseArgFilter arg filter to always merge into any supplied argFilter.
     * @type TEventArgs - name of the event args (EventResult) interface, like NewProposalEventResult
     */
    createEventFetcherFactory(baseEvent, preProcessEvent, baseArgFilter = {}) {
        if (!baseEvent) {
            throw new Error("baseEvent was not supplied");
        }
        /**
         * This is the function that returns the EventFetcher<TEventArgs>
         * argFilter - Optional event argument filter, like `{ _proposalId: [someHash] }`.
         * filterObject - Optional event filter.  Default is `{ fromBlock: "latest" }`
         * callback.
         * immediateWatchCallback - when supplied, starts watch immediately
         * immediateRequiredDepth - only used when immediateWatchCallback is supplied. If set
         * then will not invoke the callback until the transaction has been mined to the requiredDepth.
         */
        return (argFilter = {}, filterObject = {}, immediateWatchCallback, immediateRequiredDepth = 0) => {
            const handleEvent = this.createBaseWeb3EventHandler(filterObject.suppressDups, preProcessEvent);
            const baseFetcher = baseEvent(Object.assign(argFilter, baseArgFilter), filterObject);
            /**
             * If `immediateWatchCallback` is defined then we should start watching immediately.
             */
            if (immediateWatchCallback) {
                baseFetcher.watch((error, log) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                    yield handleEvent(error, log, true, immediateWatchCallback, immediateRequiredDepth);
                }));
            }
            /**
             * return the fetcher
             */
            return {
                get(callback, requiredDepth = 0) {
                    return new Promise((resolve, reject) => {
                        baseFetcher.get((error, log) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                            if (error) {
                                return reject(error);
                            }
                            resolve(yield handleEvent(error, log, false, callback, requiredDepth));
                        }));
                    });
                },
                watch(callback, requiredDepth = 0) {
                    baseFetcher.watch((error, log) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                        yield handleEvent(error, log, true, callback, requiredDepth);
                    }));
                },
                subscribe(eventName, callback, requiredDepth = 0) {
                    if (!callback) {
                        /* tslint:disable-next-line:no-empty */
                        callback = () => { };
                    }
                    const subscription = pubSubEventService_1.PubSubEventService.subscribe(eventName, callback);
                    this.watch((error, args) => {
                        pubSubEventService_1.PubSubEventService.publish(eventName, args);
                    }, requiredDepth);
                    return new Web3EventSubscription(subscription, baseFetcher);
                },
                stopWatching(callback) {
                    baseFetcher.stopWatching(callback);
                },
                stopWatchingAsync() {
                    return utilsInternal_1.UtilsInternal.stopWatchingAsync(baseFetcher);
                },
            };
        };
    }
    /**
     * Converts a `EventFetcherFactory<TEventArgs>` into a
     * `EntityFetcherFactory<TEntity, TEventArgs>`.  So whenever a web3 event
     * is received by the given `EventFetcherFactory`, we transform the `TEventArgs`
     * into `TEntities`.
     *
     * Note that the callback parameter of `EntityFetcher.get` is optional; you
     * may alternatively obtain the promise of a `Array<TEntity>` from the return value
     * of `get`.
     *
     * See [Arc Web3 Events in Arc.js](/Events#web3events).
     *
     * @param eventFetcherFactory
     * @param transformEventCallback Function to convert an instance of TEventArgs into
     * the promise of an instance of TEntity.  If it returns `undefined` then no entity
     * is returned for that event, so this is a programatic way in which events
     * can be filtered.
     * @param givenCallback Function that will be invoked upon the receipt of each event,
     * @param baseArgFilter arg filter to always merge into any supplied argFilter.
     */
    createEntityFetcherFactory(eventFetcherFactory, transformEventCallback, baseArgFilter = {}) {
        if (!eventFetcherFactory) {
            throw new Error("eventFetcherFactory was not supplied");
        }
        if (!transformEventCallback) {
            throw new Error("transformEventCallback was not supplied");
        }
        /**
         * This is the function that returns the EntityFetcher<TEventArgs>
         * argFilter - Optional event argument filter, like `{ _proposalId: [someHash] }`.
         * filterObject - Optional event filter.  Default is `{ fromBlock: "latest" }`
         * callback.
         * immediateWatchCallback - when supplied, starts watch immediately
         * immediateRequiredDepth - only used when immediateWatchCallback is supplied. If set
         * then will not invoke the callback until the transaction has been mined to the requiredDepth.
         */
        return (argFilter = {}, filterObject = {}, immediateWatchCallback, immediateRequiredDepth = 0) => {
            // handler that takes the events and issues givenCallback appropriately
            const handleEvent = (error, log, 
            // singly true to issue callback on every arg rather than on the array
            singly, 
            /*
             * invoke this callback on every event (watch)
             * or on the array of events (get), depending on the value of singly.
             * when singly, callback gets the entity.
             * watch is always singly.
             * when not singly, callback gets a promise of the array of entities.
             * get is not singly.  so get gets a promise of an array.
             */
            callback) => {
                const promiseOfEntities = new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                    if (error) {
                        return reject(error);
                    }
                    // it's not an array when singly
                    if (!Array.isArray(log)) {
                        log = [log];
                    }
                    const entities = new Array();
                    // transform all the log entries into entities
                    for (const event of log) {
                        const transformedEntity = yield transformEventCallback(event);
                        if (typeof transformedEntity !== "undefined") {
                            if (callback && singly) {
                                callback(error, transformedEntity);
                            }
                            entities.push(transformedEntity);
                        }
                    }
                    resolve(entities);
                }));
                // invoke the given callback with the promise of an array of entities
                if (callback && !singly) {
                    callback(error, promiseOfEntities);
                }
                return promiseOfEntities;
            };
            const baseFetcher = eventFetcherFactory(Object.assign(argFilter, baseArgFilter), filterObject);
            /**
             * If `immediateWatchCallback` is defined then we should start watching immediately.
             */
            if (immediateWatchCallback) {
                baseFetcher.watch((error, log) => {
                    handleEvent(error, log, true, immediateWatchCallback);
                }, immediateRequiredDepth);
            }
            /**
             * return the fetcher
             */
            return {
                get(callback, requiredDepth = 0) {
                    // remember get is singly, so there is a promise of an array
                    return new Promise((resolve, reject) => {
                        baseFetcher.get((error, log) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                            if (error) {
                                return reject(error);
                            }
                            resolve(yield handleEvent(error, log, false, callback));
                        }), requiredDepth);
                    });
                },
                watch(callback, requiredDepth = 0) {
                    // remember watch is singly, no promises
                    baseFetcher.watch((error, log) => {
                        handleEvent(error, log, true, callback);
                    }, requiredDepth);
                },
                subscribe(eventName, callback, requiredDepth = 0) {
                    if (!callback) {
                        /* tslint:disable-next-line:no-empty */
                        callback = () => { };
                    }
                    const subscription = pubSubEventService_1.PubSubEventService.subscribe(eventName, callback);
                    this.watch((error, entity) => {
                        pubSubEventService_1.PubSubEventService.publish(eventName, entity);
                    }, requiredDepth);
                    return new Web3EventSubscription(subscription, baseFetcher);
                },
                stopWatching(callback) {
                    baseFetcher.stopWatching(callback);
                },
                stopWatchingAsync() {
                    return utilsInternal_1.UtilsInternal.stopWatchingAsync(baseFetcher);
                },
            };
        };
    }
    /**
     * Returns a function that we will use internally to handle each Web3 event
     * @param suppressDups
     * @param preProcessEvent
     */
    createBaseWeb3EventHandler(suppressDups, preProcessEvent) {
        let receivedEvents;
        if (!!suppressDups) {
            receivedEvents = new Set();
        }
        return (error, log, 
        // singly true to issue callback on every arg rather than on the array
        singly, 
        /*
         * invoke this callback on every event (watch)
         * or on the array of events (get), depending on the value of singly
         */
        callback, requiredDepth = 0) => tslib_1.__awaiter(this, void 0, void 0, function* () {
            /**
             * convert to 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;
                    }
                });
            }
            if (preProcessEvent) {
                const processedResult = preProcessEvent(error, log);
                error = processedResult.error;
                log = processedResult.log;
            }
            // invoke callback if there is one
            if (callback) {
                for (const e of log) {
                    if (requiredDepth) {
                        if (requiredDepth === -1) {
                            requiredDepth = undefined;
                        } // to use the default value
                        yield transactionService_1.TransactionService.watchForConfirmedTransaction(e.transactionHash, null, requiredDepth);
                    }
                    if (singly) {
                        callback(error, e);
                    }
                }
                if (!singly) {
                    callback(error, log);
                }
            }
            // return array of DecodedLogEntryEvents in any case
            return log;
        });
    }
}
exports.Web3EventService = Web3EventService;
class Web3EventSubscription {
    constructor(subscription, fetcher) {
        this.subscription = subscription;
        this.fetcher = fetcher;
    }
    /**
     * Unsubscribe from all of the events
     * @param milliseconds number of milliseconds to timeout.
     * Default is -1 which means not to timeout at all.
     */
    unsubscribe(milliseconds = -1) {
        return new Promise((resolve) => {
            return this.fetcher.stopWatchingAsync()
                .then(() => {
                this.subscription.unsubscribe.call(this.subscription, milliseconds)
                    .then(() => { resolve(); });
            });
        });
    }
}
exports.Web3EventSubscription = Web3EventSubscription;
//# sourceMappingURL=web3EventService.js.map