#include <assert.h>
#include <bitcoin/block.h>
#include <bitcoin/pullpush.h>
#include <bitcoin/tx.h>
#include <ccan/cast/cast.h>
#include <ccan/crypto/sha256/sha256.h>
#include <ccan/endian/endian.h>
#include <ccan/mem/mem.h>
#include <ccan/read_write_all/read_write_all.h>
#include <ccan/str/hex/hex.h>
#include <common/type_to_string.h>
#include <stdio.h>
#include <wire/wire.h>

#define SEGREGATED_WITNESS_FLAG 0x1

static u8 bitcoin_asset[] =
    {
	0x01, 0x5c, 0xe7, 0xb9, 0x63, 0xd3, 0x7f, 0x8f, 0x2d, 0x51, 0xca,
	0xfb, 0xba, 0x92, 0x8a, 0xaa, 0x9e, 0x22, 0x0b, 0x8b, 0xbc, 0x66,
	0x05, 0x71, 0x49, 0x9c, 0x03, 0x62, 0x8a, 0x38, 0x51, 0xb8, 0xce,
    };

int bitcoin_tx_add_output(struct bitcoin_tx *tx, const u8 *script,
			  struct amount_sat amount)
{
	size_t i = tx->wtx->num_outputs;
	struct wally_tx_output *output;
	int ret;
	u64 satoshis = amount.satoshis; /* Raw: low-level helper */
	assert(i < tx->wtx->outputs_allocation_len);

	assert(tx->wtx != NULL);

	if (is_elements) {
		u8 value[9];
		ret = wally_tx_confidential_value_from_satoshi(satoshis, value,
							       sizeof(value));
		assert(ret == WALLY_OK);
		ret = wally_tx_elements_output_init_alloc(
		    script, tal_bytelen(script), bitcoin_asset,
		    sizeof(bitcoin_asset), value, sizeof(value), NULL, 0, NULL,
		    0, NULL, 0, &output);
		assert(ret == WALLY_OK);
		/* Cheat a bit by also setting the numeric satoshi value,
		 * otherwise we end up converting a number of times */
		output->satoshi = satoshis;
	} else {
		ret = wally_tx_output_init_alloc(satoshis, script,
						 tal_bytelen(script), &output);
	}
	ret = wally_tx_add_output(tx->wtx, output);
	assert(ret == WALLY_OK);

	wally_tx_output_free(output);
	bitcoin_tx_output_set_amount(tx, i, amount);

	return i;
}

int bitcoin_tx_add_multi_outputs(struct bitcoin_tx *tx,
				 struct bitcoin_tx_output **outputs)
{
	for (size_t j = 0; j < tal_count(outputs); j++)
		bitcoin_tx_add_output(tx, outputs[j]->script,
				      outputs[j]->amount);

	return tx->wtx->num_outputs;
}

static bool bitcoin_tx_output_is_fee(const struct bitcoin_tx *tx, int outnum)
{
	assert(outnum < tx->wtx->num_outputs);
	return is_elements && tx->wtx->outputs[outnum].script_len == 0;
}

/**
 * Compute how much fee we are actually sending with this transaction.
 */
static u64 bitcoin_tx_compute_fee(const struct bitcoin_tx *tx)
{
	u64 fee = 0, satoshi;

	for (size_t i=0; i<tal_count(tx->input_amounts); i++)
		fee += tx->input_amounts[i]->satoshis; /* Raw: fee computation */

	for (size_t i=0; i<tx->wtx->num_outputs; i++) {
		if (bitcoin_tx_output_is_fee(tx, i))
			continue;

		if (!is_elements) {
			fee -= tx->wtx->outputs[i].satoshi; /* Raw: low-level helper */
		} else {
			beint64_t tmp;
			memcpy(&tmp, &tx->wtx->outputs[i].value[1] , sizeof(tmp));
			satoshi = be64_to_cpu(tmp);
			fee -= satoshi;
		}
	}

	return fee;
}

int bitcoin_tx_add_fee_output(struct bitcoin_tx *tx)
{
	struct amount_sat fee;
	int pos = -1;
	u64 rawsats = bitcoin_tx_compute_fee(tx); /* Raw: pedantic much? */
	fee.satoshis = rawsats; /* Raw: need amounts later */

	/* If we aren't using elements, we don't add explicit fee outputs */
	if (!is_elements || rawsats == 0)
		return -1;

	/* Try to find any existing fee output */
	for (int i=0; i<tx->wtx->num_outputs; i++) {
		if (bitcoin_tx_output_is_fee(tx, i)) {
			assert(pos == -1);
			pos = i;
		}
	}

	if (pos == -1) {
		return bitcoin_tx_add_output(tx, NULL, fee);
	} else {
		bitcoin_tx_output_set_amount(tx, pos, fee);
		return pos;
	}
}

