--
-- Error Redirect version 2 with GUI
--
-- by Thomas Watts
-- Original Error Redirect by Bastian Pflieger <wb@illogical.de>
-- Credits: Idea by Bunny, compiling original default filter lists
--
-- supports "myAddOns": http://www.curse.com/downloads/details/207/
--

--need localization
if( not MessageRedirectLocalization ) then
	local err = geterrorhandler();
	err("MessageRedirect not found.  Options pane not loaded.");
	return;
end

-- Defined in both files
local EXCEPTIONS_PER_PAGE = 8;
local MAX_EXCEPTION_PAGES = 4;
local MaximumExceptions = EXCEPTIONS_PER_PAGE * MAX_EXCEPTION_PAGES;

--LibSharedMedia3 was requested, LSM2 for fallback
--moved to MessageRedirect.LSM

ER_Exceptions = nil; --global
local LastMessage = nil;
local DefaultColor = MessageRedirectLocalization.DefaultColor;
local DefaultFrame = DEFAULT_CHAT_FRAME:GetName();
local DEFAULT_SETTINGS = {
	Red = {
		Enabled = true,
		Frame = DefaultFrame,
		Color = DefaultColor,
	},
	Yellow = {
		Enabled = true,
		Frame = DefaultFrame,
		Color = "yellow",
	},
	LuaErrors = {
		Enabled = nil,
		Frame = DefaultFrame,
		Color = DefaultColor,
	},
	Overflow = {
		Enabled = true,
		Time = 2,
	},
	Exceptions = {
		Length = 0,
	},
};


local function ErrorRedirect_BinarySearch(tFind)
	if( not ER_Exceptions ) then
		return -1;
	end
	local start, last = 1, ER_Exceptions.Length;
	local curr, cStr = math.ceil((last + start) / 2), nil;
  
	while(start <= last) do
		cStr = ER_Exceptions[curr];
		if( not cStr and curr ~= last ) then
			cStr = ER_Exceptions[curr + 1];
		end
		if( not cStr ) then
			local errH = geterrorhandler();
			errH("BinarySearch failed on '"..tFind.."' Fixed filter "..(curr + 1).." does not exist.");
			return -1;
		end
		cStr = cStr["String"];
		if( cStr == tFind ) then		return curr;
		elseif( tFind > cStr ) then		start = curr + 1;
		else							last = curr - 1;
		end
		curr = math.ceil((last + start) / 2);
	end
	return curr;
end

local function ErrorRedirect_TableRemove(index)
	if( not index ) then
		local errH = geterrorhandler();
		errH("ErrorRedirect_TableRemove bad argument #2, nil passed, integer or string expected");
		return;
	end

	if( type(index) == "string" ) then
		local axed = 0;
		local len = MessageRedirect.MessageLength;
		for i = len, 1, -1 do
			if( MessageRedirect.MessageList[i][1] == index ) then
				table.remove(MessageRedirect.MessageList, i);
				axed = axed + 1;
			end
		end
		--Bad way to do the below, as both numbers could possibly be MaximumExceptions
		--but at least one index will be removed, so it's fine
		MessageRedirect.MessageIndex = (len - axed + 1);
		if( MessageRedirect.MessageIndex > MaximumExceptions ) then
			MessageRedirect.MessageIndex = 1;
		end
		MessageRedirect.MessageLength = len - axed;
		return;
	end

	table.remove(ER_Exceptions, index);
	ER_Exceptions.Length = ER_Exceptions.Length - 1;
end

local function ErrorRedirect_RetrieveIndex(ind)
	local iT = tonumber(ind);

	if( iT ) then
		if( iT < 1 or iT > ER_Exceptions.Length ) then -- bounds checkings
			return nil;
		end
		return iT;
	else
		--Searches through all filters for the partial word
		local str = nil;
		for i, ck in ipairs(ER_Exceptions) do
			str = ck.String;
			if( str ~= "" and string.find(str, ind) ) then
				return i;
			end
		end
	end
	return nil; -- this will throw an error
