// Generated by tmpl
// https://github.com/benbjohnson/tmpl
//
// DO NOT EDIT!
// Source: array_cursor.gen.go.tmpl

package reads

import (
	"errors"
	"fmt"
	"math"

	"github.com/influxdata/flux/interval"
	"github.com/influxdata/flux/values"
	errors2 "github.com/influxdata/influxdb/v2/kit/platform/errors"
	"github.com/influxdata/influxdb/v2/tsdb/cursors"
)

const (
	// MaxPointsPerBlock is the maximum number of points in an encoded
	// block in a TSM file. It should match the value in the tsm1
	// package, but we don't want to import it.
	MaxPointsPerBlock = 1000
)

func newLimitArrayCursor(cur cursors.Cursor) cursors.Cursor {
	switch cur := cur.(type) {

	case cursors.FloatArrayCursor:
		return newFloatLimitArrayCursor(cur)

	case cursors.IntegerArrayCursor:
		return newIntegerLimitArrayCursor(cur)

	case cursors.UnsignedArrayCursor:
		return newUnsignedLimitArrayCursor(cur)

	case cursors.StringArrayCursor:
		return newStringLimitArrayCursor(cur)

	case cursors.BooleanArrayCursor:
		return newBooleanLimitArrayCursor(cur)

	default:
		panic(fmt.Sprintf("unreachable: %T", cur))
	}
}

func newWindowFirstArrayCursor(cur cursors.Cursor, window interval.Window) cursors.Cursor {
	if window.IsZero() {
		return newLimitArrayCursor(cur)
	}
	switch cur := cur.(type) {

	case cursors.FloatArrayCursor:
		return newFloatWindowFirstArrayCursor(cur, window)

	case cursors.IntegerArrayCursor:
		return newIntegerWindowFirstArrayCursor(cur, window)

	case cursors.UnsignedArrayCursor:
		return newUnsignedWindowFirstArrayCursor(cur, window)

	case cursors.StringArrayCursor:
		return newStringWindowFirstArrayCursor(cur, window)

	case cursors.BooleanArrayCursor:
		return newBooleanWindowFirstArrayCursor(cur, window)

	default:
		panic(fmt.Sprintf("unreachable: %T", cur))
	}
}

func newWindowLastArrayCursor(cur cursors.Cursor, window interval.Window) cursors.Cursor {
	if window.IsZero() {
		return newLimitArrayCursor(cur)
	}
	switch cur := cur.(type) {

	case cursors.FloatArrayCursor:
		return newFloatWindowLastArrayCursor(cur, window)

	case cursors.IntegerArrayCursor:
		return newIntegerWindowLastArrayCursor(cur, window)

	case cursors.UnsignedArrayCursor:
		return newUnsignedWindowLastArrayCursor(cur, window)

	case cursors.StringArrayCursor:
		return newStringWindowLastArrayCursor(cur, window)

	case cursors.BooleanArrayCursor:
		return newBooleanWindowLastArrayCursor(cur, window)

	default:
		panic(fmt.Sprintf("unreachable: %T", cur))
	}
}

func newWindowCountArrayCursor(cur cursors.Cursor, window interval.Window) cursors.Cursor {
	switch cur := cur.(type) {

	case cursors.FloatArrayCursor:
		return newFloatWindowCountArrayCursor(cur, window)

	case cursors.IntegerArrayCursor:
		return newIntegerWindowCountArrayCursor(cur, window)

	case cursors.UnsignedArrayCursor:
		return newUnsignedWindowCountArrayCursor(cur, window)

	case cursors.StringArrayCursor:
		return newStringWindowCountArrayCursor(cur, window)

	case cursors.BooleanArrayCursor:
		return newBooleanWindowCountArrayCursor(cur, window)

	default:
		panic(fmt.Sprintf("unreachable: %T", cur))
	}
}

func newWindowSumArrayCursor(cur cursors.Cursor, window interval.Window) (cursors.Cursor, error) {
	switch cur := cur.(type) {

	case cursors.FloatArrayCursor:
		return newFloatWindowSumArrayCursor(cur, window), nil

	case cursors.IntegerArrayCursor:
		return newIntegerWindowSumArrayCursor(cur, window), nil

	case cursors.UnsignedArrayCursor:
		return newUnsignedWindowSumArrayCursor(cur, window), nil

	default:
		return nil, &errors2.Error{
			Code: errors2.EInvalid,
			Msg:  fmt.Sprintf("unsupported input type for sum aggregate: %s", arrayCursorType(cur)),
		}
	}
}

func newWindowMinArrayCursor(cur cursors.Cursor, window interval.Window) cursors.Cursor {
	switch cur := cur.(type) {

	case cursors.FloatArrayCursor:
		return newFloatWindowMinArrayCursor(cur, window)

	case cursors.IntegerArrayCursor:
		return newIntegerWindowMinArrayCursor(cur, window)

	case cursors.UnsignedArrayCursor:
		return newUnsignedWindowMinArrayCursor(cur, window)

	default:
		panic(fmt.Sprintf("unsupported for aggregate min: %T", cur))
	}
}

func newWindowMaxArrayCursor(cur cursors.Cursor, window interval.Window) cursors.Cursor {
	switch cur := cur.(type) {

	case cursors.FloatArrayCursor:
		return newFloatWindowMaxArrayCursor(cur, window)

	case cursors.IntegerArrayCursor:
		return newIntegerWindowMaxArrayCursor(cur, window)

	case cursors.UnsignedArrayCursor:
		return newUnsignedWindowMaxArrayCursor(cur, window)

	default:
		panic(fmt.Sprintf("unsupported for aggregate max: %T", cur))
	}
}

func newWindowMeanArrayCursor(cur cursors.Cursor, window interval.Window) (cursors.Cursor, error) {
	switch cur := cur.(type) {

	case cursors.FloatArrayCursor:
		return newFloatWindowMeanArrayCursor(cur, window), nil

	case cursors.IntegerArrayCursor:
		return newIntegerWindowMeanArrayCursor(cur, window), nil

	case cursors.UnsignedArrayCursor:
		return newUnsignedWindowMeanArrayCursor(cur, window), nil

	default:
		return nil, &errors2.Error{
			Code: errors2.EInvalid,
			Msg:  fmt.Sprintf("unsupported input type for mean aggregate: %s", arrayCursorType(cur)),
		}
	}
}

// ********************
// Float Array Cursor

type floatArrayFilterCursor struct {
	cursors.FloatArrayCursor
	cond expression
	m    *singleValue
	res  *cursors.FloatArray
	tmp  *cursors.FloatArray
}

func newFloatFilterArrayCursor(cond expression) *floatArrayFilterCursor {
	return &floatArrayFilterCursor{
		cond: cond,
		m:    &singleValue{},
		res:  cursors.NewFloatArrayLen(MaxPointsPerBlock),
		tmp:  &cursors.FloatArray{},
	}
}

func (c *floatArrayFilterCursor) reset(cur cursors.FloatArrayCursor) {
	c.FloatArrayCursor = cur
	c.tmp.Timestamps, c.tmp.Values = nil, nil
}

func (c *floatArrayFilterCursor) Stats() cursors.CursorStats { return c.FloatArrayCursor.Stats() }

func (c *floatArrayFilterCursor) Next() *cursors.FloatArray {
	pos := 0
	c.res.Timestamps = c.res.Timestamps[:cap(c.res.Timestamps)]
	c.res.Values = c.res.Values[:cap(c.res.Values)]

	var a *cursors.FloatArray

	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.FloatArrayCursor.Next()
	}

LOOP:
	for len(a.Timestamps) > 0 {
		for i, v := range a.Values {
			c.m.v = v
			if c.cond.EvalBool(c.m) {
				c.res.Timestamps[pos] = a.Timestamps[i]
				c.res.Values[pos] = v
				pos++
				if pos >= MaxPointsPerBlock {
					c.tmp.Timestamps = a.Timestamps[i+1:]
					c.tmp.Values = a.Values[i+1:]
					break LOOP
				}
			}
		}

		// Clear buffered timestamps & values if we make it through a cursor.
		// The break above will skip this if a cursor is partially read.
		c.tmp.Timestamps = nil
		c.tmp.Values = nil

		a = c.FloatArrayCursor.Next()
	}

	c.res.Timestamps = c.res.Timestamps[:pos]
	c.res.Values = c.res.Values[:pos]

	return c.res
}

type floatMultiShardArrayCursor struct {
	cursors.FloatArrayCursor
	cursorContext
	filter *floatArrayFilterCursor
}

func (c *floatMultiShardArrayCursor) reset(cur cursors.FloatArrayCursor, itrs cursors.CursorIterators, cond expression) {
	if cond != nil {
		if c.filter == nil {
			c.filter = newFloatFilterArrayCursor(cond)
		}
		c.filter.reset(cur)
		cur = c.filter
	}

	c.FloatArrayCursor = cur
	c.itrs = itrs
	c.err = nil
}

func (c *floatMultiShardArrayCursor) Err() error { return c.err }

func (c *floatMultiShardArrayCursor) Stats() cursors.CursorStats {
	return c.FloatArrayCursor.Stats()
}

func (c *floatMultiShardArrayCursor) Next() *cursors.FloatArray {
	for {
		a := c.FloatArrayCursor.Next()
		if a.Len() == 0 {
			if c.nextArrayCursor() {
				continue
			}
		}
		return a
	}
}

func (c *floatMultiShardArrayCursor) nextArrayCursor() bool {
	if len(c.itrs) == 0 {
		return false
	}

	c.FloatArrayCursor.Close()

	var itr cursors.CursorIterator
	var cur cursors.Cursor
	for cur == nil && len(c.itrs) > 0 {
		itr, c.itrs = c.itrs[0], c.itrs[1:]
		cur, _ = itr.Next(c.ctx, c.req)
	}

	var ok bool
	if cur != nil {
		var next cursors.FloatArrayCursor
		next, ok = cur.(cursors.FloatArrayCursor)
		if !ok {
			cur.Close()
			next = FloatEmptyArrayCursor
			c.err = errors.New("expected float cursor")
		} else {
			if c.filter != nil {
				c.filter.reset(next)
				next = c.filter
			}
		}
		c.FloatArrayCursor = next
	} else {
		c.FloatArrayCursor = FloatEmptyArrayCursor
	}

	return ok
}

type floatLimitArrayCursor struct {
	cursors.FloatArrayCursor
	res  *cursors.FloatArray
	done bool
}

func newFloatLimitArrayCursor(cur cursors.FloatArrayCursor) *floatLimitArrayCursor {
	return &floatLimitArrayCursor{
		FloatArrayCursor: cur,
		res:              cursors.NewFloatArrayLen(1),
	}
}

func (c *floatLimitArrayCursor) Stats() cursors.CursorStats { return c.FloatArrayCursor.Stats() }

func (c *floatLimitArrayCursor) Next() *cursors.FloatArray {
	if c.done {
		return &cursors.FloatArray{}
	}
	a := c.FloatArrayCursor.Next()
	if len(a.Timestamps) == 0 {
		return a
	}
	c.done = true
	c.res.Timestamps[0] = a.Timestamps[0]
	c.res.Values[0] = a.Values[0]
	return c.res
}

type floatWindowLastArrayCursor struct {
	cursors.FloatArrayCursor
	windowEnd int64
	res       *cursors.FloatArray
	tmp       *cursors.FloatArray
	window    interval.Window
}

// Window array cursors assume that every != 0 && every != MaxInt64.
// Such a cursor will panic in the first case and possibly overflow in the second.
func newFloatWindowLastArrayCursor(cur cursors.FloatArrayCursor, window interval.Window) *floatWindowLastArrayCursor {
	return &floatWindowLastArrayCursor{
		FloatArrayCursor: cur,
		windowEnd:        math.MinInt64,
		res:              cursors.NewFloatArrayLen(MaxPointsPerBlock),
		tmp:              &cursors.FloatArray{},
		window:           window,
	}
}

func (c *floatWindowLastArrayCursor) Stats() cursors.CursorStats {
	return c.FloatArrayCursor.Stats()
}

func (c *floatWindowLastArrayCursor) Next() *cursors.FloatArray {
	cur := -1

NEXT:
	var a *cursors.FloatArray

	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.FloatArrayCursor.Next()
	}

	if a.Len() == 0 {
		c.res.Timestamps = c.res.Timestamps[:cur+1]
		c.res.Values = c.res.Values[:cur+1]
		return c.res
	}

	for i, t := range a.Timestamps {
		if t >= c.windowEnd {
			cur++
		}

		if cur == MaxPointsPerBlock {
			c.tmp.Timestamps = a.Timestamps[i:]
			c.tmp.Values = a.Values[i:]
			return c.res
		}

		c.res.Timestamps[cur] = t
		c.res.Values[cur] = a.Values[i]

		c.windowEnd = int64(c.window.GetLatestBounds(values.Time(t)).Stop())
	}

	c.tmp.Timestamps = nil
	c.tmp.Values = nil

	goto NEXT
}

type floatWindowFirstArrayCursor struct {
	cursors.FloatArrayCursor
	windowEnd int64
	res       *cursors.FloatArray
	tmp       *cursors.FloatArray
	window    interval.Window
}

// Window array cursors assume that every != 0 && every != MaxInt64.
// Such a cursor will panic in the first case and possibly overflow in the second.
func newFloatWindowFirstArrayCursor(cur cursors.FloatArrayCursor, window interval.Window) *floatWindowFirstArrayCursor {
	return &floatWindowFirstArrayCursor{
		FloatArrayCursor: cur,
		windowEnd:        math.MinInt64,
		res:              cursors.NewFloatArrayLen(MaxPointsPerBlock),
		tmp:              &cursors.FloatArray{},
		window:           window,
	}
}

