package tsdb

import (
	"bytes"
	"errors"
	"sort"
	"sync"

	"github.com/influxdata/influxdb/v2/models"
	"github.com/influxdata/influxql"
)

type SeriesCursor interface {
	Close() error
	Next() (*SeriesCursorRow, error)
}

type SeriesCursorRequest struct {
	Measurements MeasurementIterator
}

// seriesCursor is an implementation of SeriesCursor over an IndexSet.
type seriesCursor struct {
	once     sync.Once
	indexSet IndexSet
	mitr     MeasurementIterator
	keys     [][]byte
	ofs      int
	row      SeriesCursorRow
	cond     influxql.Expr
}

type SeriesCursorRow struct {
	Name []byte
	Tags models.Tags
}

func (r *SeriesCursorRow) Compare(other *SeriesCursorRow) int {
	if r == other {
		return 0
	} else if r == nil {
		return -1
	} else if other == nil {
		return 1
	}
	cmp := bytes.Compare(r.Name, other.Name)
	if cmp != 0 {
		return cmp
	}
	return models.CompareTags(r.Tags, other.Tags)
}

// newSeriesCursor returns a new instance of SeriesCursor.
func newSeriesCursor(req SeriesCursorRequest, indexSet IndexSet, cond influxql.Expr) (_ SeriesCursor, err error) {
	// Only equality operators are allowed.
	influxql.WalkFunc(cond, func(node influxql.Node) {
		switch n := node.(type) {
		case *influxql.BinaryExpr:
			switch n.Op {
			case influxql.EQ, influxql.NEQ, influxql.EQREGEX, influxql.NEQREGEX, influxql.OR, influxql.AND:
			default:
				err = errors.New("invalid tag comparison operator")
			}
		}
	})
	if err != nil {
		return nil, err
	}

	mitr := req.Measurements
	if mitr == nil {
		mitr, err = indexSet.MeasurementIterator()
		if err != nil {
			return nil, err
		}
	}

	return &seriesCursor{
		indexSet: indexSet,
		mitr:     mitr,
		cond:     cond,
	}, nil
}

// Close closes the iterator.
func (cur *seriesCursor) Close() (err error) {
	cur.once.Do(func() {
		if cur.mitr != nil {
			err = cur.mitr.Close()
		}
	})
	return err
}

// Next emits the next point in the iterator.
func (cur *seriesCursor) Next() (*SeriesCursorRow, error) {
	for {
		// Read series keys for next measurement if no more keys remaining.
		// Exit if there are no measurements remaining.
		if cur.ofs == len(cur.keys) {
			m, err := cur.mitr.Next()
			if err != nil {
				return nil, err
			} else if m == nil {
				return nil, nil
			}

			if err := cur.readSeriesKeys(m); err != nil {
				return nil, err
			}
			continue
		}

		cur.row.Name, cur.row.Tags = ParseSeriesKey(cur.keys[cur.ofs])
		cur.ofs++

		//if itr.opt.Authorizer != nil && !itr.opt.Authorizer.AuthorizeSeriesRead(itr.indexSet.Database(), name, tags) {
		//	continue
		//}

		return &cur.row, nil
	}
}

func (cur *seriesCursor) readSeriesKeys(name []byte) error {
	sitr, err := cur.indexSet.MeasurementSeriesByExprIterator(name, cur.cond)
	if err != nil {
		return err
	} else if sitr == nil {
		return nil
	}
	defer sitr.Close()

	// Slurp all series keys.
	cur.ofs = 0
	cur.keys = cur.keys[:0]
	for {
		elem, err := sitr.Next()
		if err != nil {
			return err
		} else if elem.SeriesID == 0 {
			break
		}

		key := cur.indexSet.SeriesFile.SeriesKey(elem.SeriesID)
		if len(key) == 0 {
			continue
		}
		cur.keys = append(cur.keys, key)
	}

	// Sort keys.
	sort.Sort(seriesKeys(cur.keys))
	return nil
}
