// Package h2 is a teaching subset of the HTTP/2 wire protocol: a frame
// reader/writer, an HPACK decoder with the RFC 7541 dynamic-table
// mechanics, and per-stream/connection flow-control accounting. It is
// stdlib-only and exists to make multiplexing, header compression, and
// flow control *legible* — the production codec is golang.org/x/net/http2.
package h2

import (
	"encoding/binary"
	"fmt"
	"io"
)

// FrameType is the 8-bit HTTP/2 frame type (RFC 9113 §6).
type FrameType uint8

const (
	FrameData         FrameType = 0x0
	FrameHeaders      FrameType = 0x1
	FramePriority     FrameType = 0x2
	FrameRSTStream    FrameType = 0x3
	FrameSettings     FrameType = 0x4
	FramePushPromise  FrameType = 0x5
	FramePing         FrameType = 0x6
	FrameGoAway       FrameType = 0x7
	FrameWindowUpdate FrameType = 0x8
	FrameContinuation FrameType = 0x9
)

func (t FrameType) String() string {
	names := []string{"DATA", "HEADERS", "PRIORITY", "RST_STREAM",
		"SETTINGS", "PUSH_PROMISE", "PING", "GOAWAY", "WINDOW_UPDATE",
		"CONTINUATION"}
	if int(t) < len(names) {
		return names[t]
	}
	return fmt.Sprintf("UNKNOWN(0x%x)", uint8(t))
}

// Frame flag bits (meaning depends on the frame type).
const (
	FlagEndStream  uint8 = 0x1 // DATA, HEADERS
	FlagAck        uint8 = 0x1 // SETTINGS, PING
	FlagEndHeaders uint8 = 0x4 // HEADERS, PUSH_PROMISE, CONTINUATION
	FlagPadded     uint8 = 0x8 // DATA, HEADERS, PUSH_PROMISE
	FlagPriority   uint8 = 0x20
)

// ClientPreface is the fixed 24-byte connection preamble a client sends
// before any frames.
const ClientPreface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"

// MaxFrameLen guards against absurd lengths from a hostile/corrupt peer.
const MaxFrameLen = 1 << 24 // 16 MiB; the 24-bit length field maximum

// FrameHeader is the 9-byte frame header.
type FrameHeader struct {
	Length   uint32 // 24-bit payload length (not incl. the 9-byte header)
	Type     FrameType
	Flags    uint8
	StreamID uint32 // 31-bit; 0 == connection-level
}

// Frame is a header plus its raw payload.
type Frame struct {
	Header  FrameHeader
	Payload []byte
}

func (f *Frame) EndStream() bool {
	return (f.Header.Type == FrameData || f.Header.Type == FrameHeaders) &&
		f.Header.Flags&FlagEndStream != 0
}

func (f *Frame) String() string {
	end := ""
	if f.EndStream() {
		end = " END_STREAM"
	}
	return fmt.Sprintf("stream=%-3d %-13s len=%-6d flags=0x%02x%s",
		f.Header.StreamID, f.Header.Type, f.Header.Length, f.Header.Flags, end)
}

// ReadFrame reads one frame (9-byte header + payload) from r.
func ReadFrame(r io.Reader) (*Frame, error) {
	var hdr [9]byte
	if _, err := io.ReadFull(r, hdr[:]); err != nil {
		return nil, err
	}
	length := uint32(hdr[0])<<16 | uint32(hdr[1])<<8 | uint32(hdr[2])
	if length > MaxFrameLen {
		return nil, fmt.Errorf("h2: frame length %d exceeds max %d", length, MaxFrameLen)
	}
	fh := FrameHeader{
		Length:   length,
		Type:     FrameType(hdr[3]),
		Flags:    hdr[4],
		StreamID: binary.BigEndian.Uint32(hdr[5:9]) & 0x7fffffff, // mask the reserved bit
	}
	payload := make([]byte, length)
	if _, err := io.ReadFull(r, payload); err != nil {
		return nil, err
	}
	return &Frame{Header: fh, Payload: payload}, nil
}

// WriteFrame serializes a frame to w (header + payload).
func WriteFrame(w io.Writer, f *Frame) error {
	if len(f.Payload) > MaxFrameLen {
		return fmt.Errorf("h2: payload too large: %d", len(f.Payload))
	}
	var hdr [9]byte
	n := uint32(len(f.Payload))
	hdr[0], hdr[1], hdr[2] = byte(n>>16), byte(n>>8), byte(n)
	hdr[3] = byte(f.Header.Type)
	hdr[4] = f.Header.Flags
	binary.BigEndian.PutUint32(hdr[5:9], f.Header.StreamID&0x7fffffff)
	if _, err := w.Write(hdr[:]); err != nil {
		return err
	}
	_, err := w.Write(f.Payload)
	return err
}

// ReadAllFrames reads frames until EOF (after the caller has consumed the
// client preface, if present).
func ReadAllFrames(r io.Reader) ([]*Frame, error) {
	var out []*Frame
	for {
		f, err := ReadFrame(r)
		if err == io.EOF {
			return out, nil
		}
		if err != nil {
			return out, err
		}
		out = append(out, f)
	}
}
