package bitcoin

// TODO: coinbase transaction

// https://learnmeabitcoin.com/technical/blkdat

import (
	"github.com/wader/fq/format"
	"github.com/wader/fq/pkg/decode"
	"github.com/wader/fq/pkg/interp"
	"github.com/wader/fq/pkg/scalar"
)

var bitcoinScriptFormat decode.Group

func init() {
	interp.RegisterFormat(decode.Format{
		Name:        format.BITCOIN_TRANSACTION,
		Description: "Bitcoin transaction",
		Dependencies: []decode.Dependency{
			{Names: []string{format.BITCOIN_SCRIPT}, Group: &bitcoinScriptFormat},
		},
		DecodeFn: decodeBitcoinTranscation,
	})
}

// Prefix with fd, and the next 2 bytes is the VarInt (in little-endian).
// Prefix with fe, and the next 4 bytes is the VarInt (in little-endian).
// Prefix with ff, and the next 8 bytes is the VarInt (in little-endian).
func decodeVarInt(d *decode.D) uint64 {
	n := d.U8()
	switch n {
	case 0xfd:
		return d.U16()
	case 0xfe:
		return d.U32()
	case 0xff:
		return d.U64()
	default:
		return n
	}
}

// all zero
var txIDCoinbaseBytes = [32]byte{}

func decodeBitcoinTranscation(d *decode.D, in interface{}) interface{} {
	d.Endian = decode.LittleEndian

	d.FieldU32("version")
	witness := false
	if d.PeekBits(8) == 0 {
		witness = true
		d.FieldU8("marker")
		d.FieldU8("flag")
	}
	inputCount := d.FieldUintFn("input_count", decodeVarInt)
	d.FieldArray("inputs", func(d *decode.D) {
		for i := uint64(0); i < inputCount; i++ {
			d.FieldStruct("input", func(d *decode.D) {
				d.FieldRawLen("txid", 32*8, scalar.RawBytesMap{
					{Bytes: txIDCoinbaseBytes[:], Scalar: scalar.BitBuf{Description: "coinbase"}},
				}, rawHexReverse)
				d.FieldU32("vout")
				scriptSigSize := d.FieldUintFn("scriptsig_size", decodeVarInt)
				d.FieldFormatOrRawLen("scriptsig", int64(scriptSigSize)*8, bitcoinScriptFormat, nil)
				// TODO: better way to know if there should be a valid script
				d.FieldU32("sequence", scalar.UintHex)
			})
		}
	})
	outputCount := d.FieldUintFn("output_count", decodeVarInt)
	d.FieldArray("outputs", func(d *decode.D) {
		for i := uint64(0); i < outputCount; i++ {
			d.FieldStruct("output", func(d *decode.D) {
				d.FieldU64("value")
				scriptSigSize := d.FieldUintFn("scriptpub_size", decodeVarInt)
				// TODO: better way to know if there should be a valid script
				d.FieldFormatOrRawLen("scriptpub", int64(scriptSigSize)*8, bitcoinScriptFormat, nil)
			})
		}
	})

	if witness {
		d.FieldArray("witnesses", func(d *decode.D) {
			for i := uint64(0); i < inputCount; i++ {
				d.FieldStruct("witness", func(d *decode.D) {
					witnessSize := d.FieldUintFn("witness_size", decodeVarInt)
					d.FieldArray("items", func(d *decode.D) {
						for j := uint64(0); j < witnessSize; j++ {
							itemSize := d.FieldUintFn("item_size", decodeVarInt)
							d.FieldRawLen("item", int64(itemSize)*8)
						}
					})
				})
			}
		})
	}
	d.FieldU32("locktime")

	return nil
}
