"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TransactionAnnounceService = void 0;
/*
 *
 * Copyright 2018-present NEM
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
const ora_1 = require("ora");
const rxjs_1 = require("rxjs");
const operators_1 = require("rxjs/operators");
const symbol_sdk_1 = require("symbol-sdk");
const announce_resolver_1 = require("../resolvers/announce.resolver");
const formatter_service_1 = require("./formatter.service");
class TransactionAnnounceService {
    /**
     * Constructor.
     * @param {Profile} profile
     * @param {AnnounceTransactionsOptions} options
     */
    constructor(profile, options) {
        this.profile = profile;
        this.options = options;
        this.spinner = (0, ora_1.default)('Processing');
        const repositoryFactory = profile.repositoryFactory;
        this.transactionHttp = repositoryFactory.createTransactionRepository();
        this.listener = repositoryFactory.createListener();
    }
    /**
     * Creates an instance of TransactionAnnounceService.
     * @static
     * @param {Profile} profile
     * @param {AnnounceTransactionsOptions} options
     * @returns {TransactionAnnounceService}
     */
    static create(profile, options) {
        return new TransactionAnnounceService(profile, options);
    }
    /**
     * Announce any transactions.
     * Takes no more than 1 aggregate bounded with its hash lock.
     * @param {SignedTransaction[]} transactions
     * @returns {Promise<void>}
     */
    async announce(transactions) {
        // does the user wants to announce?
        if (!(await new announce_resolver_1.AnnounceResolver().resolve(this.options))) {
            return;
        }
        // split transactions by announce mode
        const aggregateTxTypes = [symbol_sdk_1.TransactionType.AGGREGATE_BONDED, symbol_sdk_1.TransactionType.HASH_LOCK];
        const normalTx = transactions.filter(({ type }) => !aggregateTxTypes.includes(type));
        const hashLockTx = transactions.filter(({ type }) => aggregateTxTypes.includes(type));
        if (normalTx.length) {
            this.announceTransactions(normalTx);
        }
        if (hashLockTx.length) {
            this.announceAggregateTransaction(hashLockTx);
        }
    }
    /**
     * Announces transactions that are not aggregate bounded.
     * @private
     * @param {SignedTransaction[]} transactions
     */
    announceTransactions(transactions) {
        if (this.options.sync) {
            for (const tx of transactions) {
                this.announceTransactionSync(tx);
            }
            return;
        }
        for (const tx of transactions) {
            this.announceTransaction(tx);
        }
    }
    /**
     * Announces a transaction.
     * @param {SignedTransaction} signedTransaction
     */
    announceTransaction(signedTransaction) {
        this.transactionHttp.announce(signedTransaction).subscribe((ignored) => {
            this.spinner.stop();
            console.log(formatter_service_1.FormatterService.success('Transaction announced correctly'));
            console.log(formatter_service_1.FormatterService.info("To check if the network confirms or rejects the transaction, run the command 'symbol-cli transaction status'"));
        }, (err) => {
            this.spinner.stop();
            console.log(formatter_service_1.FormatterService.error(err));
        });
    }
    /**
     * Announces a transaction waiting for the response.
     * @param {SignedTransaction} Signed transaction.
     */
    announceTransactionSync(signedTransaction) {
        this.spinner.start();
        const senderAddress = signedTransaction.getSignerAddress();
        this.listener.open().then(() => {
            (0, rxjs_1.merge)(this.transactionHttp.announce(signedTransaction), this.listener
                .confirmed(senderAddress)
                .pipe((0, operators_1.filter)((transaction) => transaction.transactionInfo !== undefined &&
                transaction.transactionInfo.hash === signedTransaction.hash)), this.listener.status(senderAddress).pipe((0, operators_1.filter)((error) => error.hash === signedTransaction.hash), (0, operators_1.tap)((error) => {
                throw new Error(error.code);
            }))).subscribe((response) => {
                if (response instanceof symbol_sdk_1.TransactionAnnounceResponse) {
                    this.spinner.stop();
                    console.log(formatter_service_1.FormatterService.success('Transaction announced'));
                    this.spinner.start();
                }
                else if (response instanceof symbol_sdk_1.Transaction) {
                    this.listener.close();
                    this.spinner.stop();
                    console.log(formatter_service_1.FormatterService.success('Transaction confirmed'));
                }
            }, (err) => {
                this.listener.close();
                this.spinner.stop();
                console.log(formatter_service_1.FormatterService.error(err));
            });
        }, (err) => {
            this.listener.close();
            this.spinner.stop();
            console.log(formatter_service_1.FormatterService.error(err));
        });
    }
    /**
     * Announces a hash lock transaction. Once this is confirmed, announces an aggregate transaction.
     * @param {SignedTransaction[]} transactions
     */
    announceAggregateTransaction(transactions) {
        this.spinner.start();
        if (transactions.length !== 2) {
            throw new Error('announceAggregateTransaction must be provided with 2 transactions');
        }
        const hashLock = transactions.find(({ type }) => type === symbol_sdk_1.TransactionType.HASH_LOCK);
        const aggregate = transactions.find(({ type }) => type === symbol_sdk_1.TransactionType.AGGREGATE_BONDED);
        if (!hashLock || !aggregate) {
            throw new Error('announceAggregateTransaction must be provided with an haslock and an aggregate bonded');
        }
        let confirmations = 0;
        const senderAddress = hashLock.getSignerAddress();
        this.listener.open().then(() => {
            (0, rxjs_1.merge)(this.transactionHttp.announce(hashLock), this.listener.status(senderAddress).pipe((0, operators_1.filter)((error) => error.hash === hashLock.hash), (0, operators_1.tap)((error) => {
                throw new Error(error.code);
            })), this.listener.confirmed(senderAddress).pipe((0, operators_1.filter)((transaction) => transaction.transactionInfo !== undefined && transaction.transactionInfo.hash === hashLock.hash), (0, operators_1.mergeMap)((ignored) => this.transactionHttp.announceAggregateBonded(aggregate)))).subscribe((ignored) => {
                confirmations = confirmations + 1;
                if (confirmations === 1) {
                    this.spinner.stop();
                    console.log(formatter_service_1.FormatterService.success('Hash lock transaction announced'));
                    this.spinner.start();
                }
                else if (confirmations === 2) {
                    this.listener.close();
                    this.spinner.stop();
                    console.log(formatter_service_1.FormatterService.success('Hash lock transaction confirmed'));
                    console.log(formatter_service_1.FormatterService.success('Aggregate transaction announced'));
                }
            }, (err) => {
                this.listener.close();
                this.spinner.stop();
                console.log(formatter_service_1.FormatterService.error(err));
            });
        }, (err) => {
            this.listener.close();
            this.spinner.stop();
            console.log(formatter_service_1.FormatterService.error(err));
        });
    }
}
exports.TransactionAnnounceService = TransactionAnnounceService;
