// Copyright (c) 2013 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package btcwire

import (
	"bytes"
	"io"
)

// MaxBlocksPerMsg is the maximum number of blocks allowed per message.
const MaxBlocksPerMsg = 500

// TxLoc holds locator data for the offset and length of where a transaction is
// located within a MsgBlock data buffer.
type TxLoc struct {
	TxStart int
	TxLen   int
}

// MsgBlock implements the Message interface and represents a bitcoin
// block message.  It is used to deliver block and transaction information in
// response to a getdata message (MsgGetData) for a given block hash.
//
// NOTE: Unlike the other message types which contain slices, the number of
// transactions has a specific entry (Header.TxnCount) that must be kept in
// sync.  The AddTransaction and ClearTransactions functions properly sync the
// value, but if you are manually modifying the public members, you will need
// to ensure you update the Header.TxnCount when you add and remove
// transactions.
type MsgBlock struct {
	Header       BlockHeader
	Transactions []*MsgTx
}

// AddTransaction adds a transaction to the message and updates Header.TxnCount
// accordingly.
func (msg *MsgBlock) AddTransaction(tx *MsgTx) error {
	// TODO: Return error if adding the transaction would make the message
	// too large.
	msg.Transactions = append(msg.Transactions, tx)
	msg.Header.TxnCount = uint64(len(msg.Transactions))
	return nil

}

// ClearTransactions removes all transactions from the message and updates
// Header.TxnCount accordingly.
func (msg *MsgBlock) ClearTransactions() {
	msg.Transactions = []*MsgTx{}
	msg.Header.TxnCount = 0
}

// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
// This is part of the Message interface implementation.
func (msg *MsgBlock) BtcDecode(r io.Reader, pver uint32) error {
	err := readBlockHeader(r, pver, &msg.Header)
	if err != nil {
		return err
	}

	for i := uint64(0); i < msg.Header.TxnCount; i++ {
		tx := MsgTx{}
		err := tx.BtcDecode(r, pver)
		if err != nil {
			return err
		}
		msg.Transactions = append(msg.Transactions, &tx)
	}

	return nil
}

// BtcDecodeTxLoc decodes r using the bitcoin protocol encoding into the
// receiver and returns a slice containing the start and length each transaction
// within the raw data.
func (msg *MsgBlock) BtcDecodeTxLoc(r *bytes.Buffer, pver uint32) ([]TxLoc, error) {
	var fullLen int
	fullLen = r.Len()

	err := readBlockHeader(r, pver, &msg.Header)
	if err != nil {
		return nil, err
	}

	var txLocs []TxLoc
	txLocs = make([]TxLoc, msg.Header.TxnCount)

	for i := uint64(0); i < msg.Header.TxnCount; i++ {
		txLocs[i].TxStart = fullLen - r.Len()
		tx := MsgTx{}
		err := tx.BtcDecode(r, pver)
		if err != nil {
			return nil, err
		}
		msg.Transactions = append(msg.Transactions, &tx)
		txLocs[i].TxLen = (fullLen - r.Len()) - txLocs[i].TxStart
	}

	return txLocs, nil
}

// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
// This is part of the Message interface implementation.
func (msg *MsgBlock) BtcEncode(w io.Writer, pver uint32) error {
	msg.Header.TxnCount = uint64(len(msg.Transactions))

	err := writeBlockHeader(w, pver, &msg.Header)
	if err != nil {
		return err
	}

	for _, tx := range msg.Transactions {
		err = tx.BtcEncode(w, pver)
		if err != nil {
			return err
		}
	}

	return nil
}

// Command returns the protocol command string for the message.  This is part
// of the Message interface implementation.
func (msg *MsgBlock) Command() string {
	return cmdBlock
}

// MaxPayloadLength returns the maximum length the payload can be for the
// receiver.  This is part of the Message interface implementation.
func (msg *MsgBlock) MaxPayloadLength(pver uint32) uint32 {
	// Block header at 81 bytes + max transactions which can vary up to the
	// max message payload.
	return maxMessagePayload
}

// BlockSha computes the block identifier hash for this block.
func (msg *MsgBlock) BlockSha(pver uint32) (ShaHash, error) {
	return msg.Header.BlockSha(pver)
}

// TxShas returns a slice of hashes of all of transactions in this block.
func (msg *MsgBlock) TxShas(pver uint32) ([]ShaHash, error) {
	var shaList []ShaHash
	for _, tx := range msg.Transactions {
		sha, err := tx.TxSha(pver)
		if err != nil {
			return nil, err
		}
		shaList = append(shaList, sha)
	}
	return shaList, nil
}

// NewMsgBlock returns a new bitcoin block message that conforms to the
// Message interface.  See MsgBlock for details.
func NewMsgBlock(blockHeader *BlockHeader) *MsgBlock {
	return &MsgBlock{
		Header: *blockHeader,
	}
}