-- This Source Code Form is subject to the terms of the bCDDL, v. 1.1.
-- If a copy of the bCDDL was not distributed with this
-- file, You can obtain one at http://beamng.com/bCDDL-1.1.txt

local M = {}
--Mandatory controller parameters
M.type = "main"
M.relevantDevice = nil
M.engineInfo = {}
M.fireEngineTemperature = 0
M.throttle = 0
M.brake = 0
M.clutchRatio = 0

--LOCAL--
local modes = {"Direct", "Gradual"}

local elevatorMode = 1
local aileronMode = 1
local rudderMode = 0
local engines = {}

--API--
M.throttle = 0
M.elevatorInput = 0
M.rudderInput = 0
M.aileronInput = 0
M.aileronAxis = false
M.elevatorAxis = false
M.rudderAxis = false
M.controlsEnabled = true

M.setThrottle = function (value)
  if not M.controlsEnabled then return end
  
  electrics.values.aircraft_throttle = math.min(0.99, value)
end

M.toggleEngine = function(id)
  if not M.controlsEnabled then return end
  
  local electric = "propellerEngine" .. tostring(id) .. "_ignition"
  local currentValue = electrics.values[electric] or 0
  local engineNo = tostring(id + 1)
  
  if currentValue > 0.5 then 
    electrics.values[electric] = 0 
    gui.message("Engine #" .. engineNo .. " is now OFF", 3, "Engines")
  else 
    gui.message("Engine #" .. engineNo .. " is now ON", 3, "Engines")
    electrics.values[electric] = 1 
  end
end

M.toggleElevatorMode = function()
  elevatorMode = elevatorMode + 1
  if elevatorMode >= #modes then elevatorMode = 0 end
  
  --what is it tho?
  gui.message("New elevator mode: " .. modes[elevatorMode+1], 3, "Aircraft")
end

M.toggleAileronMode = function()
  aileronMode = aileronMode + 1
  if aileronMode >= #modes then aileronMode = 0 end
  
  --what is it tho?
  gui.message("New aileron mode: " .. modes[aileronMode+1], 3, "Aircraft")
end

M.toggleRudderMode = function()
  rudderMode = rudderMode + 1
  if rudderMode >= #modes then rudderMode = 0 end
  
  --what is it tho?
  gui.message("New rudder mode: " .. modes[rudderMode+1], 3, "Aircraft")
end

