--[[

This script is used to create "linear animation objects" or "lina_objects". 

CREATION / INSTANTIATION
-------------------------
You create an animation object with the following syntax:

lina_myLinaVar  =   lina_objects(...)   where the number of args are variable, being either 1, 2 or 3 args.

1 arg)      You supply a rate only (units/second). Used for animating both up/down.
2 args)     You supply an "up" rate and a down "rate"  (units / second)
3 args)     You supply a lower limit value,  an upper limit value and the 'total duration' it takes to animate 
            between those limit values.  You supply 3 arguments when creating the instance.

SETTABLE PROPERTIES:
-----------------------
.up_rate    --  animation rate when target is higher than the instv
.dn_rate    --  animation rate when the target is lower than the instv
.lo_limit   --  lower limit of the animation values  (false or a number)
.hi_limit   --  upper limit of the animation values  (false or a number)
.duration   --  length of animation in seconds
.target     --  the value you want to animate to.

TO USE
--------------------
To use, you simply set the .target property and the object will begin animating towards that target value, writing the 
animation value to the .instv property as it goes.  If you are using a custom dataref to animate an OBJ, then the 
.instv property is the value you use to drive the dataref.


HI / LO LIMITS
---------------
Each object has a .lo_limit and .hi_limit property which serves as a "hard stop", i.e. the animation will not go over or
under these limits no matter where you set the .target property.  When you instantiate a lina object using methods 1 or
2 above, then the lo/hi limits are initialized to 'false' and disabled.  You may set these properties to number values 
after the fact.  When using method 3 above, since you are supplying low and hi limits as part of the instantiation 
arguments, then these limits will apply.  You can set both the lo/hi limit properties to false to remove the limits.




lina_myvarName = lina_objects(args)

The arg list is variable in length, you can provide 1, 2 or 3 args depending on how you want to configure the anim.

When supplying 1 argument, you are supplying a "universal rate",  in units/second.  This rate is used when animating
both up and down.  

--]]====================================================================================================================
--                                      LINA 'Class Definition'
--======================================================================================================================

local   lina_objects        =   {}      --  global table to hold all the lina objects
local   lina_objects_mt     =   {}      --  meta table for lina_objects

------------------------------------------------------------------------------------------------------------------------
function    lina_objects:setLowLimit(l_low_limit)       
    self.lo_limit   =   l_low_limit
end
------------------------------------------------------------------------------------------------------------------------
function    lina_objects:setHiLimit(l_hi_limit)     
    self.hi_limit   =   l_hi_limit  
end
------------------------------------------------------------------------------------------------------------------------
function    lina_objects:setTarget(l_target)        
    self.target     =   l_target
end
------------------------------------------------------------------------------------------------------------------------
function    lina_objects:sync(sync_value)   
    self.target =   sync_value
    self.instv      =   sync_value
    self.d_error    =   0
end 

funcion     lina_objects:setPause(value)
    self.paused =   value
end
------------------------------------------------------------------------------------------------------------------------
function    lina_objects:setRate(...)

    local   args = {...}

    if      #args   ==  2   then    --  (universal_rate, i.e.  x units/second)
        self.up_rate        =   args[2]
        self.dn_rate        =   args[2]
        self.active_rate    =   args[2]
        self.duration       =   false

    elseif  #args   ==  3   then    --  (up_rate,  down_rate)  animates from instv to target at rates specified
        self.up_rate        =   args[2]
        self.dn_rate        =   args[3]     
        self.duration       =   false

    elseif  #args   ==  4   then    --  (lo_limit,  hi_limit,   duration)  assumes uni_rate
        self.lo_limit       =   args[2]
        self.hi_limit       =   args[3]
        self.duration       =   args[4]
        self.up_rate        =   (self.hi_limit - self.lo_limit) / self.duration     
        self.dn_rate        =   self.up_rate
        self.active_rate    =   self.up_rate
    end     
end

--======================================================================================================================
--                                          XPlane DATAERFS
--======================================================================================================================

xdr_sim_paused      =   find_dataref("sim/time/paused")     --  we don't want to animate when the sim is paused

--======================================================================================================================
--                                      LINA OBJ CLASS DEFINITION
--======================================================================================================================

function    lina_objects_mt.__call(...)

    local   o = {}

    setmetatable(o, {__index = lina_objects})

    --  user set properties
    o.up_rate       =   1           --  animation rate when target is higher than the instv
    o.dn_rate       =   1           --  animation rate when the target is lower than the instv
    o.lo_limit      =   false       --  lower limit of the animation values (commonly 0)
    o.hi_limit      =   false       --  upper limit of the animation values (commonly 1)
    o.duration      =   false       --  length of animation in seconds
    o.target        =   0.0
    o.paused        =   false


    --  calculated properties
    o.instv         =   0.0
    o.d_error       =   0.0         --  delta error
    o.d_second      =   0.0         --  delta value, per second
    o.inTransit     =   false
    o.direction     =   0
    o.last_target   =   0
    o.active_rate   =   0

    local   args = {...}