func (c *floatWindowFirstArrayCursor) Stats() cursors.CursorStats {
	return c.FloatArrayCursor.Stats()
}

func (c *floatWindowFirstArrayCursor) Next() *cursors.FloatArray {
	c.res.Timestamps = c.res.Timestamps[:0]
	c.res.Values = c.res.Values[:0]

NEXT:
	var a *cursors.FloatArray

	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.FloatArrayCursor.Next()
	}

	if a.Len() == 0 {
		return c.res
	}

	for i, t := range a.Timestamps {
		if t < c.windowEnd {
			continue
		}

		c.windowEnd = int64(c.window.GetLatestBounds(values.Time(t)).Stop())

		c.res.Timestamps = append(c.res.Timestamps, t)
		c.res.Values = append(c.res.Values, a.Values[i])

		if c.res.Len() == MaxPointsPerBlock {
			c.tmp.Timestamps = a.Timestamps[i+1:]
			c.tmp.Values = a.Values[i+1:]
			return c.res
		}
	}

	c.tmp.Timestamps = nil
	c.tmp.Values = nil

	goto NEXT
}

type floatWindowCountArrayCursor struct {
	cursors.FloatArrayCursor
	res    *cursors.IntegerArray
	tmp    *cursors.FloatArray
	window interval.Window
}

func newFloatWindowCountArrayCursor(cur cursors.FloatArrayCursor, window interval.Window) *floatWindowCountArrayCursor {
	resLen := MaxPointsPerBlock
	if window.IsZero() {
		resLen = 1
	}
	return &floatWindowCountArrayCursor{
		FloatArrayCursor: cur,
		res:              cursors.NewIntegerArrayLen(resLen),
		tmp:              &cursors.FloatArray{},
		window:           window,
	}
}

func (c *floatWindowCountArrayCursor) Stats() cursors.CursorStats {
	return c.FloatArrayCursor.Stats()
}

func (c *floatWindowCountArrayCursor) Next() *cursors.IntegerArray {
	pos := 0
	c.res.Timestamps = c.res.Timestamps[:cap(c.res.Timestamps)]
	c.res.Values = c.res.Values[:cap(c.res.Values)]

	var a *cursors.FloatArray
	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.FloatArrayCursor.Next()
	}

	if a.Len() == 0 {
		return &cursors.IntegerArray{}
	}

	rowIdx := 0
	var acc int64 = 0

	var windowEnd int64
	if !c.window.IsZero() {
		windowEnd = int64(c.window.GetLatestBounds(values.Time(a.Timestamps[rowIdx])).Stop())
	} else {
		windowEnd = math.MaxInt64
	}
	windowHasPoints := false

	// enumerate windows
WINDOWS:
	for {
		for ; rowIdx < a.Len(); rowIdx++ {
			ts := a.Timestamps[rowIdx]
			if !c.window.IsZero() && ts >= windowEnd {
				// new window detected, close the current window
				// do not generate a point for empty windows
				if windowHasPoints {
					c.res.Timestamps[pos] = windowEnd
					c.res.Values[pos] = acc
					pos++
					if pos >= MaxPointsPerBlock {
						// the output array is full,
						// save the remaining points in the input array in tmp.
						// they will be processed in the next call to Next()
						c.tmp.Timestamps = a.Timestamps[rowIdx:]
						c.tmp.Values = a.Values[rowIdx:]
						break WINDOWS
					}
				}

				// start the new window
				acc = 0
				windowEnd = int64(c.window.GetLatestBounds(values.Time(ts)).Stop())
				windowHasPoints = false

				continue WINDOWS
			} else {
				acc++
				windowHasPoints = true
			}
		}

		// Clear buffered timestamps & values if we make it through a cursor.
		// The break above will skip this if a cursor is partially read.
		c.tmp.Timestamps = nil
		c.tmp.Values = nil

		// get the next chunk
		a = c.FloatArrayCursor.Next()
		if a.Len() == 0 {
			// write the final point
			// do not generate a point for empty windows
			if windowHasPoints {
				c.res.Timestamps[pos] = windowEnd
				c.res.Values[pos] = acc
				pos++
			}
			break WINDOWS
		}
		rowIdx = 0
	}

	c.res.Timestamps = c.res.Timestamps[:pos]
	c.res.Values = c.res.Values[:pos]

	return c.res
}

type floatWindowSumArrayCursor struct {
	cursors.FloatArrayCursor
	res    *cursors.FloatArray
	tmp    *cursors.FloatArray
	window interval.Window
}

func newFloatWindowSumArrayCursor(cur cursors.FloatArrayCursor, window interval.Window) *floatWindowSumArrayCursor {
	resLen := MaxPointsPerBlock
	if window.IsZero() {
		resLen = 1
	}
	return &floatWindowSumArrayCursor{
		FloatArrayCursor: cur,
		res:              cursors.NewFloatArrayLen(resLen),
		tmp:              &cursors.FloatArray{},
		window:           window,
	}
}

func (c *floatWindowSumArrayCursor) Stats() cursors.CursorStats {
	return c.FloatArrayCursor.Stats()
}

func (c *floatWindowSumArrayCursor) Next() *cursors.FloatArray {
	pos := 0
	c.res.Timestamps = c.res.Timestamps[:cap(c.res.Timestamps)]
	c.res.Values = c.res.Values[:cap(c.res.Values)]

	var a *cursors.FloatArray
	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.FloatArrayCursor.Next()
	}

	if a.Len() == 0 {
		return &cursors.FloatArray{}
	}

	rowIdx := 0
	var acc float64 = 0

	var windowEnd int64
	if !c.window.IsZero() {
		windowEnd = int64(c.window.GetLatestBounds(values.Time(a.Timestamps[rowIdx])).Stop())
	} else {
		windowEnd = math.MaxInt64
	}
	windowHasPoints := false

	// enumerate windows
WINDOWS:
	for {
		for ; rowIdx < a.Len(); rowIdx++ {
			ts := a.Timestamps[rowIdx]
			if !c.window.IsZero() && ts >= windowEnd {
				// new window detected, close the current window
				// do not generate a point for empty windows
				if windowHasPoints {
					c.res.Timestamps[pos] = windowEnd
					c.res.Values[pos] = acc
					pos++
					if pos >= MaxPointsPerBlock {
						// the output array is full,
						// save the remaining points in the input array in tmp.
						// they will be processed in the next call to Next()
						c.tmp.Timestamps = a.Timestamps[rowIdx:]
						c.tmp.Values = a.Values[rowIdx:]
						break WINDOWS
					}
				}

				// start the new window
				acc = 0
				windowEnd = int64(c.window.GetLatestBounds(values.Time(ts)).Stop())
				windowHasPoints = false

				continue WINDOWS
			} else {
				acc += a.Values[rowIdx]
				windowHasPoints = true
			}
		}

		// Clear buffered timestamps & values if we make it through a cursor.
		// The break above will skip this if a cursor is partially read.
		c.tmp.Timestamps = nil
		c.tmp.Values = nil

		// get the next chunk
		a = c.FloatArrayCursor.Next()
		if a.Len() == 0 {
			// write the final point
			// do not generate a point for empty windows
			if windowHasPoints {
				c.res.Timestamps[pos] = windowEnd
				c.res.Values[pos] = acc
				pos++
			}
			break WINDOWS
		}
		rowIdx = 0
	}

	c.res.Timestamps = c.res.Timestamps[:pos]
	c.res.Values = c.res.Values[:pos]

	return c.res
}

type floatWindowMinArrayCursor struct {
	cursors.FloatArrayCursor
	res    *cursors.FloatArray
	tmp    *cursors.FloatArray
	window interval.Window
}

func newFloatWindowMinArrayCursor(cur cursors.FloatArrayCursor, window interval.Window) *floatWindowMinArrayCursor {
	resLen := MaxPointsPerBlock
	if window.IsZero() {
		resLen = 1
	}
	return &floatWindowMinArrayCursor{
		FloatArrayCursor: cur,
		res:              cursors.NewFloatArrayLen(resLen),
		tmp:              &cursors.FloatArray{},
		window:           window,
	}
}

func (c *floatWindowMinArrayCursor) Stats() cursors.CursorStats {
	return c.FloatArrayCursor.Stats()
}

func (c *floatWindowMinArrayCursor) Next() *cursors.FloatArray {
	pos := 0
	c.res.Timestamps = c.res.Timestamps[:cap(c.res.Timestamps)]
	c.res.Values = c.res.Values[:cap(c.res.Values)]

	var a *cursors.FloatArray
	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.FloatArrayCursor.Next()
	}

	if a.Len() == 0 {
		return &cursors.FloatArray{}
	}

	rowIdx := 0
	var acc float64 = math.MaxFloat64
	var tsAcc int64

	var windowEnd int64
	if !c.window.IsZero() {
		windowEnd = int64(c.window.GetLatestBounds(values.Time(a.Timestamps[rowIdx])).Stop())
	} else {
		windowEnd = math.MaxInt64
	}
	windowHasPoints := false

	// enumerate windows
WINDOWS:
	for {
		for ; rowIdx < a.Len(); rowIdx++ {
			ts := a.Timestamps[rowIdx]
			if !c.window.IsZero() && ts >= windowEnd {
				// new window detected, close the current window
				// do not generate a point for empty windows
				if windowHasPoints {
					c.res.Timestamps[pos] = tsAcc
					c.res.Values[pos] = acc
					pos++
					if pos >= MaxPointsPerBlock {
						// the output array is full,
						// save the remaining points in the input array in tmp.
						// they will be processed in the next call to Next()
						c.tmp.Timestamps = a.Timestamps[rowIdx:]
						c.tmp.Values = a.Values[rowIdx:]
						break WINDOWS
					}
				}

				// start the new window
				acc = math.MaxFloat64
				windowEnd = int64(c.window.GetLatestBounds(values.Time(ts)).Stop())
				windowHasPoints = false

				continue WINDOWS
			} else {
				if !windowHasPoints || a.Values[rowIdx] < acc {
					acc = a.Values[rowIdx]
					tsAcc = a.Timestamps[rowIdx]
				}
				windowHasPoints = true
			}
		}

		// Clear buffered timestamps & values if we make it through a cursor.
		// The break above will skip this if a cursor is partially read.
		c.tmp.Timestamps = nil
		c.tmp.Values = nil

		// get the next chunk
		a = c.FloatArrayCursor.Next()
		if a.Len() == 0 {
			// write the final point
			// do not generate a point for empty windows
			if windowHasPoints {
				c.res.Timestamps[pos] = tsAcc
				c.res.Values[pos] = acc
				pos++
			}
			break WINDOWS
		}
		rowIdx = 0
	}

	c.res.Timestamps = c.res.Timestamps[:pos]
	c.res.Values = c.res.Values[:pos]

	return c.res
}

type floatWindowMaxArrayCursor struct {
	cursors.FloatArrayCursor
	res    *cursors.FloatArray
	tmp    *cursors.FloatArray
	window interval.Window
}

func newFloatWindowMaxArrayCursor(cur cursors.FloatArrayCursor, window interval.Window) *floatWindowMaxArrayCursor {
	resLen := MaxPointsPerBlock
	if window.IsZero() {
		resLen = 1
	}
	return &floatWindowMaxArrayCursor{
		FloatArrayCursor: cur,
		res:              cursors.NewFloatArrayLen(resLen),
		tmp:              &cursors.FloatArray{},
		window:           window,
	}
}

func (c *floatWindowMaxArrayCursor) Stats() cursors.CursorStats {
	return c.FloatArrayCursor.Stats()
}

func (c *floatWindowMaxArrayCursor) Next() *cursors.FloatArray {
	pos := 0
	c.res.Timestamps = c.res.Timestamps[:cap(c.res.Timestamps)]
	c.res.Values = c.res.Values[:cap(c.res.Values)]

	var a *cursors.FloatArray
	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.FloatArrayCursor.Next()
	}

	if a.Len() == 0 {
		return &cursors.FloatArray{}
	}

	rowIdx := 0
	var acc float64 = -math.MaxFloat64
	var tsAcc int64

	var windowEnd int64
	if !c.window.IsZero() {
		windowEnd = int64(c.window.GetLatestBounds(values.Time(a.Timestamps[rowIdx])).Stop())
	} else {
		windowEnd = math.MaxInt64
	}
	windowHasPoints := false

	// enumerate windows
WINDOWS:
	for {
		for ; rowIdx < a.Len(); rowIdx++ {
			ts := a.Timestamps[rowIdx]
			if !c.window.IsZero() && ts >= windowEnd {
				// new window detected, close the current window
				// do not generate a point for empty windows
				if windowHasPoints {
					c.res.Timestamps[pos] = tsAcc
					c.res.Values[pos] = acc
					pos++
					if pos >= MaxPointsPerBlock {
						// the output array is full,
						// save the remaining points in the input array in tmp.
						// they will be processed in the next call to Next()
						c.tmp.Timestamps = a.Timestamps[rowIdx:]
						c.tmp.Values = a.Values[rowIdx:]
						break WINDOWS
					}
				}

				// start the new window
				acc = -math.MaxFloat64
				windowEnd = int64(c.window.GetLatestBounds(values.Time(ts)).Stop())
				windowHasPoints = false

				continue WINDOWS
			} else {
				if !windowHasPoints || a.Values[rowIdx] > acc {
					acc = a.Values[rowIdx]
					tsAcc = a.Timestamps[rowIdx]
				}
				windowHasPoints = true
			}
		}

		// Clear buffered timestamps & values if we make it through a cursor.
		// The break above will skip this if a cursor is partially read.
		c.tmp.Timestamps = nil
		c.tmp.Values = nil

		// get the next chunk
		a = c.FloatArrayCursor.Next()
		if a.Len() == 0 {
			// write the final point
			// do not generate a point for empty windows
			if windowHasPoints {
				c.res.Timestamps[pos] = tsAcc
				c.res.Values[pos] = acc
				pos++
			}
			break WINDOWS
		}
		rowIdx = 0
	}

	c.res.Timestamps = c.res.Timestamps[:pos]
	c.res.Values = c.res.Values[:pos]

	return c.res
}