end

local function ErrorRedirect_TableInsert(value)
	if( not value or not value["String"] ) then
		local errH = geterrorhandler();
		errH("ErrorRedirect_TableInsert bad argument #2, "..(value and (value["String"] or "nil passed, string value expected") or "nil passed, table expected"));
		return;
	end
	local pos = ErrorRedirect_BinarySearch(value["String"]);
	local checkFil = ER_Exceptions[ErrorRedirect_RetrieveIndex(pos)];
	if( checkFil and checkFil["String"] == value["String"] ) then
		return;
	end
	table.insert(ER_Exceptions, pos, value);
	ER_Exceptions.Length = ER_Exceptions.Length + 1;
end

local function ErrorRedirect_ParrotAddText(self, msg, r, g, b, a, group, holdTime, font)
	if( not Parrot ) then
		DEFAULT_CHAT_FRAME:AddMessage("Parrot not installed, message caught was:\n"..msg);
		return;
	end
	local sticky = (holdTime > 5) and true or false;
	--font should be font name as determined by LSM:List
	Parrot:ShowMessage(msg, "Notification", sticky, r, g, b, font);
	return;
end

local function ErrorRedirect_SCTAddText(self, msg, r, g, b, a, group, holdTime, font)
	if( not SCT ) then
		DEFAULT_CHAT_FRAME:AddMessage("Scrolling Combat Text not installed, message caught was:\n"..msg);
		return;
	end
	local sticky = (holdTime > 5) and true or false;
	local temp = nil;
	if( font ) then
		temp = SCT.db.profile[SCT.FRAMES_DATA_TABLE][SCT.FRAME1]["FONT"];
		SCT.db.profile[SCT.FRAMES_DATA_TABLE][SCT.FRAME1]["FONT"] = font;
	end
	--font should be font name as determined by LSM:List
	SCT:DisplayText(msg, { r = r, g = g, b = b }, sticky, "event", 1);
	if( font ) then
		SCT.db.profile[SCT.FRAMES_DATA_TABLE][SCT.FRAME1]["FONT"] = temp;
	end
	return;
end
ErrorRedirect_SCT = {
	AddMessage = ErrorRedirect_SCTAddText,
};

local function ErrorRedirect_MikSBTAddText(self, msg, r, g, b, a, group, holdTime, font)
	if( not MikSBT ) then
		DEFAULT_CHAT_FRAME:AddMessage("Mik's Scrolling Battle Text not installed, message caught was:\n"..msg);
		return;
	end
	local sticky = (holdTime > 5) and true or false;
	--font should be font name as determined by MikSBT.IterateFonts
	MikSBT.DisplayMessage(msg, MikSBT.DISPLAYTYPE_NOTIFICATION, sticky, r * 255, g * 255, b * 255, nil, font);
	return;
end

local function ErrorRedirect_CombatText_AddMessage(self, msg, r, g, b, a, group, holdTime, font)
	if( not CombatText_AddMessage ) then
		DEFAULT_CHAT_FRAME:AddMessage("Blizzard FCT not installed, message caught was:\n"..msg);
		return;
	end
	local sticky = (holdTime > 5) and "sticky" or nil;
	--does not support custom fonts directly
	local LSM = MessageRedirect.LSM;
	if( LSM and font ) then
		local str = CombatText_GetAvailableString();
		local ft = LSM:Fetch(LSM.MediaType.FONT, font);
		str:SetFont(ft, 25);
	else
		local str = CombatText_GetAvailableString();
		str:SetFont(STANDARD_TEXT_FONT, 25);
	end
	CombatText_AddMessage(msg, COMBAT_TEXT_SCROLL_FUNCTION, r, g, b, sticky);
	return;
end
ErrorRedirect_CTAM = {
	AddMessage = ErrorRedirect_CombatText_AddMessage,
};

