// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build go1.21

package quic

import (
	"fmt"
)

// A debugFrame is a representation of the contents of a QUIC frame,
// used for debug logs and testing but not the primary serving path.
type debugFrame interface {
	String() string
	write(w *packetWriter) bool
}

func parseDebugFrame(b []byte) (f debugFrame, n int) {
	if len(b) == 0 {
		return nil, -1
	}
	switch b[0] {
	case frameTypePadding:
		f, n = parseDebugFramePadding(b)
	case frameTypePing:
		f, n = parseDebugFramePing(b)
	case frameTypeAck, frameTypeAckECN:
		f, n = parseDebugFrameAck(b)
	case frameTypeResetStream:
		f, n = parseDebugFrameResetStream(b)
	case frameTypeStopSending:
		f, n = parseDebugFrameStopSending(b)
	case frameTypeCrypto:
		f, n = parseDebugFrameCrypto(b)
	case frameTypeNewToken:
		f, n = parseDebugFrameNewToken(b)
	case frameTypeStreamBase, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f:
		f, n = parseDebugFrameStream(b)
	case frameTypeMaxData:
		f, n = parseDebugFrameMaxData(b)
	case frameTypeMaxStreamData:
		f, n = parseDebugFrameMaxStreamData(b)
	case frameTypeMaxStreamsBidi, frameTypeMaxStreamsUni:
		f, n = parseDebugFrameMaxStreams(b)
	case frameTypeDataBlocked:
		f, n = parseDebugFrameDataBlocked(b)
	case frameTypeStreamDataBlocked:
		f, n = parseDebugFrameStreamDataBlocked(b)
	case frameTypeStreamsBlockedBidi, frameTypeStreamsBlockedUni:
		f, n = parseDebugFrameStreamsBlocked(b)
	case frameTypeNewConnectionID:
		f, n = parseDebugFrameNewConnectionID(b)
	case frameTypeRetireConnectionID:
		f, n = parseDebugFrameRetireConnectionID(b)
	case frameTypePathChallenge:
		f, n = parseDebugFramePathChallenge(b)
	case frameTypePathResponse:
		f, n = parseDebugFramePathResponse(b)
	case frameTypeConnectionCloseTransport:
		f, n = parseDebugFrameConnectionCloseTransport(b)
	case frameTypeConnectionCloseApplication:
		f, n = parseDebugFrameConnectionCloseApplication(b)
	case frameTypeHandshakeDone:
		f, n = parseDebugFrameHandshakeDone(b)
	default:
		return nil, -1
	}
	return f, n
}

// debugFramePadding is a sequence of PADDING frames.
type debugFramePadding struct {
	size int
}

func parseDebugFramePadding(b []byte) (f debugFramePadding, n int) {
	for n < len(b) && b[n] == frameTypePadding {
		n++
	}
	f.size = n
	return f, n
}

func (f debugFramePadding) String() string {
	return fmt.Sprintf("PADDING*%v", f.size)
}

func (f debugFramePadding) write(w *packetWriter) bool {
	if w.avail() == 0 {
		return false
	}
	for i := 0; i < f.size && w.avail() > 0; i++ {
		w.b = append(w.b, frameTypePadding)
	}
	return true
}

// debugFramePing is a PING frame.
type debugFramePing struct{}

func parseDebugFramePing(b []byte) (f debugFramePing, n int) {
	return f, 1
}

func (f debugFramePing) String() string {
	return "PING"
}

func (f debugFramePing) write(w *packetWriter) bool {
	return w.appendPingFrame()
}

// debugFrameAck is an ACK frame.
type debugFrameAck struct {
	ackDelay unscaledAckDelay
	ranges   []i64range[packetNumber]
}

func parseDebugFrameAck(b []byte) (f debugFrameAck, n int) {
	f.ranges = nil
	_, f.ackDelay, n = consumeAckFrame(b, func(_ int, start, end packetNumber) {
		f.ranges = append(f.ranges, i64range[packetNumber]{
			start: start,
			end:   end,
		})
	})
	// Ranges are parsed smallest to highest; reverse ranges slice to order them high to low.
	for i := 0; i < len(f.ranges)/2; i++ {
		j := len(f.ranges) - 1
		f.ranges[i], f.ranges[j] = f.ranges[j], f.ranges[i]
	}
	return f, n
}