type floatWindowMeanArrayCursor struct {
	cursors.FloatArrayCursor
	res    *cursors.FloatArray
	tmp    *cursors.FloatArray
	window interval.Window
}

func newFloatWindowMeanArrayCursor(cur cursors.FloatArrayCursor, window interval.Window) *floatWindowMeanArrayCursor {
	resLen := MaxPointsPerBlock
	if window.IsZero() {
		resLen = 1
	}
	return &floatWindowMeanArrayCursor{
		FloatArrayCursor: cur,
		res:              cursors.NewFloatArrayLen(resLen),
		tmp:              &cursors.FloatArray{},
		window:           window,
	}
}

func (c *floatWindowMeanArrayCursor) Stats() cursors.CursorStats {
	return c.FloatArrayCursor.Stats()
}

func (c *floatWindowMeanArrayCursor) Next() *cursors.FloatArray {
	pos := 0
	c.res.Timestamps = c.res.Timestamps[:cap(c.res.Timestamps)]
	c.res.Values = c.res.Values[:cap(c.res.Values)]

	var a *cursors.FloatArray
	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.FloatArrayCursor.Next()
	}

	if a.Len() == 0 {
		return &cursors.FloatArray{}
	}

	rowIdx := 0
	var sum float64
	var count int64

	var windowEnd int64
	if !c.window.IsZero() {
		windowEnd = int64(c.window.GetLatestBounds(values.Time(a.Timestamps[rowIdx])).Stop())
	} else {
		windowEnd = math.MaxInt64
	}
	windowHasPoints := false

	// enumerate windows
WINDOWS:
	for {
		for ; rowIdx < a.Len(); rowIdx++ {
			ts := a.Timestamps[rowIdx]
			if !c.window.IsZero() && ts >= windowEnd {
				// new window detected, close the current window
				// do not generate a point for empty windows
				if windowHasPoints {
					c.res.Timestamps[pos] = windowEnd
					c.res.Values[pos] = sum / float64(count)
					pos++
					if pos >= MaxPointsPerBlock {
						// the output array is full,
						// save the remaining points in the input array in tmp.
						// they will be processed in the next call to Next()
						c.tmp.Timestamps = a.Timestamps[rowIdx:]
						c.tmp.Values = a.Values[rowIdx:]
						break WINDOWS
					}
				}

				// start the new window
				sum = 0
				count = 0
				windowEnd = int64(c.window.GetLatestBounds(values.Time(ts)).Stop())
				windowHasPoints = false

				continue WINDOWS
			} else {
				sum += a.Values[rowIdx]
				count++
				windowHasPoints = true
			}
		}

		// Clear buffered timestamps & values if we make it through a cursor.
		// The break above will skip this if a cursor is partially read.
		c.tmp.Timestamps = nil
		c.tmp.Values = nil

		// get the next chunk
		a = c.FloatArrayCursor.Next()
		if a.Len() == 0 {
			// write the final point
			// do not generate a point for empty windows
			if windowHasPoints {
				c.res.Timestamps[pos] = windowEnd
				c.res.Values[pos] = sum / float64(count)
				pos++
			}
			break WINDOWS
		}
		rowIdx = 0
	}

	c.res.Timestamps = c.res.Timestamps[:pos]
	c.res.Values = c.res.Values[:pos]

	return c.res
}

type floatEmptyArrayCursor struct {
	res cursors.FloatArray
}

var FloatEmptyArrayCursor cursors.FloatArrayCursor = &floatEmptyArrayCursor{}

func (c *floatEmptyArrayCursor) Err() error                 { return nil }
func (c *floatEmptyArrayCursor) Close()                     {}
func (c *floatEmptyArrayCursor) Stats() cursors.CursorStats { return cursors.CursorStats{} }
func (c *floatEmptyArrayCursor) Next() *cursors.FloatArray  { return &c.res }

// ********************
// Integer Array Cursor

type integerArrayFilterCursor struct {
	cursors.IntegerArrayCursor
	cond expression
	m    *singleValue
	res  *cursors.IntegerArray
	tmp  *cursors.IntegerArray
}

func newIntegerFilterArrayCursor(cond expression) *integerArrayFilterCursor {
	return &integerArrayFilterCursor{
		cond: cond,
		m:    &singleValue{},
		res:  cursors.NewIntegerArrayLen(MaxPointsPerBlock),
		tmp:  &cursors.IntegerArray{},
	}
}

func (c *integerArrayFilterCursor) reset(cur cursors.IntegerArrayCursor) {
	c.IntegerArrayCursor = cur
	c.tmp.Timestamps, c.tmp.Values = nil, nil
}

func (c *integerArrayFilterCursor) Stats() cursors.CursorStats { return c.IntegerArrayCursor.Stats() }

func (c *integerArrayFilterCursor) Next() *cursors.IntegerArray {
	pos := 0
	c.res.Timestamps = c.res.Timestamps[:cap(c.res.Timestamps)]
	c.res.Values = c.res.Values[:cap(c.res.Values)]

	var a *cursors.IntegerArray

	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.IntegerArrayCursor.Next()
	}

LOOP:
	for len(a.Timestamps) > 0 {
		for i, v := range a.Values {
			c.m.v = v
			if c.cond.EvalBool(c.m) {
				c.res.Timestamps[pos] = a.Timestamps[i]
				c.res.Values[pos] = v
				pos++
				if pos >= MaxPointsPerBlock {
					c.tmp.Timestamps = a.Timestamps[i+1:]
					c.tmp.Values = a.Values[i+1:]
					break LOOP
				}
			}
		}

		// Clear buffered timestamps & values if we make it through a cursor.
		// The break above will skip this if a cursor is partially read.
		c.tmp.Timestamps = nil
		c.tmp.Values = nil

		a = c.IntegerArrayCursor.Next()
	}

	c.res.Timestamps = c.res.Timestamps[:pos]
	c.res.Values = c.res.Values[:pos]

	return c.res
}

type integerMultiShardArrayCursor struct {
	cursors.IntegerArrayCursor
	cursorContext
	filter *integerArrayFilterCursor
}

func (c *integerMultiShardArrayCursor) reset(cur cursors.IntegerArrayCursor, itrs cursors.CursorIterators, cond expression) {
	if cond != nil {
		if c.filter == nil {
			c.filter = newIntegerFilterArrayCursor(cond)
		}
		c.filter.reset(cur)
		cur = c.filter
	}

	c.IntegerArrayCursor = cur
	c.itrs = itrs
	c.err = nil
}

func (c *integerMultiShardArrayCursor) Err() error { return c.err }

func (c *integerMultiShardArrayCursor) Stats() cursors.CursorStats {
	return c.IntegerArrayCursor.Stats()
}

func (c *integerMultiShardArrayCursor) Next() *cursors.IntegerArray {
	for {
		a := c.IntegerArrayCursor.Next()
		if a.Len() == 0 {
			if c.nextArrayCursor() {
				continue
			}
		}
		return a
	}
}

func (c *integerMultiShardArrayCursor) nextArrayCursor() bool {
	if len(c.itrs) == 0 {
		return false
	}

	c.IntegerArrayCursor.Close()

	var itr cursors.CursorIterator
	var cur cursors.Cursor
	for cur == nil && len(c.itrs) > 0 {
		itr, c.itrs = c.itrs[0], c.itrs[1:]
		cur, _ = itr.Next(c.ctx, c.req)
	}

	var ok bool
	if cur != nil {
		var next cursors.IntegerArrayCursor
		next, ok = cur.(cursors.IntegerArrayCursor)
		if !ok {
			cur.Close()
			next = IntegerEmptyArrayCursor
			c.err = errors.New("expected integer cursor")
		} else {
			if c.filter != nil {
				c.filter.reset(next)
				next = c.filter
			}
		}
		c.IntegerArrayCursor = next
	} else {
		c.IntegerArrayCursor = IntegerEmptyArrayCursor
	}

	return ok
}

type integerLimitArrayCursor struct {
	cursors.IntegerArrayCursor
	res  *cursors.IntegerArray
	done bool
}

func newIntegerLimitArrayCursor(cur cursors.IntegerArrayCursor) *integerLimitArrayCursor {
	return &integerLimitArrayCursor{
		IntegerArrayCursor: cur,
		res:                cursors.NewIntegerArrayLen(1),
	}
}

func (c *integerLimitArrayCursor) Stats() cursors.CursorStats { return c.IntegerArrayCursor.Stats() }

func (c *integerLimitArrayCursor) Next() *cursors.IntegerArray {
	if c.done {
		return &cursors.IntegerArray{}
	}
	a := c.IntegerArrayCursor.Next()
	if len(a.Timestamps) == 0 {
		return a
	}
	c.done = true
	c.res.Timestamps[0] = a.Timestamps[0]
	c.res.Values[0] = a.Values[0]
	return c.res
}

type integerWindowLastArrayCursor struct {
	cursors.IntegerArrayCursor
	windowEnd int64
	res       *cursors.IntegerArray
	tmp       *cursors.IntegerArray
	window    interval.Window
}

// Window array cursors assume that every != 0 && every != MaxInt64.
// Such a cursor will panic in the first case and possibly overflow in the second.
func newIntegerWindowLastArrayCursor(cur cursors.IntegerArrayCursor, window interval.Window) *integerWindowLastArrayCursor {
	return &integerWindowLastArrayCursor{
		IntegerArrayCursor: cur,
		windowEnd:          math.MinInt64,
		res:                cursors.NewIntegerArrayLen(MaxPointsPerBlock),
		tmp:                &cursors.IntegerArray{},
		window:             window,
	}
}

func (c *integerWindowLastArrayCursor) Stats() cursors.CursorStats {
	return c.IntegerArrayCursor.Stats()
}

func (c *integerWindowLastArrayCursor) Next() *cursors.IntegerArray {
	cur := -1

NEXT:
	var a *cursors.IntegerArray

	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.IntegerArrayCursor.Next()
	}

	if a.Len() == 0 {
		c.res.Timestamps = c.res.Timestamps[:cur+1]
		c.res.Values = c.res.Values[:cur+1]
		return c.res
	}

	for i, t := range a.Timestamps {
		if t >= c.windowEnd {
			cur++
		}

		if cur == MaxPointsPerBlock {
			c.tmp.Timestamps = a.Timestamps[i:]
			c.tmp.Values = a.Values[i:]
			return c.res
		}

		c.res.Timestamps[cur] = t
		c.res.Values[cur] = a.Values[i]

		c.windowEnd = int64(c.window.GetLatestBounds(values.Time(t)).Stop())
	}

	c.tmp.Timestamps = nil
	c.tmp.Values = nil

	goto NEXT
}

type integerWindowFirstArrayCursor struct {
	cursors.IntegerArrayCursor
	windowEnd int64
	res       *cursors.IntegerArray
	tmp       *cursors.IntegerArray
	window    interval.Window
}

// Window array cursors assume that every != 0 && every != MaxInt64.
// Such a cursor will panic in the first case and possibly overflow in the second.
func newIntegerWindowFirstArrayCursor(cur cursors.IntegerArrayCursor, window interval.Window) *integerWindowFirstArrayCursor {
	return &integerWindowFirstArrayCursor{
		IntegerArrayCursor: cur,
		windowEnd:          math.MinInt64,
		res:                cursors.NewIntegerArrayLen(MaxPointsPerBlock),
		tmp:                &cursors.IntegerArray{},
		window:             window,
	}
}

func (c *integerWindowFirstArrayCursor) Stats() cursors.CursorStats {
	return c.IntegerArrayCursor.Stats()
}

func (c *integerWindowFirstArrayCursor) Next() *cursors.IntegerArray {
	c.res.Timestamps = c.res.Timestamps[:0]
	c.res.Values = c.res.Values[:0]

NEXT:
	var a *cursors.IntegerArray

	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.IntegerArrayCursor.Next()
	}

	if a.Len() == 0 {
		return c.res
	}

	for i, t := range a.Timestamps {
		if t < c.windowEnd {
			continue
		}

		c.windowEnd = int64(c.window.GetLatestBounds(values.Time(t)).Stop())

		c.res.Timestamps = append(c.res.Timestamps, t)
		c.res.Values = append(c.res.Values, a.Values[i])

		if c.res.Len() == MaxPointsPerBlock {
			c.tmp.Timestamps = a.Timestamps[i+1:]
			c.tmp.Values = a.Values[i+1:]
			return c.res
		}
	}

	c.tmp.Timestamps = nil
	c.tmp.Values = nil

	goto NEXT
}

type integerWindowCountArrayCursor struct {
	cursors.IntegerArrayCursor
	res    *cursors.IntegerArray
	tmp    *cursors.IntegerArray
	window interval.Window
}

