package orderedmap

import (
	"sync"
)

type OrderedMap struct {
	head       *Element
	tail       *Element
	dictionary map[interface{}]*Element
	size       int
	mutex      sync.RWMutex
}

func New() *OrderedMap {
	return &OrderedMap{
		dictionary: make(map[interface{}]*Element),
	}
}

func (orderedMap *OrderedMap) Get(key interface{}) (interface{}, bool) {
	orderedMap.mutex.RLock()
	defer orderedMap.mutex.RUnlock()

	if orderedMapElement, orderedMapElementExists := orderedMap.dictionary[key]; !orderedMapElementExists {
		return nil, false
	} else {
		return orderedMapElement.value, true
	}
}

func (orderedMap *OrderedMap) Set(key interface{}, newValue interface{}) bool {
	if oldValue, oldValueExists := orderedMap.Get(key); oldValueExists && oldValue == newValue {
		return false
	}

	orderedMap.mutex.Lock()
	defer orderedMap.mutex.Unlock()

	if oldValue, oldValueExists := orderedMap.dictionary[key]; oldValueExists {
		if oldValue.value == newValue {
			return false
		}

		oldValue.value = newValue

		return true
	}

	newElement := &Element{
		key:   key,
		value: newValue,
	}

	if orderedMap.head == nil {
		orderedMap.head = newElement
	} else {
		orderedMap.tail.next = newElement
		newElement.prev = orderedMap.tail
	}
	orderedMap.tail = newElement
	orderedMap.size++

	orderedMap.dictionary[key] = newElement

	return true
}

func (orderedMap *OrderedMap) ForEach(consumer func(key, value interface{}) bool) bool {
	orderedMap.mutex.RLock()
	currentEntry := orderedMap.head
	orderedMap.mutex.RUnlock()

	for currentEntry != nil {
		if !consumer(currentEntry.key, currentEntry.value) {
			return false
		}

		orderedMap.mutex.RLock()
		currentEntry = currentEntry.next
		orderedMap.mutex.RUnlock()
	}

	return true
}

func (orderedMap *OrderedMap) Delete(key interface{}) bool {
	if _, valueExists := orderedMap.Get(key); !valueExists {
		return false
	}

	orderedMap.mutex.Lock()
	defer orderedMap.mutex.Unlock()

	value, valueExists := orderedMap.dictionary[key]
	if !valueExists {
		return false
	}

	delete(orderedMap.dictionary, key)
	orderedMap.size--

	if value.prev != nil {
		value.prev.next = value.next
	} else {
		orderedMap.head = value.next
	}

	if value.next != nil {
		value.next.prev = value.prev
	} else {
		orderedMap.tail = value.prev
	}

	return true
}

func (orderedMap *OrderedMap) Size() int {
	orderedMap.mutex.RLock()
	defer orderedMap.mutex.RUnlock()

	return orderedMap.size
}