package wire

import (
	"bytes"
	"errors"
	"io"
	"math"

	"github.com/lucas-clemente/quic-go/internal/protocol"
	"github.com/lucas-clemente/quic-go/internal/utils"
	"github.com/lucas-clemente/quic-go/qerr"
)

// A ConnectionCloseFrame in QUIC
type ConnectionCloseFrame struct {
	ErrorCode    qerr.ErrorCode
	ReasonPhrase string
}

// ParseConnectionCloseFrame reads a CONNECTION_CLOSE frame
func ParseConnectionCloseFrame(r *bytes.Reader, version protocol.VersionNumber) (*ConnectionCloseFrame, error) {
	if _, err := r.ReadByte(); err != nil { // read the TypeByte
		return nil, err
	}

	var errorCode qerr.ErrorCode
	var reasonPhraseLen uint64
	if version.UsesIETFFrameFormat() {
		ec, err := utils.BigEndian.ReadUint16(r)
		if err != nil {
			return nil, err
		}
		errorCode = qerr.ErrorCode(ec)
		reasonPhraseLen, err = utils.ReadVarInt(r)
		if err != nil {
			return nil, err
		}
	} else {
		ec, err := utils.BigEndian.ReadUint32(r)
		if err != nil {
			return nil, err
		}
		errorCode = qerr.ErrorCode(ec)
		length, err := utils.BigEndian.ReadUint16(r)
		if err != nil {
			return nil, err
		}
		reasonPhraseLen = uint64(length)
	}

	// shortcut to prevent the unnecessary allocation of dataLen bytes
	// if the dataLen is larger than the remaining length of the packet
	// reading the whole reason phrase would result in EOF when attempting to READ
	if int(reasonPhraseLen) > r.Len() {
		return nil, io.EOF
	}

	reasonPhrase := make([]byte, reasonPhraseLen)
	if _, err := io.ReadFull(r, reasonPhrase); err != nil {
		// this should never happen, since we already checked the reasonPhraseLen earlier
		return nil, err
	}

	return &ConnectionCloseFrame{
		ErrorCode:    errorCode,
		ReasonPhrase: string(reasonPhrase),
	}, nil
}

// Length of a written frame
func (f *ConnectionCloseFrame) Length(version protocol.VersionNumber) protocol.ByteCount {
	if version.UsesIETFFrameFormat() {
		return 1 + 2 + utils.VarIntLen(uint64(len(f.ReasonPhrase))) + protocol.ByteCount(len(f.ReasonPhrase))
	}
	return 1 + 4 + 2 + protocol.ByteCount(len(f.ReasonPhrase))
}

// Write writes an CONNECTION_CLOSE frame.
func (f *ConnectionCloseFrame) Write(b *bytes.Buffer, version protocol.VersionNumber) error {
	b.WriteByte(0x02)

	if len(f.ReasonPhrase) > math.MaxUint16 {
		return errors.New("ConnectionFrame: ReasonPhrase too long")
	}

	if version.UsesIETFFrameFormat() {
		utils.BigEndian.WriteUint16(b, uint16(f.ErrorCode))
		utils.WriteVarInt(b, uint64(len(f.ReasonPhrase)))
	} else {
		utils.BigEndian.WriteUint32(b, uint32(f.ErrorCode))
		utils.BigEndian.WriteUint16(b, uint16(len(f.ReasonPhrase)))
	}
	b.WriteString(f.ReasonPhrase)

	return nil
}