func newIntegerWindowCountArrayCursor(cur cursors.IntegerArrayCursor, window interval.Window) *integerWindowCountArrayCursor {
	resLen := MaxPointsPerBlock
	if window.IsZero() {
		resLen = 1
	}
	return &integerWindowCountArrayCursor{
		IntegerArrayCursor: cur,
		res:                cursors.NewIntegerArrayLen(resLen),
		tmp:                &cursors.IntegerArray{},
		window:             window,
	}
}

func (c *integerWindowCountArrayCursor) Stats() cursors.CursorStats {
	return c.IntegerArrayCursor.Stats()
}

func (c *integerWindowCountArrayCursor) Next() *cursors.IntegerArray {
	pos := 0
	c.res.Timestamps = c.res.Timestamps[:cap(c.res.Timestamps)]
	c.res.Values = c.res.Values[:cap(c.res.Values)]

	var a *cursors.IntegerArray
	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.IntegerArrayCursor.Next()
	}

	if a.Len() == 0 {
		return &cursors.IntegerArray{}
	}

	rowIdx := 0
	var acc int64 = 0

	var windowEnd int64
	if !c.window.IsZero() {
		windowEnd = int64(c.window.GetLatestBounds(values.Time(a.Timestamps[rowIdx])).Stop())
	} else {
		windowEnd = math.MaxInt64
	}
	windowHasPoints := false

	// enumerate windows
WINDOWS:
	for {
		for ; rowIdx < a.Len(); rowIdx++ {
			ts := a.Timestamps[rowIdx]
			if !c.window.IsZero() && ts >= windowEnd {
				// new window detected, close the current window
				// do not generate a point for empty windows
				if windowHasPoints {
					c.res.Timestamps[pos] = windowEnd
					c.res.Values[pos] = acc
					pos++
					if pos >= MaxPointsPerBlock {
						// the output array is full,
						// save the remaining points in the input array in tmp.
						// they will be processed in the next call to Next()
						c.tmp.Timestamps = a.Timestamps[rowIdx:]
						c.tmp.Values = a.Values[rowIdx:]
						break WINDOWS
					}
				}

				// start the new window
				acc = 0
				windowEnd = int64(c.window.GetLatestBounds(values.Time(ts)).Stop())
				windowHasPoints = false

				continue WINDOWS
			} else {
				acc++
				windowHasPoints = true
			}
		}

		// Clear buffered timestamps & values if we make it through a cursor.
		// The break above will skip this if a cursor is partially read.
		c.tmp.Timestamps = nil
		c.tmp.Values = nil

		// get the next chunk
		a = c.IntegerArrayCursor.Next()
		if a.Len() == 0 {
			// write the final point
			// do not generate a point for empty windows
			if windowHasPoints {
				c.res.Timestamps[pos] = windowEnd
				c.res.Values[pos] = acc
				pos++
			}
			break WINDOWS
		}
		rowIdx = 0
	}

	c.res.Timestamps = c.res.Timestamps[:pos]
	c.res.Values = c.res.Values[:pos]

	return c.res
}

type integerWindowSumArrayCursor struct {
	cursors.IntegerArrayCursor
	res    *cursors.IntegerArray
	tmp    *cursors.IntegerArray
	window interval.Window
}

func newIntegerWindowSumArrayCursor(cur cursors.IntegerArrayCursor, window interval.Window) *integerWindowSumArrayCursor {
	resLen := MaxPointsPerBlock
	if window.IsZero() {
		resLen = 1
	}
	return &integerWindowSumArrayCursor{
		IntegerArrayCursor: cur,
		res:                cursors.NewIntegerArrayLen(resLen),
		tmp:                &cursors.IntegerArray{},
		window:             window,
	}
}

func (c *integerWindowSumArrayCursor) Stats() cursors.CursorStats {
	return c.IntegerArrayCursor.Stats()
}

func (c *integerWindowSumArrayCursor) Next() *cursors.IntegerArray {
	pos := 0
	c.res.Timestamps = c.res.Timestamps[:cap(c.res.Timestamps)]
	c.res.Values = c.res.Values[:cap(c.res.Values)]

	var a *cursors.IntegerArray
	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.IntegerArrayCursor.Next()
	}

	if a.Len() == 0 {
		return &cursors.IntegerArray{}
	}

	rowIdx := 0
	var acc int64 = 0

	var windowEnd int64
	if !c.window.IsZero() {
		windowEnd = int64(c.window.GetLatestBounds(values.Time(a.Timestamps[rowIdx])).Stop())
	} else {
		windowEnd = math.MaxInt64
	}
	windowHasPoints := false

	// enumerate windows
WINDOWS:
	for {
		for ; rowIdx < a.Len(); rowIdx++ {
			ts := a.Timestamps[rowIdx]
			if !c.window.IsZero() && ts >= windowEnd {
				// new window detected, close the current window
				// do not generate a point for empty windows
				if windowHasPoints {
					c.res.Timestamps[pos] = windowEnd
					c.res.Values[pos] = acc
					pos++
					if pos >= MaxPointsPerBlock {
						// the output array is full,
						// save the remaining points in the input array in tmp.
						// they will be processed in the next call to Next()
						c.tmp.Timestamps = a.Timestamps[rowIdx:]
						c.tmp.Values = a.Values[rowIdx:]
						break WINDOWS
					}
				}

				// start the new window
				acc = 0
				windowEnd = int64(c.window.GetLatestBounds(values.Time(ts)).Stop())
				windowHasPoints = false

				continue WINDOWS
			} else {
				acc += a.Values[rowIdx]
				windowHasPoints = true
			}
		}

		// Clear buffered timestamps & values if we make it through a cursor.
		// The break above will skip this if a cursor is partially read.
		c.tmp.Timestamps = nil
		c.tmp.Values = nil

		// get the next chunk
		a = c.IntegerArrayCursor.Next()
		if a.Len() == 0 {
			// write the final point
			// do not generate a point for empty windows
			if windowHasPoints {
				c.res.Timestamps[pos] = windowEnd
				c.res.Values[pos] = acc
				pos++
			}
			break WINDOWS
		}
		rowIdx = 0
	}

	c.res.Timestamps = c.res.Timestamps[:pos]
	c.res.Values = c.res.Values[:pos]

	return c.res
}

type integerWindowMinArrayCursor struct {
	cursors.IntegerArrayCursor
	res    *cursors.IntegerArray
	tmp    *cursors.IntegerArray
	window interval.Window
}

func newIntegerWindowMinArrayCursor(cur cursors.IntegerArrayCursor, window interval.Window) *integerWindowMinArrayCursor {
	resLen := MaxPointsPerBlock
	if window.IsZero() {
		resLen = 1
	}
	return &integerWindowMinArrayCursor{
		IntegerArrayCursor: cur,
		res:                cursors.NewIntegerArrayLen(resLen),
		tmp:                &cursors.IntegerArray{},
		window:             window,
	}
}

func (c *integerWindowMinArrayCursor) Stats() cursors.CursorStats {
	return c.IntegerArrayCursor.Stats()
}

func (c *integerWindowMinArrayCursor) Next() *cursors.IntegerArray {
	pos := 0
	c.res.Timestamps = c.res.Timestamps[:cap(c.res.Timestamps)]
	c.res.Values = c.res.Values[:cap(c.res.Values)]

	var a *cursors.IntegerArray
	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.IntegerArrayCursor.Next()
	}

	if a.Len() == 0 {
		return &cursors.IntegerArray{}
	}

	rowIdx := 0
	var acc int64 = math.MaxInt64
	var tsAcc int64

	var windowEnd int64
	if !c.window.IsZero() {
		windowEnd = int64(c.window.GetLatestBounds(values.Time(a.Timestamps[rowIdx])).Stop())
	} else {
		windowEnd = math.MaxInt64
	}
	windowHasPoints := false

	// enumerate windows
WINDOWS:
	for {
		for ; rowIdx < a.Len(); rowIdx++ {
			ts := a.Timestamps[rowIdx]
			if !c.window.IsZero() && ts >= windowEnd {
				// new window detected, close the current window
				// do not generate a point for empty windows
				if windowHasPoints {
					c.res.Timestamps[pos] = tsAcc
					c.res.Values[pos] = acc
					pos++
					if pos >= MaxPointsPerBlock {
						// the output array is full,
						// save the remaining points in the input array in tmp.
						// they will be processed in the next call to Next()
						c.tmp.Timestamps = a.Timestamps[rowIdx:]
						c.tmp.Values = a.Values[rowIdx:]
						break WINDOWS
					}
				}

				// start the new window
				acc = math.MaxInt64
				windowEnd = int64(c.window.GetLatestBounds(values.Time(ts)).Stop())
				windowHasPoints = false

				continue WINDOWS
			} else {
				if !windowHasPoints || a.Values[rowIdx] < acc {
					acc = a.Values[rowIdx]
					tsAcc = a.Timestamps[rowIdx]
				}
				windowHasPoints = true
			}
		}

		// Clear buffered timestamps & values if we make it through a cursor.
		// The break above will skip this if a cursor is partially read.
		c.tmp.Timestamps = nil
		c.tmp.Values = nil

		// get the next chunk
		a = c.IntegerArrayCursor.Next()
		if a.Len() == 0 {
			// write the final point
			// do not generate a point for empty windows
			if windowHasPoints {
				c.res.Timestamps[pos] = tsAcc
				c.res.Values[pos] = acc
				pos++
			}
			break WINDOWS
		}
		rowIdx = 0
	}

	c.res.Timestamps = c.res.Timestamps[:pos]
	c.res.Values = c.res.Values[:pos]

	return c.res
}

type integerWindowMaxArrayCursor struct {
	cursors.IntegerArrayCursor
	res    *cursors.IntegerArray
	tmp    *cursors.IntegerArray
	window interval.Window
}

func newIntegerWindowMaxArrayCursor(cur cursors.IntegerArrayCursor, window interval.Window) *integerWindowMaxArrayCursor {
	resLen := MaxPointsPerBlock
	if window.IsZero() {
		resLen = 1
	}
	return &integerWindowMaxArrayCursor{
		IntegerArrayCursor: cur,
		res:                cursors.NewIntegerArrayLen(resLen),
		tmp:                &cursors.IntegerArray{},
		window:             window,
	}
}

func (c *integerWindowMaxArrayCursor) Stats() cursors.CursorStats {
	return c.IntegerArrayCursor.Stats()
}

func (c *integerWindowMaxArrayCursor) Next() *cursors.IntegerArray {
	pos := 0
	c.res.Timestamps = c.res.Timestamps[:cap(c.res.Timestamps)]
	c.res.Values = c.res.Values[:cap(c.res.Values)]

	var a *cursors.IntegerArray
	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.IntegerArrayCursor.Next()
	}

	if a.Len() == 0 {
		return &cursors.IntegerArray{}
	}

	rowIdx := 0
	var acc int64 = math.MinInt64
	var tsAcc int64

	var windowEnd int64
	if !c.window.IsZero() {
		windowEnd = int64(c.window.GetLatestBounds(values.Time(a.Timestamps[rowIdx])).Stop())
	} else {
		windowEnd = math.MaxInt64
	}
	windowHasPoints := false

	// enumerate windows
WINDOWS:
	for {
		for ; rowIdx < a.Len(); rowIdx++ {
			ts := a.Timestamps[rowIdx]
			if !c.window.IsZero() && ts >= windowEnd {
				// new window detected, close the current window
				// do not generate a point for empty windows
				if windowHasPoints {
					c.res.Timestamps[pos] = tsAcc
					c.res.Values[pos] = acc
					pos++
					if pos >= MaxPointsPerBlock {
						// the output array is full,
						// save the remaining points in the input array in tmp.
						// they will be processed in the next call to Next()
						c.tmp.Timestamps = a.Timestamps[rowIdx:]
						c.tmp.Values = a.Values[rowIdx:]
						break WINDOWS
					}
				}

				// start the new window
				acc = math.MinInt64
				windowEnd = int64(c.window.GetLatestBounds(values.Time(ts)).Stop())
				windowHasPoints = false

				continue WINDOWS
			} else {
				if !windowHasPoints || a.Values[rowIdx] > acc {
					acc = a.Values[rowIdx]
					tsAcc = a.Timestamps[rowIdx]
				}
				windowHasPoints = true
			}
		}

		// Clear buffered timestamps & values if we make it through a cursor.
		// The break above will skip this if a cursor is partially read.
		c.tmp.Timestamps = nil
		c.tmp.Values = nil

		// get the next chunk
		a = c.IntegerArrayCursor.Next()
		if a.Len() == 0 {
			// write the final point
			// do not generate a point for empty windows
			if windowHasPoints {
				c.res.Timestamps[pos] = tsAcc
				c.res.Values[pos] = acc
				pos++
			}
			break WINDOWS
		}
		rowIdx = 0
	}

	c.res.Timestamps = c.res.Timestamps[:pos]
	c.res.Values = c.res.Values[:pos]

	return c.res
}

type integerWindowMeanArrayCursor struct {
	cursors.IntegerArrayCursor
	res    *cursors.FloatArray
	tmp    *cursors.IntegerArray
	window interval.Window
}

func newIntegerWindowMeanArrayCursor(cur cursors.IntegerArrayCursor, window interval.Window) *integerWindowMeanArrayCursor {
	resLen := MaxPointsPerBlock
	if window.IsZero() {
		resLen = 1
	}
	return &integerWindowMeanArrayCursor{
		IntegerArrayCursor: cur,
		res:                cursors.NewFloatArrayLen(resLen),
		tmp:                &cursors.IntegerArray{},
		window:             window,
	}
}

func (c *integerWindowMeanArrayCursor) Stats() cursors.CursorStats {
	return c.IntegerArrayCursor.Stats()
}

