package pick

import (
	"errors"
	"sync"
	"time"
)

type StringsSlice struct {
	s []string
	p *picker
}

// NewStringsSlice creates new StringsSlice.
func NewStringsSlice(items []string, mode pickMode) *StringsSlice {
	maxIdx := len(items) - 1

	if maxIdx < 0 {
		maxIdx = 0
	}

	return &StringsSlice{s: items, p: NewPicker(uint32(maxIdx), mode)}
}

// Pick an element from the strings slice.
func (s *StringsSlice) Pick() string {
	if len(s.s) == 0 {
		return ""
	}

	return s.s[s.p.NextIndex()]
}

type StringsSliceWithInterval struct {
	s []string
	p *picker
	d time.Duration

	idxMu sync.RWMutex
	idx   uint32

	close    chan struct{}
	closedMu sync.RWMutex
	closed   bool
}

// NewStringsSliceWithInterval creates new StringsSliceWithInterval.
func NewStringsSliceWithInterval(items []string, mode pickMode, interval time.Duration) *StringsSliceWithInterval {
	maxIdx := len(items) - 1

	if maxIdx < 0 {
		maxIdx = 0
	}

	if interval <= time.Duration(0) {
		panic("NewStringsSliceWithInterval: wrong interval")
	}

	s := &StringsSliceWithInterval{
		s:     items,
		p:     NewPicker(uint32(maxIdx), mode),
		d:     interval,
		close: make(chan struct{}, 1),
	}

	s.next()

	go s.rotate()

	return s
}

func (s *StringsSliceWithInterval) rotate() {
	defer close(s.close)

	timer := time.NewTimer(s.d)
	defer timer.Stop()

	for {
		select {
		case <-s.close:
			return

		case <-timer.C:
			s.next()
			timer.Reset(s.d)
		}
	}
}

func (s *StringsSliceWithInterval) next() {
	idx := s.p.NextIndex()

	s.idxMu.Lock()
	s.idx = idx
	s.idxMu.Unlock()
}

// Pick an element from the strings slice.
func (s *StringsSliceWithInterval) Pick() string {
	if s.isClosed() {
		panic("StringsSliceWithInterval.Pick(): closed")
	}

	if len(s.s) == 0 {
		return ""
	}

	s.idxMu.RLock()
	defer s.idxMu.RUnlock()

	return s.s[s.idx]
}

func (s *StringsSliceWithInterval) isClosed() (closed bool) {
	s.closedMu.RLock()
	closed = s.closed
	s.closedMu.RUnlock()

	return
}

func (s *StringsSliceWithInterval) Close() error {
	if s.isClosed() {
		return errors.New("closed")
	}

	s.closedMu.Lock()
	s.closed = true
	s.closedMu.Unlock()

	s.close <- struct{}{}

	return nil
}