--  NOTE:  Using the __call metamethod, the table itself is always the first arguement
    if      #args   ==  2   then    --  (universal_rate, i.e.  x units/second)
        o.up_rate       =   args[2]
        o.dn_rate       =   args[2]
        o.active_rate   =   args[2]
        o.duration      =   false

    elseif  #args   ==  3   then    --  (up_rate,  down_rate)  animates from instv to target at rates specified
        o.up_rate       =   args[2]
        o.dn_rate       =   args[3]     
        o.duration      =   false

    elseif  #args   ==  4   then    --  (lo_limit,  hi_limit,   duration)  assumes uni_rate
        o.lo_limit      =   args[2]
        o.hi_limit      =   args[3]
        o.duration      =   args[4]
        o.up_rate       =   (o.hi_limit - o.lo_limit) / o.duration      
        o.dn_rate       =   o.up_rate
        o.active_rate   =   o.up_rate
    end

    o.setTarget = lina_objects.setTarget

    table.insert(lina_objects, o)
    return o
end

setmetatable(lina_objects,  lina_objects_mt)

--======================================================================================================================
--                                      OBJECT INSTANTIATIONS
--======================================================================================================================

test_lina_1     =   lina_objects(100);          test_lina_1:setTarget(100)      --  1 arg
test_lina_2     =   lina_objects(100,  1)       test_lina_2:setTarget(100)      --  2 args
test_lina_3     =   lina_objects(0, 10, 10)     test_lina_3:setTarget(10)       --  3 args

--======================================================================================================================
--                                              UTILITY FUNCTIONS
--======================================================================================================================

function    lina_flcb()

    --  If the sim is not paused, then calculate the error (.target - .instv).  If the error is smaller than a very
    --  small number, we consider the target 'reached', set the .instv = .target, set some status variables and the                     
    --  animation stops.  If the error is large enough, we need to continue animating.  If the error is positive, then
    --  we need to animate 'up', otherwise, we need to animate 'down'.  When animating up, we set a status variable to
    --  +1 and when animating down, we set the status variable to -1.  We calculate the animation increment for the
    --  animation frame (1 flight loop frame) and set the .instv property.  Custom datarefs or other variables can then
    --  read the .instv property. We continue this process until the .instv 'reaches' the .target value.


    if  xdr_sim_paused == 0  then   --  sim is running
        for i, v in ipairs(lina_objects) do

            v.d_error   =   v.target - v.instv      --  calculate current error

            if  math.abs(v.d_error) <= v.active_rate * SIM_PERIOD   then    --  reached target.  set .target = .instv
                v.instv         =   v.target
                v.direction     =   0
                v.inTransit     =   false
                v.d_error       =   0       

            else    -- not reached target, we need to continue animating the values

                v.inTransit     =   true            --  'convenience' status var.  Other code can 'test' against this.

                if  v.d_error   <   0   then        --  instv is higher than target.  animate down
                    v.direction     =   -1              

                    v.active_rate   =   v.dn_rate

                    if  v.lo_limit and v.instv <= v.lo_limit    then
                        v.instv = v.lo_limit
                    else
                        v.instv =   v.instv - v.dn_rate * SIM_PERIOD
                    end


                else    --  instv is lower than target, animate up.
                    v.direction     =   1

                    v.active_rate = v.up_rate

                    if  v.hi_limit and v.instv >= v.hi_limit    then
                        v.instv = v.hi_limit
                    else
                        v.instv =   v.instv + v.up_rate * SIM_PERIOD
                    end
                end

                v.last_target   =   v.target                
            end         
        end
    end

    testdr = lina_objects[1].instv
end


--======================================================================================================================
--                                      XPlane MAJOR CALLBACKS
--======================================================================================================================

function    after_physics()

    lina_flcb()     --  call the lina_flcb() function every flight loop.

end



--======================================================================================================================
--                                          Example use (flap moving annunciator)
--======================================================================================================================

lina_flap_position_ratio        =   lina_objects(0, 1, 10)      --  define flap ratio 0 to 1 over 10 seconds,  default 0
flap_moving_annunciators        =   false

lina_flap_position_ratio:setTarget(10)          --  Set animation in motion (.target = 10, .instv = 0)

if  lina.flap_position_ratio.inTransit  then

    flap_moving_annunciators = true

else

    flap_moving_annunciators = false

end