package h2

import (
	"errors"
	"fmt"
)

// HeaderField is one decoded header (name/value) and whether it was sent
// "sensitive" (never-indexed), which a proxy must preserve.
type HeaderField struct {
	Name      string
	Value     string
	Sensitive bool
}

func (h HeaderField) String() string { return h.Name + ": " + h.Value }

// staticTable is RFC 7541 Appendix A (index 1..61). Index 0 is unused.
var staticTable = [][2]string{
	{"", ""}, // 0 (unused)
	{":authority", ""}, {":method", "GET"}, {":method", "POST"},
	{":path", "/"}, {":path", "/index.html"}, {":scheme", "http"},
	{":scheme", "https"}, {":status", "200"}, {":status", "204"},
	{":status", "206"}, {":status", "304"}, {":status", "400"},
	{":status", "404"}, {":status", "500"}, {"accept-charset", ""},
	{"accept-encoding", "gzip, deflate"}, {"accept-language", ""},
	{"accept-ranges", ""}, {"accept", ""}, {"access-control-allow-origin", ""},
	{"age", ""}, {"allow", ""}, {"authorization", ""},
	{"cache-control", ""}, {"content-disposition", ""}, {"content-encoding", ""},
	{"content-language", ""}, {"content-length", ""}, {"content-location", ""},
	{"content-range", ""}, {"content-type", ""}, {"cookie", ""},
	{"date", ""}, {"etag", ""}, {"expect", ""},
	{"expires", ""}, {"from", ""}, {"host", ""},
	{"if-match", ""}, {"if-modified-since", ""}, {"if-none-match", ""},
	{"if-range", ""}, {"if-unmodified-since", ""}, {"last-modified", ""},
	{"link", ""}, {"location", ""}, {"max-forwards", ""},
	{"proxy-authenticate", ""}, {"proxy-authorization", ""}, {"range", ""},
	{"referer", ""}, {"refresh", ""}, {"retry-after", ""},
	{"server", ""}, {"set-cookie", ""}, {"strict-transport-security", ""},
	{"transfer-encoding", ""}, {"user-agent", ""}, {"vary", ""},
	{"via", ""}, {"www-authenticate", ""},
}

// ErrHuffman is returned when a header string is Huffman-coded; the lab
// decoder handles the (un-Huffman) RFC 7541 §C.3 examples and any block
// you encode with this package. Real captures use Huffman — point that
// path at golang.org/x/net/http2/hpack.
var ErrHuffman = errors.New("h2: Huffman-coded string (use x/net/http2/hpack for real captures)")

type dynEntry struct {
	name, value string
}

// Decoder holds the per-connection HPACK dynamic table. It MUST persist
// across all HEADERS frames on one connection — that is the subtle bug a
// proxy hits when it carelessly rewrites headers (encoder/decoder tables
// must stay in lockstep).
type Decoder struct {
	dyn     []dynEntry // index 0 == most recently added (logical index 62)
	size    int        // current dynamic table size in bytes
	maxSize int
}

func NewDecoder(maxDynamicTableSize int) *Decoder {
	return &Decoder{maxSize: maxDynamicTableSize}
}

// lookup resolves a 1-based HPACK index into the combined static+dynamic
// table.
func (d *Decoder) lookup(idx int) (HeaderField, error) {
	if idx >= 1 && idx <= 61 {
		e := staticTable[idx]
		return HeaderField{Name: e[0], Value: e[1]}, nil
	}
	j := idx - 62
	if j >= 0 && j < len(d.dyn) {
		return HeaderField{Name: d.dyn[j].name, Value: d.dyn[j].value}, nil
	}
	return HeaderField{}, fmt.Errorf("h2: hpack index %d out of range", idx)
}

func (d *Decoder) add(name, value string) {
	d.dyn = append([]dynEntry{{name, value}}, d.dyn...) // prepend: newest first
	d.size += len(name) + len(value) + 32               // RFC 7541 §4.1 overhead
	d.evict()
}

func (d *Decoder) evict() {
	for d.size > d.maxSize && len(d.dyn) > 0 {
		last := d.dyn[len(d.dyn)-1]
		d.size -= len(last.name) + len(last.value) + 32
		d.dyn = d.dyn[:len(d.dyn)-1]
	}
}

func (d *Decoder) setMaxSize(n int) {
	d.maxSize = n
	d.evict()
}

