package tsm1

import (
	"sort"

	"github.com/influxdata/influxdb/tsdb"
)

{{range .}}

// {{.Name}}Values represents a slice of {{.Name}} values.
type {{.Name}}Values []{{.Name}}Value

{{if ne .Name ""}}
func New{{.Name}}ArrayFromValues(v {{.Name}}Values) *tsdb.{{.Name}}Array {
	a := tsdb.New{{.Name}}ArrayLen(len(v))
	for i, val := range v {
		a.Timestamps[i] = val.UnixNano()
		a.Values[i] = val.RawValue()
	}
	return a
}
{{end}}

func (a {{.Name}}Values) MinTime() int64 {
	return a[0].UnixNano()
}

func (a {{.Name}}Values) MaxTime() int64 {
	return a[len(a)-1].UnixNano()
}

func (a {{.Name}}Values) Size() int {
	sz := 0
	for _, v := range a {
		sz += v.Size()
	}
	return sz
}

// Deduplicate returns a new slice with any values that have the same timestamp removed.
// The Value that appears last in the slice is the one that is kept.  The returned
// Values are sorted if necessary.
func (a {{.Name}}Values) Deduplicate() {{.Name}}Values {
	if len(a) <= 1 {
		return a
	}

	// See if we're already sorted and deduped
	var needSort bool
	for i := 1; i < len(a); i++ {
		if a[i-1].UnixNano() >= a[i].UnixNano() {
			needSort = true
			break
		}
	}

	if !needSort {
		return a
	}

	sort.Stable(a)
	var i int
	for j := 1; j < len(a); j++ {
		v := a[j]
		if v.UnixNano() != a[i].UnixNano() {
			i++
		}
		a[i] = v

	}
	return a[:i+1]
}

// Exclude returns the subset of values not in [min, max].  The values must
// be deduplicated and sorted before calling Exclude or the results are undefined.
func (a {{.Name}}Values) Exclude(min, max int64) {{.Name}}Values {
	rmin, rmax := a.FindRange(min, max)
	if rmin == -1 && rmax == -1 {
		return a
	}

	// a[rmin].UnixNano() ≥ min
	// a[rmax].UnixNano() ≥ max

	if rmax < len(a) {
		if a[rmax].UnixNano() == max {
			rmax++
		}
		rest := len(a)-rmax
		if rest > 0 {
			b := a[:rmin+rest]
			copy(b[rmin:], a[rmax:])
			return b
		}
	}

	return a[:rmin]
}

// Include returns the subset values between min and max inclusive. The values must
// be deduplicated and sorted before calling Exclude or the results are undefined.
func (a {{.Name}}Values) Include(min, max int64) {{.Name}}Values {
	rmin, rmax := a.FindRange(min, max)
	if rmin == -1 && rmax == -1 {
		return nil
	}

	// a[rmin].UnixNano() ≥ min
    // a[rmax].UnixNano() ≥ max

	if rmax < len(a) && a[rmax].UnixNano() == max {
		rmax++
	}

	if rmin > -1 {
		b := a[:rmax-rmin]
		copy(b, a[rmin:rmax])
		return b
	}

    return a[:rmax]
}

// search performs a binary search for UnixNano() v in a
// and returns the position, i, where v would be inserted.
// An additional check of a[i].UnixNano() == v is necessary
// to determine if the value v exists.
func (a {{.Name}}Values) search(v int64) int {
	// Define: f(x) → a[x].UnixNano() < v
	// Define: f(-1) == true, f(n) == false
	// Invariant: f(lo-1) == true, f(hi) == false
	lo := 0
	hi := len(a)
	for lo < hi {
		mid := int(uint(lo+hi) >> 1)
		if a[mid].UnixNano() < v {
			lo = mid + 1 // preserves f(lo-1) == true
		} else {
			hi = mid // preserves f(hi) == false
		}
	}

	// lo == hi
	return lo
}

