package models

import (
	"bytes"
	"strings"
)

// TagKeysSet provides set operations for combining Tags.
type TagKeysSet struct {
	i    int
	keys [2][][]byte
	tmp  [][]byte
}

// Clear removes all the elements of TagKeysSet and ensures all internal
// buffers are reset.
func (set *TagKeysSet) Clear() {
	set.clear(set.keys[0])
	set.clear(set.keys[1])
	set.clear(set.tmp)
	set.i = 0
	set.keys[0] = set.keys[0][:0]
}

func (set *TagKeysSet) clear(b [][]byte) {
	b = b[:cap(b)]
	for i := range b {
		b[i] = nil
	}
}

// KeysBytes returns the merged keys in lexicographical order.
// The slice is valid until the next call to UnionKeys, UnionBytes or Reset.
func (set *TagKeysSet) KeysBytes() [][]byte {
	return set.keys[set.i&1]
}

// Keys returns a copy of the merged keys in lexicographical order.
func (set *TagKeysSet) Keys() []string {
	keys := set.KeysBytes()
	s := make([]string, 0, len(keys))
	for i := range keys {
		s = append(s, string(keys[i]))
	}
	return s
}

func (set *TagKeysSet) String() string {
	var s []string
	for _, k := range set.KeysBytes() {
		s = append(s, string(k))
	}
	return strings.Join(s, ",")
}

// IsSupersetKeys returns true if the TagKeysSet is a superset of all the keys
// contained in other.
func (set *TagKeysSet) IsSupersetKeys(other Tags) bool {
	keys := set.keys[set.i&1]
	i, j := 0, 0
	for i < len(keys) && j < len(other) {
		if cmp := bytes.Compare(keys[i], other[j].Key); cmp > 0 {
			return false
		} else if cmp == 0 {
			j++
		}
		i++
	}

	return j == len(other)
}

// IsSupersetBytes returns true if the TagKeysSet is a superset of all the keys
// in other.
// Other must be lexicographically sorted or the results are undefined.
func (set *TagKeysSet) IsSupersetBytes(other [][]byte) bool {
	keys := set.keys[set.i&1]
	i, j := 0, 0
	for i < len(keys) && j < len(other) {
		if cmp := bytes.Compare(keys[i], other[j]); cmp > 0 {
			return false
		} else if cmp == 0 {
			j++
		}
		i++
	}

	return j == len(other)
}

// UnionKeys updates the set so that it is the union of itself and all the
// keys contained in other.
func (set *TagKeysSet) UnionKeys(other Tags) {
	if set.IsSupersetKeys(other) {
		return
	}

	if l := len(other); cap(set.tmp) < l {
		set.tmp = make([][]byte, l)
	} else {
		set.tmp = set.tmp[:l]
	}

	for i := range other {
		set.tmp[i] = other[i].Key
	}

	set.merge(set.tmp)
}

// UnionBytes updates the set so that it is the union of itself and all the
// keys contained in other.
// Other must be lexicographically sorted or the results are undefined.
func (set *TagKeysSet) UnionBytes(other [][]byte) {
	if set.IsSupersetBytes(other) {
		return
	}

	set.merge(other)
}

func (set *TagKeysSet) merge(in [][]byte) {
	keys := set.keys[set.i&1]
	l := len(keys) + len(in)
	set.i = (set.i + 1) & 1
	keya := set.keys[set.i&1]
	if cap(keya) < l {
		keya = make([][]byte, 0, l)
	} else {
		keya = keya[:0]
	}

	i, j := 0, 0
	for i < len(keys) && j < len(in) {
		ki, kj := keys[i], in[j]
		if cmp := bytes.Compare(ki, kj); cmp < 0 {
			i++
		} else if cmp > 0 {
			ki = kj
			j++
		} else {
			i++
			j++
		}

		keya = append(keya, ki)
	}

	if i < len(keys) {
		keya = append(keya, keys[i:]...)
	} else if j < len(in) {
		keya = append(keya, in[j:]...)
	}

	set.keys[set.i&1] = keya
}