local function ErrorRedirect_FindChatFrames()
	local ltmp1, ltmp2, cfr;

	-- { Frame to redirect to, Selection Label, Supports Fonts }
	MessageRedirect.AvailableFrames =
	{
		{ false, DEFAULT, false }, --follow Blizzard redirect, sound, font, and color rules
		{ MessageRedirectLocalization.discardframe, MessageRedirectLocalization.Discard, false }, --discard messages, may still play sound
		{ MessageRedirectLocalization.defaultframe, MessageRedirectLocalization.NoRedirect, false }, --follow only Blizzard redirect rules
	};
	for i = 1, NUM_CHAT_WINDOWS, 1 do
		cfr = getglobal("ChatFrame"..i);
		if( cfr ) then
			ltmp1 = cfr:GetName();
			ltmp2 = getglobal(ltmp1.."Tab"):GetText();
			if( not ltmp2 or string.len(ltmp2) <= 0 ) then
				ltmp2 = GENERAL;
			end
			table.insert(MessageRedirect.AvailableFrames, { ltmp1, ltmp2, false });
		end
	end
	--attempted support at Parrot addon support (combat text addon)
	--broken as of 2.4
	if( Parrot ) then
		Parrot.AddMessage = ErrorRedirect_ParrotAddText;
		table.insert(MessageRedirect.AvailableFrames, { "Parrot", "Parrot", true });
	end
	--attempted support at Scrolling Combat Text support (combat text addon)
	if( SCT ) then
		table.insert(MessageRedirect.AvailableFrames, { "ErrorRedirect_SCT", "SCT", true });
	else
		ErrorRedirect_SCT = nil;
	end
	--attempted support at Mik's Scrolling Battle Text addon support (combat text addon)
	if( MikSBT ) then
		MikSBT.AddMessage = ErrorRedirect_MikSBTAddText;
		table.insert(MessageRedirect.AvailableFrames, { "MikSBT", "Mik's SBT", false });
	end
	--attempted support at Blizzards Floating Combat Text support (combat text addon)
	if( CombatText_AddMessage ) then
		table.insert(MessageRedirect.AvailableFrames, { "ErrorRedirect_CTAM", "Blizzard FCT", true });
	else
		ErrorRedirect_CTAM = nil;
	end
end

local function ErrorRedirect_DeployOptions(cmd)
	local tmp = getglobal("ErrorRedirectOptions");
	if( tmp ) then
		ShowUIPanel(tmp);
	end
end

--should only use the error redirect frame if it exists and moderrors is enabled
local function ErrorRedirect_ErrorMessage(text)
	local ggl = nil;
	if( ErrorRedirect_Options.LuaErrors.Enabled ) then
		ggl = ErrorRedirect_Options.LuaErrors.Frame or ErrorRedirect_Options.Frame.Red;
		ggl = getglobal(ggl);
		if( ggl ) then
			ggl:AddMessage(text, 1.0, 0.0, 0.0, 1.0, 10, 5);
			return;
		end
	end
	ggl = geterrorhandler();
	ggl(text);
end

local function ErrorRedirect_GetIndividualColors(pColor, r, g, b)
	local colrgb = MessageRedirect.Colors;

	if( not pColor or not colrgb ) then
		return r, g, b;
	end
	colrgb = colrgb[pColor];
	r = colrgb.r;
	g = colrgb.g;
	b = colrgb.b;
	return r, g, b;
end

--Arguments
--	[table] Has Font and Sound keys that are both names
--	[table] default if the first does not have the keys above
--Returns
--	[string] Font name as returned by LSM:List
--	[string] Sound file path as returned by LSM:Fetch
local function ErrorRedirect_GetLSM_Options(filter, default)
	local LSM = MessageRedirect.LSM;
	if( not LSM ) then
		return nil, nil;
	end
	local font = filter.Font or default.Font;
	local sound = filter.Sound or default.Sound;
	if( not font or not LSM:IsValid(LSM.MediaType.FONT, font) ) then
		font = nil;
	end
	if( not sound or not LSM:IsValid(LSM.MediaType.SOUND, sound) ) then
		sound = nil;
	else
		sound = LSM:Fetch(LSM.MediaType.SOUND, sound);
	end
	return font, sound;
