package cache

import (
	"context"
	"sync"
	"time"
)

const (
	LiveInfinite = time.Duration(-1)
)

type CallCreate func(key any) (value any, created bool)
type CallCreateNew func(key any) (rKey, value any, created bool)
type CallCheck func(key, value any, exists bool) (rKey, rValue any, created bool)

type item struct {
	value  any
	expire int64
}

func NewCacheMap(ctx context.Context, maxSize int, live time.Duration) *CacheMap {
	cctx, cancel := context.WithCancel(ctx)
	res := &CacheMap{
		ctx:     cctx,
		cancel:  cancel,
		items:   make(map[any]*item),
		mi:      new(sync.RWMutex),
		live:    live,
		maxSize: maxSize,
		mc:      new(sync.RWMutex),
	}
	// todo - clean worker
	return res
}

type CacheMap struct {
	finished   bool
	ctx        context.Context
	cancel     context.CancelFunc
	items      map[any]*item
	mi         *sync.RWMutex
	live       time.Duration
	maxSize    int
	cleanChans []chan map[any]any
	mc         *sync.RWMutex
}

func (s *CacheMap) IsFinished() bool {
	return s.finished
}

func (s *CacheMap) close() {
	s.finished = true
	s.mc.Lock()
	for _, ch := range s.cleanChans {
		close(ch)
	}
	s.cleanChans = nil
	s.mc.Unlock()

}

func (s *CacheMap) eventClean(m map[any]any) {
	s.mc.RLock()
	for _, ch := range s.cleanChans {
		select {
		case ch <- m:
		default:
		}
	}
	s.mc.RUnlock()
}

func (s *CacheMap) EventCleaner() <-chan map[any]any {
	s.mc.Lock()
	ech := make(chan map[any]any, 1)
	s.cleanChans = append(s.cleanChans, ech)
	s.mc.Unlock()
	return ech
}

func (s *CacheMap) cleanWorker() {
	defer func() {
		for _, v := range s.cleanChans {
			close(v)
		}
	}()
	t := time.NewTicker(s.live)
loop:
	for {
		select {
		case <-t.C:
			// clean items
			nowTS := time.Now().Unix()
			m := make(map[any]any)
			s.mi.Lock()
			for key, item := range s.items {
				if item.expire < nowTS {
					delete(s.items, key)
					m[key] = item.value
				}
			}
			s.mi.Unlock()
			if len(m) > 0 {
				s.eventClean(m)
			}
		case <-s.ctx.Done():
			break loop
		}
	}
}

func (s *CacheMap) delete(key any) {
	delete(s.items, key)
}

// Delete removes cached object
func (s *CacheMap) Delete(key any) {
	s.mi.Lock()
	s.delete(key)
	s.mi.Unlock()
}

func (s *CacheMap) set(key, value any) {
	s.items[key] = &item{
		value:  value,
		expire: time.Now().Add(s.live).UnixNano(),
	}
}

func (s *CacheMap) keysOlder(count int) []any {
	if count > len(s.items) {
		count = len(s.items)
	}
	last, llast := time.Now().Unix(), int64(-1)
	res := make([]any, 0, count)
	for len(res) < count {
		var rKey any
		ltmp := last
		for key, val := range s.items {
			if val.expire < ltmp && val.expire > llast {
				rKey = key
				ltmp = val.expire
			}
		}
		res = append(res, rKey)
		llast = ltmp
	}
	return res
}

func (s *CacheMap) Set(key, value any) {
	s.mi.Lock()
	if s.maxSize > 0 && len(s.items) == s.maxSize {
		// remove first
		for key, val := range s.items {
			delete(s.items, key)
			s.eventClean(map[any]any{
				key: val.value,
			})
			break
		}
	}
	s.set(key, value)
	s.mi.Unlock()
}

func (s *CacheMap) SetMulti(m map[any]any) {
	s.mi.Lock()
	/*
		if s.maxSize > 0 && len(s.items)+len(m) > s.maxSize {
			// remove first
			for key, val := range s.items {
				delete(s.items, key)
				s.eventClean(map[any]any{
					key: val.value,
				})
				break
			}
		}
	*/
	for key, val := range m {
		s.set(key, val)
	}
	s.mi.Unlock()
}

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

func (s *CacheMap) get(key any) (res any, check bool) {
	var it *item
	if it, check = s.items[key]; check {
		res = it.value
	}
	return
}

func (s *CacheMap) Get(key any) (res any, check bool) {
	s.mi.RLock()
	res, check = s.get(key)
	s.mi.RUnlock()
	return
}

func (s *CacheMap) GetCreate(key any, mCreate CallCreate) (val any, check bool) {
	if val, check = s.Get(key); !check {
		s.mi.Lock()
		defer s.mi.Unlock()
		if val, check = s.get(key); check {
			return
		}

		if val, check = mCreate(key); check {
			s.set(key, val)
		}
	}
	return
}

func (s *CacheMap) GetCreateNew(key any, mCreateNew CallCreateNew) (rKey, val any, check bool) {
	rKey = key
	if val, check = s.Get(key); !check {
		s.mi.Lock()
		defer s.mi.Unlock()
		if val, check = s.get(key); check {
			return
		}
		if rKey, val, check = mCreateNew(key); check {
			s.set(rKey, val)
		}
	}
	return
}

func (s *CacheMap) GetCheck(key any, mCheck CallCheck) (res any, check bool) {
	s.mi.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.mi.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 *CacheMap) Each(callback func(any, any) bool) {
	s.mi.RLock()
	defer s.mi.RUnlock()
	for key, val := range s.items {
		if !callback(key, val.value) {
			return
		}
	}
}

func (s CacheMap) Len() (res int) {
	s.mi.RLock()
	res = len(s.items)
	s.mi.RUnlock()
	return
}

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

func (s *CacheMap) Clear() {
	s.mi.Lock()
	s.items = make(map[any]*item)
	s.mi.Unlock()
}