package cache

import (
	"log"
	"runtime"
	"sync"
	"time"
)

type CallCreate func(key interface{}) (value interface{}, created bool)
type CallCreateNew func(key interface{}) (rKey, value interface{}, created bool)
type CallCheck func(key, value interface{}, exists bool) (rKey, rValue interface{}, created bool)

func NewMap(liveDuration time.Duration, maxSize int) *CacheMap {
	res := &CacheMap{&cacheMap{
		locker:          new(sync.RWMutex),
		items:           make(map[interface{}]*cacheMapItem),
		liveDuration:    liveDuration,
		maxSize:         maxSize,
		stopCleanerChan: make(chan byte),
	}}
	runtime.SetFinalizer(res, destroyCacheMap)
	return res
}

type CacheMap struct {
	*cacheMap
}

type cacheMapItem struct {
	value  interface{}
	expire int64
}

type cacheMap struct {
	locker           *sync.RWMutex
	items            map[interface{}]*cacheMapItem
	liveDuration     time.Duration
	maxSize          int
	cleanerWork      bool
	stopCleanerChan  chan byte
	cleanedEventChan chan map[interface{}]interface{}
}

func (s *cacheMap) CleanEvent() (eventChan <-chan map[interface{}]interface{}) {
	s.locker.Lock()
	if s.cleanedEventChan == nil {
		s.cleanedEventChan = make(chan map[interface{}]interface{})
	}
	eventChan = s.cleanedEventChan
	s.locker.Unlock()
	return
}

func (s *cacheMap) runCleaner() {
	ticker := time.NewTicker(s.liveDuration / 2)
	for {
		log.Println("CLEANER")
		select {
		case <-ticker.C:
			{
				now := time.Now().UnixNano()
				s.locker.Lock()
				cleaned := make(map[interface{}]interface{})
				for key, val := range s.items {
					if now > val.expire {
						cleaned[key] = val.value
						delete(s.items, key)
					}
				}
				if len(cleaned) > 0 {
					select {
					case s.cleanedEventChan <- cleaned:
					default:
					}
				}
				if len(s.items) == 0 {
					s.cleanerWork = false
					ticker.Stop()
					s.locker.Unlock()
					return
				}
				s.locker.Unlock()
			}
		case <-s.stopCleanerChan:
			{
				s.cleanerWork = false
				ticker.Stop()
				return
			}
		}
	}
}

func (s *cacheMap) delete(key interface{}) {
	delete(s.items, key)
	if len(s.items) == 0 && s.cleanerWork {
		s.stopCleanerChan <- 0
	}
}

// Delete removes cached object
func (s *cacheMap) Delete(key interface{}) {
	s.locker.Lock()
	s.delete(key)
	s.locker.Unlock()
}

func (s *cacheMap) set(key, value interface{}) {
	s.items[key] = &cacheMapItem{
		value:  value,
		expire: time.Now().Add(s.liveDuration).UnixNano(),
	}
	if !s.cleanerWork {
		s.cleanerWork = true
		go s.runCleaner()
	}
}

func (s *cacheMap) Set(key, value interface{}) {
	s.locker.Lock()
	s.set(key, value)
	s.locker.Unlock()
}

func (s *cacheMap) SetMulti(m map[interface{}]interface{}) {
	s.locker.Lock()
	for key, val := range m {
		s.set(key, val)
	}
	s.locker.Unlock()
}

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

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

func (s *cacheMap) Get(key interface{}) (res interface{}, check bool) {
	s.locker.RLock()
	res, check = s.get(key)
	s.locker.RUnlock()
	return
}

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

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

func (s *cacheMap) GetCreateNew(key interface{}, mCreateNew CallCreateNew) (rKey, val interface{}, check bool) {
	rKey = key
	if val, check = s.Get(key); !check {
		s.locker.Lock()
		if val, check = s.get(key); check {
			s.locker.Unlock()
			return
		}

		if rKey, val, check = mCreateNew(key); check {
			s.set(rKey, val)
		}
		s.locker.Unlock()
	}
	return
}

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

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

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

func (s *cacheMap) Clear() {
	s.locker.Lock()
	s.items = make(map[interface{}]*cacheMapItem)
	s.locker.Unlock()
}

// for garbage collector
func destroyCacheMap(m *CacheMap) {
	close(m.stopCleanerChan)
	if m.cleanedEventChan != nil {
		close(m.cleanedEventChan)
	}
}