-- this is an example of how to do object oriented programming in Lua

function constrain(v, minv, maxv)
   -- constrain a value between two limits
   if v < minv then
      return minv
   end
   if v > maxv then
      return maxv
   end
   return v
end

--[[
 a PI controller with feed-forward implemented as a Lua object, using
 closure style object
--]]
local function PIFF(kFF,kP,kI,iMax)
   -- the new instance. You can put public variables inside this self
   -- declaration if you want to
   local self = {}

   -- private fields as locals
   local _kFF = kFF
   local _kP = kP or 0.0
   local _kI = kI or 0.0
   local _kD = kD or 0.0
   local _iMax = iMax
   local _last_t = nil
   local _log_data = {}
   local _I = 0
   local _counter = 0

   -- update the controller.
   function self.update(target, current)
      local now = millis():tofloat() * 0.001
      if not _last_t then
         _last_t = now
      end
      local dt = now - _last_t
      _last_t = now
      local err = target - current
      _counter = _counter + 1

      local FF = _kFF * target
      local P = _kP * err
      _I = _I + _kI * err * dt
      if _iMax then
         _I = constrain(_I, -_iMax, _iMax)
      end
      local I = _I
      local ret = FF + P + I

      _log_data = { target, current, FF, P, I, ret }
      return ret
   end

   -- log the controller internals
   function self.log(name)
      logger:write(name,'Targ,Curr,FF,P,I,Total','ffffff',table.unpack(_log_data))
   end

   -- return the instance
   return self
end


--[[
 another example of a PIFF controller as an object, this time using
 metatables. Using metatables uses less memory and object creation is
 faster, but access to variables is slower
--]]
local PIFF2 = {}
PIFF2.__index = PIFF2

function PIFF2.new(kFF,kP,kI,iMax)
   -- the new instance. You can put public variables inside this self
   -- declaration if you want to
   local self = setmetatable({},PIFF2)
   self.kFF = kFF
   self.kP = kP
   self.kI = kI
   self.iMax = iMax
   self.last_t = nil
   self.log_data = {}
   self.I = 0
   self.counter = 0
   return self
end

function PIFF2.update(self, target, current)
   local now = millis():tofloat() * 0.001
   if not self.last_t then
      self.last_t = now
   end
   local dt = now - self.last_t
   self.last_t = now
   local err = target - current
   self.counter = self.counter + 1
   local FF = self.kFF * target
   local P = self.kP * err
   self.I = self.I + self.kI * err * dt
   if self.iMax then
      self.I = constrain(self.I, -self.iMax, self.iMax)
   end
   local ret = FF + P + self.I

   self.log_data = { target, current, FF, P, self.I, ret }
   return ret
end

function PIFF2.log(self, name)
   logger:write(name,'Targ,Curr,FF,P,I,Total','ffffff',table.unpack(self.log_data))
end

--[[
 declare two PI controllers, using one of each style. Note the use of new() for the metatables style
--]]
local PI_elevator = PIFF(1.1, 0.0, 0.0, 20.0)
local PI_rudder   = PIFF2.new(1.1, 0.0, 0.0, 20.0)

function test()
  -- note the different syntax for the two varients
  elevator = PI_elevator.update(1.0, 0.5)
  rudder = PI_rudder:update(2.0, 0.7)

  PI_elevator.log("PEL")
  PI_rudder:log("PRD")

  gcs:send_text(0, "tick: " .. tostring(millis()))

  return test, 500
end

return test()
