package flaky

import (
	
	
	

	
)

var (
	// ErrNoNextSchedule is an error that is returned by ScheduleExecutor
	// if there is no next scheduled time or it is before current time.
	ErrNoNextSchedule = errors.New("flaky: no next scheduled time")

	// ErrScheduleDeadline is an error that is returned by ScheduleExecutor
	// if scheduled time exceeds the context deadline.
	ErrScheduleDeadline = errors.New("flaky: scheduled time exceeds context deadline")
)

// Schedule defines the schedule to use for WithSchedule.
type Schedule interface {
	// Next returns the next schedule time relative to now. If there is no
	// schedule past the given time, it returns zero time or a value before
	// now.
	Next(now time.Time) time.Time
}

type untilSchedule struct {
	sched Schedule
	until time.Time
}

// Until returns a schedule that stops at the given time. It allows defining the
// schedule for operations that should fail after the given time.
func ( Schedule,  time.Time) Schedule {
	return &untilSchedule{
		sched: ,
		until: ,
	}
}

// Next implements the Schedule interface.
func ( *untilSchedule) ( time.Time) time.Time {
	 := .sched.Next()
	if .until.Before() {
		return time.Time{}
	}
	return 
}

type midnightSchedule struct {
	d   time.Duration
	loc *time.Location
}

// Midnight returns a repeated Schedule for the duration d since midnight in the
// given in location. If the location is nil, it defaults to UTC.
//
// Passing a non-positive duration is equivalent to not using a schedule.
func ( time.Duration,  *time.Location) Schedule {
	if  == nil {
		 = time.UTC
	}
	return &midnightSchedule{, }
}

// Next implements the Schedule interface.
func ( *midnightSchedule) ( time.Time) time.Time {
	if .d < 0 {
		return 
	}

	 := .Location()
	 = .In(.loc)

	 := time.Date(.Year(), .Month(), .Day(), 0, 0, 0, 0, .Location())

	 := .Add(.d)
	if .Before() {
		// Go does not use leap seconds so the day is always 24 hours.
		// See https://github.com/golang/go/issues/15247
		 = .Add(24 * time.Hour)
	}

	return .In()
}

type sleepSchedule struct {
	d time.Duration
}

// Sleep returns a Schedule that delays execution for a fixed interval d.
//
// Passing a non-positive duration is equivalent to not using a schedule.
func ( time.Duration) Schedule {
	return sleepSchedule{}
}

// Next implements the Schedule interface.
func ( sleepSchedule) ( time.Time) time.Time {
	if .d < 0 {
		return 
	}
	return .Add(.d)
}

// ScheduleExecutor is an executor that restricts operation execution to the
// specified schedule. It allows executing operations that may only succeed
// at the given time.
type ScheduleExecutor struct {
	clock *clock.Clock
	sched Schedule
	exec  Executor
}

// WithSchedule restricts the executor to wait for the given scheduled time
// before executing an operation.
func ( Executor,  Schedule) *ScheduleExecutor {
	return &ScheduleExecutor{
		clock: clock.System(),
		sched: ,
		exec:  ,
	}
}

// WithClock returns a copy of the executor that uses the given clock.
func ( *ScheduleExecutor) ( *clock.Clock) *ScheduleExecutor {
	if  == nil {
		 = clock.System()
	}
	return &ScheduleExecutor{
		clock: ,
		sched: .sched,
		exec:  .exec,
	}
}

// Execute implements the Executor interface.
func ( *ScheduleExecutor) ( context.Context,  Op) error {
	 := .clock.Now()
	 := .sched.Next()

	if .Equal() {
		return .exec.Execute(, )
	}

	if .Before() {
		return ErrNoNextSchedule
	}

	 := .Sub()
	if !withinDeadline(, ) {
		return ErrScheduleDeadline
	}

	 := .clock.Timer()
	defer .Stop()
	select {
	case <-.Done():
		return .Err()
	case <-.C():
	}

	return .exec.Execute(, )
}