package frame

import (
	"strconv"
)

// STOMP header names. Some of the header
// names have commands with the same name
// (eg Ack, Message, Receipt). Commands use
// an upper-case naming convention, header
// names use pascal-case naming convention.
const (
	ContentLength = "content-length"
	ContentType   = "content-type"
	Receipt       = "receipt"
	AcceptVersion = "accept-version"
	Host          = "host"
	Version       = "version"
	Login         = "login"
	Passcode      = "passcode"
	HeartBeat     = "heart-beat"
	Session       = "session"
	Server        = "server"
	Destination   = "destination"
	Id            = "id"
	Ack           = "ack"
	Transaction   = "transaction"
	ReceiptId     = "receipt-id"
	Subscription  = "subscription"
	MessageId     = "message-id"
	Message       = "message"
	/* TR-369 section 4.4.2.1 [Subscribing a USP Endpoint to a STOMP Destination] */
	/*
		R-STOMP.14: USP Agents that receive a subscribe-dest STOMP Header in the CONNECTED
		frame MUST use that STOMP destination in the destination STOMP header when sending a
		SUBSCRIBE frame.
	*/
	SubscribeDest = "subscribe-dest"
)

// A Header represents the header part of a STOMP frame.
// The header in a STOMP frame consists of a list of header entries.
// Each header entry is a key/value pair of strings.
//
// Normally a STOMP header only has one header entry for a given key, but
// the STOMP standard does allow for multiple header entries with the same
// key. In this case, the first header entry contains the value, and any
// subsequent header entries with the same key are ignored.
//
// Example header containing 6 header entries. Note that the second
// header entry with the key "comment" would be ignored.
//
//	login:scott
//	passcode:tiger
//	host:stompserver
//	accept-version:1.0,1.1,1.2
//	comment:some comment
//	comment:another comment
type Header struct {
	slice []string
}

// NewHeader creates a new Header and populates it with header entries.
// This function expects an even number of strings as parameters. The
// even numbered indices are keys and the odd indices are values. See
// the example for more information.
func NewHeader(headerEntries ...string) *Header {
	h := &Header{}
	h.slice = append(h.slice, headerEntries...)
	if len(h.slice)%2 != 0 {
		h.slice = append(h.slice, "")
	}
	return h
}

// Add adds the key, value pair to the header.
func (h *Header) Add(key, value string) {
	h.slice = append(h.slice, key, value)
}

// AddHeader adds all of the key value pairs in header to h.
func (h *Header) AddHeader(header *Header) {
	if header != nil {
		for i := 0; i < header.Len(); i++ {
			key, value := header.GetAt(i)
			h.Add(key, value)
		}
	}
}

// Set replaces the value of any existing header entry with the specified key.
// If there is no existing header entry with the specified key, a new
// header entry is added.
func (h *Header) Set(key, value string) {
	if i, ok := h.index(key); ok {
		h.slice[i+1] = value
	} else {
		h.slice = append(h.slice, key, value)
	}
}

// Get gets the first value associated with the given key.
// If there are no values associated with the key, Get returns "".
func (h *Header) Get(key string) string {
	value, _ := h.Contains(key)
	return value
}

// GetAll returns all of the values associated with a given key.
// Normally there is only one header entry per key, but it is permitted
// to have multiple entries according to the STOMP standard.
func (h *Header) GetAll(key string) []string {
	var values []string
	for i := 0; i < len(h.slice); i += 2 {
		if h.slice[i] == key {
			values = append(values, h.slice[i+1])
		}
	}
	return values
}

// Returns the header name and value at the specified index in
// the collection. The index should be in the range 0 <= index < Len(),
// a panic will occur if it is outside this range.
func (h *Header) GetAt(index int) (key, value string) {
	index *= 2
	return h.slice[index], h.slice[index+1]
}

// Contains gets the first value associated with the given key,
// and also returns a bool indicating whether the header entry
// exists.
//
// If there are no values associated with the key, Get returns ""
// for the value, and ok is false.
func (h *Header) Contains(key string) (value string, ok bool) {
	var i int
	if i, ok = h.index(key); ok {
		value = h.slice[i+1]
	}
	return
}

// Del deletes all header entries with the specified key.
func (h *Header) Del(key string) {
	for i, ok := h.index(key); ok; i, ok = h.index(key) {
		h.slice = append(h.slice[:i], h.slice[i+2:]...)
	}
}

// Len returns the number of header entries in the header.
func (h *Header) Len() int {
	return len(h.slice) / 2
}

// Clone returns a deep copy of a Header.
func (h *Header) Clone() *Header {
	hc := &Header{slice: make([]string, len(h.slice))}
	copy(hc.slice, h.slice)
	return hc
}

// ContentLength returns the value of the "content-length" header entry.
// If the "content-length" header is missing, then ok is false. If the
// "content-length" entry is present but is not a valid non-negative integer
// then err is non-nil.
func (h *Header) ContentLength() (value int, ok bool, err error) {
	text, ok := h.Contains(ContentLength)
	if !ok {
		return 0, false, nil
	}

	n, err := strconv.ParseUint(text, 10, 32)
	if err != nil {
		return 0, true, err
	}

	value = int(n)
	ok = true
	return value, ok, nil
}

// Returns the index of a header key in Headers, and a bool to indicate
// whether it was found or not.
func (h *Header) index(key string) (int, bool) {
	for i := 0; i < len(h.slice); i += 2 {
		if h.slice[i] == key {
			return i, true
		}
	}
	return -1, false
}
