package lnwire

import (
	"bytes"
	"fmt"
	"io"
	"sync"
)

// CustomTypeStart is the start of the custom type range for peer messages as
// defined in BOLT 01.
const CustomTypeStart MessageType = 32768

var (
	// customTypeOverride contains a set of message types < CustomTypeStart
	// that lnd allows to be treated as custom messages. This allows us to
	// override messages reserved for the protocol level and treat them as
	// custom messages. This set of message types is stored as a global so
	// that we do not need to pass around state when accounting for this
	// set of messages in message creation.
	//
	// Note: This global is protected by the customTypeOverride mutex.
	customTypeOverride map[MessageType]struct{}

	// customTypeOverrideMtx manages concurrent access to
	// customTypeOverride.
	customTypeOverrideMtx sync.RWMutex
)

// SetCustomOverrides validates that the set of override types are outside of
// the custom message range (there's no reason to override messages that are
// already within the range), and updates the customTypeOverride global to hold
// this set of message types. Note that this function will completely overwrite
// the set of overrides, so should be called with the full set of types.
func SetCustomOverrides(overrideTypes []uint16) error {
	customTypeOverrideMtx.Lock()
	defer customTypeOverrideMtx.Unlock()

	customTypeOverride = make(map[MessageType]struct{}, len(overrideTypes))

	for _, t := range overrideTypes {
		msgType := MessageType(t)

		if msgType >= CustomTypeStart {
			return fmt.Errorf("can't override type: %v, already "+
				"in custom range", t)
		}

		customTypeOverride[msgType] = struct{}{}
	}

	return nil
}

// IsCustomOverride returns a bool indicating whether the message type is one
// of the protocol messages that we override for custom use.
func IsCustomOverride(t MessageType) bool {
	customTypeOverrideMtx.RLock()
	defer customTypeOverrideMtx.RUnlock()

	_, ok := customTypeOverride[t]

	return ok
}

// Custom represents an application-defined wire message.
type Custom struct {
	Type MessageType
	Data []byte
}

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

// NewCustom instantiates a new custom message.
func NewCustom(msgType MessageType, data []byte) (*Custom, error) {
	if msgType < CustomTypeStart && !IsCustomOverride(msgType) {
		return nil, fmt.Errorf("msg type: %d not in custom range: %v "+
			"and not overridden", msgType, CustomTypeStart)
	}

	return &Custom{
		Type: msgType,
		Data: data,
	}, nil
}

// Encode serializes the target Custom message into the passed io.Writer
// implementation.
//
// This is part of the lnwire.Message interface.
func (c *Custom) Encode(b *bytes.Buffer, pver uint32) error {
	_, err := b.Write(c.Data)
	return err
}

// Decode deserializes the serialized Custom message stored in the passed
// io.Reader into the target Custom message.
//
// This is part of the lnwire.Message interface.
func (c *Custom) Decode(r io.Reader, pver uint32) error {
	var b bytes.Buffer
	if _, err := io.Copy(&b, r); err != nil {
		return err
	}

	c.Data = b.Bytes()

	return nil
}

// MsgType returns the uint32 code which uniquely identifies this message as a
// Custom message on the wire.
//
// This is part of the lnwire.Message interface.
func (c *Custom) MsgType() MessageType {
	return c.Type
}