end

local function ErrorRedirect_MatchesException(msg, ignoreDisabled)
	local curr = ErrorRedirect_BinarySearch(msg);
	curr = ErrorRedirect_RetrieveIndex(curr);
	if( not curr ) then
		return nil;
	end
	local filter = ER_Exceptions[curr];
	if( (filter.Enabled or ignoreDisabled) and filter.String == msg ) then
		return filter;
	end
	return nil;
end

local function ErrorRedirect_PossibleNewException(msg, mtype)
	--Every time an error occurs it will run through the list which is currently only max 32 long
	local len = MessageRedirect.MessageLength;
	for i = len, 1, -1 do
		if( MessageRedirect.MessageList[i][1] == msg ) then
			return;
		end
	end
	local lenOver = MessageRedirect.MessageIndex;
	MessageRedirect.MessageList[lenOver] = { msg, mtype };
	if( len < lenOver ) then
		MessageRedirect.MessageLength = lenOver;
	end
	lenOver = lenOver + 1;
	if( lenOver > MaximumExceptions ) then
		lenOver = 1;
	end
	MessageRedirect.MessageIndex = lenOver;
	if( ErrorRedirectOptions_CheckVisible ) then
		ErrorRedirectOptions_CheckVisible();
	end
end

local function DevNull_AddMessage(self, msg, r, g, b, a, group, holdTime, mtype)
	return;
end

local function ErrorRedirect_AddMessage(msg, r, g, b, a, group, holdTime, mtype)
	if( not msg ) then
		return;
	end
	r = r or 1.0;	g = g or 0.1;	b = b or 0.1;	a = a or 1.0;
	group = group or 10;	holdTime = holdTime or 5;
	mtype = mtype or "Red";
	local mt = ErrorRedirect_Options[mtype];
	--if the 'mtype' group is disabled then no options are used
	if( mt and mt.Enabled ) then
		local arr = ErrorRedirect_MatchesException(msg, false) or mt;
		if( arr == mt ) then
			ErrorRedirect_PossibleNewException(msg, string.lower(mtype));
		end
		local ggl = arr.Frame or mt.Frame;
		LastMessage = msg;
		--if( ggl == MessageRedirectLocalization.discardframe ) then
			--return;
		--end
		r, g, b = ErrorRedirect_GetIndividualColors(arr.Color, r, g, b);
		local font, sound = ErrorRedirect_GetLSM_Options(arr, mt);
		if( sound ) then
			PlaySoundFile(sound);
		end
		holdTime = (arr.Stickied and 10) or holdTime;
		ggl = getglobal(ggl);
		if( ggl ) then
			--the following is possible because the 7th argument is dropped if not supported
			ggl:AddMessage(msg, r, g, b, a, group, holdTime, font);
			return;
		end
	end
	UIErrorsFrame:AddMessage(msg, r, g, b, a, group, holdTime);
end

local function TTW_CopyTable(tbl)
	if( type(tbl) ~= "table" ) then --should only reach this possibly the first call, otherwise tbl will always be a table
		return tbl;
	end
	local temp = {};
	for key, value in pairs(tbl) do
		if( type(value) ~= "table" ) then
			temp[key] = value;
		else
			temp[key] = TTW_CopyTable(value);
		end
	end
	return temp;
end

