package dataext

import "sync"

type SyncRingSet[TData comparable] struct {
	data map[TData]bool
	lock sync.Mutex
	ring *RingBuffer[TData]
}

func NewSyncRingSet[TData comparable](capacity int) *SyncRingSet[TData] {
	return &SyncRingSet[TData]{
		data: make(map[TData]bool, capacity+1),
		lock: sync.Mutex{},
		ring: NewRingBuffer[TData](capacity),
	}
}

// Add adds `value` to the set
// returns true  if the value was actually inserted (value did not exist beforehand)
// returns false if the value already existed
func (s *SyncRingSet[TData]) Add(value TData) bool {
	s.lock.Lock()
	defer s.lock.Unlock()

	if s.data == nil {
		s.data = make(map[TData]bool)
	}

	_, existsInPreState := s.data[value]
	if existsInPreState {
		return false
	}

	prev := s.ring.PushPop(value)

	s.data[value] = true
	if prev != nil {
		delete(s.data, *prev)
	}

	return true
}

func (s *SyncRingSet[TData]) AddAll(values []TData) {
	s.lock.Lock()
	defer s.lock.Unlock()

	if s.data == nil {
		s.data = make(map[TData]bool)
	}

	for _, value := range values {
		_, existsInPreState := s.data[value]
		if existsInPreState {
			continue
		}

		prev := s.ring.PushPop(value)

		s.data[value] = true
		if prev != nil {
			delete(s.data, *prev)
		}
	}
}

func (s *SyncRingSet[TData]) Remove(value TData) bool {
	s.lock.Lock()
	defer s.lock.Unlock()

	if s.data == nil {
		s.data = make(map[TData]bool)
	}

	_, existsInPreState := s.data[value]
	if !existsInPreState {
		return false
	}

	delete(s.data, value)
	s.ring.Remove(func(v TData) bool { return value == v })

	return true
}

func (s *SyncRingSet[TData]) RemoveAll(values []TData) {
	s.lock.Lock()
	defer s.lock.Unlock()

	if s.data == nil {
		s.data = make(map[TData]bool)
	}

	for _, value := range values {
		delete(s.data, value)
		s.ring.Remove(func(v TData) bool { return value == v })
	}
}

func (s *SyncRingSet[TData]) Contains(value TData) bool {
	s.lock.Lock()
	defer s.lock.Unlock()

	if s.data == nil {
		s.data = make(map[TData]bool)
	}

	_, ok := s.data[value]

	return ok
}

func (s *SyncRingSet[TData]) Get() []TData {
	s.lock.Lock()
	defer s.lock.Unlock()

	if s.data == nil {
		s.data = make(map[TData]bool)
	}

	r := make([]TData, 0, len(s.data))

	for k := range s.data {
		r = append(r, k)
	}

	return r
}

// AddIfNotContains
// returns true  if the value was actually added (value did not exist beforehand)
// returns false if the value already existed
func (s *SyncRingSet[TData]) AddIfNotContains(key TData) bool {
	return s.Add(key)
}

// RemoveIfContains
// returns true  if the value was actually removed (value did exist beforehand)
// returns false if the value did not exist in the set
func (s *SyncRingSet[TData]) RemoveIfContains(key TData) bool {
	return s.Remove(key)
}