func (c *integerWindowMeanArrayCursor) Next() *cursors.FloatArray {
	pos := 0
	c.res.Timestamps = c.res.Timestamps[:cap(c.res.Timestamps)]
	c.res.Values = c.res.Values[:cap(c.res.Values)]

	var a *cursors.IntegerArray
	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.IntegerArrayCursor.Next()
	}

	if a.Len() == 0 {
		return &cursors.FloatArray{}
	}

	rowIdx := 0
	var sum int64
	var count int64

	var windowEnd int64
	if !c.window.IsZero() {
		windowEnd = int64(c.window.GetLatestBounds(values.Time(a.Timestamps[rowIdx])).Stop())
	} else {
		windowEnd = math.MaxInt64
	}
	windowHasPoints := false

	// enumerate windows
WINDOWS:
	for {
		for ; rowIdx < a.Len(); rowIdx++ {
			ts := a.Timestamps[rowIdx]
			if !c.window.IsZero() && ts >= windowEnd {
				// new window detected, close the current window
				// do not generate a point for empty windows
				if windowHasPoints {
					c.res.Timestamps[pos] = windowEnd
					c.res.Values[pos] = float64(sum) / float64(count)
					pos++
					if pos >= MaxPointsPerBlock {
						// the output array is full,
						// save the remaining points in the input array in tmp.
						// they will be processed in the next call to Next()
						c.tmp.Timestamps = a.Timestamps[rowIdx:]
						c.tmp.Values = a.Values[rowIdx:]
						break WINDOWS
					}
				}

				// start the new window
				sum = 0
				count = 0
				windowEnd = int64(c.window.GetLatestBounds(values.Time(ts)).Stop())
				windowHasPoints = false

				continue WINDOWS
			} else {
				sum += a.Values[rowIdx]
				count++
				windowHasPoints = true
			}
		}

		// Clear buffered timestamps & values if we make it through a cursor.
		// The break above will skip this if a cursor is partially read.
		c.tmp.Timestamps = nil
		c.tmp.Values = nil

		// get the next chunk
		a = c.IntegerArrayCursor.Next()
		if a.Len() == 0 {
			// write the final point
			// do not generate a point for empty windows
			if windowHasPoints {
				c.res.Timestamps[pos] = windowEnd
				c.res.Values[pos] = float64(sum) / float64(count)
				pos++
			}
			break WINDOWS
		}
		rowIdx = 0
	}

	c.res.Timestamps = c.res.Timestamps[:pos]
	c.res.Values = c.res.Values[:pos]

	return c.res
}

type integerEmptyArrayCursor struct {
	res cursors.IntegerArray
}

var IntegerEmptyArrayCursor cursors.IntegerArrayCursor = &integerEmptyArrayCursor{}

func (c *integerEmptyArrayCursor) Err() error                  { return nil }
func (c *integerEmptyArrayCursor) Close()                      {}
func (c *integerEmptyArrayCursor) Stats() cursors.CursorStats  { return cursors.CursorStats{} }
func (c *integerEmptyArrayCursor) Next() *cursors.IntegerArray { return &c.res }

// ********************
// Unsigned Array Cursor

type unsignedArrayFilterCursor struct {
	cursors.UnsignedArrayCursor
	cond expression
	m    *singleValue
	res  *cursors.UnsignedArray
	tmp  *cursors.UnsignedArray
}

func newUnsignedFilterArrayCursor(cond expression) *unsignedArrayFilterCursor {
	return &unsignedArrayFilterCursor{
		cond: cond,
		m:    &singleValue{},
		res:  cursors.NewUnsignedArrayLen(MaxPointsPerBlock),
		tmp:  &cursors.UnsignedArray{},
	}
}

func (c *unsignedArrayFilterCursor) reset(cur cursors.UnsignedArrayCursor) {
	c.UnsignedArrayCursor = cur
	c.tmp.Timestamps, c.tmp.Values = nil, nil
}

func (c *unsignedArrayFilterCursor) Stats() cursors.CursorStats { return c.UnsignedArrayCursor.Stats() }

func (c *unsignedArrayFilterCursor) Next() *cursors.UnsignedArray {
	pos := 0
	c.res.Timestamps = c.res.Timestamps[:cap(c.res.Timestamps)]
	c.res.Values = c.res.Values[:cap(c.res.Values)]

	var a *cursors.UnsignedArray

	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.UnsignedArrayCursor.Next()
	}

LOOP:
	for len(a.Timestamps) > 0 {
		for i, v := range a.Values {
			c.m.v = v
			if c.cond.EvalBool(c.m) {
				c.res.Timestamps[pos] = a.Timestamps[i]
				c.res.Values[pos] = v
				pos++
				if pos >= MaxPointsPerBlock {
					c.tmp.Timestamps = a.Timestamps[i+1:]
					c.tmp.Values = a.Values[i+1:]
					break LOOP
				}
			}
		}

		// Clear buffered timestamps & values if we make it through a cursor.
		// The break above will skip this if a cursor is partially read.
		c.tmp.Timestamps = nil
		c.tmp.Values = nil

		a = c.UnsignedArrayCursor.Next()
	}

	c.res.Timestamps = c.res.Timestamps[:pos]
	c.res.Values = c.res.Values[:pos]

	return c.res
}

type unsignedMultiShardArrayCursor struct {
	cursors.UnsignedArrayCursor
	cursorContext
	filter *unsignedArrayFilterCursor
}

func (c *unsignedMultiShardArrayCursor) reset(cur cursors.UnsignedArrayCursor, itrs cursors.CursorIterators, cond expression) {
	if cond != nil {
		if c.filter == nil {
			c.filter = newUnsignedFilterArrayCursor(cond)
		}
		c.filter.reset(cur)
		cur = c.filter
	}

	c.UnsignedArrayCursor = cur
	c.itrs = itrs
	c.err = nil
}

func (c *unsignedMultiShardArrayCursor) Err() error { return c.err }

func (c *unsignedMultiShardArrayCursor) Stats() cursors.CursorStats {
	return c.UnsignedArrayCursor.Stats()
}

func (c *unsignedMultiShardArrayCursor) Next() *cursors.UnsignedArray {
	for {
		a := c.UnsignedArrayCursor.Next()
		if a.Len() == 0 {
			if c.nextArrayCursor() {
				continue
			}
		}
		return a
	}
}

func (c *unsignedMultiShardArrayCursor) nextArrayCursor() bool {
	if len(c.itrs) == 0 {
		return false
	}

	c.UnsignedArrayCursor.Close()

	var itr cursors.CursorIterator
	var cur cursors.Cursor
	for cur == nil && len(c.itrs) > 0 {
		itr, c.itrs = c.itrs[0], c.itrs[1:]
		cur, _ = itr.Next(c.ctx, c.req)
	}

	var ok bool
	if cur != nil {
		var next cursors.UnsignedArrayCursor
		next, ok = cur.(cursors.UnsignedArrayCursor)
		if !ok {
			cur.Close()
			next = UnsignedEmptyArrayCursor
			c.err = errors.New("expected unsigned cursor")
		} else {
			if c.filter != nil {
				c.filter.reset(next)
				next = c.filter
			}
		}
		c.UnsignedArrayCursor = next
	} else {
		c.UnsignedArrayCursor = UnsignedEmptyArrayCursor
	}

	return ok
}

type unsignedLimitArrayCursor struct {
	cursors.UnsignedArrayCursor
	res  *cursors.UnsignedArray
	done bool
}

func newUnsignedLimitArrayCursor(cur cursors.UnsignedArrayCursor) *unsignedLimitArrayCursor {
	return &unsignedLimitArrayCursor{
		UnsignedArrayCursor: cur,
		res:                 cursors.NewUnsignedArrayLen(1),
	}
}

func (c *unsignedLimitArrayCursor) Stats() cursors.CursorStats { return c.UnsignedArrayCursor.Stats() }

func (c *unsignedLimitArrayCursor) Next() *cursors.UnsignedArray {
	if c.done {
		return &cursors.UnsignedArray{}
	}
	a := c.UnsignedArrayCursor.Next()
	if len(a.Timestamps) == 0 {
		return a
	}
	c.done = true
	c.res.Timestamps[0] = a.Timestamps[0]
	c.res.Values[0] = a.Values[0]
	return c.res
}

type unsignedWindowLastArrayCursor struct {
	cursors.UnsignedArrayCursor
	windowEnd int64
	res       *cursors.UnsignedArray
	tmp       *cursors.UnsignedArray
	window    interval.Window
}

// Window array cursors assume that every != 0 && every != MaxInt64.
// Such a cursor will panic in the first case and possibly overflow in the second.
func newUnsignedWindowLastArrayCursor(cur cursors.UnsignedArrayCursor, window interval.Window) *unsignedWindowLastArrayCursor {
	return &unsignedWindowLastArrayCursor{
		UnsignedArrayCursor: cur,
		windowEnd:           math.MinInt64,
		res:                 cursors.NewUnsignedArrayLen(MaxPointsPerBlock),
		tmp:                 &cursors.UnsignedArray{},
		window:              window,
	}
}

func (c *unsignedWindowLastArrayCursor) Stats() cursors.CursorStats {
	return c.UnsignedArrayCursor.Stats()
}

func (c *unsignedWindowLastArrayCursor) Next() *cursors.UnsignedArray {
	cur := -1

NEXT:
	var a *cursors.UnsignedArray

	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.UnsignedArrayCursor.Next()
	}

	if a.Len() == 0 {
		c.res.Timestamps = c.res.Timestamps[:cur+1]
		c.res.Values = c.res.Values[:cur+1]
		return c.res
	}

	for i, t := range a.Timestamps {
		if t >= c.windowEnd {
			cur++
		}

		if cur == MaxPointsPerBlock {
			c.tmp.Timestamps = a.Timestamps[i:]
			c.tmp.Values = a.Values[i:]
			return c.res
		}

		c.res.Timestamps[cur] = t
		c.res.Values[cur] = a.Values[i]

		c.windowEnd = int64(c.window.GetLatestBounds(values.Time(t)).Stop())
	}

	c.tmp.Timestamps = nil
	c.tmp.Values = nil

	goto NEXT
}

type unsignedWindowFirstArrayCursor struct {
	cursors.UnsignedArrayCursor
	windowEnd int64
	res       *cursors.UnsignedArray
	tmp       *cursors.UnsignedArray
	window    interval.Window
}

// Window array cursors assume that every != 0 && every != MaxInt64.
// Such a cursor will panic in the first case and possibly overflow in the second.
func newUnsignedWindowFirstArrayCursor(cur cursors.UnsignedArrayCursor, window interval.Window) *unsignedWindowFirstArrayCursor {
	return &unsignedWindowFirstArrayCursor{
		UnsignedArrayCursor: cur,
		windowEnd:           math.MinInt64,
		res:                 cursors.NewUnsignedArrayLen(MaxPointsPerBlock),
		tmp:                 &cursors.UnsignedArray{},
		window:              window,
	}
}

func (c *unsignedWindowFirstArrayCursor) Stats() cursors.CursorStats {
	return c.UnsignedArrayCursor.Stats()
}

func (c *unsignedWindowFirstArrayCursor) Next() *cursors.UnsignedArray {
	c.res.Timestamps = c.res.Timestamps[:0]
	c.res.Values = c.res.Values[:0]

NEXT:
	var a *cursors.UnsignedArray

	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.UnsignedArrayCursor.Next()
	}

	if a.Len() == 0 {
		return c.res
	}

	for i, t := range a.Timestamps {
		if t < c.windowEnd {
			continue
		}

		c.windowEnd = int64(c.window.GetLatestBounds(values.Time(t)).Stop())

		c.res.Timestamps = append(c.res.Timestamps, t)
		c.res.Values = append(c.res.Values, a.Values[i])

		if c.res.Len() == MaxPointsPerBlock {
			c.tmp.Timestamps = a.Timestamps[i+1:]
			c.tmp.Values = a.Values[i+1:]
			return c.res
		}
	}

	c.tmp.Timestamps = nil
	c.tmp.Values = nil

	goto NEXT
}

type unsignedWindowCountArrayCursor struct {
	cursors.UnsignedArrayCursor
	res    *cursors.IntegerArray
	tmp    *cursors.UnsignedArray
	window interval.Window
}

func newUnsignedWindowCountArrayCursor(cur cursors.UnsignedArrayCursor, window interval.Window) *unsignedWindowCountArrayCursor {
	resLen := MaxPointsPerBlock
	if window.IsZero() {
		resLen = 1
	}
	return &unsignedWindowCountArrayCursor{
		UnsignedArrayCursor: cur,
		res:                 cursors.NewIntegerArrayLen(resLen),
		tmp:                 &cursors.UnsignedArray{},
		window:              window,
	}
}

func (c *unsignedWindowCountArrayCursor) Stats() cursors.CursorStats {
	return c.UnsignedArrayCursor.Stats()
}

func (c *unsignedWindowCountArrayCursor) Next() *cursors.IntegerArray {
	pos := 0
	c.res.Timestamps = c.res.Timestamps[:cap(c.res.Timestamps)]
	c.res.Values = c.res.Values[:cap(c.res.Values)]

	var a *cursors.UnsignedArray
	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.UnsignedArrayCursor.Next()
	}

	if a.Len() == 0 {
		return &cursors.IntegerArray{}
	}

	rowIdx := 0
	var acc int64 = 0

	var windowEnd int64
	if !c.window.IsZero() {
		windowEnd = int64(c.window.GetLatestBounds(values.Time(a.Timestamps[rowIdx])).Stop())
	} else {
		windowEnd = math.MaxInt64
	}
	windowHasPoints := false

	// enumerate windows
