package httphead

import (
	"bytes"
	"sort"
)

// Option represents a header option.
type Option struct {
	Name       []byte
	Parameters Parameters
}

// Size returns number of bytes need to be allocated for use in opt.Copy.
func (opt Option) Size() int {
	return len(opt.Name) + opt.Parameters.bytes
}

// Copy copies all underlying []byte slices into p and returns new Option.
// Note that p must be at least of opt.Size() length.
func (opt Option) Copy(p []byte) Option {
	n := copy(p, opt.Name)
	opt.Name = p[:n]
	opt.Parameters, p = opt.Parameters.Copy(p[n:])
	return opt
}

// String represents option as a string.
func (opt Option) String() string {
	return "{" + string(opt.Name) + " " + opt.Parameters.String() + "}"
}

// NewOption creates named option with given parameters.
func NewOption(name string, params map[string]string) Option {
	p := Parameters{}
	for k, v := range params {
		p.Set([]byte(k), []byte(v))
	}
	return Option{
		Name:       []byte(name),
		Parameters: p,
	}
}

// Equal reports whether option is equal to b.
func (opt Option) Equal(b Option) bool {
	if bytes.Equal(opt.Name, b.Name) {
		return opt.Parameters.Equal(b.Parameters)
	}
	return false
}

// Parameters represents option's parameters.
type Parameters struct {
	pos   int
	bytes int
	arr   [8]pair
	dyn   []pair
}

// Equal reports whether a equal to b.
func (p Parameters) Equal(b Parameters) bool {
	switch {
	case p.dyn == nil && b.dyn == nil:
	case p.dyn != nil && b.dyn != nil:
	default:
		return false
	}

	ad, bd := p.data(), b.data()
	if len(ad) != len(bd) {
		return false
	}

	sort.Sort(pairs(ad))
	sort.Sort(pairs(bd))

	for i := 0; i < len(ad); i++ {
		av, bv := ad[i], bd[i]
		if !bytes.Equal(av.key, bv.key) || !bytes.Equal(av.value, bv.value) {
			return false
		}
	}
	return true
}

// Size returns number of bytes that needed to copy p.
func (p *Parameters) Size() int {
	return p.bytes
}

// Copy copies all underlying []byte slices into dst and returns new
// Parameters.
// Note that dst must be at least of p.Size() length.
func (p *Parameters) Copy(dst []byte) (Parameters, []byte) {
	ret := Parameters{
		pos:   p.pos,
		bytes: p.bytes,
	}
	if p.dyn != nil {
		ret.dyn = make([]pair, len(p.dyn))
		for i, v := range p.dyn {
			ret.dyn[i], dst = v.copy(dst)
		}
	} else {
		for i, p := range p.arr {
			ret.arr[i], dst = p.copy(dst)
		}
	}
	return ret, dst
}

// Get returns value by key and flag about existence such value.
func (p *Parameters) Get(key string) (value []byte, ok bool) {
	for _, v := range p.data() {
		if string(v.key) == key {
			return v.value, true
		}
	}
	return nil, false
}

// Set sets value by key.
func (p *Parameters) Set(key, value []byte) {
	p.bytes += len(key) + len(value)

	if p.pos < len(p.arr) {
		p.arr[p.pos] = pair{key, value}
		p.pos++
		return
	}

	if p.dyn == nil {
		p.dyn = make([]pair, len(p.arr), len(p.arr)+1)
		copy(p.dyn, p.arr[:])
	}
	p.dyn = append(p.dyn, pair{key, value})
}

// ForEach iterates over parameters key-value pairs and calls cb for each one.
func (p *Parameters) ForEach(cb func(k, v []byte) bool) {
	for _, v := range p.data() {
		if !cb(v.key, v.value) {
			break
		}
	}
}

// String represents parameters as a string.
func (p *Parameters) String() (ret string) {
	ret = "["
	for i, v := range p.data() {
		if i > 0 {
			ret += " "
		}
		ret += string(v.key) + ":" + string(v.value)
	}
	return ret + "]"
}

func (p *Parameters) data() []pair {
	if p.dyn != nil {
		return p.dyn
	}
	return p.arr[:p.pos]
}

type pair struct {
	key, value []byte
}

func (p pair) copy(dst []byte) (pair, []byte) {
	n := copy(dst, p.key)
	p.key = dst[:n]
	m := n + copy(dst[n:], p.value)
	p.value = dst[n:m]

	dst = dst[m:]

	return p, dst
}

type pairs []pair

func (p pairs) Len() int           { return len(p) }
func (p pairs) Less(a, b int) bool { return bytes.Compare(p[a].key, p[b].key) == -1 }
func (p pairs) Swap(a, b int)      { p[a], p[b] = p[b], p[a] }
