package lnwire

import (
	"fmt"
	"io"

	"github.com/roasbeef/btcd/btcec"
	"github.com/roasbeef/btcutil"
)

// SingleFundingResponse is the message Bob sends to Alice after she initiates
// the single funder channel workflow via a SingleFundingRequest message. Once
// Alice receives Bob's reponse, then she has all the items neccessary to
// construct the funding transaction, and both commitment transactions.
type SingleFundingResponse struct {
	// ChannelID serves to uniquely identify the future channel created by
	// the initiated single funder workflow.
	ChannelID uint64

	// ChannelDerivationPoint is an secp256k1 point which will be used to
	// derive the public key the responder will use for the half of the
	// 2-of-2 multi-sig. Using the channel derivation point (CDP), and the
	// responder's identity public key (A), the channel public key is
	// computed as: C = A + CDP. In order to be valid all CDP's MUST have
	// an odd y-coordinate.
	ChannelDerivationPoint *btcec.PublicKey

	// CommitmentKey is key the responder to the funding workflow wishes to
	// use within their versino of the commitment transaction for any
	// delayed (CSV) or immediate outputs to them.
	CommitmentKey *btcec.PublicKey

	// RevocationKey is the initial key to be used for the revocation
	// clause within the self-output of the responder's commitment
	// transaction. Once an initial new state is created, the responder
	// will send a preimage which will allow the initiator to sweep the
	// responder's funds if the violate the contract.
	RevocationKey *btcec.PublicKey

	// CsvDelay is the number of blocks to use for the relative time lock
	// in the pay-to-self output of both commitment transactions.
	CsvDelay uint32

	// DeliveryPkScript defines the public key script that the initiator
	// would like to use to receive their balance in the case of a
	// cooperative close. Only the following script templates are
	// supported: P2PKH, P2WKH, P2SH, and P2WSH.
	DeliveryPkScript PkScript

	// DustLimit is the threshold below which no HTLC output should be
	// generated for remote commitment transaction; ie. HTLCs below
	// this amount are not enforceable onchain for their point of view.
	DustLimit btcutil.Amount
}

// NewSingleFundingResponse creates, and returns a new empty
// SingleFundingResponse.
func NewSingleFundingResponse(chanID uint64, rk, ck, cdp *btcec.PublicKey,
	delay uint32, deliveryScript PkScript,
	dustLimit btcutil.Amount) *SingleFundingResponse {

	return &SingleFundingResponse{
		ChannelID:              chanID,
		ChannelDerivationPoint: cdp,
		CommitmentKey:          ck,
		RevocationKey:          rk,
		CsvDelay:               delay,
		DeliveryPkScript:       deliveryScript,
		DustLimit:              dustLimit,
	}
}

// A compile time check to ensure SingleFundingResponse implements the
// lnwire.Message interface.
var _ Message = (*SingleFundingResponse)(nil)

// Decode deserializes the serialized SingleFundingResponse stored in the passed
// io.Reader into the target SingleFundingResponse using the deserialization
// rules defined by the passed protocol version.
//
// This is part of the lnwire.Message interface.
func (c *SingleFundingResponse) Decode(r io.Reader, pver uint32) error {
	// ChannelID (8)
	// ChannelDerivationPoint (33)
	// CommitmentKey (33)
	// RevocationKey (33)
	// CsvDelay (4)
	// DeliveryPkScript (final delivery)
	// DustLimit (8)
	err := readElements(r,
		&c.ChannelID,
		&c.ChannelDerivationPoint,
		&c.CommitmentKey,
		&c.RevocationKey,
		&c.CsvDelay,
		&c.DeliveryPkScript,
		&c.DustLimit)
	if err != nil {
		return err
	}

	return nil
}

// Encode serializes the target SingleFundingResponse into the passed io.Writer
// implementation. Serialization will observe the rules defined by the passed
// protocol version.
//
// This is part of the lnwire.Message interface.
func (c *SingleFundingResponse) Encode(w io.Writer, pver uint32) error {
	// ChannelID (8)
	// ChannelDerivationPoint (33)
	// CommitmentKey (33)
	// RevocationKey (33)
	// CsvDelay (4)
	// DeliveryPkScript (final delivery)
	// DustLimit (8)
	err := writeElements(w,
		c.ChannelID,
		c.ChannelDerivationPoint,
		c.CommitmentKey,
		c.RevocationKey,
		c.CsvDelay,
		c.DeliveryPkScript,
		c.DustLimit)
	if err != nil {
		return err
	}

	return nil
}

// Command returns the uint32 code which uniquely identifies this message as a
// SingleFundingRequest on the wire.
//
// This is part of the lnwire.Message interface.
func (c *SingleFundingResponse) Command() uint32 {
	return CmdSingleFundingResponse
}

// MaxPayloadLength returns the maximum allowed payload length for a
// SingleFundingResponse. This is calculated by summing the max length of all
// the fields within a SingleFundingResponse. To enforce a maximum
// DeliveryPkScript size, the size of a P2PKH public key script is used.
// Therefore, the final breakdown is: 8 + (33 * 3) + 8 + 25 + 8
//
// This is part of the lnwire.Message interface.
func (c *SingleFundingResponse) MaxPayloadLength(uint32) uint32 {
	return 148
}

// Validate examines each populated field within the SingleFundingResponse for
// field sanity. For example, all fields MUST NOT be negative, and all pkScripts
// must belong to the allowed set of public key scripts.
//
// This is part of the lnwire.Message interface.
func (c *SingleFundingResponse) Validate() error {
	// The channel derivation point must be non-nil, and have an odd
	// y-coordinate.
	if c.ChannelDerivationPoint == nil {
		return fmt.Errorf("The channel derivation point must be non-nil")
	}
	//if c.ChannelDerivationPoint.Y.Bit(0) != 1 {
	//	return fmt.Errorf("The channel derivation point must have an odd " +
	//		"y-coordinate")
	//}

	// The delivery pkScript must be amongst the supported script
	// templates.
	if !isValidPkScript(c.DeliveryPkScript) {
		return fmt.Errorf("Valid delivery public key scripts MUST be: " +
			"P2PKH, P2WKH, P2SH, or P2WSH.")
	}

	if c.DustLimit <= 0 {
		return fmt.Errorf("Dust limit shouldn't be below or equal to " +
			"zero.")
	}

	// We're good!
	return nil
}