package cache

import (
	"sync"

	"github.com/elliotchance/orderedmap/v2"
)

type RingMapInterface interface {
	Exists(key string, value interface{}) bool
}

// Copied from https://github.com/prgsmall/ringmap to use orderedmap v2

type RingMap struct {
	orderedMap *orderedmap.OrderedMap[string, interface{}]
	capacity   int
	writeLock  sync.RWMutex
}

func NewRingMap(capacity int) *RingMap {
	return &RingMap{
		orderedMap: orderedmap.NewOrderedMap[string, interface{}](),
		capacity:   capacity,
	}
}

// Convenience function to check if key and value already exists
// If key and value does not exists, it is added
// If key exists with different value, it is replaced with new value
func (m *RingMap) Exists(key string, value interface{}) bool {
	m.writeLock.RLock()
	el := m.orderedMap.GetElement(key)
	m.writeLock.RUnlock()
	exists := el != nil

	if exists && el.Value != value {
		m.Delete(key)
		exists = false
	} else {
		m.clearLast()
	}
	m.writeLock.Lock()
	defer m.writeLock.Unlock()
	m.orderedMap.Set(key, value)

	return exists
}

// Get returns the value for a key. If the key does not exist, the second return
// parameter will be false and the value will be nil.
func (m *RingMap) Get(key string) (interface{}, bool) {
	m.writeLock.RLock()
	defer m.writeLock.RUnlock()
	return m.orderedMap.Get(key)
}

// Set will set (or replace) a value for a key. If the key was new, then true
// will be returned. The returned value will be false if the value was replaced
// (even if the value was the same).  If a new key is being added and the map is
// full, then the front element will be deleted to make room for the new element.
func (m *RingMap) Set(key string, value interface{}) bool {
	_, didExist := m.Get(key)

	if !didExist {
		m.clearLast()
	}
	m.writeLock.Lock()
	defer m.writeLock.Unlock()
	m.orderedMap.Set(key, value)

	return !didExist
}

// Put will set a value for a key. If the key already exists, it will be deleted
// from and a recreated at the end of the list.  If the key was new, then true
// will be returned. The returned value will be false if the value was replaced
// (even if the value was the same).  If a new key is being added and the map is
// full, then the front element will be deleted to make room for the new element.
func (m *RingMap) Put(key string, value interface{}) bool {
	_, didExist := m.Get(key)

	if didExist {
		m.Delete(key)
	} else {
		m.clearLast()
	}
	m.writeLock.Lock()
	defer m.writeLock.Unlock()
	m.orderedMap.Set(key, value)

	return !didExist
}

// GetOrDefault returns the value for a key. If the key does not exist, returns
// the default value instead.
func (m *RingMap) GetOrDefault(key string, defaultValue interface{}) interface{} {
	m.writeLock.RLock()
	defer m.writeLock.RUnlock()
	return m.orderedMap.GetOrDefault(key, defaultValue)
}

// Len returns the number of elements in the map.
func (m *RingMap) Len() int {
	m.writeLock.RLock()
	defer m.writeLock.RUnlock()
	return m.orderedMap.Len()
}

// Capacity returns the capacity of the map
func (m *RingMap) Capacity() int {
	m.writeLock.RLock()
	defer m.writeLock.RUnlock()
	return m.capacity
}

// IsFull returns true if the number of elements in the map is Capacity()
func (m *RingMap) IsFull() bool {
	m.writeLock.RLock()
	defer m.writeLock.RUnlock()
	return m.orderedMap.Len() == m.capacity
}

// Keys returns all of the keys in the order they were inserted. If a key was
// replaced it will retain the same position. To ensure most recently set keys
// are always at the end you must always Delete before Set.
func (m *RingMap) Keys() (keys []string) {
	m.writeLock.RLock()
	defer m.writeLock.RUnlock()
	return m.orderedMap.Keys()
}

// Delete will remove a key from the map. It will return true if the key was
// removed (the key did exist).
func (m *RingMap) Delete(key string) (didDelete bool) {
	m.writeLock.Lock()
	defer m.writeLock.Unlock()
	return m.orderedMap.Delete(key)
}

// Front will return the element that is the first (oldest Set element). If
// there are no elements this will return nil.
func (m *RingMap) Front() *orderedmap.Element[string, interface{}] {
	m.writeLock.RLock()
	defer m.writeLock.RUnlock()
	return m.orderedMap.Front()
}

// Back will return the element that is the last (most recent Set element). If
// there are no elements this will return nil.
func (m *RingMap) Back() *orderedmap.Element[string, interface{}] {
	m.writeLock.RLock()
	defer m.writeLock.RUnlock()
	return m.orderedMap.Back()
}

func (m *RingMap) clearLast() {
	if m.IsFull() {
		m.Delete(m.Front().Key)
	}
}