local VERSION_RESET = "MessageRedirect:\n  old version %s  new version %s\n  Settings have been reset.  We apologize for the inconvenience.";
local function ErrorRedirect_CheckMessageOptionValidity()
	if( ErrorRedirect_Options.Version ~= MessageRedirectLocalization.Save ) then
		ErrorRedirect_Options.OldSettings = nil;
		local temp = TTW_CopyTable(ErrorRedirect_Options);
		DEFAULT_CHAT_FRAME:AddMessage(string.format(VERSION_RESET, ErrorRedirect_Options.Version or "00000", MessageRedirectLocalization.Save));
		ErrorRedirect_Options = TTW_CopyTable(DEFAULT_SETTINGS);
		ErrorRedirect_Options.OldSettings = temp;
		ErrorRedirect_Options.Version = MessageRedirectLocalization.Save;
	end
	local filter = ErrorRedirect_Options.Red;
	local ggl = filter.Frame;
	if( filter.Enabled and not getglobal(ggl) ) then
		DEFAULT_CHAT_FRAME:AddMessage("Red redirect has been reset.");
		ErrorRedirect_Options.Red.Frame = MessageRedirectLocalization.defaultframe;
	end
	filter = ErrorRedirect_Options.Yellow;
	ggl = filter.Frame;
	if( filter.Enabled and not getglobal(ggl) ) then
		DEFAULT_CHAT_FRAME:AddMessage("Yellow redirect has been reset.");
		ErrorRedirect_Options.Yellow.Frame = MessageRedirectLocalization.defaultframe;
	end
	ER_Exceptions = ErrorRedirect_Options.Exceptions;
	for i, j in pairs(ER_Exceptions) do
		if( type(j) == "table" and j.Enabled and not getglobal(j.Frame) ) then
			j.Frame = MessageRedirectLocalization.defaultframe;
		end
	end
end

local function ErrorRedirect_RegisterMyAddons()
	if( myAddOnsFrame_Register ) then
		local ERdetails = {
			name = MessageRedirectLocalization.Name,
			version = MessageRedirectLocalization.Version,
			releaseDate = MessageRedirectLocalization.ReleaseDate,
			author = "Thomas Watts",
			website = "http://www.curse.com/downloads/details/3065/",
			category = MYADDONS_CATEGORY_OTHERS,
			optionsframe = "ErrorRedirectOptions"
		};
		local ERhelp = {
			MessageRedirectLocalization.ChatHelp,
		};
		myAddOnsFrame_Register( ERdetails, ERhelp );
	end
end

local function ErrorRedirect_OnUpdate(frame, elapsed)
	frame.timer = frame.timer - elapsed;
	if( frame.timer > 0 ) then
		return;
	end
	LastMessage = nil;
	frame.timer = ErrorRedirect_Options.Overflow.Time or 0;
end

local function ErrorRedirect_ToggleOnUpdate(enable, tm)
	if( enable ) then
		ErrorRedirect_Options.Overflow.Enabled = true;
		ErrorRedirect_Options.Overflow.Time = tm or 2;
		MessageRedirectFrame.timer = tm or 2;
		MessageRedirectFrame:SetScript("OnUpdate", ErrorRedirect_OnUpdate);
	else
		ErrorRedirect_Options.Overflow.Enabled = nil;
		ErrorRedirect_Options.Overflow.Time = tm or nil;
		MessageRedirectFrame.timer = tm or 0;
		MessageRedirectFrame:SetScript("OnUpdate", nil);
	end
end

local function ErrorRedirect_OnEvent(frame, event, arg1, arg2, arg3, arg4)
	--if( event == "SYSMSG" ) then --possibly green messages too?
		--ErrorRedirect_AddMessage(arg1, arg2, arg3, arg4, 1.0, 10, 5, "Yellow");
		--return;
	--end
	if( arg1 ~= nil and ErrorRedirect_Options.Overflow.Enabled and arg1 == LastMessage ) then
		return;
	end
	if( event == "UI_INFO_MESSAGE" ) then
		ErrorRedirect_AddMessage(arg1, 1.0, 1.0, 0.0, 1.0, 10, 5, "Yellow"); --yellow
		return;
	elseif( event == "UI_ERROR_MESSAGE" ) then
		ErrorRedirect_AddMessage(arg1, 1.0, 0.1, 0.1, 1.0, 10, 5, "Red"); --red
		return;
	end
end