func (f debugFrameAck) String() string {
	s := fmt.Sprintf("ACK Delay=%v", f.ackDelay)
	for _, r := range f.ranges {
		s += fmt.Sprintf(" [%v,%v)", r.start, r.end)
	}
	return s
}

func (f debugFrameAck) write(w *packetWriter) bool {
	return w.appendAckFrame(rangeset[packetNumber](f.ranges), f.ackDelay)
}

// debugFrameResetStream is a RESET_STREAM frame.
type debugFrameResetStream struct {
	id        streamID
	code      uint64
	finalSize int64
}

func parseDebugFrameResetStream(b []byte) (f debugFrameResetStream, n int) {
	f.id, f.code, f.finalSize, n = consumeResetStreamFrame(b)
	return f, n
}

func (f debugFrameResetStream) String() string {
	return fmt.Sprintf("RESET_STREAM ID=%v Code=%v FinalSize=%v", f.id, f.code, f.finalSize)
}

func (f debugFrameResetStream) write(w *packetWriter) bool {
	return w.appendResetStreamFrame(f.id, f.code, f.finalSize)
}

// debugFrameStopSending is a STOP_SENDING frame.
type debugFrameStopSending struct {
	id   streamID
	code uint64
}

func parseDebugFrameStopSending(b []byte) (f debugFrameStopSending, n int) {
	f.id, f.code, n = consumeStopSendingFrame(b)
	return f, n
}

func (f debugFrameStopSending) String() string {
	return fmt.Sprintf("STOP_SENDING ID=%v Code=%v", f.id, f.code)
}

func (f debugFrameStopSending) write(w *packetWriter) bool {
	return w.appendStopSendingFrame(f.id, f.code)
}

// debugFrameCrypto is a CRYPTO frame.
type debugFrameCrypto struct {
	off  int64
	data []byte
}

func parseDebugFrameCrypto(b []byte) (f debugFrameCrypto, n int) {
	f.off, f.data, n = consumeCryptoFrame(b)
	return f, n
}

func (f debugFrameCrypto) String() string {
	return fmt.Sprintf("CRYPTO Offset=%v Length=%v", f.off, len(f.data))
}

func (f debugFrameCrypto) write(w *packetWriter) bool {
	b, added := w.appendCryptoFrame(f.off, len(f.data))
	copy(b, f.data)
	return added
}

// debugFrameNewToken is a NEW_TOKEN frame.
type debugFrameNewToken struct {
	token []byte
}

func parseDebugFrameNewToken(b []byte) (f debugFrameNewToken, n int) {
	f.token, n = consumeNewTokenFrame(b)
	return f, n
}

func (f debugFrameNewToken) String() string {
	return fmt.Sprintf("NEW_TOKEN Token=%x", f.token)
}

func (f debugFrameNewToken) write(w *packetWriter) bool {
	return w.appendNewTokenFrame(f.token)
}

// debugFrameStream is a STREAM frame.
type debugFrameStream struct {
	id   streamID
	fin  bool
	off  int64
	data []byte
}

func parseDebugFrameStream(b []byte) (f debugFrameStream, n int) {
	f.id, f.off, f.fin, f.data, n = consumeStreamFrame(b)
	return f, n
}

func (f debugFrameStream) String() string {
	fin := ""
	if f.fin {
		fin = " FIN"
	}
	return fmt.Sprintf("STREAM ID=%v%v Offset=%v Length=%v", f.id, fin, f.off, len(f.data))
}

func (f debugFrameStream) write(w *packetWriter) bool {
	b, added := w.appendStreamFrame(f.id, f.off, len(f.data), f.fin)
	copy(b, f.data)
	return added
}

// debugFrameMaxData is a MAX_DATA frame.
type debugFrameMaxData struct {
	max int64
}

func parseDebugFrameMaxData(b []byte) (f debugFrameMaxData, n int) {
	f.max, n = consumeMaxDataFrame(b)
	return f, n
}

func (f debugFrameMaxData) String() string {
	return fmt.Sprintf("MAX_DATA Max=%v", f.max)
}