local flapsIncrements = {0, 0.25, 0.5, 0.75, 1.0}
local currentFlapIncrement = 1
M.increaseFlaps = function()
  if not M.controlsEnabled then return end
  
  currentFlapIncrement = math.min(#flapsIncrements, currentFlapIncrement + 1)
  local electricValue = flapsIncrements[currentFlapIncrement]
  
  gui.message("Flaps: " ..tostring(currentFlapIncrement - 1), 3, "Flaps")
  electrics.values.flaps = electricValue
end

M.decreaseFlaps = function()
  if not M.controlsEnabled then return end
  
  currentFlapIncrement = math.max(1, currentFlapIncrement - 1)
  local electricValue = flapsIncrements[currentFlapIncrement]
  
  gui.message("Flaps: " ..tostring(currentFlapIncrement - 1), 3, "Flaps")
  electrics.values.flaps = electricValue
end

M.toggleGear = function()
  --first check if the electrics are disconnected
  if not M.controlsEnabled then return end 
  
  --then check for skis
  local canToggleGear = true
  local partConfig = v.config.parts
  if partConfig ~= nil then
    local frontPart = partConfig["an12_wheels_F"] or ""
    local leftPart = partConfig["an12_wheels_l"] or ""
    local rightPart = partConfig["an12_wheels_r"] or ""
    if string.find(frontPart, "ski") then canToggleGear = false end
    if string.find(leftPart, "ski") then canToggleGear = false end
    if string.find(rightPart, "ski") then canToggleGear = false end
  end
  
  --can we do it? YES WE.. wait, this is variable, nvm!
  if canToggleGear then
    local gearText = nil      
    
    --this "gearText" is messsy, added last minute
    --blame Joelvb
    local cv = electrics.values.landingGear or 0
    if cv == 1 then 
      gearText = "extending..."
      cv = 0
    else
      gearText = "retracting..."
      cv = 1
    end
    gui.message("Gear " .. gearText, 3, "Landing Gear")
    
    electrics.values["landingGear"] = cv
  else
    gui.message("Do you really think those will fit in the fuselage?", 3, "Gears")
  end
end
-------
local function init()
  --init propellers
  engines = {}
  if v.data.controllerprops ~= nil then
    for k,prop in pairs(v.data.controllerprops.engines) do
      if v.data[prop] == nil then
        goto continue
      end
      
      --set throttle electric
      electrics.values[prop .. "_throttle"] = 0
      electrics.values[prop .. "_warmup"] = 0
      
      local propTable = {}
      propTable.propPairs = {}
      propTable.broken = false
      propTable.thrust = v.data[prop].thrust or 1000
      propTable.direction = v.data[prop].direction or 1
      propTable.maxVelocity = v.data[prop].maxVelocity or 10000
      propTable.warmupSpeed = v.data[prop].warmupSpeed or 0.01
      propTable.shaftDrag = v.data[prop].shaftDrag or 350
      propTable.throttle = 0 --CHANGE YO
      propTable.warmup = 0
      propTable.electric = prop .. "_throttle"
      propTable.ignitionElectric = prop .. "_ignition"
      propTable.warmupElectric = prop .. "_warmup"
      propTable.smokeNodes = {}
      propTable.smokeAmount = v.data[prop].smokeAmount or 0
      propTable.smokeSpeed = v.data[prop].smokeSpeed or 0
      propTable.idleThrottle = v.data[prop].idleThrottle or 0
      propTable.id = v.data[prop].id 
      propTable.blurVelocity = v.data[prop].blurVelocity or 50
      propTable.name = prop
      
      --flood system
      propTable.flooding = false
      propTable.submersionCounter = 0
      propTable.floodTime = v.data[prop].floodTime or 3.5
      
      --turn the engine on
      electrics.values[propTable.ignitionElectric] = 1
      
      --prop blur set
      electrics.values["propblur" .. tostring(propTable.id)] = 0
      
      --get flood node
      if v.data[prop].floodDetectionNode ~= nil then
        for _, node in pairs(v.data.nodes) do
          if node.name == v.data[prop].floodDetectionNode then
            propTable.floodDetectionNode = node
          end
        end
      end
      
      --get break beam
      local breakBeams = v.data[prop].breakBeams
      if breakBeams == nil then 
        if v.data[prop].breakBeam ~= nil then breakBeams = {v.data[prop].breakBeam} end
      end
      
      if breakBeams ~= nil then
        propTable.breakBeams = {}
        for k3, bb in pairs(breakBeams) do
          for k2, bm in pairs(v.data.beams) do
            if bm.name == bb then table.insert(propTable.breakBeams, bm) end
          end
        end
      end
      
      --get smoke nodes if applicable
      if v.data[prop].smokeNodes ~= nil then
      for k2,nodeName in pairs(v.data[prop].smokeNodes) do
        for k2, node in pairs(v.data.nodes) do
          if nodeName == node.name then table.insert(propTable.smokeNodes, node) end
        end
      end
      end
      
      --get node pairs
      for temp,pair in pairs(v.data[prop].pairs) do
        local realPair = {}
        for temp2, pr in pairs(pair) do
          for temp3, node in pairs(v.data.nodes) do
            if node.name == pr then table.insert(realPair, node) end
          end
        end
        table.insert(propTable.propPairs, realPair)
      end
        
      table.insert(engines, propTable)
      
      ::continue::
    end
  end

  
  --init electrics
  electrics.values.flaps = 0
  electrics.values.elevator = 0
  electrics.values.rudder = 0
  electrics.values.aileron = 0
  electrics.values.aircraft_throttle = 0.0001
  electrics.values.rollDeg = 0
  electrics.values.pitchDeg = 0
  electrics.values.yawDeg = 0
  electrics.values.landingGear = 0
  electrics.values.propPitch = 0
  electrics.values.speedKM = 0
  electrics.values.speedKN = 0
  electrics.values.weaponAimY = 0
  electrics.values.weaponAimX = 0
  electrics.values.cargodoor = 0
  electrics.values.crosshair = 0
  
  electrics.values.rearbroken = 0
  electrics.values.frontbroken = 0
  
  --Nonliner gauges :|
  electrics.values.spdRus = 0
  electrics.values.spdRus750 = 0
  electrics.values.spdRus1100 = 0
  electrics.values.decisionHeight = 0
  
  --Other stuff
  --FEB 2018 : This is almost 6 months old...
  --do I even want to try implementing this??
  --MAR 2018 : Wow it's been a month... o.O
  --it's implemented now!
  --electrics.values.setDecisionHeight = -1
  M.controlsEnabled = true
  currentFlapIncrement = 1
end

local engTimer = 0

local function updateControlSurfaces(dt, doElevator, doAilerons, doRudder)
  if not M.controlsEnabled then return end 
  
  --elevator input
  if doElevator and not M.elevatorAxis then
    if elevatorMode == 0 then
      electrics.values.elevator = M.elevatorInput
    else
      local newValue = electrics.values.elevator + M.elevatorInput * (dt * 0.5)
      electrics.values.elevator = math.min(math.max(newValue, -1), 1)
    end
  end
  
  --rudder input
  if doRudder and not M.rudderAxis then
    if rudderMode == 0 then
      electrics.values.rudder = M.rudderInput
    else
      local newValue = electrics.values.rudder + M.rudderInput * (dt * 0.5)
      electrics.values.rudder = math.min(math.max(newValue, -1), 1)    
    end
  end
  
  --aileron input
  if doAilerons and not M.aileronAxis then
    if aileronMode == 0 then
      electrics.values.aileron = M.aileronInput
    else
      local newValue = electrics.values.aileron + M.aileronInput * (dt * 0.9)
      electrics.values.aileron = math.min(math.max(newValue, -1), 1)    
    end
  end
end

local function updateEngineGFX(engine, dt)
  if engine.floodDetectionNode == nil then return end
  if engine.broken then return end
  
  --detect submersion
  local inWater = obj:inWater(engine.floodDetectionNode.cid)
  if inWater then
    --increase submersion amount
    engine.submersionCounter = math.min(engine.floodTime, engine.submersionCounter + dt)
    
    --get factor for GUI message
    local guiID = string.format("EngineProblems%d", engine.id or -1)
    local submersionFactor = engine.submersionCounter / engine.floodTime
    if submersionFactor >= 0.1 then
      gui.message(string.format("Engine %d is %d%% flooded", engine.id or -1, submersionFactor * 100), 3, guiID)
    end
    
    --break this engine if we need to
    if submersionFactor == 1 then
      engine.broken = true
      gui.message(string.format("Engine %d has flooded", engine.id or -1), 3, guiID)
    end
  else
    --reduce submersion amount
    engine.submersionCounter = math.max(0, engine.submersionCounter - dt)
  end
end

local function updateGFX(dt)
  --"simulated" failures
  --engTimer = engTimer + dt
  --if engTimer > 120 then
  --  engines[1].throttle = 0
  --  engines[2].throttle = 0
  --  --engines[3].throttle = 0
  --  engines[1].broken = true
  --  engines[2].broken = true
  --  --engines[3].broken = true
  --end
  --always some brake, "simulated" axle friction
  electrics.values.brake = math.max(electrics.values.brake or 0, 0.025)
  
  electrics.values.throttle = math.min(math.max(input.throttle, 0), 1)
  --electrics.values.brake = math.min(math.max(input.brake, 0), 1)
  electrics.values.clutch = math.min(math.max(input.clutch, 0), 1)
  electrics.values.clutchRatio = 1 - electrics.values.clutch
  electrics.values.gear = "N"
  electrics.values.gearIndex = 0
  electrics.values.rpm = 0
  electrics.values.oiltemp = 0
  electrics.values.watertemp = 0
  electrics.values.fuel =  0
  electrics.values.lowfuel = false
  electrics.values.checkengine = false
  electrics.values.ignition = true
  electrics.values.engineThrottle = 0
  electrics.values.running = electrics.values.ignition
  electrics.values.landingGear = electrics.values.landingGear
  
  --compute avg throttle
  local avgThrottle = 0
  for _, engine in ipairs(engines) do
    avgThrottle = avgThrottle + engine.throttle
  end
  avgThrottle = avgThrottle / #engines
  
  M.engineInfo = {
    0
    , 1000
    , 0
    , 0
    , 0
    , string.format("%d%%",avgThrottle* 102)
    , 0
    , 0
    , 0
    , 0
    , obj:getVelocity():length()  -- airspeed
    , 0
    , 0
    , "manual"
    , obj:getID()
    , 0
    , 0
    , 1
    , 1
  }
  
  --update some electrics
  local dirVector = obj:getDirectionVector()
  local dirVectorUp = obj:getDirectionVectorUp()
  local position = obj:getPosition()

  electrics.values.rollDeg = (dirVectorUp.x * -dirVector.y + dirVectorUp.y * dirVector.x) * 57.2958
  electrics.values.pitchDeg = dirVector.z * 57.2958
  electrics.values.yawDeg = obj:getDirection() * 57.2958
  electrics.values.speedKM = obj:getVelocity():length() * 3.6
  electrics.values.speedKN = electrics.values.speedKM * 0.539957
  
  --update control surfaces
  local ap = controller.getController('autopilot')
  local updateElevator = (ap == nil or not ap.holdVS)
  updateControlSurfaces(dt, updateElevator, true, true)
 
  
  --update throttle
  if M.throttle ~= 0 and M.controlsEnabled then
    electrics.values.aircraft_throttle = electrics.values.aircraft_throttle +  ((dt * 0.5) * M.throttle)
    if electrics.values.aircraft_throttle > 0.99 then electrics.values.aircraft_throttle = 0.99 end  --hack for sounds
    if electrics.values.aircraft_throttle < 0 then electrics.values.aircraft_throttle  = 0 end
  end
  
  --update engines
  for _, engine in ipairs(engines) do
    updateEngineGFX(engine, dt)
  end
end

local function updateEngine(engine)
    --check if broken
    if engine.breakBeams ~= nil then
      for _, breakBeam in ipairs(engine.breakBeams) do
        if obj:beamIsBroken(breakBeam.cid) or engine.broken then
          engine.throttle = 0
          engine.broken = true
        end
      end
    end
    
    --update throttle
    local engineOn = (electrics.values[engine.ignitionElectric] or 0) > 0.5
    if not engine.broken and engineOn then
      engine.throttle = math.max(electrics.values.aircraft_throttle, engine.idleThrottle)
    end
    
    --nothing
    if not engineOn then
      engine.throttle = 0
    end
    
    --update electrics
    electrics.values[engine.electric] = engine.throttle
    electrics.values[engine.warmupElectric] = engine.warmup
    
    --warmup
    if engine.warmup < engine.throttle then engine.warmup = engine.warmup + engine.warmupSpeed end
    if engine.warmup > engine.throttle then engine.warmup = engine.warmup - (engine.warmupSpeed * 4) end
    
    --do particle stuff
    if engine.smokeAmount > 0 then    --node1                 node2                     velocity                  ptxid|radius|amount
      obj:addParticleByNodesRelative(engine.smokeNodes[1].cid, engine.smokeNodes[2].cid, 5 + (15 * engine.warmup), 40, 0.2, 5 * engine.throttle)
    end
    
    --don't update past here if we're broken
    if engine.broken == true then
      if engine.id ~= nil then
        electrics.values["propblur" .. tostring(engine.id)] = 0
      end
      return 
    end
    
    --add force! OwO
    local forceAmount = engine.thrust * engine.warmup
    local targetVelocity = engine.maxVelocity * engine.warmup
    for temp,pair in pairs(engine.propPairs) do
      local id1 = pair[1].cid
      local id2 = pair[2].cid
      
      --calculate velocity
      local currentVelocity = obj:getNodeVelocity(id2, id1) * engine.direction
      electrics.values["engVelocity"] = currentVelocity
      engine.lastVelocity = currentVelocity
      
      --blur
      if engine.id ~= nil then
        if currentVelocity > engine.blurVelocity then
         electrics.values["propblur" .. tostring(engine.id)] = 1
        else
          electrics.values["propblur" .. tostring(engine.id)] = 0
        end
      end
      
      --add drag
      local dragFactor = 1
      if currentVelocity <= 0 then dragFactor = 0 end
      obj:applyForce(id2, id1 , math.abs(engine.shaftDrag * dragFactor) * (engine.direction * -1))
      
      --engine doesn't need more power
      if currentVelocity > targetVelocity then return end

      local addVelocity = targetVelocity - currentVelocity
      if forceAmount > addVelocity then
        addVelocity = forceAmount
      end
      addVelocity = math.max(addVelocity, 0)
      
      --add velocity
      if addVelocity > 0 then
        obj:applyForce(id2, id1 , addVelocity * engine.direction)
      end
    
    end
end

local function update()
  --update engines
  for _, engine in ipairs(engines) do
    updateEngine(engine)
  end
end

M.init = init
M.update = update
M.updateGFX = updateGFX

--Mandatory main controller API
M.shiftUp = nop
M.shiftDown = nop
M.shiftToGearIndex = nop
M.cycleGearboxModes = nop
M.setGearboxMode = nop
M.setStarter = nop
M.setFreeze = nop
M.sendTorqueData = nop
-------------------------------

return M