// FindRange returns the positions where min and max would be
// inserted into the array. If a[0].UnixNano() > max or
// a[len-1].UnixNano() < min then FindRange returns (-1, -1)
// indicating the array is outside the [min, max]. The values must
// be deduplicated and sorted before calling Exclude or the results
// are undefined.
func (a {{.Name}}Values) FindRange(min, max int64) (int, int) {
	if len(a) == 0 || min > max {
		return -1, -1
	}

	minVal := a[0].UnixNano()
	maxVal := a[len(a)-1].UnixNano()

	if maxVal < min || minVal > max {
		return -1, -1
	}

	return a.search(min), a.search(max)
}

// Merge overlays b to top of a.  If two values conflict with
// the same timestamp, b is used.  Both a and b must be sorted
// in ascending order.
func (a {{.Name}}Values) Merge(b {{.Name}}Values) {{.Name}}Values {
	if len(a) == 0 {
		return b
	}

	if len(b) == 0 {
		return a
	}

	// Normally, both a and b should not contain duplicates.  Due to a bug in older versions, it's
	// possible stored blocks might contain duplicate values.  Remove them if they exists before
	// merging.
	a = a.Deduplicate()
	b = b.Deduplicate()

	if a[len(a)-1].UnixNano() < b[0].UnixNano() {
		return append(a, b...)
	}

	if b[len(b)-1].UnixNano() < a[0].UnixNano() {
		return append(b, a...)
	}

	out := make({{.Name}}Values, 0, len(a)+len(b))
	for len(a) > 0 && len(b) > 0 {
		if a[0].UnixNano() < b[0].UnixNano() {
			out, a = append(out, a[0]), a[1:]
		} else if len(b) > 0 && a[0].UnixNano() == b[0].UnixNano() {
			a = a[1:]
		} else {
			out, b = append(out, b[0]), b[1:]
		}
	}
	if len(a) > 0 {
		return append(out, a...)
	}
	return append(out, b...)
}

{{ if ne .Name "" }}
func (a {{.Name}}Values) Encode(buf []byte) ([]byte, error) {
	return encode{{.Name}}ValuesBlock(buf, a)
}

func Encode{{ .Name }}ArrayBlock(a *tsdb.{{ .Name }}Array, b []byte) ([]byte, error) {
	if a.Len() == 0 {
		return nil, nil
	}

	// TODO(edd): These need to be pooled.
	var vb []byte
	var tb []byte
	var err error

	if vb, err = {{ .Name }}ArrayEncodeAll(a.Values, vb); err != nil {
		return nil, err
	}

	if tb, err = TimeArrayEncodeAll(a.Timestamps, tb); err != nil {
		return nil, err
	}

	// Prepend the first timestamp of the block in the first 8 bytes and the block
	// in the next byte, followed by the block
	return packBlock(b, {{ .Type }}, tb, vb), nil
}

func encode{{ .Name }}ValuesBlock(buf []byte, values []{{.Name}}Value) ([]byte, error) {
	if len(values) == 0 {
		return nil, nil
	}

	venc := get{{ .Name }}Encoder(len(values))
	tsenc := getTimeEncoder(len(values))

	var b []byte
	err := func() error {
		for _, v := range values {
			tsenc.Write(v.UnixNano())
			venc.Write({{if .CastType}}{{.CastType}}(v.RawValue()){{else}}v.RawValue(){{end}})
		}
		venc.Flush()

		// Encoded timestamp values
		tb, err := tsenc.Bytes()
		if err != nil {
			return err
		}
		// Encoded values
		vb, err := venc.Bytes()
		if err != nil {
			return err
		}

		// Prepend the first timestamp of the block in the first 8 bytes and the block
		// in the next byte, followed by the block
		b = packBlock(buf, {{ .Type }}, tb, vb)

		return nil
	}()

	putTimeEncoder(tsenc)
	put{{.Name}}Encoder(venc)

	return b, err
}

{{ end }}

// Sort methods
func (a {{.Name}}Values) Len() int           { return len(a) }
func (a {{.Name}}Values) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a {{.Name}}Values) Less(i, j int) bool { return a[i].UnixNano() < a[j].UnixNano() }


{{ end }}