func (f debugFrameMaxData) write(w *packetWriter) bool {
	return w.appendMaxDataFrame(f.max)
}

// debugFrameMaxStreamData is a MAX_STREAM_DATA frame.
type debugFrameMaxStreamData struct {
	id  streamID
	max int64
}

func parseDebugFrameMaxStreamData(b []byte) (f debugFrameMaxStreamData, n int) {
	f.id, f.max, n = consumeMaxStreamDataFrame(b)
	return f, n
}

func (f debugFrameMaxStreamData) String() string {
	return fmt.Sprintf("MAX_STREAM_DATA ID=%v Max=%v", f.id, f.max)
}

func (f debugFrameMaxStreamData) write(w *packetWriter) bool {
	return w.appendMaxStreamDataFrame(f.id, f.max)
}

// debugFrameMaxStreams is a MAX_STREAMS frame.
type debugFrameMaxStreams struct {
	streamType streamType
	max        int64
}

func parseDebugFrameMaxStreams(b []byte) (f debugFrameMaxStreams, n int) {
	f.streamType, f.max, n = consumeMaxStreamsFrame(b)
	return f, n
}

func (f debugFrameMaxStreams) String() string {
	return fmt.Sprintf("MAX_STREAMS Type=%v Max=%v", f.streamType, f.max)
}

func (f debugFrameMaxStreams) write(w *packetWriter) bool {
	return w.appendMaxStreamsFrame(f.streamType, f.max)
}

// debugFrameDataBlocked is a DATA_BLOCKED frame.
type debugFrameDataBlocked struct {
	max int64
}

func parseDebugFrameDataBlocked(b []byte) (f debugFrameDataBlocked, n int) {
	f.max, n = consumeDataBlockedFrame(b)
	return f, n
}

func (f debugFrameDataBlocked) String() string {
	return fmt.Sprintf("DATA_BLOCKED Max=%v", f.max)
}

func (f debugFrameDataBlocked) write(w *packetWriter) bool {
	return w.appendDataBlockedFrame(f.max)
}

// debugFrameStreamDataBlocked is a STREAM_DATA_BLOCKED frame.
type debugFrameStreamDataBlocked struct {
	id  streamID
	max int64
}

func parseDebugFrameStreamDataBlocked(b []byte) (f debugFrameStreamDataBlocked, n int) {
	f.id, f.max, n = consumeStreamDataBlockedFrame(b)
	return f, n
}

func (f debugFrameStreamDataBlocked) String() string {
	return fmt.Sprintf("STREAM_DATA_BLOCKED ID=%v Max=%v", f.id, f.max)
}

func (f debugFrameStreamDataBlocked) write(w *packetWriter) bool {
	return w.appendStreamDataBlockedFrame(f.id, f.max)
}

// debugFrameStreamsBlocked is a STREAMS_BLOCKED frame.
type debugFrameStreamsBlocked struct {
	streamType streamType
	max        int64
}

func parseDebugFrameStreamsBlocked(b []byte) (f debugFrameStreamsBlocked, n int) {
	f.streamType, f.max, n = consumeStreamsBlockedFrame(b)
	return f, n
}

func (f debugFrameStreamsBlocked) String() string {
	return fmt.Sprintf("STREAMS_BLOCKED Type=%v Max=%v", f.streamType, f.max)
}

func (f debugFrameStreamsBlocked) write(w *packetWriter) bool {
	return w.appendStreamsBlockedFrame(f.streamType, f.max)
}

// debugFrameNewConnectionID is a NEW_CONNECTION_ID frame.
type debugFrameNewConnectionID struct {
	seq           int64
	retirePriorTo int64
	connID        []byte
	token         [16]byte
}

func parseDebugFrameNewConnectionID(b []byte) (f debugFrameNewConnectionID, n int) {
	f.seq, f.retirePriorTo, f.connID, f.token, n = consumeNewConnectionIDFrame(b)
	return f, n
}

func (f debugFrameNewConnectionID) String() string {
	return fmt.Sprintf("NEW_CONNECTION_ID Seq=%v Retire=%v ID=%x Token=%x", f.seq, f.retirePriorTo, f.connID, f.token[:])
}

