package cmap

import "sync"

// Map represents a map safe for concurrent use
type Map[K comparable, V any] struct {
	mutex sync.RWMutex
	data  map[K]V
}

// New creates a new generic Map
func New[K comparable, V any]() *Map[K, V] {
	return &Map[K, V]{data: map[K]V{}}
}

// Put puts the key-value pair into m
func (m *Map[K, V]) Put(key K, value V) {
	m.mutex.Lock()
	defer m.mutex.Unlock()
	m.data[key] = value
}

// GetHas returns the corresponding value for the given key in m
// as well as a bool indicating if a value was present at all
func (m *Map[K, V]) GetHas(key K) (V, bool) {
	m.mutex.RLock()
	defer m.mutex.RUnlock()
	value, ok := m.data[key]
	return value, ok
}

// Get returns the corresponding value for the given key in m
func (m *Map[K, V]) Get(key K) V {
	value, _ := m.GetHas(key)
	return value
}

// Has returns a bool indicating if a value was present for the given key
func (m *Map[K, V]) Has(key K) bool {
	_, ok := m.GetHas(key)
	return ok
}

// Delete deletes the key-value pair from m
func (m *Map[K, V]) Delete(key K) {
	m.mutex.Lock()
	defer m.mutex.Unlock()
	delete(m.data, key)
}

// Count returns the amount of key-value pairs currently stored in m
func (m *Map[K, V]) Count() int {
	m.mutex.RLock()
	defer m.mutex.RUnlock()
	return len(m.data)
}

// Iter calls f for every key-value pair stored in m.
// Be aware that this locks m for write access
// as pointer types could be modified in f
func (m *Map[K, V]) Iter(f func(key K, value V)) {
	m.mutex.Lock()
	defer m.mutex.Unlock()

	for key, value := range m.data {
		f(key, value)
	}
}

// Keys returns a slice containing all currently stored keys in m.
// Pointer values are returned as they were received
func (m *Map[K, V]) Keys() []K {
	m.mutex.RLock()
	defer m.mutex.RUnlock()

	keys := make([]K, 0, len(m.data))
	for key := range m.data {
		keys = append(keys, key)
	}
	return keys
}

// Values returns a slice containing all currently stored values in m.
// Pointer values are returned as they were received
func (m *Map[K, V]) Values() []V {
	m.mutex.RLock()
	defer m.mutex.RUnlock()

	values := make([]V, 0, len(m.data))
	for _, value := range m.data {
		values = append(values, value)
	}
	return values
}

// Do calls f with the underlying primitive map.
// Be aware that this locks m for write access
// so Do can be used for reading as well as modifiying m.
func (m *Map[K, V]) Do(f func(m map[K]V)) {
	m.mutex.Lock()
	defer m.mutex.Unlock()
	f(m.data)
}

// DoWithError calls f with the underlying primitive map and returns its error.
// Be aware that this locks m for write access
// so Do can be used for reading as well as modifiying m.
func (m *Map[K, V]) DoWithError(f func(m map[K]V) error) error {
	m.mutex.Lock()
	defer m.mutex.Unlock()
	return f(m.data)
}

// Clone returns a copy of the underlying map data
func (m *Map[K, V]) Clone() map[K]V {
	m.mutex.RLock()
	defer m.mutex.RUnlock()

	c := map[K]V{}
	for key, value := range m.data {
		c[key] = value
	}
	return c
}