/**
 * Copyright 2013 Google Inc.
 *
 * 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 com.google.bitcoin.utils;

import com.google.bitcoin.core.Transaction;
import com.google.bitcoin.core.TransactionBroadcaster;
import com.google.bitcoin.core.Wallet;
import com.google.common.util.concurrent.SettableFuture;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.ReentrantLock;

/**
 * A mock transaction broadcaster can be used in unit tests as a stand-in for a PeerGroup. It catches any transactions
 * broadcast through it and makes them available via the {@link #broadcasts} member. Reading from that
 * {@link LinkedBlockingQueue} will block the thread until a transaction is available.
 */
public class MockTransactionBroadcaster implements TransactionBroadcaster {
    private final ReentrantLock lock = Threading.lock("mock tx broadcaster");

    public static class TxFuturePair {
        public Transaction tx;
        public SettableFuture<Transaction> future;

        public TxFuturePair(Transaction tx, SettableFuture<Transaction> future) {
            this.tx = tx;
            this.future = future;
        }
    }

    private final LinkedBlockingQueue<TxFuturePair> broadcasts = new LinkedBlockingQueue<TxFuturePair>();

    public MockTransactionBroadcaster(Wallet wallet) {
        // This code achieves nothing directly, but it sets up the broadcaster/peergroup > wallet lock ordering
        // so inversions can be caught.
        lock.lock();
        try {
            wallet.getPendingTransactions();
        } finally {
            lock.unlock();
        }
    }

    @Override
    public SettableFuture<Transaction> broadcastTransaction(Transaction tx) {
        // Use a lock just to catch lock ordering inversions e.g. wallet->broadcaster.
        lock.lock();
        try {
            SettableFuture<Transaction> result = SettableFuture.create();
            broadcasts.put(new TxFuturePair(tx, result));
            return result;
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }

    public Transaction waitForTransaction() {
        return waitForTxFuture().tx;
    }

    public TxFuturePair waitForTxFuture() {
        try {
            return broadcasts.take();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    public int size() {
        return broadcasts.size();
    }
}

@@ -754,22 +754,183 @@
 in.core.
-Wallet
+VerificationException;%0Aimport com.google.bitcoin.core.Wallet;%0Aimport com.google.common.util.concurrent.FutureCallback;%0Aimport com.google.common.util.concurrent.Futures
 ;%0Aimport
@@ -1550,16 +1550,49 @@
 aster%22);
+%0A    private final Wallet wallet;
 %0A%0A    pu
@@ -2207,32 +2207,66 @@
 ;%0A        try %7B%0A
+            this.wallet = wallet;%0A
             wall
@@ -2642,24 +2642,24 @@
 e.create();%0A
-
             
@@ -2704,16 +2704,530 @@
 sult));%0A
+            Futures.addCallback(result, new FutureCallback%3CTransaction%3E() %7B%0A                @Override%0A                public void onSuccess(Transaction result) %7B%0A                    try %7B%0A                        wallet.receivePending(result, null);%0A                    %7D catch (VerificationException e) %7B%0A                        throw new RuntimeException(e);%0A                    %7D%0A                %7D%0A%0A                @Override%0A                public void onFailure(Throwable t) %7B%0A                %7D%0A            %7D);%0A
         

add to wallet.