--LSM3 takes priority over LSM2
local function DecideBestLSM()
	local temp1 = LibStub("LibSharedMedia-3.0", true);
	local temp2 = LibStub("LibSharedMedia-2.0", true);
	if( not temp1 ) then
		return (temp2 or false);
	end
	if( not temp2 ) then
		return (temp1 or false);
	end
	local t1, t2, t3 = 0, 0, 0;
	t1 = #temp1:List(temp1.MediaType.FONT);
	t2 = #temp1:List(temp1.MediaType.SOUND);
	t3 = t1 + t2;
	t1 = #temp2:List(temp2.MediaType.FONT);
	t2 = #temp2:List(temp2.MediaType.SOUND);
	if( t3 < t1 + t2 ) then
		return temp2;
	end
	return temp1;
end

local function ErrorRedirect_PLAYER_LOGIN(frame, event, arg1, arg2, arg3, arg4)
	local LSM = DecideBestLSM();
	MessageRedirect.LSM = LSM;
	--fired after VARIABLES_LOADED but before PLAYER_ENTERING_WORLD but still only once
	ErrorRedirect_CheckMessageOptionValidity();
	local ero = ErrorRedirect_Options;
	if( ero.LuaErrors.Enabled ) then
		if( geterrorhandler() == _ERRORMESSAGE ) then
			seterrorhandler(ErrorRedirect_ErrorMessage);
		else
			DEFAULT_CHAT_FRAME:AddMessage(MessageRedirectLocalization.FaultyGrab);
		end
	end
	ErrorRedirect_FindChatFrames();
	ErrorRedirect_RegisterMyAddons();
	frame:UnregisterEvent("PLAYER_LOGIN");

	--UIErrorsFrame:UnregisterEvent("SYSMSG");
	UIErrorsFrame:UnregisterEvent("UI_INFO_MESSAGE");
	UIErrorsFrame:UnregisterEvent("UI_ERROR_MESSAGE");
	--Don't need to unregister SYSMSG for UIErrorsFrame
	--UIErrorsFrame:UnregisterAllEvents();
	frame:SetScript("OnEvent", ErrorRedirect_OnEvent);
	--frame:RegisterEvent("SYSMSG");
	frame:RegisterEvent("UI_INFO_MESSAGE");
	frame:RegisterEvent("UI_ERROR_MESSAGE");

	ErrorRedirect_ToggleOnUpdate(ero.Overflow.Enabled, ero.Overflow.Time);
end

local function ErrorRedirect_OnLoad(frame)
	ErrorRedirect_Options = TTW_CopyTable(DEFAULT_SETTINGS);

	frame:SetScript("OnEvent", ErrorRedirect_PLAYER_LOGIN);
	frame:RegisterEvent("PLAYER_LOGIN");
	frame:SetScript("OnLoad", nil);
	MessageRedirect.OnLoad = nil;

	SLASH_ERROR_REDIRECT1 = "/error_redirect";
	SLASH_ERROR_REDIRECT2 = "/err";
	SlashCmdList["ERROR_REDIRECT"] = ErrorRedirect_DeployOptions;
end

DevNull.AddMessage = DevNull_AddMessage;

MessageRedirect = {
	OnUpdate = ErrorRedirect_ToggleOnUpdate,
	OnLoad = ErrorRedirect_OnLoad,
	AddMessage = ErrorRedirect_AddMessage,
	ErrorHandler = ErrorRedirect_ErrorMessage,
	RetrieveIndex = ErrorRedirect_RetrieveIndex,
	TableInsert = ErrorRedirect_TableInsert,
	TableRemove = ErrorRedirect_TableRemove,
	FindChatFrames = ErrorRedirect_FindChatFrames,
	MessageList = {},
	MessageIndex = 1,
	MessageLength = 0,
	Colors = MessageRedirectLocalization.Colors,
	DefaultColor = DefaultColor,
	DefaultFrame = DefaultFrame,
	ComboTextString = string.format("%s", MessageRedirectLocalization.ComboText[4]),
	DecideBestLSM = DecideBestLSM,
	LSM = false,
};