// Decode decodes one HPACK header block into fields, mutating the dynamic
// table per the incremental-indexing instructions in the block.
func (d *Decoder) Decode(block []byte) ([]HeaderField, error) {
	var out []HeaderField
	b := block
	for len(b) > 0 {
		b0 := b[0]
		switch {
		case b0&0x80 != 0: // 1xxxxxxx: Indexed Header Field (§6.1)
			idx, rest, err := decodeInt(b, 7)
			if err != nil {
				return out, err
			}
			b = rest
			hf, err := d.lookup(idx)
			if err != nil {
				return out, err
			}
			out = append(out, hf)

		case b0&0x40 != 0: // 01xxxxxx: Literal w/ Incremental Indexing (§6.2.1)
			hf, rest, err := d.decodeLiteral(b, 6)
			if err != nil {
				return out, err
			}
			d.add(hf.Name, hf.Value) // ADD to dynamic table
			out = append(out, hf)
			b = rest

		case b0&0x20 != 0: // 001xxxxx: Dynamic Table Size Update (§6.3)
			n, rest, err := decodeInt(b, 5)
			if err != nil {
				return out, err
			}
			d.setMaxSize(n)
			b = rest

		default: // 0000xxxx / 0001xxxx: Literal w/o or Never Indexed (§6.2.2/6.2.3)
			sensitive := b0&0x10 != 0
			hf, rest, err := d.decodeLiteral(b, 4)
			if err != nil {
				return out, err
			}
			hf.Sensitive = sensitive
			out = append(out, hf) // NOT added to the dynamic table
			b = rest
		}
	}
	return out, nil
}

// decodeLiteral reads "index-or-literal name" + "literal value" with an
// n-bit index prefix.
func (d *Decoder) decodeLiteral(b []byte, nameIdxBits uint8) (HeaderField, []byte, error) {
	idx, rest, err := decodeInt(b, nameIdxBits)
	if err != nil {
		return HeaderField{}, nil, err
	}
	var name string
	if idx == 0 { // literal name follows
		name, rest, err = decodeString(rest)
		if err != nil {
			return HeaderField{}, nil, err
		}
	} else {
		hf, err := d.lookup(idx)
		if err != nil {
			return HeaderField{}, nil, err
		}
		name = hf.Name
	}
	value, rest, err := decodeString(rest)
	if err != nil {
		return HeaderField{}, nil, err
	}
	return HeaderField{Name: name, Value: value}, rest, nil
}

// decodeInt decodes an HPACK variable-length integer with an n-bit prefix
// (RFC 7541 §5.1).
func decodeInt(b []byte, n uint8) (val int, rest []byte, err error) {
	if len(b) == 0 {
		return 0, nil, io_ErrShort
	}
	max := (1 << n) - 1
	prefix := int(b[0]) & max
	b = b[1:]
	if prefix < max {
		return prefix, b, nil
	}
	val = max
	var m uint
	for {
		if len(b) == 0 {
			return 0, nil, io_ErrShort
		}
		c := b[0]
		b = b[1:]
		val += int(c&0x7f) << m
		m += 7
		if c&0x80 == 0 {
			break
		}
		if m > 28 { // guard against integer-overflow attacks
			return 0, nil, errors.New("h2: hpack integer too large")
		}
	}
	return val, b, nil
}

// decodeString decodes an HPACK string literal (§5.2). Huffman-coded
// strings return ErrHuffman.
func decodeString(b []byte) (s string, rest []byte, err error) {
	if len(b) == 0 {
		return "", nil, io_ErrShort
	}
	huff := b[0]&0x80 != 0
	length, b, err := decodeInt(b, 7)
	if err != nil {
		return "", nil, err
	}
	if length > len(b) {
		return "", nil, io_ErrShort
	}
	raw := b[:length]
	rest = b[length:]
	if huff {
		return "", rest, ErrHuffman
	}
	return string(raw), rest, nil
}

var io_ErrShort = errors.New("h2: short hpack buffer")

// --- Encoder side (enough to build test vectors and re-emit headers) ---

// EncodeInt appends an HPACK integer with an n-bit prefix; firstByteBits
// supplies the high bits of the first byte (the instruction pattern).
func EncodeInt(dst []byte, i int, n uint8, firstByteBits byte) []byte {
	max := (1 << n) - 1
	if i < max {
		return append(dst, firstByteBits|byte(i))
	}
	dst = append(dst, firstByteBits|byte(max))
	i -= max
	for i >= 128 {
		dst = append(dst, byte(i%128+128))
		i /= 128
	}
	return append(dst, byte(i))
}

// EncodeString appends a (non-Huffman) HPACK string literal.
func EncodeString(dst []byte, s string) []byte {
	dst = EncodeInt(dst, len(s), 7, 0x00) // H bit = 0
	return append(dst, s...)
}

// EncodeIndexed appends an Indexed Header Field (§6.1).
func EncodeIndexed(dst []byte, idx int) []byte {
	return EncodeInt(dst, idx, 7, 0x80)
}

// EncodeLiteralIncremental appends a Literal w/ Incremental Indexing,
// literal name (§6.2.1, name index 0).
func EncodeLiteralIncremental(dst []byte, name, value string) []byte {
	dst = EncodeInt(dst, 0, 6, 0x40)
	dst = EncodeString(dst, name)
	return EncodeString(dst, value)
}
