// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cache

import (
	"sync"

	"golang.org/x/tools/internal/lsp/protocol"
)

type watcher struct {
	id       uint64
	callback func(changeType protocol.FileChangeType) bool
}

type WatchMap struct {
	mu       sync.Mutex
	nextID   uint64
	watchers map[interface{}][]watcher
}

func NewWatchMap() *WatchMap {
	return &WatchMap{watchers: make(map[interface{}][]watcher)}
}
func (w *WatchMap) Watch(key interface{}, callback func(protocol.FileChangeType) bool) func() {
	w.mu.Lock()
	defer w.mu.Unlock()
	id := w.nextID
	w.nextID++
	w.watchers[key] = append(w.watchers[key], watcher{
		id:       id,
		callback: callback,
	})
	return func() {
		// unwatch if invoked
		w.mu.Lock()
		defer w.mu.Unlock()
		// find and delete the watcher entry
		entries := w.watchers[key]
		for i, entry := range entries {
			if entry.id == id {
				// found it
				entries[i] = entries[len(entries)-1]
				entries = entries[:len(entries)-1]
			}
		}
	}
}

func (w *WatchMap) Notify(key interface{}, changeType protocol.FileChangeType) bool {
	// Make a copy of the watcher callbacks so we don't need to hold
	// the mutex during the callbacks (to avoid deadlocks).
	w.mu.Lock()
	entries := w.watchers[key]
	entriesCopy := make([]watcher, len(entries))
	copy(entriesCopy, entries)
	w.mu.Unlock()

	var result bool
	for _, entry := range entriesCopy {
		result = entry.callback(changeType) || result
	}
	return result
}