WINDOWS:
	for {
		for ; rowIdx < a.Len(); rowIdx++ {
			ts := a.Timestamps[rowIdx]
			if !c.window.IsZero() && ts >= windowEnd {
				// new window detected, close the current window
				// do not generate a point for empty windows
				if windowHasPoints {
					c.res.Timestamps[pos] = windowEnd
					c.res.Values[pos] = acc
					pos++
					if pos >= MaxPointsPerBlock {
						// the output array is full,
						// save the remaining points in the input array in tmp.
						// they will be processed in the next call to Next()
						c.tmp.Timestamps = a.Timestamps[rowIdx:]
						c.tmp.Values = a.Values[rowIdx:]
						break WINDOWS
					}
				}

				// start the new window
				acc = 0
				windowEnd = int64(c.window.GetLatestBounds(values.Time(ts)).Stop())
				windowHasPoints = false

				continue WINDOWS
			} else {
				acc++
				windowHasPoints = true
			}
		}

		// Clear buffered timestamps & values if we make it through a cursor.
		// The break above will skip this if a cursor is partially read.
		c.tmp.Timestamps = nil
		c.tmp.Values = nil

		// get the next chunk
		a = c.UnsignedArrayCursor.Next()
		if a.Len() == 0 {
			// write the final point
			// do not generate a point for empty windows
			if windowHasPoints {
				c.res.Timestamps[pos] = windowEnd
				c.res.Values[pos] = acc
				pos++
			}
			break WINDOWS
		}
		rowIdx = 0
	}

	c.res.Timestamps = c.res.Timestamps[:pos]
	c.res.Values = c.res.Values[:pos]

	return c.res
}

type unsignedWindowSumArrayCursor struct {
	cursors.UnsignedArrayCursor
	res    *cursors.UnsignedArray
	tmp    *cursors.UnsignedArray
	window interval.Window
}

func newUnsignedWindowSumArrayCursor(cur cursors.UnsignedArrayCursor, window interval.Window) *unsignedWindowSumArrayCursor {
	resLen := MaxPointsPerBlock
	if window.IsZero() {
		resLen = 1
	}
	return &unsignedWindowSumArrayCursor{
		UnsignedArrayCursor: cur,
		res:                 cursors.NewUnsignedArrayLen(resLen),
		tmp:                 &cursors.UnsignedArray{},
		window:              window,
	}
}

func (c *unsignedWindowSumArrayCursor) Stats() cursors.CursorStats {
	return c.UnsignedArrayCursor.Stats()
}

func (c *unsignedWindowSumArrayCursor) Next() *cursors.UnsignedArray {
	pos := 0
	c.res.Timestamps = c.res.Timestamps[:cap(c.res.Timestamps)]
	c.res.Values = c.res.Values[:cap(c.res.Values)]

	var a *cursors.UnsignedArray
	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.UnsignedArrayCursor.Next()
	}

	if a.Len() == 0 {
		return &cursors.UnsignedArray{}
	}

	rowIdx := 0
	var acc uint64 = 0

	var windowEnd int64
	if !c.window.IsZero() {
		windowEnd = int64(c.window.GetLatestBounds(values.Time(a.Timestamps[rowIdx])).Stop())
	} else {
		windowEnd = math.MaxInt64
	}
	windowHasPoints := false

	// enumerate windows
WINDOWS:
	for {
		for ; rowIdx < a.Len(); rowIdx++ {
			ts := a.Timestamps[rowIdx]
			if !c.window.IsZero() && ts >= windowEnd {
				// new window detected, close the current window
				// do not generate a point for empty windows
				if windowHasPoints {
					c.res.Timestamps[pos] = windowEnd
					c.res.Values[pos] = acc
					pos++
					if pos >= MaxPointsPerBlock {
						// the output array is full,
						// save the remaining points in the input array in tmp.
						// they will be processed in the next call to Next()
						c.tmp.Timestamps = a.Timestamps[rowIdx:]
						c.tmp.Values = a.Values[rowIdx:]
						break WINDOWS
					}
				}

				// start the new window
				acc = 0
				windowEnd = int64(c.window.GetLatestBounds(values.Time(ts)).Stop())
				windowHasPoints = false

				continue WINDOWS
			} else {
				acc += a.Values[rowIdx]
				windowHasPoints = true
			}
		}

		// Clear buffered timestamps & values if we make it through a cursor.
		// The break above will skip this if a cursor is partially read.
		c.tmp.Timestamps = nil
		c.tmp.Values = nil

		// get the next chunk
		a = c.UnsignedArrayCursor.Next()
		if a.Len() == 0 {
			// write the final point
			// do not generate a point for empty windows
			if windowHasPoints {
				c.res.Timestamps[pos] = windowEnd
				c.res.Values[pos] = acc
				pos++
			}
			break WINDOWS
		}
		rowIdx = 0
	}

	c.res.Timestamps = c.res.Timestamps[:pos]
	c.res.Values = c.res.Values[:pos]

	return c.res
}

type unsignedWindowMinArrayCursor struct {
	cursors.UnsignedArrayCursor
	res    *cursors.UnsignedArray
	tmp    *cursors.UnsignedArray
	window interval.Window
}

func newUnsignedWindowMinArrayCursor(cur cursors.UnsignedArrayCursor, window interval.Window) *unsignedWindowMinArrayCursor {
	resLen := MaxPointsPerBlock
	if window.IsZero() {
		resLen = 1
	}
	return &unsignedWindowMinArrayCursor{
		UnsignedArrayCursor: cur,
		res:                 cursors.NewUnsignedArrayLen(resLen),
		tmp:                 &cursors.UnsignedArray{},
		window:              window,
	}
}

func (c *unsignedWindowMinArrayCursor) Stats() cursors.CursorStats {
	return c.UnsignedArrayCursor.Stats()
}

func (c *unsignedWindowMinArrayCursor) Next() *cursors.UnsignedArray {
	pos := 0
	c.res.Timestamps = c.res.Timestamps[:cap(c.res.Timestamps)]
	c.res.Values = c.res.Values[:cap(c.res.Values)]

	var a *cursors.UnsignedArray
	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.UnsignedArrayCursor.Next()
	}

	if a.Len() == 0 {
		return &cursors.UnsignedArray{}
	}

	rowIdx := 0
	var acc uint64 = math.MaxUint64
	var tsAcc int64

	var windowEnd int64
	if !c.window.IsZero() {
		windowEnd = int64(c.window.GetLatestBounds(values.Time(a.Timestamps[rowIdx])).Stop())
	} else {
		windowEnd = math.MaxInt64
	}
	windowHasPoints := false

	// enumerate windows
WINDOWS:
	for {
		for ; rowIdx < a.Len(); rowIdx++ {
			ts := a.Timestamps[rowIdx]
			if !c.window.IsZero() && ts >= windowEnd {
				// new window detected, close the current window
				// do not generate a point for empty windows
				if windowHasPoints {
					c.res.Timestamps[pos] = tsAcc
					c.res.Values[pos] = acc
					pos++
					if pos >= MaxPointsPerBlock {
						// the output array is full,
						// save the remaining points in the input array in tmp.
						// they will be processed in the next call to Next()
						c.tmp.Timestamps = a.Timestamps[rowIdx:]
						c.tmp.Values = a.Values[rowIdx:]
						break WINDOWS
					}
				}

				// start the new window
				acc = math.MaxUint64
				windowEnd = int64(c.window.GetLatestBounds(values.Time(ts)).Stop())
				windowHasPoints = false

				continue WINDOWS
			} else {
				if !windowHasPoints || a.Values[rowIdx] < acc {
					acc = a.Values[rowIdx]
					tsAcc = a.Timestamps[rowIdx]
				}
				windowHasPoints = true
			}
		}

		// Clear buffered timestamps & values if we make it through a cursor.
		// The break above will skip this if a cursor is partially read.
		c.tmp.Timestamps = nil
		c.tmp.Values = nil

		// get the next chunk
		a = c.UnsignedArrayCursor.Next()
		if a.Len() == 0 {
			// write the final point
			// do not generate a point for empty windows
			if windowHasPoints {
				c.res.Timestamps[pos] = tsAcc
				c.res.Values[pos] = acc
				pos++
			}
			break WINDOWS
		}
		rowIdx = 0
	}

	c.res.Timestamps = c.res.Timestamps[:pos]
	c.res.Values = c.res.Values[:pos]

	return c.res
}

type unsignedWindowMaxArrayCursor struct {
	cursors.UnsignedArrayCursor
	res    *cursors.UnsignedArray
	tmp    *cursors.UnsignedArray
	window interval.Window
}

func newUnsignedWindowMaxArrayCursor(cur cursors.UnsignedArrayCursor, window interval.Window) *unsignedWindowMaxArrayCursor {
	resLen := MaxPointsPerBlock
	if window.IsZero() {
		resLen = 1
	}
	return &unsignedWindowMaxArrayCursor{
		UnsignedArrayCursor: cur,
		res:                 cursors.NewUnsignedArrayLen(resLen),
		tmp:                 &cursors.UnsignedArray{},
		window:              window,
	}
}

func (c *unsignedWindowMaxArrayCursor) Stats() cursors.CursorStats {
	return c.UnsignedArrayCursor.Stats()
}

func (c *unsignedWindowMaxArrayCursor) Next() *cursors.UnsignedArray {
	pos := 0
	c.res.Timestamps = c.res.Timestamps[:cap(c.res.Timestamps)]
	c.res.Values = c.res.Values[:cap(c.res.Values)]

	var a *cursors.UnsignedArray
	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.UnsignedArrayCursor.Next()
	}

	if a.Len() == 0 {
		return &cursors.UnsignedArray{}
	}

	rowIdx := 0
	var acc uint64 = 0
	var tsAcc int64

	var windowEnd int64
	if !c.window.IsZero() {
		windowEnd = int64(c.window.GetLatestBounds(values.Time(a.Timestamps[rowIdx])).Stop())
	} else {
		windowEnd = math.MaxInt64
	}
	windowHasPoints := false

	// enumerate windows
WINDOWS:
	for {
		for ; rowIdx < a.Len(); rowIdx++ {
			ts := a.Timestamps[rowIdx]
			if !c.window.IsZero() && ts >= windowEnd {
				// new window detected, close the current window
				// do not generate a point for empty windows
				if windowHasPoints {
					c.res.Timestamps[pos] = tsAcc
					c.res.Values[pos] = acc
					pos++
					if pos >= MaxPointsPerBlock {
						// the output array is full,
						// save the remaining points in the input array in tmp.
						// they will be processed in the next call to Next()
						c.tmp.Timestamps = a.Timestamps[rowIdx:]
						c.tmp.Values = a.Values[rowIdx:]
						break WINDOWS
					}
				}

				// start the new window
				acc = 0
				windowEnd = int64(c.window.GetLatestBounds(values.Time(ts)).Stop())
				windowHasPoints = false

				continue WINDOWS
			} else {
				if !windowHasPoints || a.Values[rowIdx] > acc {
					acc = a.Values[rowIdx]
					tsAcc = a.Timestamps[rowIdx]
				}
				windowHasPoints = true
			}
		}

		// Clear buffered timestamps & values if we make it through a cursor.
		// The break above will skip this if a cursor is partially read.
		c.tmp.Timestamps = nil
		c.tmp.Values = nil

		// get the next chunk
		a = c.UnsignedArrayCursor.Next()
		if a.Len() == 0 {
			// write the final point
			// do not generate a point for empty windows
			if windowHasPoints {
				c.res.Timestamps[pos] = tsAcc
				c.res.Values[pos] = acc
				pos++
			}
			break WINDOWS
		}
		rowIdx = 0
	}

	c.res.Timestamps = c.res.Timestamps[:pos]
	c.res.Values = c.res.Values[:pos]

	return c.res
}

type unsignedWindowMeanArrayCursor struct {
	cursors.UnsignedArrayCursor
	res    *cursors.FloatArray
	tmp    *cursors.UnsignedArray
	window interval.Window
}

func newUnsignedWindowMeanArrayCursor(cur cursors.UnsignedArrayCursor, window interval.Window) *unsignedWindowMeanArrayCursor {
	resLen := MaxPointsPerBlock
	if window.IsZero() {
		resLen = 1
	}
	return &unsignedWindowMeanArrayCursor{
		UnsignedArrayCursor: cur,
		res:                 cursors.NewFloatArrayLen(resLen),
		tmp:                 &cursors.UnsignedArray{},
		window:              window,
	}
}

func (c *unsignedWindowMeanArrayCursor) Stats() cursors.CursorStats {
	return c.UnsignedArrayCursor.Stats()
}

func (c *unsignedWindowMeanArrayCursor) Next() *cursors.FloatArray {
	pos := 0
	c.res.Timestamps = c.res.Timestamps[:cap(c.res.Timestamps)]
	c.res.Values = c.res.Values[:cap(c.res.Values)]

	var a *cursors.UnsignedArray
	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.UnsignedArrayCursor.Next()
	}

	if a.Len() == 0 {
		return &cursors.FloatArray{}
	}

	rowIdx := 0
	var sum uint64
	var count int64

	var windowEnd int64
	if !c.window.IsZero() {
		windowEnd = int64(c.window.GetLatestBounds(values.Time(a.Timestamps[rowIdx])).Stop())
	} else {
		windowEnd = math.MaxInt64
	}
	windowHasPoints := false

	// enumerate windows
