
local L = AceLibrary("AceLocale-2.2");

HeyFu = AceLibrary("AceAddon-2.0"):new("FuBarPlugin-2.0", "AceDB-2.0", "AceDebug-2.0", "AceConsole-2.0")
local dewdrop = AceLibrary("Dewdrop-2.0");
local tablet = AceLibrary("Tablet-2.0");
HeyFu.History = {};
HeyFu.hasNoneIcon = "Interface\\Icons\\Spell_Shadow_PsychicScream";
HeyFu.hasChatIcon = "Interface\\Icons\\Ability_Physical_Taunt";
HeyFu.hasSecIcon = "Interface\\Icons\\Spell_Shadow_SoothingKiss";
HeyFu.hasIcon = HeyFu.hasNoneIcon;
HeyFu.Unread = 0;

StaticPopupDialogs["HEYFUCONFIRM"] = { 
				text = "Clear the Archive?", 
				button1 = "Yea", 
				button2 = "Nay", 
				timeout = 0, 
				whileDead = 1, 
				OnAccept = function() HeyFu.db.char.Conversations = {} dewdrop:Refresh(1) end
			}
local History = HeyFu.History;
local TimePeriod = { Week = 7*24*60*60, Day = 24*60*60, Hour = 60*60, Recent = 60*15 }
local PreviousPeriod = {Week = "Day", Day = "Hour", Hour = "Recent"}

AceLibrary("AceEvent-2.0"):embed(History);
AceLibrary("AceDebug-2.0"):embed(History);
AceLibrary("AceHook-2.1"):embed(History);

