package neo

import (
	
	
	
)

// Timer abstracts a single event.
type Timer interface {
	C() <-chan time.Time
	Stop() bool
	Reset(d time.Duration)
}

// Ticker abstracts a channel that delivers ``ticks'' of a clock at intervals.
type Ticker interface {
	C() <-chan time.Time
	Stop()
	Reset(d time.Duration)
}

// NewTime returns new temporal simulator.
func ( time.Time) *Time {
	return &Time{
		now:     ,
		moments: map[int]moment{},
	}
}

// Time simulates temporal interactions.
//
// All methods are goroutine-safe.
type Time struct {
	// mux guards internal state. Note that all methods without Unlocked
	// suffix acquire mux.
	mux      sync.Mutex
	now      time.Time
	momentID int

	moments   map[int]moment
	observers []chan struct{}
}

func ( *Time) ( time.Duration) Timer {
	 := &timer{
		time: ,
		ch:   make(chan time.Time, 1),
	}
	.id = .plan(.When(), .do)
	return 
}

func ( *Time) ( time.Duration) Ticker {
	 := &ticker{
		time: ,
		ch:   make(chan time.Time, 1),
		dur:  ,
	}
	.id = .plan(.When(), .do)
	return 
}

func ( *Time) ( time.Time,  func( time.Time)) int {
	 := .momentID
	.momentID++
	.moments[] = moment{
		when: ,
		do:   ,
	}
	.observeUnlocked()
	return 
}

func ( *Time) ( time.Time,  func( time.Time)) int {
	.mux.Lock()
	defer .mux.Unlock()

	return .planUnlocked(, )
}

// stop removes the moment with the given ID from the list of scheduled moments.
// It returns true if a moment existed for the given ID, otherwise it is no-op.
func ( *Time) ( int) bool {
	.mux.Lock()
	defer .mux.Unlock()

	,  := .moments[]
	delete(.moments, )
	return 
}

// reset adjusts the moment with the given ID to run after the d duration. It
// creates a new moment if the moment does not already exist. If durp pointer
// is not nil, it is updated with d value while reset is holding Time’s lock.
func ( *Time) ( time.Duration,  int,  func( time.Time),  *time.Duration) {
	.mux.Lock()
	defer .mux.Unlock()
	.resetUnlocked(, , , )
}

// resetUnlocked is like reset but does not acquire the Time’s lock.
func ( *Time) ( time.Duration,  int,  func( time.Time),  *time.Duration) {
	if  != nil {
		* = 
	}

	,  := .moments[]
	if ! {
		 = moment{do: }
	}

	.when = .now.Add()
	.moments[] = 
}

// tickUnlocked applies all scheduled temporal effects.
func ( *Time) () moments {
	var  moments

	for ,  := range .moments {
		if .when.After(.now) {
			continue
		}
		delete(.moments, )
		 = append(, )
	}
	sort.Sort()

	return 
}

// Now returns the current time.
func ( *Time) () time.Time {
	.mux.Lock()
	defer .mux.Unlock()
	return .now
}

// Set travels to specified time.
//
// Also triggers temporal effects.
func ( *Time) ( time.Time) {
	.mux.Lock()
	defer .mux.Unlock()
	.setUnlocked()
}

// Travel adds duration to current time and returns result.
//
// Also triggers temporal effects.
func ( *Time) ( time.Duration) time.Time {
	.mux.Lock()
	defer .mux.Unlock()
	 := .now.Add()
	.setUnlocked()
	return 
}

// TravelDate applies AddDate to current time and returns result.
//
// Also triggers temporal effects.
func ( *Time) (, ,  int) time.Time {
	.mux.Lock()
	defer .mux.Unlock()
	 := .now.AddDate(, , )
	.setUnlocked()
	return 
}

// setUnlocked sets the current time to the given now time and triggers temporal
// effects.
func ( *Time) ( time.Time) {
	.now = 
	.tickUnlocked().do()
}

// Sleep blocks until duration is elapsed.
func ( *Time) ( time.Duration) { <-.After() }

// When returns relative time point.
func ( *Time) ( time.Duration) time.Time {
	return .Now().Add()
}

// After returns new channel that will receive time.Time value with current tme after
// specified duration.
func ( *Time) ( time.Duration) <-chan time.Time {
	 := make(chan time.Time, 1)
	.plan(.When(), func( time.Time) {
		 <- 
	})
	return 
}

// Observe return channel that closes on clock calls. The current implementation
// also closes the channel on Ticker’s ticks.
func ( *Time) () <-chan struct{} {
	 := make(chan struct{})
	.mux.Lock()
	.observers = append(.observers, )
	.mux.Unlock()

	return 
}

func ( *Time) () {
	for ,  := range .observers {
		close()
	}
	.observers = .observers[:0]
}