#ifndef LIGHTNING_COMMON_COIN_MVT_H
#define LIGHTNING_COMMON_COIN_MVT_H
#include "config.h"

#include <bitcoin/tx.h>
#include <common/channel_id.h>

#define COIN_MVT_VERSION 2
#define ACCOUNT_NAME_WALLET "wallet"
#define ACCOUNT_NAME_EXTERNAL "external"

/* /!\ You cannot change this order, it's committed to the db! /!\ */
enum mvt_tag {
	MVT_DEPOSIT = 0,
	MVT_WITHDRAWAL = 1,
	MVT_PENALTY = 2,
	MVT_INVOICE = 3,
	MVT_ROUTED = 4,
	MVT_PUSHED = 5,
	MVT_CHANNEL_OPEN = 6,
	MVT_CHANNEL_CLOSE = 7,
	MVT_CHANNEL_TO_US = 8,
	MVT_HTLC_TIMEOUT = 9,
	MVT_HTLC_FULFILL = 10,
	MVT_HTLC_TX = 11,
	MVT_TO_WALLET = 12,
	MVT_ANCHOR = 13,
	MVT_TO_THEM = 14,
	MVT_PENALIZED = 15,
	MVT_STOLEN = 16,
	MVT_TO_MINER = 17,
	MVT_OPENER = 18,
	MVT_LEASE_FEE = 19,
	MVT_LEASED = 20,
	MVT_STEALABLE = 21,
	MVT_CHANNEL_PROPOSED = 22,
	MVT_SPLICE = 23,
	MVT_PENALTY_ADJ = 24,
	MVT_JOURNAL = 25,
	MVT_FOREIGN = 26,
	MVT_IGNORED = 27,
#define NUM_MVT_TAGS (MVT_IGNORED + 1)
};

struct mvt_tags {
	u64 bits;
};

enum coin_mvt_dir {
	COIN_CREDIT = 1,
	COIN_DEBIT = 2,
};

struct channel_coin_mvt_id {
	/* multi-part payments may share a payment hash,
	 * so we should also record part-id and group-id for them */
	u64 part_id;
	u64 group_id;
};

/* Only one of these is set. */
struct mvt_account_id {
	const struct channel *channel;
	const char *alt_account;
};

struct channel_coin_mvt {
	/* Common fields */
	struct mvt_account_id account;
	struct mvt_tags tags;
	/* only one or the other */
	struct amount_msat credit;
	struct amount_msat debit;
	u64 timestamp;

	/* identifier */
	const struct sha256 *payment_hash;

	/* multi-part payments may share a payment hash,
	 * so we should also record part-id and group-id for them */
	const struct channel_coin_mvt_id *part_and_group;

	/* Fees collected (or paid) on this mvt */
	struct amount_msat fees;
};

struct chain_coin_mvt {
	/* account_id */
	struct mvt_account_id account;
	struct mvt_tags tags;
	/* only one or the other */
	struct amount_msat credit;
	struct amount_msat debit;
	u64 timestamp;
	struct bitcoin_outpoint outpoint;

	const struct bitcoin_txid *spending_txid;

	/* The id of the peer we have this channel with.
	 * Only on our channel_open events */
	const struct node_id *peer_id;

	/* some on-chain movements have a payment hash */
	const struct sha256 *payment_hash;

	/* block this transaction is confirmed in */
	u32 blockheight;

	/* total value of output (useful for tracking external outs) */
	struct amount_sat output_val;

	/* When we pay to external accounts, it's useful
	 * to track which internal account it originated from */
	const struct mvt_account_id *originating_acct;

	/* Number of outputs in spending tx; used by the
	 * `channel_close` event */
	u32 output_count;
};

/* Convenience macro for creating tag bitmaps */
#define mk_mvt_tags(...) mk_mvt_tags_(__VA_ARGS__, 999)
struct mvt_tags mk_mvt_tags_(enum mvt_tag tag, ...);

static inline struct mvt_tags tag_to_mvt_tags(enum mvt_tag tag)
{
	struct mvt_tags tags;
	tags.bits = ((u64)1) << tag;
	return tags;
}

/* Add a tag */
void mvt_tag_set(struct mvt_tags *tags, enum mvt_tag tag);

