package flaky

import (
	
	
	
	

	
)

// ErrDebounced is an error that DebounceExecutor returns when an Execute call
// is superseded by another operation.
var ErrDebounced = errors.New("flaky: debounced")

// DebounceExecutor is an executor that debounces an operation. That is, it
// performs an operation once it stops being called for the specified debounce
// duration.
//
// Keep in mind that using DebounceExecutor introduces latency to the operation
// for at least the specified debounce duration. In most cases it should be used
// when the operation is expensive to execute or under high event rate or load.
// As a side effect, DebounceExecutor guarantees that at most one operation is
// executing at a time.
//
type DebounceExecutor struct {
	once sync.Once
	debounceState

	c *clock.Clock
	w time.Duration

	timer clock.Timer
}

// debounceState is the debounce state for DebounceExecutor.
type debounceState struct {
	lock  chan struct{}
	exec  chan struct{}
	steal chan struct{}
	next  chan struct{}
}

// Debounce returns a new DebounceExecutor instance for the given wait duration.
func ( time.Duration) *DebounceExecutor {
	return &DebounceExecutor{
		w: ,
	}
}

// clone returns a copy of the executor.
func ( *DebounceExecutor) () *DebounceExecutor {
	.init()
	return &DebounceExecutor{
		debounceState: .debounceState,
		c:             .c,
		w:             .w,
		timer:         .timer,
	}
}

// WithClock returns a copy of the executor that uses the given clock.
func ( *DebounceExecutor) ( *clock.Clock) *DebounceExecutor {
	 = .clone()
	.timer = nil
	.c = 
	.init()
	return 
}

// WithWait returns a copy of the executor that uses the given wait duration.
//
// It shares the underlying state and can be used to debounce operation with
// non-default wait duration.
func ( *DebounceExecutor) ( time.Duration) *DebounceExecutor {
	 = .clone()
	.w = 
	.init()
	return 
}

// Execute calls the function f if and only if Execute is not called again
// during the debounce interval. Context expiration cancels an operation. It
// returns ErrDebounced error if the given operation f was superseded by another
// Execute call. If Execute is called concurrently, the last invocation wins.
// When another operation is already executing, it returns ErrDebounced.
//
// Callers should handle ErrDebounced error to avoid breaking assumptions when
// running under another DebounceExecutor.
//
// As a side effect, it guarantees that at most one f is executing at a time.
// That is, it is safe to avoid locking if the state is mutated exclusively
// under the DebounceExecutor.
func ( *DebounceExecutor) ( context.Context,  Op) error {
	.init()

	// Acquire a lock or steal it from an ongoing debounced Execute call.
	select {
	case <-.Done():
		return .Err()
	case  := <-.exec:
		.exec <- 
		return ErrDebounced
	case .lock <- struct{}{}:
	case .steal <- struct{}{}:
		// Wait until the Execute call we are stealing from stops the
		// timer and passes the lock to us.
		<-.next
	}

	// Reset timer for the debounce duration.
	if .timer == nil {
		.timer = .c.Timer(.w)
	} else {
		.timer.Reset(.w)
	}

	// Wait until the debounce timer expiration, another Execute invocation
	// that steals our lock, or the action cancellation.
	var  bool
	select {
	case <-.timer.C():
		.exec <- struct{}{}
		 := ()
		<-.exec
		<-.lock
		return unwrapInternal()
	case <-.steal:
		 = true
	case <-.Done():
	}
	if !.timer.Stop() {
		<-.timer.C()
	}
	if  {
		.next <- struct{}{}
		return ErrDebounced
	}

	<-.lock
	return .Err()
}

// init initializes the internal debouncer state.
func ( *DebounceExecutor) () {
	.once.Do(func() {
		if .c == nil {
			.c = clock.System()
		}
		if .lock == nil {
			.lock = make(chan struct{}, 1)
		}
		if .exec == nil {
			.exec = make(chan struct{}, 1)
		}
		if .steal == nil {
			.steal = make(chan struct{})
		}
		if .next == nil {
			.next = make(chan struct{})
		}
	})
}