WINDOWS:
	for {
		for ; rowIdx < a.Len(); rowIdx++ {
			ts := a.Timestamps[rowIdx]
			if !c.window.IsZero() && ts >= windowEnd {
				// new window detected, close the current window
				// do not generate a point for empty windows
				if windowHasPoints {
					c.res.Timestamps[pos] = windowEnd
					c.res.Values[pos] = float64(sum) / float64(count)
					pos++
					if pos >= MaxPointsPerBlock {
						// the output array is full,
						// save the remaining points in the input array in tmp.
						// they will be processed in the next call to Next()
						c.tmp.Timestamps = a.Timestamps[rowIdx:]
						c.tmp.Values = a.Values[rowIdx:]
						break WINDOWS
					}
				}

				// start the new window
				sum = 0
				count = 0
				windowEnd = int64(c.window.GetLatestBounds(values.Time(ts)).Stop())
				windowHasPoints = false

				continue WINDOWS
			} else {
				sum += a.Values[rowIdx]
				count++
				windowHasPoints = true
			}
		}

		// Clear buffered timestamps & values if we make it through a cursor.
		// The break above will skip this if a cursor is partially read.
		c.tmp.Timestamps = nil
		c.tmp.Values = nil

		// get the next chunk
		a = c.UnsignedArrayCursor.Next()
		if a.Len() == 0 {
			// write the final point
			// do not generate a point for empty windows
			if windowHasPoints {
				c.res.Timestamps[pos] = windowEnd
				c.res.Values[pos] = float64(sum) / float64(count)
				pos++
			}
			break WINDOWS
		}
		rowIdx = 0
	}

	c.res.Timestamps = c.res.Timestamps[:pos]
	c.res.Values = c.res.Values[:pos]

	return c.res
}

type unsignedEmptyArrayCursor struct {
	res cursors.UnsignedArray
}

var UnsignedEmptyArrayCursor cursors.UnsignedArrayCursor = &unsignedEmptyArrayCursor{}

func (c *unsignedEmptyArrayCursor) Err() error                   { return nil }
func (c *unsignedEmptyArrayCursor) Close()                       {}
func (c *unsignedEmptyArrayCursor) Stats() cursors.CursorStats   { return cursors.CursorStats{} }
func (c *unsignedEmptyArrayCursor) Next() *cursors.UnsignedArray { return &c.res }

// ********************
// String Array Cursor

type stringArrayFilterCursor struct {
	cursors.StringArrayCursor
	cond expression
	m    *singleValue
	res  *cursors.StringArray
	tmp  *cursors.StringArray
}

func newStringFilterArrayCursor(cond expression) *stringArrayFilterCursor {
	return &stringArrayFilterCursor{
		cond: cond,
		m:    &singleValue{},
		res:  cursors.NewStringArrayLen(MaxPointsPerBlock),
		tmp:  &cursors.StringArray{},
	}
}

func (c *stringArrayFilterCursor) reset(cur cursors.StringArrayCursor) {
	c.StringArrayCursor = cur
	c.tmp.Timestamps, c.tmp.Values = nil, nil
}

func (c *stringArrayFilterCursor) Stats() cursors.CursorStats { return c.StringArrayCursor.Stats() }

func (c *stringArrayFilterCursor) Next() *cursors.StringArray {
	pos := 0
	c.res.Timestamps = c.res.Timestamps[:cap(c.res.Timestamps)]
	c.res.Values = c.res.Values[:cap(c.res.Values)]

	var a *cursors.StringArray

	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.StringArrayCursor.Next()
	}

LOOP:
	for len(a.Timestamps) > 0 {
		for i, v := range a.Values {
			c.m.v = v
			if c.cond.EvalBool(c.m) {
				c.res.Timestamps[pos] = a.Timestamps[i]
				c.res.Values[pos] = v
				pos++
				if pos >= MaxPointsPerBlock {
					c.tmp.Timestamps = a.Timestamps[i+1:]
					c.tmp.Values = a.Values[i+1:]
					break LOOP
				}
			}
		}

		// Clear buffered timestamps & values if we make it through a cursor.
		// The break above will skip this if a cursor is partially read.
		c.tmp.Timestamps = nil
		c.tmp.Values = nil

		a = c.StringArrayCursor.Next()
	}

	c.res.Timestamps = c.res.Timestamps[:pos]
	c.res.Values = c.res.Values[:pos]

	return c.res
}

type stringMultiShardArrayCursor struct {
	cursors.StringArrayCursor
	cursorContext
	filter *stringArrayFilterCursor
}

func (c *stringMultiShardArrayCursor) reset(cur cursors.StringArrayCursor, itrs cursors.CursorIterators, cond expression) {
	if cond != nil {
		if c.filter == nil {
			c.filter = newStringFilterArrayCursor(cond)
		}
		c.filter.reset(cur)
		cur = c.filter
	}

	c.StringArrayCursor = cur
	c.itrs = itrs
	c.err = nil
}

func (c *stringMultiShardArrayCursor) Err() error { return c.err }

func (c *stringMultiShardArrayCursor) Stats() cursors.CursorStats {
	return c.StringArrayCursor.Stats()
}

func (c *stringMultiShardArrayCursor) Next() *cursors.StringArray {
	for {
		a := c.StringArrayCursor.Next()
		if a.Len() == 0 {
			if c.nextArrayCursor() {
				continue
			}
		}
		return a
	}
}

func (c *stringMultiShardArrayCursor) nextArrayCursor() bool {
	if len(c.itrs) == 0 {
		return false
	}

	c.StringArrayCursor.Close()

	var itr cursors.CursorIterator
	var cur cursors.Cursor
	for cur == nil && len(c.itrs) > 0 {
		itr, c.itrs = c.itrs[0], c.itrs[1:]
		cur, _ = itr.Next(c.ctx, c.req)
	}

	var ok bool
	if cur != nil {
		var next cursors.StringArrayCursor
		next, ok = cur.(cursors.StringArrayCursor)
		if !ok {
			cur.Close()
			next = StringEmptyArrayCursor
			c.err = errors.New("expected string cursor")
		} else {
			if c.filter != nil {
				c.filter.reset(next)
				next = c.filter
			}
		}
		c.StringArrayCursor = next
	} else {
		c.StringArrayCursor = StringEmptyArrayCursor
	}

	return ok
}

type stringLimitArrayCursor struct {
	cursors.StringArrayCursor
	res  *cursors.StringArray
	done bool
}

func newStringLimitArrayCursor(cur cursors.StringArrayCursor) *stringLimitArrayCursor {
	return &stringLimitArrayCursor{
		StringArrayCursor: cur,
		res:               cursors.NewStringArrayLen(1),
	}
}

func (c *stringLimitArrayCursor) Stats() cursors.CursorStats { return c.StringArrayCursor.Stats() }

func (c *stringLimitArrayCursor) Next() *cursors.StringArray {
	if c.done {
		return &cursors.StringArray{}
	}
	a := c.StringArrayCursor.Next()
	if len(a.Timestamps) == 0 {
		return a
	}
	c.done = true
	c.res.Timestamps[0] = a.Timestamps[0]
	c.res.Values[0] = a.Values[0]
	return c.res
}

type stringWindowLastArrayCursor struct {
	cursors.StringArrayCursor
	windowEnd int64
	res       *cursors.StringArray
	tmp       *cursors.StringArray
	window    interval.Window
}

// Window array cursors assume that every != 0 && every != MaxInt64.
// Such a cursor will panic in the first case and possibly overflow in the second.
func newStringWindowLastArrayCursor(cur cursors.StringArrayCursor, window interval.Window) *stringWindowLastArrayCursor {
	return &stringWindowLastArrayCursor{
		StringArrayCursor: cur,
		windowEnd:         math.MinInt64,
		res:               cursors.NewStringArrayLen(MaxPointsPerBlock),
		tmp:               &cursors.StringArray{},
		window:            window,
	}
}

func (c *stringWindowLastArrayCursor) Stats() cursors.CursorStats {
	return c.StringArrayCursor.Stats()
}

func (c *stringWindowLastArrayCursor) Next() *cursors.StringArray {
	cur := -1

NEXT:
	var a *cursors.StringArray

	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.StringArrayCursor.Next()
	}

	if a.Len() == 0 {
		c.res.Timestamps = c.res.Timestamps[:cur+1]
		c.res.Values = c.res.Values[:cur+1]
		return c.res
	}

	for i, t := range a.Timestamps {
		if t >= c.windowEnd {
			cur++
		}

		if cur == MaxPointsPerBlock {
			c.tmp.Timestamps = a.Timestamps[i:]
			c.tmp.Values = a.Values[i:]
			return c.res
		}

		c.res.Timestamps[cur] = t
		c.res.Values[cur] = a.Values[i]

		c.windowEnd = int64(c.window.GetLatestBounds(values.Time(t)).Stop())
	}

	c.tmp.Timestamps = nil
	c.tmp.Values = nil

	goto NEXT
}

type stringWindowFirstArrayCursor struct {
	cursors.StringArrayCursor
	windowEnd int64
	res       *cursors.StringArray
	tmp       *cursors.StringArray
	window    interval.Window
}

// Window array cursors assume that every != 0 && every != MaxInt64.
// Such a cursor will panic in the first case and possibly overflow in the second.
func newStringWindowFirstArrayCursor(cur cursors.StringArrayCursor, window interval.Window) *stringWindowFirstArrayCursor {
	return &stringWindowFirstArrayCursor{
		StringArrayCursor: cur,
		windowEnd:         math.MinInt64,
		res:               cursors.NewStringArrayLen(MaxPointsPerBlock),
		tmp:               &cursors.StringArray{},
		window:            window,
	}
}

func (c *stringWindowFirstArrayCursor) Stats() cursors.CursorStats {
	return c.StringArrayCursor.Stats()
}

func (c *stringWindowFirstArrayCursor) Next() *cursors.StringArray {
	c.res.Timestamps = c.res.Timestamps[:0]
	c.res.Values = c.res.Values[:0]

NEXT:
	var a *cursors.StringArray

	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.StringArrayCursor.Next()
	}

	if a.Len() == 0 {
		return c.res
	}

	for i, t := range a.Timestamps {
		if t < c.windowEnd {
			continue
		}

		c.windowEnd = int64(c.window.GetLatestBounds(values.Time(t)).Stop())

		c.res.Timestamps = append(c.res.Timestamps, t)
		c.res.Values = append(c.res.Values, a.Values[i])

		if c.res.Len() == MaxPointsPerBlock {
			c.tmp.Timestamps = a.Timestamps[i+1:]
			c.tmp.Values = a.Values[i+1:]
			return c.res
		}
	}

	c.tmp.Timestamps = nil
	c.tmp.Values = nil

	goto NEXT
}

type stringWindowCountArrayCursor struct {
	cursors.StringArrayCursor
	res    *cursors.IntegerArray
	tmp    *cursors.StringArray
	window interval.Window
}

func newStringWindowCountArrayCursor(cur cursors.StringArrayCursor, window interval.Window) *stringWindowCountArrayCursor {
	resLen := MaxPointsPerBlock
	if window.IsZero() {
		resLen = 1
	}
	return &stringWindowCountArrayCursor{
		StringArrayCursor: cur,
		res:               cursors.NewIntegerArrayLen(resLen),
		tmp:               &cursors.StringArray{},
		window:            window,
	}
}

func (c *stringWindowCountArrayCursor) Stats() cursors.CursorStats {
	return c.StringArrayCursor.Stats()
}

func (c *stringWindowCountArrayCursor) Next() *cursors.IntegerArray {
	pos := 0
	c.res.Timestamps = c.res.Timestamps[:cap(c.res.Timestamps)]
	c.res.Values = c.res.Values[:cap(c.res.Values)]

	var a *cursors.StringArray
	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.StringArrayCursor.Next()
	}

	if a.Len() == 0 {
		return &cursors.IntegerArray{}
	}

	rowIdx := 0
	var acc int64 = 0

	var windowEnd int64
	if !c.window.IsZero() {
		windowEnd = int64(c.window.GetLatestBounds(values.Time(a.Timestamps[rowIdx])).Stop())
	} else {
		windowEnd = math.MaxInt64
	}
	windowHasPoints := false

	// enumerate windows
WINDOWS:
	for {
		for ; rowIdx < a.Len(); rowIdx++ {
			ts := a.Timestamps[rowIdx]
			if !c.window.IsZero() && ts >= windowEnd {
				// new window detected, close the current window
				// do not generate a point for empty windows
				if windowHasPoints {
					c.res.Timestamps[pos] = windowEnd
					c.res.Values[pos] = acc
					pos++
					if pos >= MaxPointsPerBlock {
						// the output array is full,
						// save the remaining points in the input array in tmp.
						// they will be processed in the next call to Next()
						c.tmp.Timestamps = a.Timestamps[rowIdx:]
						c.tmp.Values = a.Values[rowIdx:]
						break WINDOWS
					}
				}

				// start the new window
				acc = 0
				windowEnd = int64(c.window.GetLatestBounds(values.Time(ts)).Stop())
				windowHasPoints = false

				continue WINDOWS
			} else {
				acc++
				windowHasPoints = true
			}
		}

		// Clear buffered timestamps & values if we make it through a cursor.
		// The break above will skip this if a cursor is partially read.
		c.tmp.Timestamps = nil
		c.tmp.Values = nil

		// get the next chunk
		a = c.StringArrayCursor.Next()
		if a.Len() == 0 {
			// write the final point
			// do not generate a point for empty windows
			if windowHasPoints {
				c.res.Timestamps[pos] = windowEnd
				c.res.Values[pos] = acc
				pos++
			}
			break WINDOWS
		}
		rowIdx = 0
	}

	c.res.Timestamps = c.res.Timestamps[:pos]
	c.res.Values = c.res.Values[:pos]

	return c.res
}

