--[[
Copyright (c) 2012-2013 Kaarle Ritvanen
See LICENSE file for license details
--]]

local topology = require('acf2.model.root').topology
local pth = require('acf2.path')

local util = require('acf2.util')
local copy = util.copy


local function aug_path(path) return pth.join('/files', unpack(path)) end

local function strip_name(name)
   return type(name) == 'string' and name:match('^[^][/=)%s]+') or name
end

local function ipath(path, index) return path..'['..index..']' end


local backend = require('acf2.object').class()

function backend:init() self.aug = require('augeas').init() end

function backend:find(path, leaf)
   util.map(
      function(comp)
	 assert(
	    comp == strip_name(comp) and (
	       type(comp) == 'number' or not comp:match('^%.+$')
	    )
	 )
      end,
      path
   )
   local res = aug_path(path)

   if #self.aug:match(res) == 0 and #path > 1 and leaf then
      local index = path[#path]
      if type(index) == 'number' then
	 local ppath = copy(path)
	 table.remove(ppath)
	 ppath = aug_path(ppath)

	 if #self.aug:match(ppath) > 0 and #self.aug:match(
	    ppath..'/*'
	 ) == 0 then
	    return ipath(ppath, index), ppath, index
	 end
      end
   end

   return res
end

function backend:get(path, top)
   local tpe = top and top.type
   local leaf = tpe and tpe ~= 'table'
   local apath, mvpath = self:find(path, leaf or not tpe)

   local matches = self.aug:match(apath)
   if mvpath then
      assert(#matches < 2)
      leaf = true
   end

   if #matches == 0 then return end

   if #matches > 1 then
      assert(not leaf)
      local res = {}
      path = copy(path)
      for i, _ in ipairs(matches) do
	 table.insert(path, i)
	 if self:get(path) then table.insert(res, i) end
	 table.remove(path)
      end
      return res
   end

   local value = self.aug:get(matches[1])
   if value then return tpe == 'table' and {1} or value end
   if leaf then return end

   local names = {}
   for _, child in ipairs(self.aug:match(apath..'/*')) do
      names[strip_name(pth.name(child))] = true
   end
   return util.keys(names)
end

function backend:set(mods)
   local gcpaths = {}

   for _, mod in ipairs(mods) do
      local path, value = unpack(mod)

      local delete = value == nil
      self.aug:rm(aug_path(path)..(delete and '' or '/*'))

      local apath, mvpath, index = self:find(path, type(value) ~= 'table')
      local mpath = mvpath or apath

      if not delete then
	 if #self.aug:match(mpath) == 0 then

	    local function order(path)
	       return topology('/augeas'..path).order
	    end
	    local ord = order(pth.join('/', unpack(path)))

	    for _, sibling in ipairs(self.aug:match(pth.parent(mpath)..'/*')) do
	       if order(strip_name(sibling):sub(7, -1)) > ord then
		  self.aug:insert(sibling, pth.name(mpath), true)
		  break
	       end
	    end
	 end

	 if mvpath then
	    local size = #self.aug:match(mvpath)
	    while size < index do
	       self.aug:insert(ipath(mvpath, size), pth.name(mvpath))
	       size = size + 1
	    end
	 end
      end

      if type(value) == 'table' then value = nil end
      if not delete or mvpath then self.aug:set(apath, value)
      elseif apath > '/' then apath = pth.parent(apath) end

      if delete or value == '' then gcpaths[mpath] = true end
   end

   local function gc(path)
      if path == '/' or #self.aug:match(path..'/*') > 0 then return end
      if self.aug:rm(path.."[. = '']") > 0 then gc(pth.parent(path)) end
   end
   for p, _ in pairs(gcpaths) do gc(p) end

   if self.aug:save() ~= 0 then
      print('Augeas save failed')
      for _, ep in ipairs(self.aug:match('/augeas//error')) do
	 print(ep, self.aug:get(ep))
      end
      assert(false)
   end
end


return backend
