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

local M = {}

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

local util = require('acf2.util')

local json = require('cjson')


local ErrorTable = class()

function ErrorTable:init() self.errors = {} end

function ErrorTable:success() return not next(self.errors) end

function ErrorTable:raise()
   if not self:success() then error(json.encode(self.errors)) end
end


local ErrorList = class(ErrorTable)

function ErrorList:init(label)
   object.super(self, ErrorList):init()
   self.label = label
end

function ErrorList:insert(msg)
   table.insert(util.setdefault(self.errors, self.label, {}), msg)
end


M.ErrorDict = class(ErrorTable)

function M.ErrorDict:collect(func, ...)
   local function pack(success, ...)
      local arg = {...}
      return success, success and arg or arg[1]
   end

   local arg = {...}
   local success, res = pack(
      xpcall(
	 function() return func(unpack(arg)) end,
	 function(err)
	    local _, _, data = err:find('.-: (.+)')
	    local success, res = pcall(json.decode, data)
	    if success and type(res) == 'table' then return res end
	    return data..'\n'..debug.traceback()
	 end
      )
   )

   if success then return unpack(res) end

   if type(res) == 'table' then
      for label, errors in pairs(res) do
	 for _, err in ipairs(errors) do
	    table.insert(util.setdefault(self.errors, label, {}), err)
	 end
      end
   else error(res) end
end


function M.raise(label, msg)
   local err = ErrorList(label)
   err:insert(msg)
   err:raise()
end

function M.relabel(label, ...)
   local err = M.ErrorDict()
   local res = {err:collect(...)}
   if err:success() then return unpack(res) end

   elist = ErrorList(label)
   for lbl, el in pairs(err.errors) do
      for _, e in ipairs(el) do elist:insert(lbl..': '..e) end
   end
   elist:raise()
end

function M.call(...)
   local err = M.ErrorDict()
   local res = {err:collect(...)}
   if err:success() then return true, unpack(res) end
   if err.errors.system then error(err.errors.system[1]) end
   return false, err.errors
end


return M
