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

local loadmods = require('aconf.loader')
local topology = require('aconf.model.root').topology
local object = require('aconf.object')
local pth = require('aconf.path')

local util = require('aconf.util')
local contains = util.contains
local copy = util.copy
local setdefault = util.setdefault

local stringy = require('stringy')


local DataStore = object.class(
   require('aconf.transaction.base').TransactionBackend
)

function DataStore:init()
   object.super(self, DataStore):init()
   self.backends = util.map(
      function(m) return m() end,
      loadmods('persistence/backends')
   )
   self.triggers = {pre={}, post={}}
   self:flush_cache()
end

function DataStore:flush_cache() self.cache = {} end

function DataStore:trigger(phase, path, func)
   local funcs = setdefault(self.triggers[phase], path, {})
   if not contains(funcs, func) then table.insert(funcs, func) end
end

function DataStore:split_path(path)
   local comps = pth.split(path)
   local backend = self.backends[comps[1]]
   assert(backend)
   table.remove(comps, 1)
   return backend, comps
end

function DataStore:get(path)
   if not self.cache[path] then

      local backend, comps = self:split_path(path)
      local top = topology(path)

      local value, ts = backend:get(comps, top)

      if top then
	 local t = top.type
	 if t and value ~= nil then
	    local atype = type(value)

	    if t == 'table' then assert(atype == 'table')

	    else
	       assert(atype ~= 'table')

	       if t == 'string' then value = tostring(value)
	       elseif t == 'number' then value = tonumber(value)

	       elseif t == 'boolean' then
		  if atype == 'string' then value = value:lower() end
		  if value == 1 or contains(
		     {'1', 't', 'true', 'y', 'yes'}, value
                  ) then
		     value = true
		  elseif value == 0 or contains(
		     {'0', 'f', 'false', 'n', 'no'}, value
	          ) then
		     value = false
		  else value = value and true or false end

	       elseif contains({'binary', 'reference'}, t) then
		  assert(atype == 'string')

	       else assert(false) end
	    end
	 end
      end

      self.cache[path] = {copy(value), ts or self.mod_time[path] or 0}
   end

   local value, ts = table.unpack(self.cache[path])
   return copy(value), ts
end

function DataStore:_set_multiple(mods)
   self:flush_cache()

   local bms = {}
   local trigger = {}
   
   for _, mod in ipairs(mods) do
      local path, value = table.unpack(mod)

      local tp = path
      while not trigger[tp] do
	 trigger[tp] = true
	 tp = pth.parent(tp)
      end

      local backend, comps = self:split_path(path)
      table.insert(setdefault(bms, backend, {}), {comps, value})
   end

   local function exec_triggers(phase)
      for path, _ in pairs(trigger) do
	 for _, func in ipairs(self.triggers[phase][path] or {}) do func() end
      end
   end

   exec_triggers('pre')
   for backend, bm in pairs(bms) do backend:set(bm) end
   exec_triggers('post')
end


return DataStore()
