package lighthouse.model;

import com.google.protobuf.ByteString;
import javafx.beans.property.*;
import lighthouse.protocol.LHProtos;
import lighthouse.protocol.LHUtils;
import lighthouse.protocol.Project;
import lighthouse.wallet.PledgingWallet;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptBuilder;

import static lighthouse.protocol.LHUtils.unchecked;

/**
 * This class wraps a LHProtos.Project and exposes JFX properties for things that users are interested in. It performs
 * some validation and links the properties to a protobuf builder.
 */
public class ProjectModel {
    public final StringProperty title = new SimpleStringProperty();
    public final StringProperty memo = new SimpleStringProperty();
    public final StringProperty serverName = new SimpleStringProperty();
    public final StringProperty address = new SimpleStringProperty();
    // Value in satoshis.
    public final LongProperty goalAmount = new SimpleLongProperty();
    public final ObjectProperty<ByteString> image = new SimpleObjectProperty<>();
    private final LongProperty minPledgeAmount = new SimpleLongProperty();

    private LHProtos.ProjectDetails.Builder proto;


    public static final int ESTIMATED_INPUT_SIZE = Script.SIG_SIZE + 35 /* bytes for a compressed pubkey */ + 32 /* hash */ + 4;
    public static final int MAX_NUM_INPUTS = (Transaction.MAX_STANDARD_TX_SIZE - 64) /* for output */ / ESTIMATED_INPUT_SIZE;

    public ProjectModel(PledgingWallet wallet) {
        this(Project.makeDetails("", "", wallet.freshReceiveAddress(), Coin.SATOSHI, wallet.freshAuthKey(),
                wallet.getKeychainLookaheadSize()));
    }

    public ProjectModel(LHProtos.ProjectDetails.Builder liveProto) {
        this.proto = liveProto;
        final LHProtos.Project.Builder wrapper = LHProtos.Project.newBuilder().setSerializedPaymentDetails(liveProto.build().toByteString());
        Project project = unchecked(() -> new Project(wrapper.build()));
        title.set(project.getTitle());
        memo.set(project.getMemo());
        goalAmount.set(project.getGoalAmount().value);
        minPledgeAmount.set(recalculateMinPledgeAmount(goalAmount.longValue()));

        if (liveProto.hasPaymentUrl()) {
            String host = LHUtils.validateServerPath(liveProto.getPaymentUrl(), project.getID());
            if (host == null)
                throw new IllegalArgumentException("Server path not valid for Lighthouse protocol: " + liveProto.getPaymentUrl());
            serverName.set(host);
        }

        // Connect the properties.
        title.addListener(o -> proto.getExtraDetailsBuilder().setTitle(title.get()));
        memo.addListener(o -> proto.setMemo(memo.get()));
        // Just adjust the first output. GUI doesn't handle multioutput contracts right now (they're useless anyway).
        goalAmount.addListener(o -> {
            long value = goalAmount.longValue();
            minPledgeAmount.set(recalculateMinPledgeAmount(value));
            proto.getOutputsBuilder(0).setAmount(value);
        });

        minPledgeAmount.addListener(o -> proto.getExtraDetailsBuilder().setMinPledgeSize(minPledgeAmountProperty().get()));

        serverName.addListener(o -> {
            final String name = serverName.get();
            if (name.isEmpty())
                proto.clearPaymentUrl();
            else
                proto.setPaymentUrl(LHUtils.makeServerPath(name, LHUtils.titleToUrlString(title.get())));
        });

        Address addr = project.getOutputs().get(0).getAddressFromP2PKHScript(project.getParams());
        if (addr == null)
            throw new IllegalArgumentException("Output type is not a pay to address: " + project.getOutputs().get(0));
        address.set(addr.toString());
        address.addListener(o -> {
            try {
                Address addr2 = new Address(project.getParams(), address.get());
                proto.getOutputsBuilder(0).setScript(ByteString.copyFrom(ScriptBuilder.createOutputScript(addr2).getProgram()));
            } catch (AddressFormatException e) {
                // Ignored: wait for the user to make it valid.
            }
        });

        if (proto.getExtraDetailsBuilder().hasCoverImage())
            image.set(proto.getExtraDetailsBuilder().getCoverImage());
        image.addListener(o -> {
            proto.getExtraDetailsBuilder().setCoverImage(image.get());
        });
    }

    private long recalculateMinPledgeAmount(long value) {
        // This isn't a perfect estimation by any means especially as we allow P2SH outputs to be pledged, which
        // could end up gobbling up a lot of space in the contract, but it'll do for now. How many pledges can we
        // have assuming Lighthouse makes them all i.e. pay to address?
        return Math.max(value / MAX_NUM_INPUTS, Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.multiply(4).value);
    }

    public LHProtos.Project.Builder getProto() {
        LHProtos.Project.Builder proto = LHProtos.Project.newBuilder();
        proto.setSerializedPaymentDetails(getDetailsProto().build().toByteString());
        return proto;
    }

    public Project getProject() {
        return unchecked(() -> new Project(getProto().build()));
    }

    public LHProtos.ProjectDetails.Builder getDetailsProto() {
        return proto;
    }

    public Coin getMinPledgeAmount() {
        return Coin.valueOf(minPledgeAmount.get());
    }

    public ReadOnlyLongProperty minPledgeAmountProperty() {
        return minPledgeAmount;
    }
}

@@ -270,133 +270,9 @@
 ore.
-Address;%0Aimport org.bitcoinj.core.AddressFormatException;%0Aimport org.bitcoinj.core.Coin;%0Aimport org.bitcoinj.core.Transaction
+*
 ;%0Aim
@@ -3506,28 +3506,40 @@
         
-Address addr
+TransactionOutput output
  = proje
@@ -3560,16 +3560,47 @@
 ).get(0)
+;%0A        Address addr = output
 .getAddr
@@ -3658,32 +3658,125 @@
  (addr == null)%0A
+            addr = output.getAddressFromP2SH(project.getParams());%0A        if (addr == null)%0A
             thro
@@ -3826,18 +3826,16 @@
  is not 
-a 
 pay to a
@@ -3844,41 +3844,25 @@
 ress
+/p2sh
 : %22 + 
-project.getOutputs().get(0)
+output
 );%0A 

Fix crash that can occur when editing projects that have a P2SH target address