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

--- @module aconf.model
local M = {}

local model = require('aconf.model.model')
local node = require('aconf.model.node')
local object = require('aconf.object')
local pth = require('aconf.path')

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


M.RootModel = model.new()

function M.RootModel:init(context)
   object.super(self, M.RootModel):init(
      util.update(context, {path='/', addr='/null'})
   )
end

function M.RootModel:has_permission(user, permission)
   return permission == 'read'
end

function M.RootModel:meta(path)
   local obj = self:fetch(path, true)
   if object.isinstance(obj, node.TreeNode) then return node.meta(obj) end
   return node.mmeta(self:fetch(pth.parent(path), true), pth.name(path))
end


local order = 0
M.topology = setmetatable(
   {},
   {
      __call=function(self, addr, create)
	 util.setdefaults(self, {members={}, paths={}, referrers={}})

	 if type(addr) == 'table' then addr = util.copy(addr)
	 else addr = pth.split(addr) end
	 if not addr[1] then return self end

	 local comp = pth.escape(addr[1])
	 local top
	 if create then
	    top = setdefault(self.members, comp, {order=order})
	    order = order + 1
	 else
	    top = self.members[comp] or self.members['*']
	    if not top then return end
	 end
	 table.remove(addr, 1)

	 return setmetatable(top, getmetatable(self))(addr, create)
      end
   }
)

--- inject a new field to the root model.
-- @tparam string name name of the field
-- @param field (<i>[&lt;Field&gt;](#Overview_of_field_classes)</i> or
-- <i>[&lt;Model&gt;](#new)</i>) type of the field, specified by a
-- field class or instance. Can also be specified by a model.
-- @tparam ?{[string]=any,...} params field parameters. Applicable
-- when the type of the field is specified by a model or a field
-- class.
function M.register(name, field, params)
   if not params then params = {} end
   params.create = true
   M.RootModel[name] = model.to_field(field, params)

   local root = M.RootModel{}

   for _, record in ipairs(node.topology(root:fetch(name))) do
      local top = M.topology(record.addr, true)

      local function set(k, v, t)
	 if not t then t = top end
	 setdefault(t, k, v)
	 assert(t[k] == v)
      end

      set('type', record.type)
      set('subtype', record.subtype)
      table.insert(top.paths, record.path)

      local mode = record.be_mode
      if mode then
	 if type(mode) == 'string' then mode = {['.']=mode} end
	 for addr, m in pairs(mode) do
	    set('mode', m, addr == '.' and top or top(addr, true))
	 end
      end

      if record.scope then
	 local scope = node.addr(
	    root:fetch(pth.to_absolute(record.scope, pth.parent(record.path)))
	 )
	 set('scope', scope)
	 table.insert(
	    M.topology(pth.rawjoin(scope, record.search), true).referrers,
	    record.path
	 )
      end
   end
end


return M
