package common

import (
	"sync"
	"sync/atomic"
)

type PollCache interface {
	Get(id int) (*Poll, error)
	GetUnsafe(id int) (*Poll, error)
	BulkGet(ids []int) (list []*Poll)
	Set(item *Poll) error
	Add(item *Poll) error
	AddUnsafe(item *Poll) error
	Remove(id int) error
	RemoveUnsafe(id int) error
	Flush()
	Length() int
	SetCapacity(capacity int)
	GetCapacity() int
}

type MemoryPollCache struct {
	items    map[int]*Poll
	length   int64
	capacity int

	sync.RWMutex
}

// NewMemoryPollCache gives you a new instance of MemoryPollCache
func NewMemoryPollCache(capacity int) *MemoryPollCache {
	return &MemoryPollCache{
		items:    make(map[int]*Poll),
		capacity: capacity,
	}
}

func (mus *MemoryPollCache) Get(id int) (*Poll, error) {
	mus.RLock()
	item, ok := mus.items[id]
	mus.RUnlock()
	if ok {
		return item, nil
	}
	return item, ErrNoRows
}

func (mus *MemoryPollCache) BulkGet(ids []int) (list []*Poll) {
	list = make([]*Poll, len(ids))
	mus.RLock()
	for i, id := range ids {
		list[i] = mus.items[id]
	}
	mus.RUnlock()
	return list
}

func (mus *MemoryPollCache) GetUnsafe(id int) (*Poll, error) {
	item, ok := mus.items[id]
	if ok {
		return item, nil
	}
	return item, ErrNoRows
}

func (mus *MemoryPollCache) Set(item *Poll) error {
	mus.Lock()
	user, ok := mus.items[item.ID]
	if ok {
		mus.Unlock()
		*user = *item
	} else if int(mus.length) >= mus.capacity {
		mus.Unlock()
		return ErrStoreCapacityOverflow
	} else {
		mus.items[item.ID] = item
		mus.Unlock()
		atomic.AddInt64(&mus.length, 1)
	}
	return nil
}

func (mus *MemoryPollCache) Add(item *Poll) error {
	if int(mus.length) >= mus.capacity {
		return ErrStoreCapacityOverflow
	}
	mus.Lock()
	mus.items[item.ID] = item
	mus.length = int64(len(mus.items))
	mus.Unlock()
	return nil
}

func (mus *MemoryPollCache) AddUnsafe(item *Poll) error {
	if int(mus.length) >= mus.capacity {
		return ErrStoreCapacityOverflow
	}
	mus.items[item.ID] = item
	mus.length = int64(len(mus.items))
	return nil
}

func (mus *MemoryPollCache) Remove(id int) error {
	mus.Lock()
	_, ok := mus.items[id]
	if !ok {
		mus.Unlock()
		return ErrNoRows
	}
	delete(mus.items, id)
	mus.Unlock()
	atomic.AddInt64(&mus.length, -1)
	return nil
}

func (mus *MemoryPollCache) RemoveUnsafe(id int) error {
	_, ok := mus.items[id]
	if !ok {
		return ErrNoRows
	}
	delete(mus.items, id)
	atomic.AddInt64(&mus.length, -1)
	return nil
}

func (mus *MemoryPollCache) Flush() {
	mus.Lock()
	mus.items = make(map[int]*Poll)
	mus.length = 0
	mus.Unlock()
}

// ! Is this concurrent?
// Length returns the number of users in the memory cache
func (mus *MemoryPollCache) Length() int {
	return int(mus.length)
}

func (mus *MemoryPollCache) SetCapacity(capacity int) {
	mus.capacity = capacity
}

func (mus *MemoryPollCache) GetCapacity() int {
	return mus.capacity
}

type NullPollCache struct {
}

// NewNullPollCache gives you a new instance of NullPollCache
func NewNullPollCache() *NullPollCache {
	return &NullPollCache{}
}

func (mus *NullPollCache) Get(id int) (*Poll, error) {
	return nil, ErrNoRows
}

func (mus *NullPollCache) BulkGet(ids []int) (list []*Poll) {
	return list
}

func (mus *NullPollCache) GetUnsafe(id int) (*Poll, error) {
	return nil, ErrNoRows
}

func (mus *NullPollCache) Set(_ *Poll) error {
	return nil
}

func (mus *NullPollCache) Add(item *Poll) error {
	_ = item
	return nil
}

func (mus *NullPollCache) AddUnsafe(item *Poll) error {
	_ = item
	return nil
}

func (mus *NullPollCache) Remove(id int) error {
	return nil
}

func (mus *NullPollCache) RemoveUnsafe(id int) error {
	return nil
}

func (mus *NullPollCache) Flush() {
}

func (mus *NullPollCache) Length() int {
	return 0
}

func (mus *NullPollCache) SetCapacity(_ int) {
}

func (mus *NullPollCache) GetCapacity() int {
	return 0
}