package bigcache

import (
	"sync"
)

type iteratorError string

func (e iteratorError) Error() string {
	return string(e)
}

// ErrInvalidIteratorState is reported when iterator is in invalid state
const ErrInvalidIteratorState = iteratorError("Iterator is in invalid state. Use SetNext() to move to next position")

// ErrCannotRetrieveEntry is reported when entry cannot be retrieved from underlying
const ErrCannotRetrieveEntry = iteratorError("Could not retrieve entry from cache")

var emptyEntryInfo = EntryInfo{}

// EntryInfo holds informations about entry in the cache
type EntryInfo struct {
	timestamp uint64
	hash      uint64
	key       string
	value     []byte
	err       error
}

// Key returns entry's underlying key
func (e EntryInfo) Key() string {
	return e.key
}

// Hash returns entry's hash value
func (e EntryInfo) Hash() uint64 {
	return e.hash
}

// Timestamp returns entry's timestamp (time of insertion)
func (e EntryInfo) Timestamp() uint64 {
	return e.timestamp
}

// Value returns entry's underlying value
func (e EntryInfo) Value() []byte {
	return e.value
}

// EntryInfoIterator allows to iterate over entries in the cache
type EntryInfoIterator struct {
	mutex            sync.Mutex
	cache            *BigCache
	currentShard     int
	currentIndex     int
	currentEntryInfo EntryInfo
	elements         []uint64
	elementsCount    int
	valid            bool
}

// SetNext moves to next element and returns true if it exists.
func (it *EntryInfoIterator) SetNext() bool {
	it.mutex.Lock()

	it.valid = false
	it.currentIndex++

	if it.elementsCount > it.currentIndex {
		it.valid = true

		empty := it.setCurrentEntry()
		it.mutex.Unlock()

		if empty {
			return it.SetNext()
		}
		return true
	}

	for i := it.currentShard + 1; i < it.cache.config.Shards; i++ {
		it.elements, it.elementsCount = it.cache.shards[i].copyHashedKeys()

		// Non empty shard - stick with it
		if it.elementsCount > 0 {
			it.currentIndex = 0
			it.currentShard = i
			it.valid = true

			empty := it.setCurrentEntry()
			it.mutex.Unlock()

			if empty {
				return it.SetNext()
			}
			return true
		}
	}
	it.mutex.Unlock()
	return false
}

func (it *EntryInfoIterator) setCurrentEntry() bool {
	var entryNotFound = false
	entry, err := it.cache.shards[it.currentShard].getEntry(it.elements[it.currentIndex])

	if err == ErrEntryNotFound {
		it.currentEntryInfo = emptyEntryInfo
		entryNotFound = true
	} else if err != nil {
		it.currentEntryInfo = EntryInfo{
			err: err,
		}
	} else {
		it.currentEntryInfo = EntryInfo{
			timestamp: readTimestampFromEntry(entry),
			hash:      readHashFromEntry(entry),
			key:       readKeyFromEntry(entry),
			value:     readEntry(entry),
			err:       err,
		}
	}

	return entryNotFound
}

func newIterator(cache *BigCache) *EntryInfoIterator {
	elements, count := cache.shards[0].copyHashedKeys()

	return &EntryInfoIterator{
		cache:         cache,
		currentShard:  0,
		currentIndex:  -1,
		elements:      elements,
		elementsCount: count,
	}
}

// Value returns current value from the iterator
func (it *EntryInfoIterator) Value() (EntryInfo, error) {
	if !it.valid {
		return emptyEntryInfo, ErrInvalidIteratorState
	}

	return it.currentEntryInfo, it.currentEntryInfo.err
}
