package bsv

import "math/big"

// BlockHeader mirrors lib/block/blockheader.
type BlockHeader struct {
	Version    int32
	PrevHash   []byte // 32 bytes, internal (little endian) order
	MerkleRoot []byte // 32 bytes, internal order
	Time       uint32
	Bits       uint32
	Nonce      uint32
}

// NewBlockHeader builds a BlockHeader from a buffer, hex string, or object.
func NewBlockHeader(arg interface{}) (*BlockHeader, error) {
	switch v := arg.(type) {
	case []byte:
		return BlockHeaderFromBuffer(v)
	case *BufferReader:
		return BlockHeaderFromBufferReader(v)
	case map[string]interface{}:
		return blockHeaderFromObject(v)
	case string:
		return BlockHeaderFromString(v)
	default:
		return nil, newError("InvalidArgument", "Unrecognized argument for BlockHeader")
	}
}

func blockHeaderFromObject(data map[string]interface{}) (*BlockHeader, error) {
	h := &BlockHeader{}
	switch v := data["version"].(type) {
	case int:
		h.Version = int32(v)
	case int32:
		h.Version = v
	case int64:
		h.Version = int32(v)
	case uint32:
		h.Version = int32(v)
	case float64:
		h.Version = int32(v)
	}
	if v, ok := data["prevHash"].(string); ok {
		h.PrevHash = reverseBuf(fromHex(v))
	} else if v, ok := data["prevHash"].([]byte); ok {
		h.PrevHash = v
	}
	if v, ok := data["merkleRoot"].(string); ok {
		h.MerkleRoot = reverseBuf(fromHex(v))
	} else if v, ok := data["merkleRoot"].([]byte); ok {
		h.MerkleRoot = v
	}
	switch v := data["time"].(type) {
	case int:
		h.Time = uint32(v)
	case int32:
		h.Time = uint32(v)
	case int64:
		h.Time = uint32(v)
	case uint32:
		h.Time = v
	case uint64:
		h.Time = uint32(v)
	case float64:
		h.Time = uint32(v)
	}
	switch v := data["bits"].(type) {
	case int:
		h.Bits = uint32(v)
	case int32:
		h.Bits = uint32(v)
	case int64:
		h.Bits = uint32(v)
	case uint32:
		h.Bits = v
	case uint64:
		h.Bits = uint32(v)
	case float64:
		h.Bits = uint32(v)
	}
	switch v := data["nonce"].(type) {
	case int:
		h.Nonce = uint32(v)
	case int32:
		h.Nonce = uint32(v)
	case int64:
		h.Nonce = uint32(v)
	case uint32:
		h.Nonce = v
	case uint64:
		h.Nonce = uint32(v)
	case float64:
		h.Nonce = uint32(v)
	}
	return h, nil
}

// BlockHeaderFromBufferReader parses a header from a reader.
func BlockHeaderFromBufferReader(br *BufferReader) (*BlockHeader, error) {
	h := &BlockHeader{}
	h.Version = br.ReadInt32LE()
	h.PrevHash = br.Read(32)
	h.MerkleRoot = br.Read(32)
	h.Time = br.ReadUInt32LE()
	h.Bits = br.ReadUInt32LE()
	h.Nonce = br.ReadUInt32LE()
	return h, nil
}

// BlockHeaderFromBuffer parses a header from a raw 80-byte buffer.
func BlockHeaderFromBuffer(buf []byte) (*BlockHeader, error) {
	br, err := NewBufferReader(buf)
	if err != nil {
		return nil, err
	}
	return BlockHeaderFromBufferReader(br)
}

// BlockHeaderFromString parses a hex header.
func BlockHeaderFromString(str string) (*BlockHeader, error) {
	return BlockHeaderFromBuffer(fromHex(str))
}

// ToBufferWriter serializes the header.
func (h *BlockHeader) ToBufferWriter(bw *BufferWriter) *BufferWriter {
	if bw == nil {
		bw = NewBufferWriter()
	}
	bw.WriteInt32LE(h.Version)
	bw.Write(h.PrevHash)
	bw.Write(h.MerkleRoot)
	bw.WriteUInt32LE(h.Time)
	bw.WriteUInt32LE(h.Bits)
	bw.WriteUInt32LE(h.Nonce)
	return bw
}

// ToBuffer serializes the header.
func (h *BlockHeader) ToBuffer() []byte {
	return h.ToBufferWriter(nil).ToBuffer()
}

// ToString returns the hex serialization.
func (h *BlockHeader) ToString() string { return toHex(h.ToBuffer()) }

// ToObject returns a plain object representation.
func (h *BlockHeader) ToObject() map[string]interface{} {
	return map[string]interface{}{
		"hash":       h.Hash(),
		"version":    h.Version,
		"prevHash":   toHex(reverseBuf(h.PrevHash)),
		"merkleRoot": toHex(reverseBuf(h.MerkleRoot)),
		"time":       h.Time,
		"bits":       h.Bits,
		"nonce":      h.Nonce,
	}
}

