
local me = { name = "error"}
local mod = thismod
mod[me.name] = me

--[[
ErrorLog.lua

The idea is to handle manageable errors inside the mod, and not have them propogated to big red error boxes. Therefore if one module has an error onupdate, the user won't be spammed by an error box, and the rest of the mod will still function.

The errors we can handle are anything that occurs inside normal module's <onevent>, <onupdate>, <onparse> and <onnetmessage> handlers, which is pretty much everything. Errors from command line handlers, in Core.lua or in this file, or from clicking buttons or other form elements aren't handled.

Since we are suppressing the normal error handler, we need to inform the user that something has happened. We have our own message box-like frame, and it will pop up on the first error. After that it won't pop up for an error of the same type. If another different error occurs, it will pop up again, but never after that.
]]

-- Called by Core.lua at startup
me.onload = function()
	
	me.createerrorframe()
		
end

--[[
me.data = { <error1>, <error2>, ... }

<error> = 
{
	module = string, name of a module
	process = "onupdate" or "onevent" or "onparse",
	extradata = (string) printout of args, event, onparse arguments etc
	stacktrace = (string), output of debugstack()
	message = (string) original error message, i.e. contents of normal error box (i think).
	time = (string) output of date() at time of first occurrence.
}
]]
me.data = { }

me.createerrorframe = function()
	
	me.frame = mod.gui.createframe(mod.frame, 400, 150, mod.gui.backdropcolor)
	me.frame:SetPoint("CENTER", UIParent, "CENTER")
	me.frame:Hide()
	
	local offset = mod.helpmenu.textinset
	
	-- Top line
	local element = mod.gui.createfontstring(me.frame, 18, true)
	me.frame.header = element
	
	element:SetJustifyH("LEFT")
	element:SetPoint("TOPLEFT", mod.helpmenu.textinset, -offset)
	
	element:SetWidth(me.frame:GetWidth() - 2 * mod.helpmenu.textinset)
	element:SetText(string.format(mod.string.get("errorbox", "header"), mod.global.name))
	
	element:SetTextColor(1.0, 0.5, 0.5)
	
	offset = offset + element:GetHeight()
	
	-- Details
	offset = offset + 10
	
	element = mod.gui.createfontstring(me.frame, 15, true)
	me.frame.text = element
	
	element:SetJustifyH("LEFT")
	element:SetPoint("TOPLEFT", mod.helpmenu.textinset, -offset)
	
	element:SetWidth(me.frame:GetWidth() - 2 * mod.helpmenu.textinset)
	
	-- up to here
	element:SetText(mod.string.get("errorbox", "header"), mod.global.name)
	
	offset = offset + element:GetHeight()
	
	-- Close Button
	offset = offset + 10
	
	element = mod.gui.createbutton(me.frame, 150, 40, mod.helpmenu.background.button, 15)
	me.frame.button = element
	
	element:SetPoint("BOTTOMLEFT", mod.helpmenu.textinset, mod.helpmenu.textinset)
	element:SetText(mod.string.get("raidtable", "tooltip", "close"))
	
	offset = offset + element:GetHeight()
	
	-- click event
	element:SetScript("OnClick", 
		function()
			me.frame:Hide()
		end
	)
	
	-- finalise
	me.frame:SetHeight(mod.helpmenu.textinset * 2 + offset)
	
end

--[[
me.extradataproviders

This is a collection of functions that gather important extra data for an error log. The extra data depends on the process where the error occurred. If it was an OnEvent handler, we want to know what <event> was, as well as <arg1>, etc if they are defined.

The keys in the table are the values of <error.process>, and the values are functions that take an unknown number of arguments. They will be passed any extra arguments sent to the <me.reporterror> function after the normal ones.
]]
me.extradataproviders = 
{
	-- OnEvent: extra data is the event and non-nil arg1, arg2, etc
	onevent = function(...)
		
		local extradata = event .. " { "
		local data
		
		for x = 1, 9 do
			data = getglobal("arg" .. x)
			
			if data then
				extradata = extradata  .. data .. ", "
			else
				break
			end
		end
		
		extradata = extradata .. "}"
		
		return extradata
	end,
	
	-- OnParse: extra data is the identifier and any arguments, also arg1
	onparse = function(...)
		
		local extradata = select(1, ...) .. " - { "
		local data
		
		for x = 2, select("#", ...) do
			data = select(x, ...)
			
			extradata = extradata .. tostring(data) .. ", "
		end
		
		extradata = extradata .. "} - " .. tostring(arg1)
		
		return extradata
	end,
	
	-- OnNetMessage: extra data is the command and any data (NOT Author)
	onnetmessage = function(...)
		
		local extradata = select(1, ...)
		
		local data = select(2, ...)
		
		if data then
			extradata = extradata .. " - " .. data
		end
		
		return extradata
		
	end
}

--[[
mod.log.reporterror(module, process, message, ...)

Reports an error in a module's handler for one of the addon's services.

<module>		string; name of the module (the .name property, if you have the module itself)
<process>	string; method where the error occurred, e.g. "onevent" or "onparse"
<message>	string; original error message (return value of pcall)
...			extra data identifying arguments to <process> or other important notes 
]]
me.reporterror = function(module, process, message, ...)
		
	-- ignore duplicate errors
	if me.checkduplicate(module, process) then
		return
	end
	
	-- look for extra data
	local extradata
	
	if me.extradataproviders[process] then
		extradata = me.extradataproviders[process](...)
	end
	
	-- add it to data
	local newerror = 
	{
		module = module,
		process = process,
		extradata = extradata,
		message = message,
		stacktrace = debugstack(),
		time = date(),
	}
	
	table.insert(me.data, newerror)
	
	if #me.data == 1 then
		me.reportfirsterror()
	
	elseif #me.data == 2 then
		me.reportseconderror()
		
	end
		
end

--[[
me.checkduplicate(module, process)

Checks whether an error of this type has already been logged.

module = string, name of a module
process = "onevent" or "onupdate" or "onparse"

Return: true if this is a duplicate, nil otherwise.
]]
me.checkduplicate = function(module, process)
	
	for _, value in ipairs(me.data) do
		if value.module == module  and value.process == process then
			return true
		end
	end
	
end

--[[
me.reportfirsterror()

Brings up a dialogue saying that an error happened, and to check the error log.
]]
me.reportfirsterror = function()
	
	local thiserror = me.data[#me.data]
	
	local startheight = me.frame.text:GetHeight()
	
	me.frame.text:SetText(string.format(mod.string.get("errorbox", "body"), "|cffffff00" .. thiserror.module .. "." .. thiserror.process .. "|r") .. mod.string.get("errorbox", "first"))
	
	local heightchange = me.frame.text:GetHeight() - startheight
	
	me.frame:SetHeight(me.frame:GetHeight() + heightchange)
	me.frame:Show()
	
end

--[[
me.reportseconderror()

Brings up a dialogue saying that another new error has occurred, and that no more error messages will appear.
]]
me.reportseconderror = function()
	
	local thiserror = me.data[#me.data]
	
	local startheight = me.frame.text:GetHeight()
	
	me.frame.text:SetText(string.format(mod.string.get("errorbox", "body"), "|cffffff00" .. thiserror.module .. "." .. thiserror.process .. "|r") .. mod.string.get("errorbox", "second"))
	
	local heightchange = me.frame.text:GetHeight() - startheight
	
	me.frame:SetHeight(me.frame:GetHeight() + heightchange)
	me.frame:Show()
	
end