package internal

import (
	"context"
	"sync"
	"time"
)

var semTimers = sync.Pool{
	New: func() interface{} {
		t := time.NewTimer(time.Hour)
		t.Stop()
		return t
	},
}

// FastSemaphore is a channel-based semaphore optimized for performance.
// It uses a fast path that avoids timer allocation when tokens are available.
// The channel is pre-filled with tokens: Acquire = receive, Release = send.
// Closing the semaphore unblocks all waiting goroutines.
//
// Performance: ~30 ns/op with zero allocations on fast path.
// Fairness: Eventual fairness (no starvation) but not strict FIFO.
type FastSemaphore struct {
	tokens chan struct{}
	max    int32
}

// NewFastSemaphore creates a new fast semaphore with the given capacity.
func NewFastSemaphore(capacity int32) *FastSemaphore {
	ch := make(chan struct{}, capacity)
	// Pre-fill with tokens
	for i := int32(0); i < capacity; i++ {
		ch <- struct{}{}
	}
	return &FastSemaphore{
		tokens: ch,
		max:    capacity,
	}
}

// TryAcquire attempts to acquire a token without blocking.
// Returns true if successful, false if no tokens available.
func (s *FastSemaphore) TryAcquire() bool {
	select {
	case <-s.tokens:
		return true
	default:
		return false
	}
}

// Acquire acquires a token, blocking if necessary until one is available.
// Returns an error if the context is cancelled or the timeout expires.
// Uses a fast path to avoid timer allocation when tokens are immediately available.
func (s *FastSemaphore) Acquire(ctx context.Context, timeout time.Duration, timeoutErr error) error {
	// Check context first
	select {
	case <-ctx.Done():
		return ctx.Err()
	default:
	}

	// Try fast path first (no timer needed)
	select {
	case <-s.tokens:
		return nil
	default:
	}

	// Slow path: need to wait with timeout
	timer := semTimers.Get().(*time.Timer)
	defer semTimers.Put(timer)
	timer.Reset(timeout)

	select {
	case <-s.tokens:
		if !timer.Stop() {
			<-timer.C
		}
		return nil
	case <-ctx.Done():
		if !timer.Stop() {
			<-timer.C
		}
		return ctx.Err()
	case <-timer.C:
		return timeoutErr
	}
}

// AcquireBlocking acquires a token, blocking indefinitely until one is available.
func (s *FastSemaphore) AcquireBlocking() {
	<-s.tokens
}

// Release releases a token back to the semaphore.
func (s *FastSemaphore) Release() {
	s.tokens <- struct{}{}
}

// Close closes the semaphore, unblocking all waiting goroutines.
// After close, all Acquire calls will receive a closed channel signal.
func (s *FastSemaphore) Close() {
	close(s.tokens)
}

// Len returns the current number of acquired tokens.
func (s *FastSemaphore) Len() int32 {
	return s.max - int32(len(s.tokens))
}

// FIFOSemaphore is a channel-based semaphore with strict FIFO ordering.
// Unlike FastSemaphore, this guarantees that threads are served in the exact order they call Acquire().
// The channel is pre-filled with tokens: Acquire = receive, Release = send.
// Closing the semaphore unblocks all waiting goroutines.
//
// Performance: ~115 ns/op with zero allocations (slower than FastSemaphore due to timer allocation).
// Fairness: Strict FIFO ordering guaranteed by Go runtime.
type FIFOSemaphore struct {
	tokens chan struct{}
	max    int32
}

// NewFIFOSemaphore creates a new FIFO semaphore with the given capacity.
func NewFIFOSemaphore(capacity int32) *FIFOSemaphore {
	ch := make(chan struct{}, capacity)
	// Pre-fill with tokens
	for i := int32(0); i < capacity; i++ {
		ch <- struct{}{}
	}
	return &FIFOSemaphore{
		tokens: ch,
		max:    capacity,
	}
}

// TryAcquire attempts to acquire a token without blocking.
// Returns true if successful, false if no tokens available.
func (s *FIFOSemaphore) TryAcquire() bool {
	select {
	case <-s.tokens:
		return true
	default:
		return false
	}
}

// Acquire acquires a token, blocking if necessary until one is available.
// Returns an error if the context is cancelled or the timeout expires.
// Always uses timer to guarantee FIFO ordering (no fast path).
func (s *FIFOSemaphore) Acquire(ctx context.Context, timeout time.Duration, timeoutErr error) error {
	// No fast path - always use timer to guarantee FIFO
	timer := semTimers.Get().(*time.Timer)
	defer semTimers.Put(timer)
	timer.Reset(timeout)

	select {
	case <-s.tokens:
		if !timer.Stop() {
			<-timer.C
		}
		return nil
	case <-ctx.Done():
		if !timer.Stop() {
			<-timer.C
		}
		return ctx.Err()
	case <-timer.C:
		return timeoutErr
	}
}

// AcquireBlocking acquires a token, blocking indefinitely until one is available.
func (s *FIFOSemaphore) AcquireBlocking() {
	<-s.tokens
}

// Release releases a token back to the semaphore.
func (s *FIFOSemaphore) Release() {
	s.tokens <- struct{}{}
}

// Close closes the semaphore, unblocking all waiting goroutines.
// After close, all Acquire calls will receive a closed channel signal.
func (s *FIFOSemaphore) Close() {
	close(s.tokens)
}

// Len returns the current number of acquired tokens.
func (s *FIFOSemaphore) Len() int32 {
	return s.max - int32(len(s.tokens))
}
