package dataext

import (
	"errors"
	"gogs.mikescher.com/BlackForestBytes/goext/langext"
	"sync"
)

var ErrEmptyStack = errors.New("stack is empty")

type Stack[T any] struct {
	lock *sync.Mutex
	data []T
}

func NewStack[T any](threadsafe bool, initialCapacity int) *Stack[T] {
	var lck *sync.Mutex = nil
	if threadsafe {
		lck = &sync.Mutex{}
	}
	return &Stack[T]{
		lock: lck,
		data: make([]T, 0, initialCapacity),
	}
}

func (s *Stack[T]) Push(v T) {
	if s.lock != nil {
		s.lock.Lock()
		defer s.lock.Unlock()
	}

	s.data = append(s.data, v)
}

func (s *Stack[T]) Pop() (T, error) {
	if s.lock != nil {
		s.lock.Lock()
		defer s.lock.Unlock()
	}

	l := len(s.data)
	if l == 0 {
		return *new(T), ErrEmptyStack
	}

	result := s.data[l-1]
	s.data = s.data[:l-1]

	return result, nil
}

func (s *Stack[T]) OptPop() *T {
	if s.lock != nil {
		s.lock.Lock()
		defer s.lock.Unlock()
	}

	l := len(s.data)
	if l == 0 {
		return nil
	}

	result := s.data[l-1]
	s.data = s.data[:l-1]

	return langext.Ptr(result)
}

func (s *Stack[T]) Peek() (T, error) {
	if s.lock != nil {
		s.lock.Lock()
		defer s.lock.Unlock()
	}

	l := len(s.data)

	if l == 0 {
		return *new(T), ErrEmptyStack
	}

	return s.data[l-1], nil
}

func (s *Stack[T]) OptPeek() *T {
	if s.lock != nil {
		s.lock.Lock()
		defer s.lock.Unlock()
	}

	l := len(s.data)

	if l == 0 {
		return nil
	}

	return langext.Ptr(s.data[l-1])
}

func (s *Stack[T]) Length() int {
	if s.lock != nil {
		s.lock.Lock()
		defer s.lock.Unlock()
	}

	return len(s.data)
}

func (s *Stack[T]) Empty() bool {
	if s.lock != nil {
		s.lock.Lock()
		defer s.lock.Unlock()
	}

	return len(s.data) == 0
}