package cachetoken

import (
	"hash/fnv"
	"sync"
)

const keyShards = 64

func newShardedLock() *shardedLock {
	var sl shardedLock
	sl.locks = make([]*sync.RWMutex, keyShards)
	for n := 0; n < keyShards; n++ {
		sl.locks[n] = new(sync.RWMutex)
	}
	return &sl
}

// shardedLock is a lock that will be sharded based on criteria sent by the
// caller. this improves lock performance since we will only lock based on the
// element being worked on.
//
//   sl := newShardedLock()
//   go func() {
//     sl.Shard("a").Lock()
//     // do stuff related to "a"
//     sl.Shard("a").Unlock()
//   }()
//
//   go func() {
//     sl.Shard("b").Lock()
//     // do stuff related to "b"
//     sl.Shard("b").Unlock()
//   }()
type shardedLock struct {
	locks []*sync.RWMutex
}

func (sl *shardedLock) Shard(k string) *sync.RWMutex {
	return sl.locks[sl.shard(k)]
}

func (sl *shardedLock) shard(k string) uint32 {
	h := fnv.New32a()
	// Hash.Write never returns an error, see https://golang.org/pkg/hash/#Hash
	h.Write([]byte(k)) //nolint:errcheck
	return h.Sum32() % keyShards
}