// getHash computes the double-SHA256 of the serialized header (internal order).
func (h *BlockHeader) getHash() []byte {
	return (Hash{}).Sha256sha256(h.ToBuffer())
}

// Hash returns the big-endian hex hash of the header.
func (h *BlockHeader) Hash() string {
	return toHex(reverseBuf(h.getHash()))
}

// GetTargetDifficulty decodes the difficulty bits into a target BN.
func (h *BlockHeader) GetTargetDifficulty(bits uint32) *BN {
	if bits == 0 {
		bits = h.Bits
	}
	target := NewBNFromNumber(int64(bits & 0xffffff))
	mov := int64(8 * ((bits >> 24) - 3))
	for mov > 0 {
		target = target.Mul(big.NewInt(2))
		mov--
	}
	return target
}

// ValidProofOfWork checks whether the hash satisfies the target difficulty.
func (h *BlockHeader) ValidProofOfWork() bool {
	pow, _ := NewBNFromHex(h.Hash())
	target := h.GetTargetDifficulty(0)
	return pow.Cmp(target.Int) <= 0
}

// BlockHeaderConstants.
const (
	BlockHeaderStartOfHeader = 8
	MaxTimeOffset            = 2 * 60 * 60
)

// Block mirrors lib/block/block.
type Block struct {
	Header       *BlockHeader
	Transactions []*Transaction
}

// BlockMaxBlockSize mirrors Block.MAX_BLOCK_SIZE.
const BlockMaxBlockSize = 128000000

// NewBlock builds a Block from a buffer, hex, or object.
func NewBlock(arg interface{}) (*Block, error) {
	switch v := arg.(type) {
	case []byte:
		return BlockFromBuffer(v)
	case string:
		return BlockFromString(v)
	case *Block:
		return v, nil
	default:
		return nil, newError("InvalidArgument", "Unrecognized argument for Block")
	}
}

// BlockFromString parses a hex block.
func BlockFromString(str string) (*Block, error) {
	return BlockFromBuffer(fromHex(str))
}

// BlockFromBuffer parses a raw block buffer.
func BlockFromBuffer(buf []byte) (*Block, error) {
	br, err := NewBufferReader(buf)
	if err != nil {
		return nil, err
	}
	header, err := BlockHeaderFromBufferReader(br)
	if err != nil {
		return nil, err
	}
	block := &Block{Header: header, Transactions: []*Transaction{}}
	txCount, err := br.ReadVarintNum()
	if err != nil {
		return nil, err
	}
	for i := 0; i < txCount; i++ {
		tx, err := TransactionFromBufferReader(br)
		if err != nil {
			return nil, err
		}
		block.Transactions = append(block.Transactions, tx)
	}
	return block, nil
}

// BlockFromRawBlock parses a block from raw wire data that includes the
// 8-byte magic/size prefix (mirrors Block.fromRawBlock). The size field
// limits how much of the buffer is consumed as the block.
func BlockFromRawBlock(data []byte) (*Block, error) {
	if len(data) < 8 {
		return nil, newError("InvalidArgument", "raw block data too short")
	}
	blockSize := int(uint32(data[4]) | uint32(data[5])<<8 | uint32(data[6])<<16 | uint32(data[7])<<24)
	blockEnd := 8 + blockSize
	if blockEnd > len(data) {
		blockEnd = len(data)
	}
	if blockEnd > len(data) {
		return nil, newError("InvalidArgument", "raw block data truncated")
	}
	return BlockFromBuffer(data[8:blockEnd])
}

// ToBufferWriter serializes the block.
func (b *Block) ToBufferWriter(bw *BufferWriter) *BufferWriter {
	if bw == nil {
		bw = NewBufferWriter()
	}
	b.Header.ToBufferWriter(bw)
	bw.WriteVarintNum(len(b.Transactions))
	for _, tx := range b.Transactions {
		bw.Write(tx.ToBuffer())
	}
	return bw
}

// ToBuffer serializes the block.
func (b *Block) ToBuffer() []byte {
	return b.ToBufferWriter(nil).ToBuffer()
}

// ToString returns the hex serialization.
func (b *Block) ToString() string { return toHex(b.ToBuffer()) }

// ToObject returns a plain object representation.
func (b *Block) ToObject() map[string]interface{} {
	txs := make([]map[string]interface{}, 0, len(b.Transactions))
	for _, tx := range b.Transactions {
		txs = append(txs, map[string]interface{}{"txid": tx.GetID(), "hex": tx.ToHex()})
	}
	return map[string]interface{}{
		"header":       b.Header.ToObject(),
		"transactions": txs,
	}
}