local json = require("json")

function GetVersion()
  return 2
end

function GetCommands()
  return json.encode(
    {
      {
        type = "api",
        data = json.encode({command = "getdeviceconfig", ipc = true}),
        func = "HandleGetDeviceConfig"
      },
      {
        type = "function",
        func = "ReadConfigFromFile"
      },
      {
        type = "function",
        func = "GetUptime"
      },
      {
        type = "function",
        func = "GetOpenVpnConfig"
      },
      {
        type = "api",
        data = json.encode({command = "UscGetRaInstallerAccess"}),
        func = "HandleInstallerAccess"
      },
      {
        -- ID 1850 is the Recording-App channel, IDs 1854 to 1856 are Database-Max-Size, Database-Used-Size and Diagram-Count
        type = "api",
        data = json.encode({command = "GetValues", values = {{id = 1854}, {id = 1855}, {id = 1856}}}),
        func = "HandleGetRecordingValues"
      }
    }
  )
end

function HandleGetDeviceConfig(response)
  local j = json.decode(response)
  local devcfg = GetFromTable(j, "response", "deviceConfig", "ipc")
  if devcfg
  then
    local fwv = GetFromTable(devcfg, "CurrentFirmwareVersion")
    local manu = GetFromTable(devcfg, "ManufacturerId")
    local device = GetFromTable(devcfg, "DeviceId")
    return json.encode({firmwareVersion = fwv, model = (manu .. device)})
  end

  return "Invalid response for GetDeviceConfig: " .. response
end

function ReadConfigFromFile()
  local file = io.open("/var/tmp/usc/AppPrgData.json", "r")
  if file == nil then
    -- file does not exist, by default everything is disabled
    -- {"appLoaded":false,"datalogger":{"enabled":false},"notifications":{"enabled":false},"recordings":{"enabled":false}}
    return json.encode({ra = {appLoaded = false, datalogger = {enabled = false}, notifications = {enabled = false}, recordings = {enabled = false}}})
  end

  local data = file:read("*all")
  file:close()

  -- check if data is valid json
  local ok, jsonData = pcall(json.decode, data)
  if ok then
    return json.encode({ra = jsonData})
  end

  return "Config contains invalid json: " .. data
end

function GetUptime()
  local file = assert(io.open("/proc/uptime"))
  local line = file:read("*line")
  file:close()

  local uptime = assert(tonumber(string.match(line, "%d+")))
  local hours = math.floor(uptime / 3600)
  local minutes = math.floor((uptime - hours * 3600) / 60)
  local seconds = uptime - hours * 3600 - minutes * 60

  return json.encode({uptime = string.format("%02d:%02d:%02d", hours, minutes, seconds)})
end

function GetOpenVpnConfig()
  local scriptfile = "/etc/init.d/S60openvpn"

  local enabled = RunWithCallback(scriptfile, "status",
    function(handle)
      -- script returns "(enabled|disabled) / (running|stopped)"
      local line = handle:read("*line")
      local enabled = line:find("enabled", 1, true) ~= nil
      return enabled
    end)

  local mode = not enabled and "unconfigured" or RunWithCallback(scriptfile, "show_config",
    function(handle)
      for line in handle:lines() do
        local matched = line:match("Configured in (%a+) mode")
        if matched then
          return matched
        end
      end
      return nil
    end)

  return json.encode({ra = {openvpn = {enabled = enabled, mode = mode}}})
end

function HandleInstallerAccess(response)
  local j = json.decode(response)
  local timePoint = GetFromTable(j, "response", "lastUsedTimePoint") or ""

  return json.encode({ra = {sda = {installerAccessLastUsedTimePoint = timePoint}}})
end

function HandleGetRecordingValues(response)
  local j = json.decode(response)
  local values = GetFromTable(j, "response", "values")
  if #values ~= 3 then
    return "GetValues did not return 3 values."
  end

  local maxSize = tonumber(GetFromTable(values, 1, "value")) or 0
  local usedSize = tonumber(GetFromTable(values, 2, "value")) or 0
  local diagramCount = tonumber(GetFromTable(values, 3, "value")) or 0

  return json.encode({ra = {recordings = {databaseMaxSize = maxSize, databaseUsedSize = usedSize, diagramCount = diagramCount}}})
end


-- helper functions

function GetFromTable(tbl, key, ...)
  return not key and tbl or tbl[key] and GetFromTable(tbl[key], ...)
end

function RunWithCallback(script, args, callback)
  do
    local file = io.open(script)
    assert(file, "File does not exist: " .. script)
    file:close()
  end

  assert(type(callback) == "function", "callback is not a function")
  local handle = assert(io.popen(string.format("%s %s", script, args)))
  -- capture variable number of output values from callback in a table, unpack in return
  local out = table.pack(callback(handle))
  handle:close()

  return table.unpack(out)
end

function toboolean(val)
  if type(val) == "boolean" then
    return val
  end
  if type(val) == "string" then
    return val:lower() == "true"
  end
  return false;
end
