package application

import (
	"time"
)

// FrameRater implements a frame rate controller
type FrameRater struct {
	targetFPS      uint          // desired number of frames per second
	targetDuration time.Duration // calculated desired duration of frame
	frameStart     time.Time     // start time of last frame
	frameTimes     time.Duration // accumulated frame times for potential FPS calculation
	frameCount     uint          // accumulated number of frames for FPS calculation
	lastUpdate     time.Time     // time of last FPS calculation update
	timer          *time.Timer   // timer for sleeping during frame
}

// NewFrameRater returns a frame rate controller object for the specified
// number of target frames per second
func NewFrameRater(targetFPS uint) *FrameRater {

	f := new(FrameRater)
	f.targetDuration = time.Second / time.Duration(targetFPS)
	f.frameTimes = 0
	f.frameCount = 0
	f.lastUpdate = time.Now()
	f.timer = time.NewTimer(0)
	<-f.timer.C
	return f
}

// Start should be called at the start of the frame
func (f *FrameRater) Start() {

	f.frameStart = time.Now()
}

// Wait should be called at the end of the frame
// If necessary it will sleep to achieve the desired frame rate
func (f *FrameRater) Wait() {

	// Calculates the time duration of this frame
	elapsed := time.Now().Sub(f.frameStart)
	// Accumulates this frame time for potential FPS calculation
	f.frameCount++
	f.frameTimes += elapsed
	// If this frame duration is less than the target duration, sleeps
	// during the difference
	diff := f.targetDuration - elapsed
	if diff > 0 {
		f.timer.Reset(diff)
		<-f.timer.C
	}
}

// FPS calculates and returns the current measured FPS and the maximum
// potential FPS after the specified time interval has elapsed.
// It returns an indication if the results are valid
func (f *FrameRater) FPS(t time.Duration) (float64, float64, bool) {

	// If the time from the last update has not passed, nothing to do
	elapsed := time.Now().Sub(f.lastUpdate)
	if elapsed < t {
		return 0, 0, false
	}

	// Calculates the measured average frame rate
	fps := float64(f.frameCount) / elapsed.Seconds()
	// Calculates the average duration of a frame and the potential FPS
	frameDur := f.frameTimes.Seconds() / float64(f.frameCount)
	pfps := 1.0 / frameDur
	// Resets the frame counter and times
	f.frameCount = 0
	f.frameTimes = 0
	f.lastUpdate = time.Now()
	return fps, pfps, true
}