int bitcoin_tx_add_input(struct bitcoin_tx *tx, const struct bitcoin_txid *txid,
			 u32 outnum, u32 sequence,
			 struct amount_sat amount, u8 *script)
{
	size_t i = tx->wtx->num_inputs;
	struct wally_tx_input *input;
	assert(i < tx->wtx->inputs_allocation_len);

	assert(tx->wtx != NULL);
	wally_tx_input_init_alloc(txid->shad.sha.u.u8,
				  sizeof(struct bitcoin_txid), outnum, sequence,
				  script, tal_bytelen(script),
				  NULL /* Empty witness stack */, &input);
	input->features = is_elements ? WALLY_TX_IS_ELEMENTS : 0;
	wally_tx_add_input(tx->wtx, input);
	wally_tx_input_free(input);

	/* Now store the input amount if we know it, so we can sign later */
	tx->input_amounts[i] = tal_free(tx->input_amounts[i]);
	tx->input_amounts[i] = tal_dup(tx, struct amount_sat, &amount);

	return i;
}

bool bitcoin_tx_check(const struct bitcoin_tx *tx)
{
	u8 *newtx;
	size_t written;
	int flags = WALLY_TX_FLAG_USE_WITNESS;

	if (wally_tx_get_length(tx->wtx, flags, &written) != WALLY_OK)
		return false;

	if (is_elements) {
		flags |= WALLY_TX_FLAG_USE_ELEMENTS;
	}

	newtx = tal_arr(tmpctx, u8, written);
	if (wally_tx_to_bytes(tx->wtx, flags, newtx, written, &written) !=
	    WALLY_OK)
		return false;

	if (written != tal_bytelen(newtx))
		return false;

	return true;
}

void bitcoin_tx_output_set_amount(struct bitcoin_tx *tx, int outnum,
				  struct amount_sat amount)
{
	u64 satoshis = amount.satoshis; /* Raw: low-level helper */
	struct wally_tx_output *output = &tx->wtx->outputs[outnum];
	assert(outnum < tx->wtx->num_outputs);
	if (is_elements) {
		int ret = wally_tx_confidential_value_from_satoshi(satoshis, output->value,
							       output->value_len);
		assert(ret == WALLY_OK);
	} else {
		output->satoshi = satoshis;
	}
}

const u8 *bitcoin_tx_output_get_script(const tal_t *ctx,
				       const struct bitcoin_tx *tx, int outnum)
{
	const struct wally_tx_output *output;
	u8 *res;
	assert(outnum < tx->wtx->num_outputs);
	output = &tx->wtx->outputs[outnum];
	if (output->features & WALLY_TX_IS_COINBASE)
		return NULL;

	res = tal_arr(ctx, u8, output->script_len);
	memcpy(res, output->script, output->script_len);
	return res;
}

struct amount_sat bitcoin_tx_output_get_amount(const struct bitcoin_tx *tx,
					       int outnum)
{
	struct amount_sat amount;
	struct wally_tx_output *output;
	u64 satoshis;
	assert(outnum < tx->wtx->num_outputs);
	output = &tx->wtx->outputs[outnum];

	if (is_elements && !memeq(output->asset, output->asset_len,
				  bitcoin_asset, sizeof(bitcoin_asset))) {
		/* If this is an asset based tx, and we don't know the asset
		 * type, i.e., it's not bitcoin, return a 0 amount */
		satoshis = 0;
	} else if (is_elements) {
		be64 raw;
		memcpy(&raw, output->value + 1, sizeof(raw));
		satoshis = be64_to_cpu(raw);
	}else {
		satoshis = tx->wtx->outputs[outnum].satoshi;
	}

	amount.satoshis = satoshis; /* Raw: helper */
	return amount;
}

void bitcoin_tx_input_set_witness(struct bitcoin_tx *tx, int innum,
				  u8 **witness)
{
	struct wally_tx_witness_stack *stack = NULL;
	size_t stack_size = tal_count(witness);

	/* Free any lingering witness */
	if (witness) {
		wally_tx_witness_stack_init_alloc(stack_size, &stack);
		for (size_t i = 0; i < stack_size; i++)
			wally_tx_witness_stack_add(stack, witness[i],
						   tal_bytelen(witness[i]));
	}
	wally_tx_set_input_witness(tx->wtx, innum, stack);
	if (stack)
		wally_tx_witness_stack_free(stack);
	if (taken(witness))
	    tal_free(witness);
}

