package tsm1

import (
	"sync"
	"sync/atomic"

	"github.com/influxdata/influxql"
)

// entry is a set of values and some metadata.
type entry struct {
	// Tracks the number of values in the entry. Must always be accessed via
	// atomic; must be 8b aligned.
	n int64

	mu     sync.RWMutex
	values Values // All stored values.

	// The type of values stored. Read only so doesn't need to be protected by mu.
	vtype byte
}

// newEntryValues returns a new instance of entry with the given values.  If the
// values are not valid, an error is returned.
func newEntryValues(values []Value) (*entry, error) {
	e := &entry{
		values: make(Values, 0, len(values)),
		n:      int64(len(values)),
	}
	e.values = append(e.values, values...)

	// No values, don't check types and ordering
	if len(values) == 0 {
		return e, nil
	}

	et := valueType(values[0])
	for _, v := range values {
		// Make sure all the values are the same type
		if et != valueType(v) {
			return nil, errFieldTypeConflict
		}
	}

	// Set the type of values stored.
	e.vtype = et

	return e, nil
}

// add adds the given values to the entry.
func (e *entry) add(values []Value) error {
	if len(values) == 0 {
		return nil // Nothing to do.
	}

	// Are any of the new values the wrong type?
	if e.vtype != 0 {
		for _, v := range values {
			if e.vtype != valueType(v) {
				return errFieldTypeConflict
			}
		}
	}

	// entry currently has no values, so add the new ones and we're done.
	e.mu.Lock()
	if len(e.values) == 0 {
		e.values = values
		atomic.StoreInt64(&e.n, int64(len(e.values)))
		e.vtype = valueType(values[0])
		e.mu.Unlock()
		return nil
	}

	// Append the new values to the existing ones...
	e.values = append(e.values, values...)
	atomic.StoreInt64(&e.n, int64(len(e.values)))
	e.mu.Unlock()
	return nil
}

// deduplicate sorts and orders the entry's values. If values are already deduped and sorted,
// the function does no work and simply returns.
func (e *entry) deduplicate() {
	e.mu.Lock()
	defer e.mu.Unlock()

	if len(e.values) <= 1 {
		return
	}
	e.values = e.values.Deduplicate()
	atomic.StoreInt64(&e.n, int64(len(e.values)))
}

// count returns the number of values in this entry.
func (e *entry) count() int {
	return int(atomic.LoadInt64(&e.n))
}

// filter removes all values with timestamps between min and max inclusive.
func (e *entry) filter(min, max int64) {
	e.mu.Lock()
	if len(e.values) > 1 {
		e.values = e.values.Deduplicate()
	}
	e.values = e.values.Exclude(min, max)
	atomic.StoreInt64(&e.n, int64(len(e.values)))
	e.mu.Unlock()
}

// size returns the size of this entry in bytes.
func (e *entry) size() int {
	e.mu.RLock()
	sz := e.values.Size()
	e.mu.RUnlock()
	return sz
}

// AppendTimestamps appends ts with the timestamps from the entry.
func (e *entry) AppendTimestamps(ts []int64) []int64 {
	e.mu.RLock()
	defer e.mu.RUnlock()
	n := e.values.Len()
	if n > 0 {
		for i := range e.values {
			ts = append(ts, e.values[i].UnixNano())
		}
	}
	return ts
}

// InfluxQLType returns for the entry the data type of its values.
func (e *entry) InfluxQLType() (influxql.DataType, error) {
	e.mu.RLock()
	defer e.mu.RUnlock()
	return e.values.InfluxQLType()
}

// BlockType returns the data type for the entry as a block type.
func (e *entry) BlockType() byte {
	// This value is mutated on create and does not need to be
	// protected by a mutex.
	return valueTypeToBlockType(e.vtype)
}
