-- openwisp uci utils
local io = require('io')
local lfs = require('lfs')

local utils = {}

function utils.starts_with_dot(str) return str:sub(1, 1) == '.' end

function utils.split(input, sep)
  if input == '' or input == nil then return {} end
  if sep == nil then sep = '%s' end
  local t = {};
  local i = 1
  for str in string.gmatch(input, '([^' .. sep .. ']+)') do
    t[i] = str
    i = i + 1
  end
  return t
end

function utils.basename(path)
  local parts = utils.split(path, '/')
  return parts[table.getn(parts)]
end

function utils.dirname(path)
  local parts = utils.split(path, '/')
  local returnPath = '/'
  local length = table.getn(parts)
  for i, part in ipairs(parts) do
    if i < length then returnPath = returnPath .. part .. '/' end
  end
  return returnPath
end

function utils.add_values_to_set(set, values)
  for _, el in pairs(values) do set[el] = true end
  return set
end

-- writes uci section, eg:
--
-- utils.write_uci_section(cursor, 'network', {
--     [".name"] = "wan",
--     [".type"] = "interface",
--     [".anonymous"] = false,
--     proto = "none",
--     ifname = "eth0.2",
-- })
--
-- will write to "network" the following:
--
--     config interface 'wan'
--         option proto 'none'
--         option ifname 'eth0.2'
--
function utils.write_uci_section(cursor, config, section, merge_list)
  local name
  -- add named section
  if not section['.anonymous'] then
    name = section['.name']
    cursor:set(config, name, section['.type'])
    -- add anonymous section
  else
    name = cursor:add(config, section['.type'])
  end
  -- write options for section
  for key, value in utils.sorted_pairs(section) do
    utils.write_uci_option(cursor, config, name, key, value, merge_list)
  end
end

-- abstraction for "uci set" which handles corner cases
function utils.write_uci_option(cursor, config, name, key, value, merge_list)
  -- ignore properties starting with .
  if utils.starts_with_dot(key) then return end
  -- avoid duplicate list settings
  if type(value) == 'table' and merge_list then
    -- create set with unique values
    local set = {}
    -- read existing value
    local current = cursor:get(config, name, key)
    if type(current) == 'table' then set = utils.add_values_to_set(set, current) end
    set = utils.add_values_to_set(set, value)
    -- reset value var with set contents
    value = {}
    for item_value, present in pairs(set) do table.insert(value, item_value) end
  end
  cursor:set(config, name, key, value)
end

-- returns true if uci section is empty
function utils.is_uci_empty(table)
  for key, value in pairs(table) do
    if not utils.starts_with_dot(key) then return false end
  end
  return true
end

-- removes uci options
-- and removes section if empty
-- this is the inverse operation of `write_uci_section`
function utils.remove_uci_options(cursor, config, section)
  local name = section['.name']
  -- loop over keys in section and
  -- remove each one from cursor
  for key, value in pairs(section) do
    if not utils.starts_with_dot(key) then cursor:delete(config, name, key) end
  end
  -- remove entire section if empty
  local uci = cursor:get_all(config, name)
  if uci and utils.is_uci_empty(uci) then cursor:delete(config, name) end
end

-- returns true if a table is empty
function utils.is_table_empty(t)
  return not t or next(t) == nil
end

-- Code by David Kastrup
-- http://lua-users.org/wiki/DirTreeIterator
function utils.dirtree(dir_param)
  assert(dir_param and dir_param ~= '', 'directory parameter is missing or empty')
  if string.sub(dir_param, -1) == '/' then dir_param = string.sub(dir_param, 1, -2) end
  local function yieldtree(dir)
    for entry in lfs.dir(dir) do
      if entry ~= '.' and entry ~= '..' then
        entry = dir .. '/' .. entry
        local attr = lfs.attributes(entry)
        coroutine.yield(entry, attr)
        if attr.mode == 'directory' then yieldtree(entry) end
      end
    end
  end
  return coroutine.wrap(function() yieldtree(dir_param) end)
end

function utils.file_exists(path)
  local f = io.open(path, 'r')
  if f ~= nil then
    io.close(f)
    return true
  end
  return false
end

function utils.file_to_set(path)
  local f = io.open(path, 'r')
  local set = {}
  for line in f:lines() do set[line] = true end
  return set
end

function utils.set_to_file(set, path)
  local f = io.open(path, 'w')
  for file, bool in pairs(set) do f:write(file, '\n') end
  io.close(f)
  return true
end

function utils.starts_with(str, start) return str:sub(1, #start) == start end

-- iterates over a table in alphabeticaly order
function utils.sorted_pairs(t)
  -- collect keys
  local keys = {}
  for key in pairs(t) do keys[#keys + 1] = key end

  table.sort(keys)

  -- return the iterator function
  local i = 0
  return function()
    i = i + 1
    if keys[i] then return keys[i], t[keys[i]] end
  end
end

return utils