void bitcoin_tx_input_set_script(struct bitcoin_tx *tx, int innum, u8 *script)
{
	wally_tx_set_input_script(tx->wtx, innum, script, tal_bytelen(script));
}

const u8 *bitcoin_tx_input_get_witness(const tal_t *ctx,
				       const struct bitcoin_tx *tx, int innum,
				       int witnum)
{
	const u8 *witness_item;
	struct wally_tx_witness_item *item;
	assert(innum < tx->wtx->num_inputs);
	assert(witnum < tx->wtx->inputs[innum].witness->num_items);
	item = &tx->wtx->inputs[innum].witness->items[witnum];
	witness_item =
	    tal_dup_arr(ctx, u8, item->witness, item->witness_len, 0);
	return witness_item;
}

void bitcoin_tx_input_get_txid(const struct bitcoin_tx *tx, int innum,
			       struct bitcoin_txid *out)
{
	assert(innum < tx->wtx->num_inputs);
	assert(sizeof(struct bitcoin_txid) ==
	       sizeof(tx->wtx->inputs[innum].txhash));
	memcpy(out, tx->wtx->inputs[innum].txhash, sizeof(struct bitcoin_txid));
}

/* BIP144:
 * If the witness is empty, the old serialization format should be used. */
static bool uses_witness(const struct bitcoin_tx *tx)
{
	size_t i;

	for (i = 0; i < tx->wtx->num_inputs; i++) {
		if (tx->wtx->inputs[i].witness)
			return true;
	}
	return false;
}

/* For signing, we ignore input scripts on other inputs, and pretend
 * the current input has a certain script: this is indicated by a
 * non-NULL override_script.
 *
 * For this (and other signing weirdness like SIGHASH_SINGLE), we
 * also need the current input being signed; that's in input_num.
 * We also need sighash_type.
 */
static void push_tx(const struct bitcoin_tx *tx,
		    const u8 *override_script,
		    size_t input_num,
		    void (*push)(const void *, size_t, void *), void *pushp,
		    bool bip144)
{
	int res;
	size_t len, written;
	u8 *serialized;;
	u8 flag = 0;

        if (bip144 && uses_witness(tx))
		flag |= WALLY_TX_FLAG_USE_WITNESS;

	if (is_elements)
		flag |= WALLY_TX_FLAG_USE_ELEMENTS;

	res = wally_tx_get_length(tx->wtx, flag & WALLY_TX_FLAG_USE_WITNESS, &len);
	assert(res == WALLY_OK);
	serialized = tal_arr(tmpctx, u8, len);

	res = wally_tx_to_bytes(tx->wtx, flag, serialized, len, &written);
	assert(res == WALLY_OK);
	assert(len == written);
	push(serialized, len, pushp);
	tal_free(serialized);
}

static void push_sha(const void *data, size_t len, void *shactx_)
{
	struct sha256_ctx *ctx = shactx_;
	sha256_update(ctx, memcheck(data, len), len);
}

static void push_linearize(const void *data, size_t len, void *pptr_)
{
	u8 **pptr = pptr_;
	size_t oldsize = tal_count(*pptr);

	tal_resize(pptr, oldsize + len);
	memcpy(*pptr + oldsize, memcheck(data, len), len);
}

u8 *linearize_tx(const tal_t *ctx, const struct bitcoin_tx *tx)
{
	u8 *arr = tal_arr(ctx, u8, 0);
	push_tx(tx, NULL, 0, push_linearize, &arr, true);
	return arr;
}

size_t bitcoin_tx_weight(const struct bitcoin_tx *tx)
{
	size_t weight;
	int ret = wally_tx_get_weight(tx->wtx, &weight);
	assert(ret == WALLY_OK);
	return weight;
}

void bitcoin_txid(const struct bitcoin_tx *tx, struct bitcoin_txid *txid)
{
	struct sha256_ctx ctx = SHA256_INIT;

	/* For TXID, we never use extended form. */
	push_tx(tx, NULL, 0, push_sha, &ctx, false);
	sha256_double_done(&ctx, &txid->shad);
}

/* Use the bitcoin_tx destructor to also free the wally_tx */
static void bitcoin_tx_destroy(struct bitcoin_tx *tx)
{
	wally_tx_free(tx->wtx);
}

