// Copyright 2014 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.

package hpack

import (
	
)

const (
	uint32Max              = ^uint32(0)
	initialHeaderTableSize = 4096
)

type Encoder struct {
	dynTab dynamicTable
	// minSize is the minimum table size set by
	// SetMaxDynamicTableSize after the previous Header Table Size
	// Update.
	minSize uint32
	// maxSizeLimit is the maximum table size this encoder
	// supports. This will protect the encoder from too large
	// size.
	maxSizeLimit uint32
	// tableSizeUpdate indicates whether "Header Table Size
	// Update" is required.
	tableSizeUpdate bool
	w               io.Writer
	buf             []byte
}

// NewEncoder returns a new Encoder which performs HPACK encoding. An
// encoded data is written to w.
func ( io.Writer) *Encoder {
	 := &Encoder{
		minSize:         uint32Max,
		maxSizeLimit:    initialHeaderTableSize,
		tableSizeUpdate: false,
		w:               ,
	}
	.dynTab.table.init()
	.dynTab.setMaxSize(initialHeaderTableSize)
	return 
}

// WriteField encodes f into a single Write to e's underlying Writer.
// This function may also produce bytes for "Header Table Size Update"
// if necessary. If produced, it is done before encoding f.
func ( *Encoder) ( HeaderField) error {
	.buf = .buf[:0]

	if .tableSizeUpdate {
		.tableSizeUpdate = false
		if .minSize < .dynTab.maxSize {
			.buf = appendTableSize(.buf, .minSize)
		}
		.minSize = uint32Max
		.buf = appendTableSize(.buf, .dynTab.maxSize)
	}

	,  := .searchTable()
	if  {
		.buf = appendIndexed(.buf, )
	} else {
		 := .shouldIndex()
		if  {
			.dynTab.add()
		}

		if  == 0 {
			.buf = appendNewName(.buf, , )
		} else {
			.buf = appendIndexedName(.buf, , , )
		}
	}
	,  := .w.Write(.buf)
	if  == nil &&  != len(.buf) {
		 = io.ErrShortWrite
	}
	return 
}

// searchTable searches f in both stable and dynamic header tables.
// The static header table is searched first. Only when there is no
// exact match for both name and value, the dynamic header table is
// then searched. If there is no match, i is 0. If both name and value
// match, i is the matched index and nameValueMatch becomes true. If
// only name matches, i points to that index and nameValueMatch
// becomes false.
func ( *Encoder) ( HeaderField) ( uint64,  bool) {
	,  = staticTable.search()
	if  {
		return , true
	}

	,  := .dynTab.table.search()
	if  || ( == 0 &&  != 0) {
		return  + uint64(staticTable.len()), 
	}

	return , false
}

// SetMaxDynamicTableSize changes the dynamic header table size to v.
// The actual size is bounded by the value passed to
// SetMaxDynamicTableSizeLimit.
func ( *Encoder) ( uint32) {
	if  > .maxSizeLimit {
		 = .maxSizeLimit
	}
	if  < .minSize {
		.minSize = 
	}
	.tableSizeUpdate = true
	.dynTab.setMaxSize()
}

// MaxDynamicTableSize returns the current dynamic header table size.
func ( *Encoder) () ( uint32) {
	return .dynTab.maxSize
}

// SetMaxDynamicTableSizeLimit changes the maximum value that can be
// specified in SetMaxDynamicTableSize to v. By default, it is set to
// 4096, which is the same size of the default dynamic header table
// size described in HPACK specification. If the current maximum
// dynamic header table size is strictly greater than v, "Header Table
// Size Update" will be done in the next WriteField call and the
// maximum dynamic header table size is truncated to v.
func ( *Encoder) ( uint32) {
	.maxSizeLimit = 
	if .dynTab.maxSize >  {
		.tableSizeUpdate = true
		.dynTab.setMaxSize()
	}
}

// shouldIndex reports whether f should be indexed.
func ( *Encoder) ( HeaderField) bool {
	return !.Sensitive && .Size() <= .dynTab.maxSize
}

// appendIndexed appends index i, as encoded in "Indexed Header Field"
// representation, to dst and returns the extended buffer.
func appendIndexed( []byte,  uint64) []byte {
	 := len()
	 = appendVarInt(, 7, )
	[] |= 0x80
	return 
}

// appendNewName appends f, as encoded in one of "Literal Header field
// - New Name" representation variants, to dst and returns the
// extended buffer.
//
// If f.Sensitive is true, "Never Indexed" representation is used. If
// f.Sensitive is false and indexing is true, "Incremental Indexing"
// representation is used.
func appendNewName( []byte,  HeaderField,  bool) []byte {
	 = append(, encodeTypeByte(, .Sensitive))
	 = appendHpackString(, .Name)
	return appendHpackString(, .Value)
}

// appendIndexedName appends f and index i referring indexed name
// entry, as encoded in one of "Literal Header field - Indexed Name"
// representation variants, to dst and returns the extended buffer.
//
// If f.Sensitive is true, "Never Indexed" representation is used. If
// f.Sensitive is false and indexing is true, "Incremental Indexing"
// representation is used.
func appendIndexedName( []byte,  HeaderField,  uint64,  bool) []byte {
	 := len()
	var  byte
	if  {
		 = 6
	} else {
		 = 4
	}
	 = appendVarInt(, , )
	[] |= encodeTypeByte(, .Sensitive)
	return appendHpackString(, .Value)
}

// appendTableSize appends v, as encoded in "Header Table Size Update"
// representation, to dst and returns the extended buffer.
func appendTableSize( []byte,  uint32) []byte {
	 := len()
	 = appendVarInt(, 5, uint64())
	[] |= 0x20
	return 
}

// appendVarInt appends i, as encoded in variable integer form using n
// bit prefix, to dst and returns the extended buffer.
//
// See
// https://httpwg.org/specs/rfc7541.html#integer.representation
func appendVarInt( []byte,  byte,  uint64) []byte {
	 := uint64((1 << ) - 1)
	if  <  {
		return append(, byte())
	}
	 = append(, byte())
	 -= 
	for ;  >= 128;  >>= 7 {
		 = append(, byte(0x80|(&0x7f)))
	}
	return append(, byte())
}

// appendHpackString appends s, as encoded in "String Literal"
// representation, to dst and returns the extended buffer.
//
// s will be encoded in Huffman codes only when it produces strictly
// shorter byte string.
func appendHpackString( []byte,  string) []byte {
	 := HuffmanEncodeLength()
	if  < uint64(len()) {
		 := len()
		 = appendVarInt(, 7, )
		 = AppendHuffmanString(, )
		[] |= 0x80
	} else {
		 = appendVarInt(, 7, uint64(len()))
		 = append(, ...)
	}
	return 
}

// encodeTypeByte returns type byte. If sensitive is true, type byte
// for "Never Indexed" representation is returned. If sensitive is
// false and indexing is true, type byte for "Incremental Indexing"
// representation is returned. Otherwise, type byte for "Without
// Indexing" is returned.
func encodeTypeByte(,  bool) byte {
	if  {
		return 0x10
	}
	if  {
		return 0x40
	}
	return 0
}