package http3

import (
	
	
	
	
	
	
	
	
	

	
	
	
	
)

const maxQuarterStreamID = 1<<60 - 1

// invalidStreamID is a stream ID that is invalid. The first valid stream ID in QUIC is 0.
const invalidStreamID = quic.StreamID(-1)

// rawConn is an HTTP/3 connection.
// It provides HTTP/3 specific functionality by wrapping a quic.Conn,
// in particular handling of unidirectional HTTP/3 streams, SETTINGS and datagrams.
type rawConn struct {
	conn *quic.Conn

	logger *slog.Logger

	enableDatagrams bool

	streamMx sync.Mutex
	streams  map[quic.StreamID]*stateTrackingStream

	rcvdControlStr      atomic.Bool
	rcvdQPACKEncoderStr atomic.Bool
	rcvdQPACKDecoderStr atomic.Bool
	controlStrHandler   func(*quic.ReceiveStream, *frameParser) // is called *after* the SETTINGS frame was parsed

	onStreamsEmpty func()

	settings         *Settings
	receivedSettings chan struct{}

	qlogger   qlogwriter.Recorder
	qloggerWG sync.WaitGroup // tracks goroutines that may produce qlog events
}

func newRawConn(
	 *quic.Conn,
	 bool,
	 func(),
	 func(*quic.ReceiveStream, *frameParser),
	 qlogwriter.Recorder,
	 *slog.Logger,
) *rawConn {
	 := &rawConn{
		conn:              ,
		logger:            ,
		enableDatagrams:   ,
		receivedSettings:  make(chan struct{}),
		streams:           make(map[quic.StreamID]*stateTrackingStream),
		qlogger:           ,
		onStreamsEmpty:    ,
		controlStrHandler: ,
	}
	if  != nil {
		context.AfterFunc(.Context(), .closeQlogger)
	}
	return 
}

func ( *rawConn) () (*quic.SendStream, error) {
	return .conn.OpenUniStream()
}

// openControlStream opens the control stream and sends the SETTINGS frame.
// It returns the control stream (needed by the server for sending GOAWAY later).
func ( *rawConn) ( *settingsFrame) (*quic.SendStream, error) {
	.qloggerWG.Add(1)
	defer .qloggerWG.Done()

	,  := .conn.OpenUniStream()
	if  != nil {
		return nil, 
	}
	 := make([]byte, 0, 64)
	 = quicvarint.Append(, streamTypeControlStream)
	 = .Append()
	if .qlogger != nil {
		 := qlog.SettingsFrame{
			MaxFieldSectionSize: .MaxFieldSectionSize,
			Other:               maps.Clone(.Other),
		}
		if .Datagram {
			.Datagram = pointer(true)
		}
		if .ExtendedConnect {
			.ExtendedConnect = pointer(true)
		}
		.qlogger.RecordEvent(qlog.FrameCreated{
			StreamID: .StreamID(),
			Raw:      qlog.RawInfo{Length: len()},
			Frame:    qlog.Frame{Frame: },
		})
	}
	if ,  := .Write();  != nil {
		return nil, 
	}
	return , nil
}

func ( *rawConn) ( *quic.Stream) *stateTrackingStream {
	 := newStateTrackingStream(, , func( []byte) error { return .sendDatagram(.StreamID(), ) })

	.streamMx.Lock()
	.streams[.StreamID()] = 
	.qloggerWG.Add(1)
	.streamMx.Unlock()
	return 
}

func ( *rawConn) () net.Addr {
	return .conn.RemoteAddr()
}

func ( *rawConn) () quic.ConnectionState {
	return .conn.ConnectionState()
}

func ( *rawConn) ( quic.StreamID) {
	.streamMx.Lock()
	defer .streamMx.Unlock()

	if ,  := .streams[];  {
		delete(.streams, )
		.qloggerWG.Done()
	}
	if len(.streams) == 0 {
		.onStreamsEmpty()
	}
}

func ( *rawConn) () bool {
	.streamMx.Lock()
	defer .streamMx.Unlock()

	return len(.streams) > 0
}

func ( *rawConn) ( quic.ApplicationErrorCode,  string) error {
	return .conn.CloseWithError(, )
}