/* Extract the primary tag */
enum mvt_tag primary_mvt_tag(struct mvt_tags tags);

/* Useful for assertions */
bool mvt_tags_valid(struct mvt_tags tags);

/* Useful constructor for mvt_account_id: exactly one of channel/account_name must be NULL */
void set_mvt_account_id(struct mvt_account_id *acct_id,
			const struct channel *channel,
			const char *account_name TAKES);

/* Allocating version */
struct mvt_account_id *new_mvt_account_id(const tal_t *ctx,
					  const struct channel *channel,
					  const char *account_name TAKES);

/* Either part_id and group_id both NULL, or neither are */
struct channel_coin_mvt *new_channel_coin_mvt(const tal_t *ctx,
					      const struct channel *channel,
					      u64 timestamp,
					      const struct sha256 *payment_hash TAKES,
					      const u64 *part_id,
					      const u64 *group_id,
					      enum coin_mvt_dir direction,
					      struct amount_msat amount,
					      struct mvt_tags tags,
					      struct amount_msat fees)
	NON_NULL_ARGS(2);

struct chain_coin_mvt *new_onchaind_withdraw(const tal_t *ctx,
					     const struct bitcoin_outpoint *outpoint,
					     const struct bitcoin_txid *spend_txid,
					     u32 blockheight,
					     struct amount_sat amount,
					     struct mvt_tags tags)
	NON_NULL_ARGS(2, 3);

struct chain_coin_mvt *new_onchaind_deposit(const tal_t *ctx,
					    const struct bitcoin_outpoint *outpoint,
					    u32 blockheight,
					    struct amount_sat amount,
					    struct mvt_tags tags)
	NON_NULL_ARGS(2);

struct chain_coin_mvt *new_coin_channel_close(const tal_t *ctx,
					      const struct channel *channel,
					      const char *alt_account,
					      const struct bitcoin_txid *txid,
					      const struct bitcoin_outpoint *out,
					      u32 blockheight,
					      const struct amount_msat amount,
					      const struct amount_sat output_val,
					      u32 output_count,
					      bool is_splice)
	NON_NULL_ARGS(4, 5);

struct chain_coin_mvt *new_coin_channel_open_proposed(const tal_t *ctx,
						      const struct channel *channel,
						      const struct bitcoin_outpoint *out,
						      const struct node_id *peer_id,
						      const struct amount_msat amount,
						      const struct amount_sat output_val,
						      bool is_opener,
						      bool is_leased)
	NON_NULL_ARGS(2, 3, 4);

struct chain_coin_mvt *new_coin_channel_open(const tal_t *ctx,
					     const struct channel *channel,
					     const struct bitcoin_outpoint *out,
					     const struct node_id *peer_id,
					     u32 blockheight,
					     const struct amount_msat amount,
					     const struct amount_sat output_val,
					     bool is_opener,
					     bool is_leased)
	NON_NULL_ARGS(2, 3, 4);

struct chain_coin_mvt *new_onchain_htlc_deposit(const tal_t *ctx,
						const struct bitcoin_outpoint *outpoint,
						u32 blockheight,
						struct amount_sat amount,
						const struct sha256 *payment_hash)
	NON_NULL_ARGS(2, 5);

struct chain_coin_mvt *new_onchain_htlc_withdraw(const tal_t *ctx,
						 const struct bitcoin_outpoint *outpoint,
						 u32 blockheight,
						 struct amount_sat amount,
						 const struct sha256 *payment_hash)
	NON_NULL_ARGS(2, 5);

struct chain_coin_mvt *new_coin_wallet_deposit(const tal_t *ctx,
					       const struct bitcoin_outpoint *outpoint,
					       u32 blockheight,
					       struct amount_sat amount,
					       struct mvt_tags tags)
	NON_NULL_ARGS(2);


struct chain_coin_mvt *new_coin_wallet_withdraw(const tal_t *ctx,
						const struct bitcoin_txid *spend_txid,
						const struct bitcoin_outpoint *outpoint,
						u32 blockheight,
						struct amount_sat amount,
						struct mvt_tags tags)
	NON_NULL_ARGS(2, 3);

