/*
 * Copyright by the original author or authors.
 *
 * 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.
 */

package org.bitcoinj.examples;

import org.bitcoinj.base.BitcoinNetwork;
import org.bitcoinj.base.ScriptType;
import org.bitcoinj.core.Address;
import org.bitcoinj.base.Coin;
import org.bitcoinj.core.Context;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.crypto.KeyCrypterException;
import org.bitcoinj.kits.WalletAppKit;
import org.bitcoinj.utils.BriefLogFormatter;
import org.bitcoinj.wallet.KeyChainGroupStructure;
import org.bitcoinj.wallet.SendRequest;
import org.bitcoinj.wallet.Wallet;

import java.io.File;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * ForwardingService demonstrates basic usage of the library. It sits on the network and when it receives coins, simply
 * sends them onwards to an address given on the command line.
 */
public class ForwardingService {
    static final String usage = "Usage: address-to-send-back-to [mainnet|testnet|signet|regtest]";
    static final int requiredConfirmations = 1;
    static final int MAX_CONNECTIONS = 4;
    private final BitcoinNetwork network;
    private final Address forwardingAddress;
    private final WalletAppKit kit;

    public static void main(String[] args) throws Exception {
        // This line makes the log output more compact and easily read, especially when using the JDK log adapter.
        BriefLogFormatter.init();
        Context.propagate(new Context());

        if (args.length < 1) {
            System.err.println(usage);
            throw new IllegalArgumentException("Address required");
        }

        // Figure out which network we should connect to. Each network gets its own set of files.
        var networkString = (args.length > 1) ? args[1] : "mainnet";
        var network = BitcoinNetwork.fromString(networkString).orElseThrow();
        var address = Address.fromString(NetworkParameters.of(network), args[0]);

        forward(network, address);
    }

    public static void forward(BitcoinNetwork network, Address address) {
        System.out.println("Network: " + network.id());
        System.out.println("Forwarding address: " + address);

        // Create the Service (and WalletKit)
        ForwardingService forwardingService = new ForwardingService(address, network);

        // Start the Service (and WalletKit)
        forwardingService.start();

        // Start listening and forwarding
        forwardingService.waitForCoins();

        // After we start listening, we can tell the user the receiving address
        System.out.printf("Waiting to receive coins on %s\n", forwardingService.receivingAddress());
        System.out.printf("Will send coins to %s\n", address);
        System.out.println("Waiting for coins to arrive. Press Ctrl-C to quit.");

        try {
            Thread.sleep(Long.MAX_VALUE);
        } catch (InterruptedException ignored) {}
    }

    /**
     * Forwarding service. Creating this object creates the {@link WalletAppKit} object.
     *
     * @param forwardingAddress Address to forward to
     * @param network Network to listen on
     */
    public ForwardingService(Address forwardingAddress, BitcoinNetwork network) {
        this.forwardingAddress = forwardingAddress;
        this.network = network;

        // Start up a basic app using a class that automates some boilerplate.
        kit = new WalletAppKit(network,
                ScriptType.P2WPKH,
                KeyChainGroupStructure.BIP32,
                new File("."),
                getPrefix(network));
    }

    /**
     * Start the WalletAppKit
     */
    public void start() {
        if (network == BitcoinNetwork.REGTEST) {
            // Regression test mode is designed for testing and development only, so there's no public network for it.
            // If you pick this mode, you're expected to be running a local "bitcoind -regtest" instance.
            kit.connectToLocalHost();
        }

        // Download the blockchain and wait until it's done.
        kit.startAsync();
        kit.awaitRunning();
        kit.peerGroup().setMaxConnections(MAX_CONNECTIONS);
    }

    /**
     * Setup the listener to forward received coins and wait
     */
    public void waitForCoins() {
        // We want to know when we receive money.
        kit.wallet().addCoinsReceivedEventListener((w, tx, prevBalance, newBalance) -> {
            // Runs in the dedicated "user thread" (see bitcoinj docs for more info on this).
            //
            // The transaction "tx" can either be pending, or included into a block (we didn't see the broadcast).
            Coin value = tx.getValueSentToMe(w);
            System.out.println("Received tx for " + value.toFriendlyString() + ": " + tx);
            System.out.println("Transaction will be forwarded after it confirms.");
            // Wait until it's made it into the block chain (may run immediately if it's already there).
            //
            // For this dummy app of course, we could just forward the unconfirmed transaction. If it were
            // to be double spent, no harm done. Wallet.allowSpendingUnconfirmedTransactions() would have to
            // be called in onSetupCompleted() above. But we don't do that here to demonstrate the more common
            // case of waiting for a block.
            waitForConfirmation(tx);
        });
    }

    /**
     * Wait for confirmation on a transaction.
     * @param tx the transaction we are waiting for
     */
    void waitForConfirmation(Transaction tx) {
        tx.getConfidence().getDepthFuture(requiredConfirmations).whenComplete((result, t) -> {
            if (result != null) {
                System.out.println("Confirmation received.");
                forwardCoins(forwardingAddress);
            } else {
                // This kind of future can't fail, just rethrow in case something weird happens.
                throw new RuntimeException(t);
            }
        });
    }

    private void forwardCoins(Address forwardingAddress) {
        try {
            // Now send the coins onwards.
            SendRequest sendRequest = SendRequest.emptyWallet(forwardingAddress);
            Wallet.SendResult sendResult = kit.wallet().sendCoins(sendRequest);
            checkNotNull(sendResult);  // We should never try to send more coins than we have!
            System.out.println("Sending ...");
            // Register a callback that is invoked when the transaction has propagated across the network.
            sendResult.broadcastComplete.thenAccept(transaction -> {
                // The wallet has changed now, it'll get auto saved shortly or when the app shuts down.
                System.out.println("Sent coins onwards! Transaction hash is " + transaction.getTxId());
            });
        } catch (KeyCrypterException | InsufficientMoneyException e) {
            // We don't use encrypted wallets in this example - can never happen.
            throw new RuntimeException(e);
        }
    }

    /**
     * @return The current receiving address of the forwarding wallet
     */
    public Address receivingAddress() {
        return kit.wallet().currentReceiveAddress();
    }

    static String getPrefix(BitcoinNetwork network) {
        return String.format("forwarding-service-%s", network.toString());
    }
}

@@ -7009,26 +7009,25 @@
 em.out.print
-ln
+f
 (%22Sending ..
@@ -7028,12 +7028,45 @@
 ing 
-...%22
+tx %25s ...%5Cn%22, sendResult.tx.getTxId()
 );%0A 
@@ -7403,54 +7403,9 @@
 rds!
- Transaction hash is %22 + transaction.getTxId()
+%22
 );%0A 

print the hash of the forwarding transaction earlier