package stack

// The db_stack package contains the implementation of a generic slice backed stack
// used for tracking various states in the hub, i.e. headers and txcounts

import (
	"sync"

	"github.com/lbryio/herald.go/internal"
	"golang.org/x/exp/constraints"
)

type SliceBacked[T any] struct {
	slice []T
	len   uint32
	mut   sync.RWMutex
}

func NewSliceBacked[T any](size int) *SliceBacked[T] {
	return &SliceBacked[T]{
		slice: make([]T, size),
		len:   0,
		mut:   sync.RWMutex{},
	}
}

func (s *SliceBacked[T]) Push(v T) {
	s.mut.Lock()
	defer s.mut.Unlock()

	if s.len == uint32(len(s.slice)) {
		s.slice = append(s.slice, v)
	} else {
		s.slice[s.len] = v
	}
	s.len++
}

func (s *SliceBacked[T]) Pop() T {
	s.mut.Lock()
	defer s.mut.Unlock()

	if s.len == 0 {
		var null T
		return null
	}
	s.len--
	return s.slice[s.len]
}

func (s *SliceBacked[T]) Get(i uint32) T {
	s.mut.RLock()
	defer s.mut.RUnlock()

	if i >= s.len {
		var null T
		return null
	}
	return s.slice[i]
}

func (s *SliceBacked[T]) GetTip() T {
	s.mut.RLock()
	defer s.mut.RUnlock()

	if s.len == 0 {
		var null T
		return null
	}
	return s.slice[s.len-1]
}

func (s *SliceBacked[T]) Len() uint32 {
	s.mut.RLock()
	defer s.mut.RUnlock()

	return s.len
}

func (s *SliceBacked[T]) Cap() int {
	s.mut.RLock()
	defer s.mut.RUnlock()

	return cap(s.slice)
}

func (s *SliceBacked[T]) GetSlice() []T {
	// This is not thread safe so I won't bother with locking
	return s.slice
}

func BisectRight[T constraints.Ordered](s *SliceBacked[T], searchKeys []T) []uint32 {
	s.mut.RLock()
	defer s.mut.RUnlock()

	found := make([]uint32, len(searchKeys))
	for i, k := range searchKeys {
		found[i] = internal.BisectRight(s.slice[:s.Len()], k)
	}

	return found
}