struct chain_coin_mvt *new_coin_external_spend(const tal_t *ctx,
					       const struct bitcoin_outpoint *outpoint,
					       const struct bitcoin_txid *txid,
					       u32 blockheight,
					       struct amount_sat amount,
					       struct mvt_tags tags)
	NON_NULL_ARGS(2, 3);

struct chain_coin_mvt *new_coin_external_deposit(const tal_t *ctx,
						 const struct bitcoin_outpoint *outpoint,
						 u32 blockheight,
						 struct amount_sat amount,
						 struct mvt_tags tags)
	NON_NULL_ARGS(2);

struct channel_coin_mvt *new_coin_channel_push(const tal_t *ctx,
					       const struct channel *channel,
					       enum coin_mvt_dir direction,
					       struct amount_msat amount,
					       struct mvt_tags tags)
	NON_NULL_ARGS(2);

/* FIXME: Does not set originating_acct, caller must do that! */
struct chain_coin_mvt *new_foreign_deposit(const tal_t *ctx,
					   const struct bitcoin_outpoint *outpoint,
					   u32 blockheight,
					   struct amount_sat amount,
					   const char *account,
					   u64 timestamp)
	NON_NULL_ARGS(2, 5);

struct chain_coin_mvt *new_foreign_withdrawal(const tal_t *ctx,
					      const struct bitcoin_outpoint *outpoint,
					      const struct bitcoin_txid *spend_txid,
					      struct amount_sat amount,
					      u32 blockheight,
					      const char *account,
					      u64 timestamp)
	NON_NULL_ARGS(2, 3, 6);

/* Generic versions (prefer the above ones: these are for migrations) */
struct channel_coin_mvt *new_channel_coin_mvt_general(const tal_t *ctx,
						      const struct channel *channel,
						      const struct channel_id *cid,
						      u64 timestamp,
						      const struct sha256 *payment_hash TAKES,
						      const u64 *part_id,
						      const u64 *group_id,
						      enum coin_mvt_dir direction,
						      struct amount_msat amount,
						      struct mvt_tags tags,
						      struct amount_msat fees);

struct chain_coin_mvt *new_coin_channel_open_general(const tal_t *ctx,
						     const struct channel *channel,
						     const struct channel_id *cid,
						     u64 timestamp,
						     const struct bitcoin_outpoint *out,
						     const struct node_id *peer_id,
						     u32 blockheight,
						     const struct amount_msat amount,
						     const struct amount_sat output_val,
						     bool is_opener,
						     bool is_leased);

struct channel_coin_mvt *new_coin_channel_push_general(const tal_t *ctx,
						       const struct channel *channel,
						       const struct channel_id *cid,
						       u64 timestamp,
						       enum coin_mvt_dir direction,
						       struct amount_msat amount,
						       struct mvt_tags tags);

/* There are three standard accounts:
 * "wallet" for our internal wallet,
 * "external" for other bitcoin sources,
 * <channelid> for lightning channels.
 *
 * Exactly one of these is true:
 */
static inline bool is_wallet_account(const char *acctname)
{
	return streq(acctname, ACCOUNT_NAME_WALLET);
}

static inline bool is_external_account(const char *acctname)
{
	return streq(acctname, ACCOUNT_NAME_EXTERNAL);
}

static inline bool is_channel_account(const char *acctname)
{
	return !is_wallet_account(acctname) && !is_external_account(acctname);
}

/* Is this an xternal account? */
bool chain_mvt_is_external(const struct chain_coin_mvt *mvt);

const char *mvt_tag_str(enum mvt_tag tag);
const char **mvt_tag_strs(const tal_t *ctx, struct mvt_tags tags);

/* Parse a single mvt tag.  Returns false or populates *tag */
bool mvt_tag_parse(const char *buf, size_t len, enum mvt_tag *tag);

void towire_chain_coin_mvt(u8 **pptr, const struct chain_coin_mvt *mvt);
void fromwire_chain_coin_mvt(const u8 **cursor, size_t *max, struct chain_coin_mvt *mvt);

/* Time helper for deterministic timestamps: always moves forwards */
u64 coinmvt_current_time(void);

#endif /* LIGHTNING_COMMON_COIN_MVT_H */
