// Copyright 2016 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package atomiccache provides a map-based cache that supports very fast
// reads.
package atomiccache

import (
	"sync"
	"sync/atomic"
)

type mapType map[interface{}]interface{}

// Cache is a map-based cache that supports fast reads via use of atomics.
// Writes are slow, requiring a copy of the entire cache.
// The zero Cache is an empty cache, ready for use.
type Cache struct {
	val atomic.Value // mapType
	mu  sync.Mutex   // used only by writers
}

// Get returns the value of the cache at key. If there is no value,
// getter is called to provide one, and the cache is updated.
// The getter function may be called concurrently. It should be pure,
// returning the same value for every call.
func (c *Cache) Get(key interface{}, getter func() interface{}) interface{} {
	mp, _ := c.val.Load().(mapType)
	if v, ok := mp[key]; ok {
		return v
	}

	// Compute value without lock.
	// Might duplicate effort but won't hold other computations back.
	newV := getter()

	c.mu.Lock()
	mp, _ = c.val.Load().(mapType)
	newM := make(mapType, len(mp)+1)
	for k, v := range mp {
		newM[k] = v
	}
	newM[key] = newV
	c.val.Store(newM)
	c.mu.Unlock()
	return newV
}
