-- 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 = "auxiliary"
M.relevantDevice = nil

--LOCAL--
local instrumentDefs = {}

-------

local function initComponent(component)
  --check required vars
  if component.input_electric == nil then
    log('E', 'instrument_system.initComponent', 'No input electric defined for ' .. component.name .. '. skipping this!')
    return
  end
  if component.output_electric == nil then
    log('E', 'instrument_system.initComponent', 'No output electric defined for ' .. component.name .. '. skipping this!')
    return
  end
  if component.intervals == nil then
    log('E', 'instrument_system.initComponent', 'No intervals defined for ' .. component.name .. '. skipping this component!')
    return
  end

  --verify intervals
  local lastIntervalVerified = component.intervals[1][1]
  for _, interval in ipairs(component.intervals) do
    local val = interval[1]
    if val < lastIntervalVerified then
      log('W', 'instrument_system.initComponent', 'Intervals are not in order for ' .. component.name .. '. skipping this component!')
      return
    end
    lastIntervalVerified = val
  end
  
  --set defaults
  component.smoothing = component.smoothing or 0
  component.output_scale = component.output_scale or 1.0
  component.input_scale = component.input_scale or 1.0
  component.looped = component.looped or false
  
  --init smoother if we need to
  if component.smoothing > 0 then
    component.smoother = newExponentialSmoothing(component.smoothing)
  end
  
  --success
  return component
end

local function initElectrics()
  for _, instrument in ipairs(instrumentDefs) do
    for _, component in ipairs(instrument.components) do
      electrics.values[component.output_electric] = 0
    end
  end
end

local function init()
  instrumentDefs = {}
  
  local instrumentDefDirectory =  v.data.vehicleDirectory .. "instruments"
  if not FS:directoryExists(instrumentDefDirectory) then
    log('E', 'instrument_system.init', 'Instrument directory doesnt exist.')
    return 
  end
    
  local files = FS:findFiles(instrumentDefDirectory, "*.json", -1, true, false)
  for _, file in ipairs(files) do
    --load this instrument def
    log('I', 'instrument_system.init', "Loading file " .. file)
    local idef = jsonReadFile(file)
    
    if idef.components == nil then
      log('E', 'instrument_system.init', 'No components defined for ' .. file .. '. skipping this instrument entirely!')
      return
    end
    
    --init components
    local validComponents = {}
    for k, component in pairs(idef.components) do
      component.name = k
      local componentTable = initComponent(component)      
      if componentTable ~= nil then
        validComponents[component.name] = componentTable
      end
    end

    --init logic
    local logicTable = nil
    if idef.logic ~= nil then
      local logicPath =  v.data.vehicleDirectory  .. "instruments/" .. idef.logic 
      if FS:fileExists(logicPath .. ".lua") then
        --print("trying to require " .. logicPath)
        logicTable = require(logicPath)
        logicTable.components = validComponents
      end
    end
    
    --add to table
    local instrumentTable = {}
    instrumentTable.name = idef.name
    instrumentTable.logic = logicTable
    instrumentTable.components = validComponents
    table.insert(instrumentDefs, instrumentTable)
    
    ::continue::
  end
  
  --init component electrics for prop system
  initElectrics()
  
  --call all logic init functions
  for _, instrument in ipairs(instrumentDefs) do
    if instrument.logic ~= nil then
      if instrument.logic.onInit ~= nil then instrument.logic.onInit() end
    end
  end
end

--update stuff
local function updateComponent(component, dt)
  --get current value and scale it
  local cValue = electrics.values[component.input_electric]
  if cValue == nil then return end --input_electric has no value
  cValue = cValue * component.input_scale
  
  if component.looped then
    local limitTo = component.intervals[#component.intervals]
    while cValue > limitTo do
      cValue = cValue - limitTo
    end
  end
  
  --get target output value
  local targetOutputValue = 0
  local interpStart = nil
  local interpEnd = nil
  
  --loop through the range and find where to interpolate between
  for i = 1, #component.intervals, 1 do
    local interval = component.intervals[i]
    local cvTarget = interval[1]
    if cValue >= cvTarget then
      interpStart = component.intervals[i]
      interpEnd = component.intervals[i + 1]
    end
  end

  --not in range
  if interpEnd == nil and interpStart == nil then
    return
  end
  
  --our output
  local targetOutputPercentage = 0
  
  if interpStart ~= nil and interpEnd ~= nil then
    --in range
    --name our table vars
    local isp = interpStart[2]
    local isv = interpStart[1]
    
    local iep = interpEnd[2]
    local iev = interpEnd[1]
    
    --deal with them
    local localizedCv = cValue - isv
    local localizedIev = iev - isv
    
    local intervalAmount = localizedCv / localizedIev
    targetOutputPercentage = isp + (iep - isp) * intervalAmount
  elseif interpStart ~= nil and interpEnd == nil then
    --at end of range
    targetOutputPercentage = interpStart[2]
  end

  --smooth if needed, then output
  if component.smoothing > 0 then
    targetOutputPercentage  = component.smoother:get(targetOutputPercentage)
  end
  electrics.values[component.output_electric]  = targetOutputPercentage * component.output_scale
end

local function updateInstrument(inst, dt)
  if inst.logic ~= nil then
    if inst.logic.updateGFX ~= nil then inst.logic.updateGFX(dt) end
  end
  for _, component in pairs(inst.components) do
    updateComponent(component, dt)
  end
end

local function updateGFX(dt)
  for _, instrument in ipairs(instrumentDefs) do
    updateInstrument(instrument, dt)
  end
end

M.init = init
M.updateGFX = updateGFX

return M