function HeyFu:OnInitialize()
	self:SetDebugging(true);
	self.version = HeyFu.version..".".. string.sub("$Revision: 45346 $", 12, -3);
	self:RegisterDB("HeyFuDB", "HeyFuCharDB" );
	self:RegisterDefaults('realm', {NameList = {}});
	self:RegisterDefaults('char', {
				Conversations = {}, 
				LastSpoken = "", 
				ReplyLast = {},
				NamePrefix = false,
				Secretary = false,
				DnD = false,
				DnDMessage = "",
				AutoPurge = false,
				Notification = true,
				DirectionIndicator = {"",""},
				Filters = {GuildAds = false, Exclaim = false, SimpleComm = false} });
	self.clickableTooltip = true;
	History:Initialize(self.db.realm, self.db.char);
	History:CHAT_MSG_SYSTEM("You are no longer AFK.");
	HeyFu:OnTextUpdate()
	if(not self.db.char.ReplyLast) then self.db.char.ReplyLast = {} end
	if(#self.db.char.ReplyLast > 0) then
		for i,v in ipairs(self.db.char.ReplyLast) do
			History.hooks["ChatEdit_SetLastTellTarget"](v)
		end
	end
end
		
-- using an AceOptions data table
function HeyFu:OnMenuRequest(level, value, inTooltip)
	if(level == 5) then
		if(value[1] == "text") then
			self:InsertConversations(value[2], value[3]);
		end
		if(value[1] == "purgeplayers") then
			HeyFu:InsertPurgePlayers(value[2], value[3]);
		end
	end
	if(level == 4) then
		if(value[1] == "text") then
			self:InsertConversations(value[2], value[3]);
		end
		if(value[1] == "weekday") then
			for i,v in pairs(History:GetRecent(value[2], value[3])) do
				dewdrop:AddLine(
				'text', (History.NameList[i] or i),
				'hasArrow', true,
				'value', {'text', v, i},
				'func', function(name) self:ReplyTo(name) end,
				'arg1', i
				);
			end
			dewdrop:AddLine(
				'text', 'Purge Day',
				'func', function(Previous, Time) self:Purge(Previous, Time) end,
				'arg1', value[2],
				'arg2', value[3],
				'tooltipTitle', "Warning!",
				'tooltipText', "This option will delete this entire day."
				);
			dewdrop:AddLine(
				'text', 'Purge specific players',
				'hasArrow', true,
				'value', {'purgeplayers', value[2], value[3]}
				);
		end
		if(value[1] == "purgeplayers") then
			HeyFu:InsertPurgePlayers(value[2], value[3]);
		end
	end
	if(level == 3) then
		if(type(value) == "table" and value[1] == "Archive") then
			for i,v in pairs(History:GetRecent(TimePeriod[value[2]], TimePeriod[PreviousPeriod[value[2]]])) do
				dewdrop:AddLine(
				'text', (History.NameList[i] or i),
				'hasArrow', true,
				'value', {'text', v, i},
				'func', function(name) self:ReplyTo(name) end,
				'arg1', i
				);
			end
			dewdrop:AddLine(
				'text', 'Purge '..value[2],
				'func', function(Previous, Time) self:Purge(Previous, Time) end,
				'arg1', TimePeriod[value[2]],
				'arg2', TimePeriod[PreviousPeriod[value[2]]],
				'tooltipTitle', "Warning!",
				'tooltipText', "This option will delete this entire time period."
				);
			dewdrop:AddLine(
				'text', 'Purge specific players',
				'hasArrow', true,
				'value', {'purgeplayers', TimePeriod[value[2]], TimePeriod[PreviousPeriod[value[2]]]}
				);
		end
		if(value == "week") then
			local Now = date("*t");
			Now.hour = 0;
			Now.minute = 0;
			Now.second = 0;
			Now = time(Now);
			local OneDay = 60*60*24;
			for i=1,7 do
				local Day = (i * OneDay);
				dewdrop:AddLine(
					'text', date("%A", Day),
					'hasArrow', true,
					'value', {'weekday', Day, Day - OneDay}
					);
			end
		end
		if(value == "filter") then
			dewdrop:AddLine(
				'text', 'GuildAds',
				'checked', self.db.char.Filters.GuildAds,
				'func', function() self.db.char.Filters.GuildAds = not self.db.char.Filters.GuildAds end
				);
			dewdrop:AddLine(
				'text', '!Prefix',
				'checked', self.db.char.Filters.Exclaim,
				'func', function() self.db.char.Filters.Exclaim = not self.db.char.Filters.Exclaim end
				);
			dewdrop:AddLine(
				'text', '-=Prefix',
				'checked', self.db.char.Filters.SimpleComm,
				'func', function() self.db.char.Filters.SimpleComm = not self.db.char.Filters.SimpleComm end
				);
			dewdrop:AddLine(
				'text', 'Custom Prefixes',
				'hasEditBox', true,
				'hasArrow', true,
				'tooltipTitle', "Instructions:",
				'tooltipText', "Please ensure you enter your filters as a comma separated entry.",
				'editBoxText', self.db.char.Filters.SingleWord,
				'editBoxFunc', 
					function(text) 
						self.db.char.Filters.SingleWord = text 
					end
					);
		end
		if(value == "directions") then
			dewdrop:AddLine(
				'text', 'None',
				'checked', self.db.char.DirectionIndicator[1] == "" ,
				'isRadio', true,
				'func', function() self.db.char.DirectionIndicator = {"",""} end
				);
			dewdrop:AddLine(
				'text', 'Arrows',
				'checked',  self.db.char.DirectionIndicator[1] == "->",
				'isRadio', true,
				'func', function() self.db.char.DirectionIndicator = {"->","<-"} end
				);
			dewdrop:AddLine(
				'text', 'Words',
				'checked',  self.db.char.DirectionIndicator[1] == "From:",
				'isRadio', true,
				'func', function() self.db.char.DirectionIndicator = {"From:", "To:"} end
				);
		end
	end
	if(level == 2) then
		if(type(value) == "table" and value[1] == "text") then
			self:InsertConversations(value[2], value[3]);
		end
		if(value == 'archive') then
			dewdrop:AddLine(
				'text', 'Last...',
				'isTitle', true
				);
			dewdrop:AddLine(
				'text', 'Hour',
				'hasArrow', true,
				'value', {'Archive', 'Hour'}
				);
			dewdrop:AddLine(
				'text', 'Day',
				'hasArrow', true,
				'value', {'Archive', 'Day'}
				);
			dewdrop:AddLine(
				'text', 'Week',
				'hasArrow', true,
				'value', 'week'
				);
			dewdrop:AddLine(
				'text', 'Clear Archive',
				'func', function() StaticPopup_Show("HEYFUCONFIRM") end
				);
		elseif(value == "settings") then
			dewdrop:AddLine(
				'text', 'Filter',
				'hasArrow', true,
				'value', 'filter'
				);
			dewdrop:AddLine(
				'text', 'Directions',
				'hasArrow', true,
				'value', 'directions'
				);
			dewdrop:AddLine(
				'text', 'Paste Prefix',
				'checked', self.db.char.NamePrefix,
				'func', function() self.db.char.NamePrefix = not self.db.char.NamePrefix end,
				'tooltipTitle', "Paste Prefix",
				'tooltipText', "Prefix the pasted tells with your designated chatprefix."
				);
			dewdrop:AddLine(
				'text', 'Chat Notification',
				'tooltipTitle', 'Chat Notification',
				'tooltipText', 'Toggles whether you see the "X tells.. on the icon or not',
				'checked', self.db.char.Notification,
				'func', function() self.db.char.Notification = not self.db.char.Notification end
				);
			dewdrop:AddLine(
				'text', 'Personal Secretary',
				'checked', self.db.char.Secretary,
				'func', function() self.db.char.Secretary = not self.db.char.Secretary end,
				'tooltipTitle', "Personal Secretary",
				'tooltipText', "Have HeyFu record and playback tells made while AFK."
				);
			dewdrop:AddLine(
				'text', 'AutoPurge',
				'checked', self.db.char.AutoPurge,
				'func', function() self.db.char.AutoPurge = not self.db.char.AutoPurge end,
				'tooltipTitle', "AutoPurging",
				'tooltipText', "HeyFu will automatically purge conversations older than a week."
				);
		elseif(value == "DnD") then
		
		end

	end
	if(level == 1) then
		dewdrop:AddLine(
			'text', 'Recent Chats',
			'isTitle', true
			);
		for i,v in pairs(History:GetRecent()) do
			dewdrop:AddLine(
			'text', (History.NameList[i] or i),
			'hasArrow', true,
			'value', {'text', v, i},
			'func', function(name) self:ReplyTo(name) end,
			'arg1', i
			);
		end
		dewdrop:AddLine();
		dewdrop:AddLine(
			'text', 'Archive',
			'hasArrow', true,
			'value', 'archive'
			);
		dewdrop:AddLine(
			'text', 'Settings',
			'hasArrow', true,
			'value', 'settings'
			);
		if(self.db.char.Secretary) then
			dewdrop:AddLine();
			dewdrop:AddLine(
				'text', 'Do Not Disturb',
				'hasEditBox', true,
				'hasArrow', true,
				'tooltipTitle', 'Instructions:',
				'tooltipText', 'Please enter the autoreply message you require.',
				'checked', self.db.char.DnD,
				'func', function() 
							if not self.db.char.DnD then 
								self.db.char.DnDStart = time() 
								self.db.char.DnD = true;
							else
								History:CHAT_MSG_SYSTEM("Playback")
							end 
						end,
				'editBoxText', self.db.char.DnDMessage,
				'editBoxFunc',
					function(text)
						self.db.char.DnDMessage = text
					end
					);

			dewdrop:AddLine();
		end
	end
end

function HeyFu:ReplyTo(person)
	local reply = "/w "..person.." ";
	if (not ChatFrameEditBox:IsShown()) then
		ChatFrame_OpenChat(reply, DEFAULT_CHAT_FRAME);
	else
		ChatFrameEditBox:SetText(reply..ChatFrameEditBox:GetText());
	end
end

function HeyFu:InsertTo(text, Name, Direction)
	if(self.db.char.NamePrefix) then
		text = Direction.." ["..Name.."]: "..text;
	end
	if (not ChatFrameEditBox:IsShown()) then
		ChatFrame_OpenChat(text, DEFAULT_CHAT_FRAME);
	else
		ChatFrameEditBox:Insert(text);
	end
end
function HeyFu:InsertConversations(Messages, Name)
	for _,v in ipairs(Messages) do
		local time, message = v:Info();
		local short = nil;
		if(message and #message > 30) then
			short = string.sub(message,1,30) .. "..."
		end
		if(not v:IsPlayer()) then
			dewdrop:AddLine(
				'text', self.db.char.DirectionIndicator[1]..date("[%X]: ", time)..(short or message),
				'func', function(text, Name) self:InsertTo(text, Name, self.db.char.DirectionIndicator[1]) end,
				'arg1', message,
				'arg2', Name,
				'tooltipFunc', function(message) GameTooltip:AddLine(message, 1,1,1,1,true) end,
				'tooltipArg1', message
				);
		else
			dewdrop:AddLine(
				'text', self.db.char.DirectionIndicator[2]..date("[%X]: ", time)..(short or message),
				'textR', 0.5,
				'textG', 0.5,
				'textB', 0.5,
				'func', function(text, Name) self:InsertTo(text, Name, self.db.char.DirectionIndicator[2]) end,
				'arg1', message,
				'arg2', Name,
				'tooltipFunc', function(message) GameTooltip:AddLine(message, 1,1,1,1,true) end,
				'tooltipArg1', message
				);
		end
	end
end

function HeyFu:InsertPurgePlayers(Time1, Time2)
	for target, convos in pairs(History:GetRecent(Time1, Time2)) do
		dewdrop:AddLine(
			'text', (History.NameList[target] or target),
			'func', function(Time1, Time2, Target) HeyFu:Purge(Time1, Time2, nil, Target) end,
			'arg1', Time1,
			'arg2', Time2, 
			'arg3', target
			);
	end
end

function HeyFu:Purge(Time1, Time2, Period, Specific)
	for target,convos in pairs(Specific and {[Specific] = HeyFu.db.char.Conversations[Specific]} or HeyFu.db.char.Conversations) do
		local bin = {};
		for i,v in ipairs(convos) do
			if(v:Within(Time1, Time2)) then
				table.insert(bin, i);
				-- self:Debug("removing: "..v.message);
			end
		end
		table.sort(bin, function(a,b) return a > b end);
		for i,v in ipairs(bin) do
			table.remove(HeyFu.db.char.Conversations[target], v);
		end
	end
end

function HeyFu:OnTextUpdate()
	if(HeyFu.db.char.Notification and HeyFu.Unread > 0) then
		self:SetText( HeyFu.Unread > 1 and HeyFu.Unread.." tells..." or "1 tell...")
		self:SetIcon(self.hasChatIcon);
	else
		self:SetIcon((self.db.char.Secretary and self.db.char.AFK) and self.hasSecIcon or self.hasNoneIcon);
		self:SetText( "HeyFu"..(self.db.char.AFK and ":AFK" or "") );
	end
end

function HeyFu:OnTooltipUpdate()
    local Tooltip = tablet:AddCategory(
        'id', "Alpha",
        'columns', 2,
        'child_textR', 1,
        'child_textG', 1,
        'child_textB', 0,
        'child_textR2', 1,
        'child_textG2', 1,
        'child_textB2', 1,
		'child_wrap2', false)

	local Convos = History:GetRecent(time() - (60*15), nil, History.db.LastSpoken)[History.db.LastSpoken];
	if(Convos and HeyFu.Unread > 0) then
		
		while(table.maxn(Convos) > 10) do table.remove(Convos, 1) end

		Tooltip:AddLine(
				'text', History.db.LastSpoken
				);
		local count = 10;
		for i,v in pairs(Convos or {}) do
			local time, message = v:Info();
			if(not v:IsPlayer()) then
				Tooltip:AddLine(
					'text', self.db.char.DirectionIndicator[1]..date("[%X]:",time),
					'text2', History:SplitString(message)
			         );
			else
				Tooltip:AddLine(
					'text', self.db.char.DirectionIndicator[2]..date("[%X]:", time),
					'text2', History:SplitString(message),
					'textR2', 0.5,
					'textG2', 0.5,
					'textB2', 0.5
					);
			end
			if(count == 0) then break; end
			count = count - 1
		end
	end
    
    tablet:SetHint("Click to clear the unread count.")
    -- as a rule, if you have an OnClick or OnDoubleClick or OnMouseUp or OnMouseDown, you should set a hint.
end

function HeyFu:OnClick()
	if(self.db.char.Notification) then
		HeyFu.Unread = 0;
	end
	if(IsShiftKeyDown()) then
		self.db.char.Notification = not self.db.char.Notification
	end
	self:UpdateText();
end


function History:ToString()
	return "History";
end

--
--
-- Conversation history management section
--
--

History.Message = {}
History.SecReplied = {}

-- Message metatable so we can use +/- time to store who's saying what
function History.Message:New(time, message)
	o = {};
	o.message	= message;
	o.time		= time;
	setmetatable(o, self);
	self.__index = self
	return o;
end

-- Return actual time
function History.Message:Info()
	return math.abs(self.time), self.message;
end

function History.Message:__tostring()
	
end

function History.Message:IsPlayer()
	return self.time < 0;
end

function History.Message:Within(time1, time2)
	local myTime = math.abs(self.time);
	if(not time2) then
		if(myTime > (time() - time1)) then
			return true
		else
			return false
		end
	else
		if(myTime > (time() - time1) and myTime < (time() - time2)) then
			return true
		else
			return false
		end
	end
end

function History:Initialize(RealmDB, CharDB)
	self.db = CharDB;
	self.NameList = {}
	self:RestoreMetatables();
	self:SetDebugging(true);
	self.CheckTime = time();
	self:RegisterEvent("CHAT_MSG_WHISPER");
	self:RegisterEvent("CHAT_MSG_WHISPER_INFORM");
	self:RegisterEvent("CHAT_MSG_SYSTEM");
	self:Hook("ChatEdit_SetLastTellTarget", "SetLast", true)
	if(teknicolor) then -- Take advantage of Teknicolor's color settings.
		self.NameList = teknicolor.nametable;
	end
end

function History:SetLast(target)
	local found = false;
	for index, value in ipairs(self.db.ReplyLast) do
		if ( strupper(target) == strupper(value) ) then
			found = index;
			break;
		end
	end
	if(found) then
		table.remove(self.db.ReplyLast, found)
	end
	table.insert(self.db.ReplyLast, target)
	
	if(#self.db.ReplyLast > NUM_REMEMBERED_TELLS) then
		table.remove(self.db.ReplyLast)
	end
	
	self.hooks["ChatEdit_SetLastTellTarget"](target);
end

function History:AddMessage(time, message, name)
	if(self.db.Filters.GuildAds and string.sub(message, 1, 3) == "<GA") then
		return;
	end
	if(self.db.Filters.Exclaim and  string.sub(message, 1, 1) == "!") then
		return;
	end
	if(self.db.Filters.SimpleComm and string.sub(message, 1, 2) == "-=") then
		return;
	end
	if(self.db.Filters.SingleWord) then
		for filter in string.gmatch(self.db.Filters.SingleWord, "([^ ,]+)") do
			if(string.sub(message,1,#filter) == filter) then
				return;
			end
		end
	end

	local Message = History.Message:New(time, message);
	table.insert(self.db.Conversations[name], Message);
	if(not Message:IsPlayer()) then
		self.db.LastSpoken = name;
		HeyFu.Unread = HeyFu.Unread + 1;
		HeyFu:UpdateText();
	end
end

function History:GetRecent(time1, time2, specificName)
	local Recent = {};

	for name,convos in pairs(specificName and {[specificName] = self.db.Conversations[specificName]} or self.db.Conversations) do
		for _,Message in ipairs(convos) do
			if(Message:Within(time1 or (15 * 60), time2)) then
				if(not Recent[name]) then Recent[name] = {} end
				table.insert(Recent[name], Message);
			end
		end
		if(Recent[name]) then
			table.sort(Recent[name], 
						function(a, b) 
							local atime = a:Info()
							local btime = b:Info()
							return atime < btime
						end);
		end
	end
	if(table.maxn(Recent) > 0) then
		table.sort(Recent, function(a, b)
							local atime = a[1]:Info()
							local btime = b[1]:Info()
							return atime < btime
						end);
	end
	return Recent;
end

function History:CHAT_MSG_WHISPER(message, author)
	if(string.find(message, "^<HeyFuSecretary>")) or message:find("^/") then
		return;
	end

	if(not self.db.Conversations[author]) then
		self.db.Conversations[author] = {};
	end
	self:AddMessage(time(), message, author);
	if(self.db.Secretary and self.db.AFK) then
		if(self.SecReplied[author] and self.SecReplied[author] < time() - 60*5 or not self.SecReplied[author]) then
			self.SecReplied[author] = time();
			SendChatMessage("<HeyFuSecretary>: I'm sorry, "..(UnitName("player")).." is currently AFK, but never fear, "..
			"I've taken your message down for "..(UnitSex("player") == 2 and "him" or (UnitSex("player") == 3 and "her" or "it"))..
			" to get back to.", "WHISPER", nil, author);
		end
	end
	if(self.db.Secretary and self.db.DnD) then
		if(self.SecReplied[author] and self.SecReplied[author] < time() - 60*5 or not self.SecReplied[author]) then
			self.SecReplied[author] = time();
			if(self.db.DnDMessage ~= "") then
				SendChatMessage("<HeyFuSecretary>: " .. self.db.DnDMessage, "WHISPER", nil, author)
			else
				SendChatMessage("<HeyFuSecretary>: I apologise, but "..(UnitName("player")).." is currently marked Do Not Disturb. I'll inform "..
					(UnitSex("player") == 2 and "him" or (UnitSex("player") == 3 and "her" or "it")).." of your message when they are free.", "WHISPER", nil, author);
			end
		end
	end
end

function History:CHAT_MSG_WHISPER_INFORM(message, target)
	if(not self.db.Conversations[target]) then
		self.db.Conversations[target] = {};
	end
	
	if(string.find(message, "^<HeyFuSecretary>")) then
		return;
	end	
	
	self:AddMessage(time() * -1, message, target);
end

function History:CHAT_MSG_SYSTEM(message)
	if(self.db.Secretary) then
		if(string.find(message, "^You are now AFK:")) then
			self.db.AFK = time();
			DEFAULT_CHAT_FRAME:AddMessage("HeyFu - Secretary is on the job!", 0.5, 0.7, 1);
			HeyFu:OnTextUpdate()
			return;
		end
		if(self.db.AFK and message == "You are no longer AFK.") then
			self:PlayBack(self.db.AFK)
			self.db.AFK = nil;
			HeyFu:OnTextUpdate();
		end
		if(self.db.DnD and message == "Playback") then
			self:PlayBack(self.db.DnDStart)
			self.db.DnD = false;
			HeyFu:OnTextUpdate();
		end
	end
end

function History:PlayBack(backFrom)
	local info = ChatTypeInfo["WHISPER"]
	local listing = self:GetRecent(time() - backFrom)
	local merged = {};
	for i,v in pairs(listing) do
		for _, Message in ipairs(v) do
			if(not Message:IsPlayer()) then
				local time, message = Message:Info();
				table.insert(merged, {time, i, message});
			end
		end
	end
	table.sort(merged, function(a,b) return a[1] < b[1] end);
	if(table.maxn(merged) > 0) then
		DEFAULT_CHAT_FRAME:AddMessage("HeyFu - Secretary (You have "..table.maxn(merged).." message"..(table.maxn(merged) > 1 and "s" or "").."):", 0.5, 0.7, 1); 
		for i,v in ipairs(merged) do
			DEFAULT_CHAT_FRAME:AddMessage(date("[%X] ", v[1])..(self.NameList[v[2]] or v[2])..": "..v[3],  info.r, info.g, info.b, info.id);
		end
	end
end

function History:RestoreMetatables()
	History.Message.__index = History.Message;
	for char, convos in pairs(self.db.Conversations) do
		local bin = {};
		for i,v in ipairs(convos) do 
			setmetatable(v, History.Message);
			if(HeyFu.db.char.AutoPurge and math.abs(v.time) < time() - 60*60*24*7) then -- purge anything older than a week
				table.insert(bin, i);
			end
		end
		table.sort(bin, function(a,b) return a > b end);
		for i,v in ipairs(bin) do
			table.remove(convos,v);
		end
	end
end

function History:SplitString(speech)
	local lines = {};
	local line = {};
	local width = 0;

	for w in string.gmatch(speech, "%S+") do
		width = width + string.len(w);
		table.insert(line, w); 
		if(width > 30) then
			width = 0;
			table.insert(lines, table.concat(line, " "))
			line = {}
		end
	end
	if(#line > 0) then
		table.insert(lines, table.concat(line, " "));
	end
	return table.concat(lines, "\n")
end



