package store

import (
	"sync"
)

type CallCreate func(key any) (value any, created bool)
type CallCreateMulti func(key any) (m map[any]any, created bool)
type CallCheck func(key, value any, exists bool) (rKey, rValue any, created bool)

func FromMap(m map[any]any) *Store {
	return &Store{
		locker: new(sync.RWMutex),
		items:  m,
	}
}

func New() *Store {
	return &Store{
		locker: new(sync.RWMutex),
		items:  make(map[any]any),
	}
}

type Store struct {
	locker *sync.RWMutex
	items  map[any]any
}

func (s *Store) delete(key any) (any, bool) {
	item, check := s.items[key]
	delete(s.items, key)
	return item, check
}

func (s *Store) Delete(key any) (any, bool) {
	s.locker.Lock()
	item, check := s.delete(key)
	s.locker.Unlock()
	return item, check
}

func (s *Store) DeleteMulti(keys []any) {
	s.locker.Lock()
	for _, key := range keys {
		delete(s.items, key)
	}
	s.locker.Unlock()
}

func (s *Store) set(key, val any) {
	s.items[key] = val
}

func (s *Store) setMulti(m map[any]any) {
	for key, val := range m {
		s.items[key] = val
	}
}

func (s *Store) Set(key, val any) {
	s.locker.Lock()
	s.set(key, val)
	s.locker.Unlock()
}

func (s *Store) SetMulti(m map[any]any) {
	s.locker.Lock()
	s.setMulti(m)
	s.locker.Unlock()
}

func (s *Store) get(key any) (val any, check bool) {
	val, check = s.items[key]
	return
}

func (s *Store) Get(key any) (val any, check bool) {
	s.locker.RLock()
	val, check = s.get(key)
	s.locker.RUnlock()
	return
}

func (s *Store) GetCreate(key any, mCreate CallCreate) (res any, check bool) {
	if res, check = s.Get(key); !check {
		s.locker.Lock()
		if res, check = s.get(key); check {
			s.locker.Unlock()
			return
		}

		if res, check = mCreate(key); check {
			s.set(key, res)
		}
		s.locker.Unlock()
	}
	return
}

func (s *Store) GetCreateMulti(key any, mCreateMulti CallCreateMulti) (res any, check bool) {
	if res, check = s.Get(key); !check {
		s.locker.Lock()
		if res, check = s.get(key); check {
			s.locker.Unlock()
			return
		}

		var m map[any]any
		if m, check = mCreateMulti(key); check {
			s.setMulti(m)
			res, check = s.items[key]
		}
		s.locker.Unlock()
	}
	return
}

func (s *Store) GetCheck(key any, mCheck CallCheck) (res any, check bool) {
	s.locker.Lock()
	res, check = s.get(key)
	if rKey, rVal, rCheck := mCheck(key, res, check); rCheck {
		s.set(rKey, rVal)
		key, res, check = rKey, rVal, true
	}
	s.locker.Unlock()
	return
}

// Each implements a map bypass for each key using the callback function. If the callback function returns false, then the cycle stops
func (s *Store) Each(callback func(any, any) bool) {
	s.locker.RLock()
	for key, val := range s.items {
		if !callback(key, val) {
			s.locker.RUnlock()
			return
		}
	}
	s.locker.RUnlock()
}

func (s *Store) Len() (res int) {
	s.locker.RLock()
	res = len(s.items)
	s.locker.RUnlock()
	return
}

func (s *Store) Keys() (res []any) {
	s.locker.RLock()
	res = make([]any, 0, len(s.items))
	for key := range s.items {
		res = append(res, key)
	}
	s.locker.RUnlock()
	return
}

func (s *Store) Clear() {
	s.locker.Lock()
	s.items = make(map[any]any)
	s.locker.Unlock()
}

func (s *Store) Map() (res map[any]any) {
	res = make(map[any]any)
	s.locker.RLock()
	for key, val := range s.items {
		res[key] = val
	}
	s.locker.RUnlock()
	return
}