// Copyright (c) 2024 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package fxclock

import (
	
	
	
	
)

// Clock defines how Fx accesses time.
// We keep the interface pretty minimal.
type Clock interface {
	Now() time.Time
	Since(time.Time) time.Duration
	Sleep(time.Duration)
	WithTimeout(context.Context, time.Duration) (context.Context, context.CancelFunc)
}

// System is the default implementation of Clock based on real time.
var System Clock = systemClock{}

type systemClock struct{}

func (systemClock) () time.Time {
	return time.Now()
}

func (systemClock) ( time.Time) time.Duration {
	return time.Since()
}

func (systemClock) ( time.Duration) {
	time.Sleep()
}

func (systemClock) ( context.Context,  time.Duration) (context.Context, context.CancelFunc) {
	return context.WithTimeout(, )
}

// Mock adapted from
// https://github.com/uber-go/zap/blob/7db06bc9b095571d3dc3d4eebdfbe4dd9bd20405/internal/ztest/clock.go.

// Mock is a fake source of time.
// It implements standard time operations,
// but allows the user to control the passage of time.
//
// Use the [Add] method to progress time.
type Mock struct {
	mu  sync.RWMutex
	now time.Time

	// The MockClock works by maintaining a list of waiters.
	// Each waiter knows the time at which it should be resolved.
	// When the clock advances, all waiters that are in range are resolved
	// in chronological order.
	waiters     []waiter
	waiterAdded *sync.Cond
}

var _ Clock = (*Mock)(nil)

// NewMock builds a new mock clock
// using the current actual time as the initial time.
func () *Mock {
	 := &Mock{now: time.Now()}
	.waiterAdded = sync.NewCond(&.mu)
	return 
}

// Now reports the current time.
func ( *Mock) () time.Time {
	.mu.RLock()
	defer .mu.RUnlock()
	return .now
}

// Since reports the time elapsed since t.
// This is short for Now().Sub(t).
func ( *Mock) ( time.Time) time.Duration {
	return .Now().Sub()
}

// Sleep pauses the current goroutine for the given duration.
//
// With the mock clock, this will freeze
// until the clock is advanced with [Add] past the deadline.
func ( *Mock) ( time.Duration) {
	 := make(chan struct{})
	.runAt(.Now().Add(), func() { close() })
	<-
}

// WithTimeout returns a new context with a deadline of now + d.
//
// When the deadline is passed, the returned context's Done channel is closed
// and the context's Err method returns context.DeadlineExceeded.
// If the cancel function is called before the deadline is passed,
// the context's Err method returns context.Canceled.
func ( *Mock) ( context.Context,  time.Duration) (context.Context, context.CancelFunc) {
	// Unfortunately, we can't use context.WithCancelCause here.
	// Per its documentation (and verified by trying it):
	//
	//   ctx, cancel := context.WithCancelCause(parent)
	//   cancel(myError)
	//   ctx.Err() // returns context.Canceled
	//   context.Cause(ctx) // returns myError
	//
	// So it won't do for our purposes.
	 := .Now().Add()
	,  := context.WithCancel()
	 := &deadlineCtx{
		inner:       ,
		cancelInner: ,
		done:        make(chan struct{}),
		deadline:    ,
	}
	 = 

	.runAt(, func() {
		.cancel(context.DeadlineExceeded)
	})
	return , func() { .cancel(context.Canceled) }
}

type deadlineCtx struct {
	inner       context.Context
	cancelInner func()

	done     chan struct{}
	deadline time.Time

	mu  sync.Mutex // guards err; the rest is immutable
	err error
}

var _ context.Context = (*deadlineCtx)(nil)

func ( *deadlineCtx) () ( time.Time,  bool) { return .deadline, true }
func ( *deadlineCtx) () <-chan struct{}                   { return .done }
func ( *deadlineCtx) ( any) any                       { return .inner.Value() }

func ( *deadlineCtx) () error {
	.mu.Lock()
	defer .mu.Unlock()
	return .err
}

func ( *deadlineCtx) ( error) {
	.mu.Lock()
	if .err == nil {
		.err = 
		close(.done)
		.cancelInner()
	}
	.mu.Unlock()
}

// runAt schedules the given function to be run at the given time.
// The function runs without a lock held, so it may schedule more work.
func ( *Mock) ( time.Time,  func()) {
	.mu.Lock()
	defer .mu.Unlock()

	.waiters = append(.waiters, waiter{until: , fn: })
	.waiterAdded.Broadcast()
}

// AwaitScheduled blocks until there are at least N
// operations scheduled for the future.
func ( *Mock) ( int) {
	.mu.Lock()
	defer .mu.Unlock()

	// Note: waiterAdded is associated with c.mu,
	// the same lock we're holding here.
	//
	// When we call Wait(), it'll release the lock
	// and block until signaled by runAt,
	// at which point it'll reacquire the lock
	// (waiting until runAt has released it).
	for len(.waiters) <  {
		.waiterAdded.Wait()
	}
}

type waiter struct {
	until time.Time
	fn    func()
}

// Add progresses time by the given duration.
// Other operations waiting for the time to advance
// will be resolved if they are within range.
//
// Side effects of operations waiting for the time to advance
// will take effect on a best-effort basis.
// Avoid racing with operations that have side effects.
//
// Panics if the duration is negative.
func ( *Mock) ( time.Duration) {
	if  < 0 {
		panic("cannot add negative duration")
	}

	.mu.Lock()
	defer .mu.Unlock()

	sort.Slice(.waiters, func(,  int) bool {
		return .waiters[].until.Before(.waiters[].until)
	})

	 := .now.Add()
	// newTime won't be recorded until the end of this method.
	// This ensures that any waiters that are resolved
	// are resolved at the time they were expecting.

	for len(.waiters) > 0 {
		 := .waiters[0]
		if .until.After() {
			break
		}
		.waiters[0] = waiter{} // avoid memory leak
		.waiters = .waiters[1:]

		// The waiter is within range.
		// Travel to the time of the waiter and resolve it.
		.now = .until

		// The waiter may schedule more work
		// so we must release the lock.
		.mu.Unlock()
		.fn()
		// Sleeping here is necessary to let the side effects of waiters
		// take effect before we continue.
		time.Sleep(1 * time.Millisecond)
		.mu.Lock()
	}

	.now = 
}