package pid

import (
	"time"
)

type controller struct {
	Kp          float64
	Ki          float64
	Kd          float64
	lastTime    time.Time
	SetPoint    float64
	SampleTime  time.Duration
	WindupGuard float64
	Output      float64

	pTerm     float64
	iTerm     float64
	dTerm     float64
	lastError float64
}

/*
PID Controller
*/
type Controller interface {
	GetKp() float64
	GetKi() float64
	GetKd() float64
	GetSetPoint() float64
	GetSampleTime() time.Duration
	GetWindupGuard() float64
	GetOutput() float64

	SetKp(float64)
	SetKi(float64)
	SetKd(float64)
	SetSetPoint(float64)
	SetSampleTime(time.Duration)
	SetWindupGuard(float64)
	SetOutput(float64)

	Clear()
	Update(float64) float64
}

/*
create PID Controller
*/
func New(proportionalGain float64, integralGain float64, derivativeGain float64) Controller {
	return &controller{
		Kp:          proportionalGain,
		Ki:          integralGain,
		Kd:          derivativeGain,
		WindupGuard: 20.0,
		lastTime:    time.Now(),
	}
}

/*
Clears PID computations and coefficients
*/
func (c *controller) Clear() {
	c.SetPoint = 0.0
	c.pTerm = 0.0
	c.iTerm = 0.0
	c.dTerm = 0.0
	c.lastError = 0.0
	c.WindupGuard = 20.0
	c.Output = 0.0
}

/*
Calculates PID value for given reference feedback
.. math::
	u(t) = K_p e(t) + K_i \int_{0}^{t} e(t)dt + K_d {de}/{dt}

.. figure:: images/pid_1.png
	:align:   center

	Test PID with Kp=1.2, Ki=1, Kd=0.001 (test_pid.py)
*/
func (c *controller) Update(feedbackValue float64) float64 {
	var (
		errorValue            = c.SetPoint - feedbackValue
		currentTime           = time.Now()
		deltaTime, deltaError = currentTime.Sub(c.lastTime), errorValue - c.lastError
	)

	if deltaTime >= c.SampleTime {
		c.pTerm = c.Kp * errorValue
		c.iTerm += errorValue * deltaTime.Seconds()

		if c.iTerm < -c.WindupGuard {
			c.iTerm = -c.WindupGuard
		} else if c.iTerm > c.WindupGuard {
			c.iTerm = c.WindupGuard
		}

		c.dTerm = 0.0
		if deltaTime.Seconds() > 0.0 {
			c.dTerm = deltaError / deltaTime.Seconds()
		}
		c.lastTime = currentTime
		c.lastError = errorValue

		c.Output = c.pTerm + (c.Ki * c.iTerm) + (c.Kd * c.dTerm)
	}
	return c.Output
}

func (c *controller) GetKp() float64               { return c.Kp }
func (c *controller) GetKi() float64               { return c.Ki }
func (c *controller) GetKd() float64               { return c.Kd }
func (c *controller) GetSetPoint() float64         { return c.SetPoint }
func (c *controller) GetSampleTime() time.Duration { return c.SampleTime }
func (c *controller) GetWindupGuard() float64      { return c.WindupGuard }
func (c *controller) GetOutput() float64           { return c.Output }

func (c *controller) SetKp(v float64)               { c.Kp = v }
func (c *controller) SetKi(v float64)               { c.Ki = v }
func (c *controller) SetKd(v float64)               { c.Kd = v }
func (c *controller) SetSetPoint(v float64)         { c.SetPoint = v }
func (c *controller) SetSampleTime(v time.Duration) { c.SampleTime = v }
func (c *controller) SetWindupGuard(v float64)      { c.WindupGuard = v }
func (c *controller) SetOutput(v float64)           { c.Output = v }