type stringEmptyArrayCursor struct {
	res cursors.StringArray
}

var StringEmptyArrayCursor cursors.StringArrayCursor = &stringEmptyArrayCursor{}

func (c *stringEmptyArrayCursor) Err() error                 { return nil }
func (c *stringEmptyArrayCursor) Close()                     {}
func (c *stringEmptyArrayCursor) Stats() cursors.CursorStats { return cursors.CursorStats{} }
func (c *stringEmptyArrayCursor) Next() *cursors.StringArray { return &c.res }

// ********************
// Boolean Array Cursor

type booleanArrayFilterCursor struct {
	cursors.BooleanArrayCursor
	cond expression
	m    *singleValue
	res  *cursors.BooleanArray
	tmp  *cursors.BooleanArray
}

func newBooleanFilterArrayCursor(cond expression) *booleanArrayFilterCursor {
	return &booleanArrayFilterCursor{
		cond: cond,
		m:    &singleValue{},
		res:  cursors.NewBooleanArrayLen(MaxPointsPerBlock),
		tmp:  &cursors.BooleanArray{},
	}
}

func (c *booleanArrayFilterCursor) reset(cur cursors.BooleanArrayCursor) {
	c.BooleanArrayCursor = cur
	c.tmp.Timestamps, c.tmp.Values = nil, nil
}

func (c *booleanArrayFilterCursor) Stats() cursors.CursorStats { return c.BooleanArrayCursor.Stats() }

func (c *booleanArrayFilterCursor) Next() *cursors.BooleanArray {
	pos := 0
	c.res.Timestamps = c.res.Timestamps[:cap(c.res.Timestamps)]
	c.res.Values = c.res.Values[:cap(c.res.Values)]

	var a *cursors.BooleanArray

	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.BooleanArrayCursor.Next()
	}

LOOP:
	for len(a.Timestamps) > 0 {
		for i, v := range a.Values {
			c.m.v = v
			if c.cond.EvalBool(c.m) {
				c.res.Timestamps[pos] = a.Timestamps[i]
				c.res.Values[pos] = v
				pos++
				if pos >= MaxPointsPerBlock {
					c.tmp.Timestamps = a.Timestamps[i+1:]
					c.tmp.Values = a.Values[i+1:]
					break LOOP
				}
			}
		}

		// Clear buffered timestamps & values if we make it through a cursor.
		// The break above will skip this if a cursor is partially read.
		c.tmp.Timestamps = nil
		c.tmp.Values = nil

		a = c.BooleanArrayCursor.Next()
	}

	c.res.Timestamps = c.res.Timestamps[:pos]
	c.res.Values = c.res.Values[:pos]

	return c.res
}

type booleanMultiShardArrayCursor struct {
	cursors.BooleanArrayCursor
	cursorContext
	filter *booleanArrayFilterCursor
}

func (c *booleanMultiShardArrayCursor) reset(cur cursors.BooleanArrayCursor, itrs cursors.CursorIterators, cond expression) {
	if cond != nil {
		if c.filter == nil {
			c.filter = newBooleanFilterArrayCursor(cond)
		}
		c.filter.reset(cur)
		cur = c.filter
	}

	c.BooleanArrayCursor = cur
	c.itrs = itrs
	c.err = nil
}

func (c *booleanMultiShardArrayCursor) Err() error { return c.err }

func (c *booleanMultiShardArrayCursor) Stats() cursors.CursorStats {
	return c.BooleanArrayCursor.Stats()
}

func (c *booleanMultiShardArrayCursor) Next() *cursors.BooleanArray {
	for {
		a := c.BooleanArrayCursor.Next()
		if a.Len() == 0 {
			if c.nextArrayCursor() {
				continue
			}
		}
		return a
	}
}

func (c *booleanMultiShardArrayCursor) nextArrayCursor() bool {
	if len(c.itrs) == 0 {
		return false
	}

	c.BooleanArrayCursor.Close()

	var itr cursors.CursorIterator
	var cur cursors.Cursor
	for cur == nil && len(c.itrs) > 0 {
		itr, c.itrs = c.itrs[0], c.itrs[1:]
		cur, _ = itr.Next(c.ctx, c.req)
	}

	var ok bool
	if cur != nil {
		var next cursors.BooleanArrayCursor
		next, ok = cur.(cursors.BooleanArrayCursor)
		if !ok {
			cur.Close()
			next = BooleanEmptyArrayCursor
			c.err = errors.New("expected boolean cursor")
		} else {
			if c.filter != nil {
				c.filter.reset(next)
				next = c.filter
			}
		}
		c.BooleanArrayCursor = next
	} else {
		c.BooleanArrayCursor = BooleanEmptyArrayCursor
	}

	return ok
}

type booleanLimitArrayCursor struct {
	cursors.BooleanArrayCursor
	res  *cursors.BooleanArray
	done bool
}

func newBooleanLimitArrayCursor(cur cursors.BooleanArrayCursor) *booleanLimitArrayCursor {
	return &booleanLimitArrayCursor{
		BooleanArrayCursor: cur,
		res:                cursors.NewBooleanArrayLen(1),
	}
}

func (c *booleanLimitArrayCursor) Stats() cursors.CursorStats { return c.BooleanArrayCursor.Stats() }

func (c *booleanLimitArrayCursor) Next() *cursors.BooleanArray {
	if c.done {
		return &cursors.BooleanArray{}
	}
	a := c.BooleanArrayCursor.Next()
	if len(a.Timestamps) == 0 {
		return a
	}
	c.done = true
	c.res.Timestamps[0] = a.Timestamps[0]
	c.res.Values[0] = a.Values[0]
	return c.res
}

type booleanWindowLastArrayCursor struct {
	cursors.BooleanArrayCursor
	windowEnd int64
	res       *cursors.BooleanArray
	tmp       *cursors.BooleanArray
	window    interval.Window
}

// Window array cursors assume that every != 0 && every != MaxInt64.
// Such a cursor will panic in the first case and possibly overflow in the second.
func newBooleanWindowLastArrayCursor(cur cursors.BooleanArrayCursor, window interval.Window) *booleanWindowLastArrayCursor {
	return &booleanWindowLastArrayCursor{
		BooleanArrayCursor: cur,
		windowEnd:          math.MinInt64,
		res:                cursors.NewBooleanArrayLen(MaxPointsPerBlock),
		tmp:                &cursors.BooleanArray{},
		window:             window,
	}
}

func (c *booleanWindowLastArrayCursor) Stats() cursors.CursorStats {
	return c.BooleanArrayCursor.Stats()
}

func (c *booleanWindowLastArrayCursor) Next() *cursors.BooleanArray {
	cur := -1

NEXT:
	var a *cursors.BooleanArray

	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.BooleanArrayCursor.Next()
	}

	if a.Len() == 0 {
		c.res.Timestamps = c.res.Timestamps[:cur+1]
		c.res.Values = c.res.Values[:cur+1]
		return c.res
	}

	for i, t := range a.Timestamps {
		if t >= c.windowEnd {
			cur++
		}

		if cur == MaxPointsPerBlock {
			c.tmp.Timestamps = a.Timestamps[i:]
			c.tmp.Values = a.Values[i:]
			return c.res
		}

		c.res.Timestamps[cur] = t
		c.res.Values[cur] = a.Values[i]

		c.windowEnd = int64(c.window.GetLatestBounds(values.Time(t)).Stop())
	}

	c.tmp.Timestamps = nil
	c.tmp.Values = nil

	goto NEXT
}

type booleanWindowFirstArrayCursor struct {
	cursors.BooleanArrayCursor
	windowEnd int64
	res       *cursors.BooleanArray
	tmp       *cursors.BooleanArray
	window    interval.Window
}

// Window array cursors assume that every != 0 && every != MaxInt64.
// Such a cursor will panic in the first case and possibly overflow in the second.
func newBooleanWindowFirstArrayCursor(cur cursors.BooleanArrayCursor, window interval.Window) *booleanWindowFirstArrayCursor {
	return &booleanWindowFirstArrayCursor{
		BooleanArrayCursor: cur,
		windowEnd:          math.MinInt64,
		res:                cursors.NewBooleanArrayLen(MaxPointsPerBlock),
		tmp:                &cursors.BooleanArray{},
		window:             window,
	}
}

func (c *booleanWindowFirstArrayCursor) Stats() cursors.CursorStats {
	return c.BooleanArrayCursor.Stats()
}

func (c *booleanWindowFirstArrayCursor) Next() *cursors.BooleanArray {
	c.res.Timestamps = c.res.Timestamps[:0]
	c.res.Values = c.res.Values[:0]

NEXT:
	var a *cursors.BooleanArray

	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.BooleanArrayCursor.Next()
	}

	if a.Len() == 0 {
		return c.res
	}

	for i, t := range a.Timestamps {
		if t < c.windowEnd {
			continue
		}

		c.windowEnd = int64(c.window.GetLatestBounds(values.Time(t)).Stop())

		c.res.Timestamps = append(c.res.Timestamps, t)
		c.res.Values = append(c.res.Values, a.Values[i])

		if c.res.Len() == MaxPointsPerBlock {
			c.tmp.Timestamps = a.Timestamps[i+1:]
			c.tmp.Values = a.Values[i+1:]
			return c.res
		}
	}

	c.tmp.Timestamps = nil
	c.tmp.Values = nil

	goto NEXT
}

type booleanWindowCountArrayCursor struct {
	cursors.BooleanArrayCursor
	res    *cursors.IntegerArray
	tmp    *cursors.BooleanArray
	window interval.Window
}

func newBooleanWindowCountArrayCursor(cur cursors.BooleanArrayCursor, window interval.Window) *booleanWindowCountArrayCursor {
	resLen := MaxPointsPerBlock
	if window.IsZero() {
		resLen = 1
	}
	return &booleanWindowCountArrayCursor{
		BooleanArrayCursor: cur,
		res:                cursors.NewIntegerArrayLen(resLen),
		tmp:                &cursors.BooleanArray{},
		window:             window,
	}
}

func (c *booleanWindowCountArrayCursor) Stats() cursors.CursorStats {
	return c.BooleanArrayCursor.Stats()
}

func (c *booleanWindowCountArrayCursor) Next() *cursors.IntegerArray {
	pos := 0
	c.res.Timestamps = c.res.Timestamps[:cap(c.res.Timestamps)]
	c.res.Values = c.res.Values[:cap(c.res.Values)]

	var a *cursors.BooleanArray
	if c.tmp.Len() > 0 {
		a = c.tmp
	} else {
		a = c.BooleanArrayCursor.Next()
	}

	if a.Len() == 0 {
		return &cursors.IntegerArray{}
	}

	rowIdx := 0
	var acc int64 = 0

	var windowEnd int64
	if !c.window.IsZero() {
		windowEnd = int64(c.window.GetLatestBounds(values.Time(a.Timestamps[rowIdx])).Stop())
	} else {
		windowEnd = math.MaxInt64
	}
	windowHasPoints := false

	// enumerate windows
WINDOWS:
	for {
		for ; rowIdx < a.Len(); rowIdx++ {
			ts := a.Timestamps[rowIdx]
			if !c.window.IsZero() && ts >= windowEnd {
				// new window detected, close the current window
				// do not generate a point for empty windows
				if windowHasPoints {
					c.res.Timestamps[pos] = windowEnd
					c.res.Values[pos] = acc
					pos++
					if pos >= MaxPointsPerBlock {
						// the output array is full,
						// save the remaining points in the input array in tmp.
						// they will be processed in the next call to Next()
						c.tmp.Timestamps = a.Timestamps[rowIdx:]
						c.tmp.Values = a.Values[rowIdx:]
						break WINDOWS
					}
				}

				// start the new window
				acc = 0
				windowEnd = int64(c.window.GetLatestBounds(values.Time(ts)).Stop())
				windowHasPoints = false

				continue WINDOWS
			} else {
				acc++
				windowHasPoints = true
			}
		}

		// Clear buffered timestamps & values if we make it through a cursor.
		// The break above will skip this if a cursor is partially read.
		c.tmp.Timestamps = nil
		c.tmp.Values = nil

		// get the next chunk
		a = c.BooleanArrayCursor.Next()
		if a.Len() == 0 {
			// write the final point
			// do not generate a point for empty windows
			if windowHasPoints {
				c.res.Timestamps[pos] = windowEnd
				c.res.Values[pos] = acc
				pos++
			}
			break WINDOWS
		}
		rowIdx = 0
	}

	c.res.Timestamps = c.res.Timestamps[:pos]
	c.res.Values = c.res.Values[:pos]

	return c.res
}

type booleanEmptyArrayCursor struct {
	res cursors.BooleanArray
}

var BooleanEmptyArrayCursor cursors.BooleanArrayCursor = &booleanEmptyArrayCursor{}

func (c *booleanEmptyArrayCursor) Err() error                  { return nil }
func (c *booleanEmptyArrayCursor) Close()                      {}
func (c *booleanEmptyArrayCursor) Stats() cursors.CursorStats  { return cursors.CursorStats{} }
func (c *booleanEmptyArrayCursor) Next() *cursors.BooleanArray { return &c.res }

func arrayCursorType(cur cursors.Cursor) string {
	switch cur.(type) {

	case cursors.FloatArrayCursor:
		return "float"

	case cursors.IntegerArrayCursor:
		return "integer"

	case cursors.UnsignedArrayCursor:
		return "unsigned"

	case cursors.StringArrayCursor:
		return "string"

	case cursors.BooleanArrayCursor:
		return "boolean"

	default:
		return "unknown"
	}
}