struct bitcoin_tx *bitcoin_tx(const tal_t *ctx,
			      const struct chainparams *chainparams,
			      varint_t input_count, varint_t output_count)
{
	struct bitcoin_tx *tx = tal(ctx, struct bitcoin_tx);
	assert(chainparams);

	/* If we are constructing an elements transaction we need to
	 * explicitly add the fee as an extra output. So allocate one more
	 * than the outputs we need internally. */
	if (is_elements)
		output_count += 1;

	wally_tx_init_alloc(WALLY_TX_VERSION_2, 0, input_count, output_count,
			    &tx->wtx);
	tal_add_destructor(tx, bitcoin_tx_destroy);

	tx->input_amounts = tal_arrz(tx, struct amount_sat*, input_count);
	tx->wtx->locktime = 0;
	tx->wtx->version = 2;
	tx->chainparams = chainparams;
	return tx;
}

struct bitcoin_tx *pull_bitcoin_tx(const tal_t *ctx, const u8 **cursor,
				   size_t *max)
{
	size_t wsize;
	int flags = WALLY_TX_FLAG_USE_WITNESS;
	struct bitcoin_tx *tx = tal(ctx, struct bitcoin_tx);

	if (is_elements)
		flags |= WALLY_TX_FLAG_USE_ELEMENTS;

	if (wally_tx_from_bytes(*cursor, *max, flags, &tx->wtx) != WALLY_OK) {
		fromwire_fail(cursor, max);
		return tal_free(tx);
	}

	tal_add_destructor(tx, bitcoin_tx_destroy);

	/* For whatever reason the length computation gets upset if we tell it
	 * that we are using elements. It wants to discover it on its own, NO
	 * CLUES! (Ms. Doyle)
	 *
	 * https://github.com/ElementsProject/libwally-core/issues/139
	 */
	wally_tx_get_length(tx->wtx, flags & ~WALLY_TX_FLAG_USE_ELEMENTS,
			    &wsize);

	/* We don't know the input amounts yet, so set them all to NULL */
	tx->input_amounts =
	    tal_arrz(tx, struct amount_sat *, tx->wtx->inputs_allocation_len);
	tx->chainparams = NULL;

	*cursor += wsize;
	*max -= wsize;
	return tx;
}

struct bitcoin_tx *bitcoin_tx_from_hex(const tal_t *ctx, const char *hex,
				       size_t hexlen)
{
	const char *end;
	u8 *linear_tx;
	const u8 *p;
	struct bitcoin_tx *tx;
	size_t len;

	end = memchr(hex, '\n', hexlen);
	if (!end)
		end = hex + hexlen;

	len = hex_data_size(end - hex);
	p = linear_tx = tal_arr(ctx, u8, len);
	if (!hex_decode(hex, end - hex, linear_tx, len))
		goto fail;

	tx = pull_bitcoin_tx(ctx, &p, &len);
	if (!tx)
		goto fail;

	if (len)
		goto fail_free_tx;

	tal_free(linear_tx);
	return tx;

fail_free_tx:
	tal_free(tx);
fail:
	tal_free(linear_tx);
	return NULL;
}

/* <sigh>.  Bitcoind represents hashes as little-endian for RPC. */
static void reverse_bytes(u8 *arr, size_t len)
{
	unsigned int i;

	for (i = 0; i < len / 2; i++) {
		unsigned char tmp = arr[i];
		arr[i] = arr[len - 1 - i];
		arr[len - 1 - i] = tmp;
	}
}

bool bitcoin_txid_from_hex(const char *hexstr, size_t hexstr_len,
			   struct bitcoin_txid *txid)
{
	if (!hex_decode(hexstr, hexstr_len, txid, sizeof(*txid)))
		return false;
	reverse_bytes(txid->shad.sha.u.u8, sizeof(txid->shad.sha.u.u8));
	return true;
}

bool bitcoin_txid_to_hex(const struct bitcoin_txid *txid,
			 char *hexstr, size_t hexstr_len)
{
	struct sha256_double rev = txid->shad;
	reverse_bytes(rev.sha.u.u8, sizeof(rev.sha.u.u8));
	return hex_encode(&rev, sizeof(rev), hexstr, hexstr_len);
}

static char *fmt_bitcoin_tx(const tal_t *ctx, const struct bitcoin_tx *tx)
{
	u8 *lin = linearize_tx(ctx, tx);
	char *s = tal_hex(ctx, lin);
	tal_free(lin);
	return s;
}

static char *fmt_bitcoin_txid(const tal_t *ctx, const struct bitcoin_txid *txid)
{
	char *hexstr = tal_arr(ctx, char, hex_str_size(sizeof(*txid)));

	bitcoin_txid_to_hex(txid, hexstr, hex_str_size(sizeof(*txid)));
	return hexstr;
}

REGISTER_TYPE_TO_STRING(bitcoin_tx, fmt_bitcoin_tx);
REGISTER_TYPE_TO_STRING(bitcoin_txid, fmt_bitcoin_txid);