func ( *rawConn) ( *quic.ReceiveStream,  bool) {
	.qloggerWG.Add(1)
	defer .qloggerWG.Done()

	,  := quicvarint.Read(quicvarint.NewReader())
	if  != nil {
		if .logger != nil {
			.logger.Debug("reading stream type on stream failed", "stream ID", .StreamID(), "error", )
		}
		return
	}
	// We're only interested in the control stream here.
	switch  {
	case streamTypeControlStream:
	case streamTypeQPACKEncoderStream:
		if  := .rcvdQPACKEncoderStr.CompareAndSwap(false, true); ! {
			.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate QPACK encoder stream")
		}
		// Our QPACK implementation doesn't use the dynamic table yet.
		return
	case streamTypeQPACKDecoderStream:
		if  := .rcvdQPACKDecoderStr.CompareAndSwap(false, true); ! {
			.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate QPACK decoder stream")
		}
		// Our QPACK implementation doesn't use the dynamic table yet.
		return
	case streamTypePushStream:
		if  {
			// only the server can push
			.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "")
		} else {
			// we never increased the Push ID, so we don't expect any push streams
			.CloseWithError(quic.ApplicationErrorCode(ErrCodeIDError), "")
		}
		return
	default:
		.CancelRead(quic.StreamErrorCode(ErrCodeStreamCreationError))
		return
	}
	// Only a single control stream is allowed.
	if  := .rcvdControlStr.CompareAndSwap(false, true); ! {
		.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate control stream")
		return
	}
	.handleControlStream()
}

func ( *rawConn) ( *quic.ReceiveStream) {
	 := &frameParser{closeConn: .conn.CloseWithError, r: , streamID: .StreamID()}
	,  := .ParseNext(.qlogger)
	if  != nil {
		var  *quic.StreamError
		if  == io.EOF || errors.As(, &) {
			.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeClosedCriticalStream), "")
			return
		}
		.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameError), "")
		return
	}
	,  := .(*settingsFrame)
	if ! {
		.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeMissingSettings), "")
		return
	}
	.settings = &Settings{
		EnableDatagrams:       .Datagram,
		EnableExtendedConnect: .ExtendedConnect,
		Other:                 .Other,
	}
	close(.receivedSettings)
	if .Datagram {
		// If datagram support was enabled on our side as well as on the server side,
		// we can expect it to have been negotiated both on the transport and on the HTTP/3 layer.
		// Note: ConnectionState() will block until the handshake is complete (relevant when using 0-RTT).
		if .enableDatagrams && !.ConnectionState().SupportsDatagrams.Remote {
			.CloseWithError(quic.ApplicationErrorCode(ErrCodeSettingsError), "missing QUIC Datagram support")
			return
		}
		.qloggerWG.Add(1)
		go func() {
			defer .qloggerWG.Done()
			if  := .receiveDatagrams();  != nil {
				if .logger != nil {
					.logger.Debug("receiving datagrams failed", "error", )
				}
			}
		}()
	}

	if .controlStrHandler != nil {
		.controlStrHandler(, )
	}
}

func ( *rawConn) ( quic.StreamID,  []byte) error {
	// TODO: this creates a lot of garbage and an additional copy
	 := make([]byte, 0, len()+8)
	 := uint64( / 4)
	 = quicvarint.Append(, uint64(/4))
	 = append(, ...)
	if .qlogger != nil {
		.qlogger.RecordEvent(qlog.DatagramCreated{
			QuaterStreamID: ,
			Raw: qlog.RawInfo{
				Length:        len(),
				PayloadLength: len(),
			},
		})
	}
	return .conn.SendDatagram()
}

func ( *rawConn) () error {
	for {
		,  := .conn.ReceiveDatagram(context.Background())
		if  != nil {
			return 
		}
		, ,  := quicvarint.Parse()
		if  != nil {
			.CloseWithError(quic.ApplicationErrorCode(ErrCodeDatagramError), "")
			return fmt.Errorf("could not read quarter stream id: %w", )
		}
		if .qlogger != nil {
			.qlogger.RecordEvent(qlog.DatagramParsed{
				QuaterStreamID: ,
				Raw: qlog.RawInfo{
					Length:        len(),
					PayloadLength: len() - ,
				},
			})
		}
		if  > maxQuarterStreamID {
			.CloseWithError(quic.ApplicationErrorCode(ErrCodeDatagramError), "")
			return fmt.Errorf("invalid quarter stream id: %w", )
		}
		 := quic.StreamID(4 * )
		.streamMx.Lock()
		,  := .streams[]
		.streamMx.Unlock()
		if ! {
			continue
		}
		.enqueueDatagram([:])
	}
}

// ReceivedSettings returns a channel that is closed once the peer's SETTINGS frame was received.
// Settings can be optained from the Settings method after the channel was closed.
func ( *rawConn) () <-chan struct{} { return .receivedSettings }

// Settings returns the settings received on this connection.
// It is only valid to call this function after the channel returned by ReceivedSettings was closed.
func ( *rawConn) () *Settings { return .settings }

// closeQlogger waits for all goroutines that may produce qlog events to finish,
// then closes the qlogger.
func ( *rawConn) () {
	if .qlogger == nil {
		return
	}
	.qloggerWG.Wait()
	.qlogger.Close()
}