func (f debugFrameNewConnectionID) write(w *packetWriter) bool {
	return w.appendNewConnectionIDFrame(f.seq, f.retirePriorTo, f.connID, f.token)
}

// debugFrameRetireConnectionID is a NEW_CONNECTION_ID frame.
type debugFrameRetireConnectionID struct {
	seq int64
}

func parseDebugFrameRetireConnectionID(b []byte) (f debugFrameRetireConnectionID, n int) {
	f.seq, n = consumeRetireConnectionIDFrame(b)
	return f, n
}

func (f debugFrameRetireConnectionID) String() string {
	return fmt.Sprintf("RETIRE_CONNECTION_ID Seq=%v", f.seq)
}

func (f debugFrameRetireConnectionID) write(w *packetWriter) bool {
	return w.appendRetireConnectionIDFrame(f.seq)
}

// debugFramePathChallenge is a PATH_CHALLENGE frame.
type debugFramePathChallenge struct {
	data uint64
}

func parseDebugFramePathChallenge(b []byte) (f debugFramePathChallenge, n int) {
	f.data, n = consumePathChallengeFrame(b)
	return f, n
}

func (f debugFramePathChallenge) String() string {
	return fmt.Sprintf("PATH_CHALLENGE Data=%016x", f.data)
}

func (f debugFramePathChallenge) write(w *packetWriter) bool {
	return w.appendPathChallengeFrame(f.data)
}

// debugFramePathResponse is a PATH_RESPONSE frame.
type debugFramePathResponse struct {
	data uint64
}

func parseDebugFramePathResponse(b []byte) (f debugFramePathResponse, n int) {
	f.data, n = consumePathResponseFrame(b)
	return f, n
}

func (f debugFramePathResponse) String() string {
	return fmt.Sprintf("PATH_RESPONSE Data=%016x", f.data)
}

func (f debugFramePathResponse) write(w *packetWriter) bool {
	return w.appendPathResponseFrame(f.data)
}

// debugFrameConnectionCloseTransport is a CONNECTION_CLOSE frame carrying a transport error.
type debugFrameConnectionCloseTransport struct {
	code      transportError
	frameType uint64
	reason    string
}

func parseDebugFrameConnectionCloseTransport(b []byte) (f debugFrameConnectionCloseTransport, n int) {
	f.code, f.frameType, f.reason, n = consumeConnectionCloseTransportFrame(b)
	return f, n
}

func (f debugFrameConnectionCloseTransport) String() string {
	s := fmt.Sprintf("CONNECTION_CLOSE Code=%v", f.code)
	if f.frameType != 0 {
		s += fmt.Sprintf(" FrameType=%v", f.frameType)
	}
	if f.reason != "" {
		s += fmt.Sprintf(" Reason=%q", f.reason)
	}
	return s
}

func (f debugFrameConnectionCloseTransport) write(w *packetWriter) bool {
	return w.appendConnectionCloseTransportFrame(f.code, f.frameType, f.reason)
}

// debugFrameConnectionCloseApplication is a CONNECTION_CLOSE frame carrying an application error.
type debugFrameConnectionCloseApplication struct {
	code   uint64
	reason string
}

func parseDebugFrameConnectionCloseApplication(b []byte) (f debugFrameConnectionCloseApplication, n int) {
	f.code, f.reason, n = consumeConnectionCloseApplicationFrame(b)
	return f, n
}

func (f debugFrameConnectionCloseApplication) String() string {
	s := fmt.Sprintf("CONNECTION_CLOSE AppCode=%v", f.code)
	if f.reason != "" {
		s += fmt.Sprintf(" Reason=%q", f.reason)
	}
	return s
}

func (f debugFrameConnectionCloseApplication) write(w *packetWriter) bool {
	return w.appendConnectionCloseApplicationFrame(f.code, f.reason)
}

// debugFrameHandshakeDone is a HANDSHAKE_DONE frame.
type debugFrameHandshakeDone struct{}

func parseDebugFrameHandshakeDone(b []byte) (f debugFrameHandshakeDone, n int) {
	return f, 1
}

func (f debugFrameHandshakeDone) String() string {
	return "HANDSHAKE_DONE"
}

func (f debugFrameHandshakeDone) write(w *packetWriter) bool {
	return w.appendHandshakeDoneFrame()
}
