/*
 * 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.script;

import com.google.bitcoin.core.Address;
import com.google.bitcoin.core.ECKey;
import com.google.common.collect.Lists;

import java.util.Arrays;
import java.util.List;

import static com.google.bitcoin.script.ScriptOpCodes.*;
import static com.google.common.base.Preconditions.checkArgument;

/**
 * <p>Tools for the construction of commonly used script types. You don't normally need this as it's hidden behind
 * convenience methods on {@link com.google.bitcoin.core.Transaction}, but they are useful when working with the
 * protocol at a lower level.</p>
 */
public class ScriptBuilder {
    private List<ScriptChunk> chunks;

    public ScriptBuilder() {
        chunks = Lists.newLinkedList();
    }

    public ScriptBuilder op(int opcode) {
        chunks.add(new ScriptChunk(true, new byte[]{(byte)opcode}));
        return this;
    }

    public ScriptBuilder data(byte[] data) {
        byte[] copy = Arrays.copyOf(data, data.length);
        chunks.add(new ScriptChunk(false, copy));
        return this;
    }

    public ScriptBuilder smallNum(byte num) {
        checkArgument(num >= 0, "Cannot encode negative numbers with smallNum");
        checkArgument(num <= 16, "Cannot encode numbers larger than 16 with smallNum");
        chunks.add(new ScriptChunk(true, new byte[]{(byte)Script.encodeToOpN(num)}));
        return this;
    }

    public Script build() {
        return new Script(chunks);
    }

    /** Creates a scriptPubKey that encodes payment to the given address. */
    public static Script createOutputScript(Address to) {
        // OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
        return new ScriptBuilder()
                .op(OP_DUP)
                .op(OP_HASH160)
                .data(to.getHash160())
                .op(OP_EQUALVERIFY)
                .op(OP_CHECKSIG)
                .build();
    }

    /** Creates a scriptPubKey that encodes payment to the given raw public key. */
    public static Script createOutputScript(ECKey key) {
        return new ScriptBuilder().data(key.getPubKey()).op(OP_CHECKSIG).build();
    }

    private static byte[] appendByte(byte[] buf, byte flags) {
        byte[] result = Arrays.copyOf(buf, buf.length + 1);
        result[result.length - 1] = flags;
        return result;
    }

    /** Creates a scriptSig that can redeem a pay-to-address output. */
    public static Script createInputScript(ECKey.ECDSASignature signature, ECKey pubKey, int sigHashFlags) {
        byte[] pubkeyBytes = pubKey.getPubKey();
        byte[] sigBytes = appendByte(signature.encodeToDER(), (byte) sigHashFlags);
        return new ScriptBuilder().data(sigBytes).data(pubkeyBytes).build();
    }

    /** Creates a scriptSig that can redeem a pay-to-pubkey output. */
    public static Script createInputScript(ECKey.ECDSASignature signature, int sigHashFlags) {
        byte[] sigBytes = appendByte(signature.encodeToDER(), (byte) sigHashFlags);
        return new ScriptBuilder().data(sigBytes).build();
    }

    /** Creates a program that requires at least N of the given keys to sign, using OP_CHECKMULTISIG. */
    public static Script createMultiSigOutputScript(int threshold, List<ECKey> pubkeys) {
        checkArgument(threshold > 0);
        checkArgument(threshold <= pubkeys.size());
        checkArgument(pubkeys.size() <= 16);  // That's the max we can represent with a single opcode.
        ScriptBuilder builder = new ScriptBuilder();
        builder.smallNum((byte)threshold);
        for (ECKey key : pubkeys) {
            builder.data(key.getPubKey());
        }
        builder.smallNum((byte)pubkeys.size());
        builder.op(OP_CHECKMULTISIG);
        return builder.build();
    }
}

@@ -702,16 +702,60 @@
 .ECKey;%0A
+import com.google.bitcoin.core.Transaction;%0A
 import c
@@ -1726,20 +1726,19 @@
 mallNum(
-byte
+int
  num) %7B%0A
@@ -4142,16 +4142,17 @@
 m((byte)
+ 
 threshol
@@ -4367,11 +4367,731 @@
 );%0A    %7D
+%0A%0A    /** Create a program that satisfies an OP_CHECKMULTISIG program. */%0A    public static Script createMultiSigInputScript(List%3CECKey.ECDSASignature%3E signatures,%0A                                                   Transaction.SigHash sigHash, boolean anyoneCanPay) %7B%0A        checkArgument(signatures.size() %3C= 16);  // Max allowable.%0A        ScriptBuilder builder = new ScriptBuilder();%0A        builder.smallNum(0);  // Work around a bug in CHECKMULTISIG that is now a required part of the protocol.%0A        for (ECKey.ECDSASignature signature : signatures)%0A            builder.data(appendByte(signature.encodeToDER(),(byte) ((sigHash.ordinal() + 1) %7C (anyoneCanPay ? 0x80 : 0))));%0A        return builder.build();%0A    %7D
 %0A%7D%0A

add a method that creates a multisig input script.