// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package addrs

// Map represents a mapping whose keys are address types that implement
// UniqueKeyer.
//
// Since not all address types are comparable in the Go language sense, this
// type cannot work with the typical Go map access syntax, and so instead has
// a method-based syntax. Use this type only for situations where the key
// type isn't guaranteed to always be a valid key for a standard Go map.
type Map[K UniqueKeyer, V any] struct {
	// Elems is the internal data structure of the map.
	//
	// This is exported to allow for comparisons during tests and other similar
	// careful read operations, but callers MUST NOT modify this map directly.
	// Use only the methods of Map to modify the contents of this structure,
	// to ensure that it remains correct and consistent.
	Elems map[UniqueKey]MapElem[K, V]
}

type MapElem[K UniqueKeyer, V any] struct {
	Key   K
	Value V
}

func MakeMap[K UniqueKeyer, V any](initialElems ...MapElem[K, V]) Map[K, V] {
	inner := make(map[UniqueKey]MapElem[K, V], len(initialElems))
	ret := Map[K, V]{inner}
	for _, elem := range initialElems {
		ret.Put(elem.Key, elem.Value)
	}
	return ret
}

func MakeMapElem[K UniqueKeyer, V any](key K, value V) MapElem[K, V] {
	return MapElem[K, V]{key, value}
}

// Put inserts a new element into the map, or replaces an existing element
// which has an equivalent key.
func (m Map[K, V]) Put(key K, value V) {
	realKey := key.UniqueKey()
	m.Elems[realKey] = MapElem[K, V]{key, value}
}

// PutElement is like Put but takes the key and value from the given MapElement
// structure instead of as individual arguments.
func (m Map[K, V]) PutElement(elem MapElem[K, V]) {
	m.Put(elem.Key, elem.Value)
}

// Remove deletes the element with the given key from the map, or does nothing
// if there is no such element.
func (m Map[K, V]) Remove(key K) {
	realKey := key.UniqueKey()
	delete(m.Elems, realKey)
}

// Get returns the value of the element with the given key, or the zero value
// of V if there is no such element.
func (m Map[K, V]) Get(key K) V {
	realKey := key.UniqueKey()
	return m.Elems[realKey].Value
}

// GetOk is like Get but additionally returns a flag for whether there was an
// element with the given key present in the map.
func (m Map[K, V]) GetOk(key K) (V, bool) {
	realKey := key.UniqueKey()
	elem, ok := m.Elems[realKey]
	return elem.Value, ok
}

// Has returns true if and only if there is an element in the map which has the
// given key.
func (m Map[K, V]) Has(key K) bool {
	realKey := key.UniqueKey()
	_, ok := m.Elems[realKey]
	return ok
}

// Len returns the number of elements in the map.
func (m Map[K, V]) Len() int {
	return len(m.Elems)
}

// Elements returns a slice containing a snapshot of the current elements of
// the map, in an unpredictable order.
func (m Map[K, V]) Elements() []MapElem[K, V] {
	if len(m.Elems) == 0 {
		return nil
	}
	ret := make([]MapElem[K, V], 0, len(m.Elems))
	for _, elem := range m.Elems {
		ret = append(ret, elem)
	}
	return ret
}

// Keys returns a Set[K] containing a snapshot of the current keys of elements
// of the map.
func (m Map[K, V]) Keys() Set[K] {
	if len(m.Elems) == 0 {
		return nil
	}
	ret := make(Set[K], len(m.Elems))

	// We mess with the internals of Set here, rather than going through its
	// public interface, because that means we can avoid re-calling UniqueKey
	// on all of the elements when we know that our own Put method would have
	// already done the same thing.
	for realKey, elem := range m.Elems {
		ret[realKey] = elem.Key
	}
	return ret
}

// Values returns a slice containing a snapshot of the current values of
// elements of the map, in an unpredictable order.
func (m Map[K, V]) Values() []V {
	if len(m.Elems) == 0 {
		return nil
	}
	ret := make([]V, 0, len(m.Elems))
	for _, elem := range m.Elems {
		ret = append(ret, elem.Value)
	}
	return ret
}
