package org.bitprivacy;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import java.math.BigInteger;

import org.bitprivacy.MixClient;
import org.bitprivacy.SimpleMix;
import org.bitprivacy.NetMixTest.AttackerType;
import org.bitprivacy.SimpleMix.BlindingValues;

import com.dustyneuron.bitprivacy.TransactionSchemaProtos.TransactionSchema;
import com.dustyneuron.txmarket.TestPartyData;
import com.dustyneuron.txmarket.WalletHarness;
import com.dustyneuron.txmarket.TestPartyData.Coin;
import com.dustyneuron.txmarket.TestPartyData.CoinData;
import com.dustyneuron.txmarket.bitcoin.WalletUtils;
import com.dustyneuron.txmarket.blinding.Blinding;
import com.dustyneuron.txmarket.dht.TradeDHT;
import com.dustyneuron.txmarket.schema.SchemaUtils;
import com.dustyneuron.txmarket.schema.Trade;
import com.google.bitcoin.core.Address;
import com.google.bitcoin.core.NetworkParameters;
import com.google.bitcoin.core.Sha256Hash;

public class TestParty {
    final static NetworkParameters params = NetworkParameters.testNet();

    AttackerType attackerType;

    Coin coin;
    CoinData coinData;

    TransactionSchema schema;
    Trade trade;
    Address toAddress;

    WalletHarness wallet;
    MixClient client;
    boolean haveSigned;

    public TestParty(AttackerType attackerType) throws Exception {
        this(null, attackerType);
        if (attackerType == AttackerType.NONE) {
            throw new Exception(
                    "Party isn't declared an attacker but has no trade data.. eh??");
        }
    }

    public TestParty(Coin coin) {
        this(coin, AttackerType.NONE);
    }

    public TestParty(Coin coin, AttackerType attackerType) {
        this.coin = coin;
        if (coin != null) {
            coinData = TestPartyData.getCoinData(coin);
        }
        this.attackerType = attackerType;
    }

    @Override
    public String toString() {
        return coin.toString() + "/" + attackerType.toString();
    }

    public void init(TradeDHT dht, Blinding blinding) throws Exception {
        wallet = new WalletHarness(params);
        if (this.coin != null) {
            wallet.importPrivateKey(coinData.privKey);
        }
        toAddress = null;
        trade = null;
        client = new MixClient(dht, wallet.getWallet(), wallet, false, true,
                blinding, false);
        haveSigned = false;
    }

    public void createSchema(BigInteger mixAmount, int numParties,
            BlindingValues blindingValues) {
        schema = SimpleMix.getSchema(mixAmount, numParties, blindingValues);
    }

    public boolean hasTradeData() {
        return coin != null;
    }

    public void createTrade(BigInteger mixAmount, int numParties,
            BlindingValues blindingValues) throws Exception {
        if (hasTradeData()) {
            wallet.getWallet().commitTx(
                    WalletUtils.findTransaction(wallet
                            .downloadBlock(new Sha256Hash(coinData.blockId)),
                            new Sha256Hash(coinData.txId)));

            if ((blindingValues == BlindingValues.OUTPUTS)
                    || (blindingValues == BlindingValues.BOTH)) {
                toAddress = null;
            } else {
                toAddress = wallet.generate();
            }

            trade = SimpleMix.createTrade(coinData.blockId, coinData.txId,
                    coinData.txOutput, mixAmount, toAddress, numParties,
                    blindingValues);
            assertNotNull(trade);
            assertTrue(SchemaUtils.areIdentical(schema, trade.getSchema()));
            assertTrue(SchemaUtils.isInitialListingValid(trade.getSchema(),
                    trade.getAllPartiesData()));
        }
    }

    public void attack() throws Exception {
        if (attackerType == AttackerType.NONE) {
            throw new Exception(
                    "Party isn't declared an attacker but is trying to attack");
        }
        client.tryDisrupt(schema);
    }
}
