--[[
HoloFriends addon created by Holo, continued by Zappster, followed by Andymon

Get the latest version at wow.curse.com

See HoloFriends_change.log for more informations  
]]

--[[

This file defines common functions for *_friends, *_ignore and *_share

]]

local HF_Version = 0.435;

-- maximum number of merges of friends comment with blizz note without an edit of the comment
local HF_MaxMergeCount = 9;

local HF_MaxSecondsOfUnusedFactionsFriendsList = 2592000; -- 30 days


function HoloFriendsFuncs_Version()
	return HF_Version;
end


--[[
]]
function HoloFriends_SeparateFirstString(msg)
	if msg then
		-- find contiguous string of non-space characters
		local a, b, c = strfind(msg, "(%S+)");
		if a then
			return c, strsub(msg, b+2);
		else	
			return "";
		end
	end
end


--[[
  print a message in default chat with system colors
  @param msg - String
]]
function HoloFriendsFuncs_SystemMessage(msg)
	local info = ChatTypeInfo["SYSTEM"];
	if ( DEFAULT_CHAT_FRAME ) then
		DEFAULT_CHAT_FRAME:AddMessage(msg, info.r, info.g, info.b, info.id);
	end
end


--[[
  check and sync online note and list comment
  @param playerIndex - int
  @param infoIndex - int
  @param note - str
]]
function HoloFriendsFuncs_CheckComment(playerIndex, infoIndex, note)
	local list = HoloFriends_GetList();

	if ( not list[playerIndex].notify ) then return; end

	if ( not infoIndex ) then
		local name = list[playerIndex].name;
		local numNotifyFriends = GetNumFriends();
		for inum = 1, numNotifyFriends do
			local iname = GetFriendInfo(inum);
			if ( iname == name ) then
				infoIndex = inum;
			end
		end
	end

	if ( not note ) then
		_, _, _, _, _, _, note = GetFriendInfo(infoIndex);
	end

	local NMerge = HOLOFRIENDS_OPTIONS.MergeNotes;
	local NPrior = HOLOFRIENDS_OPTIONS.NotesPriority;
	local setNote = HoloFriendsFuncs_MergeComment(list[playerIndex], note, 0, NMerge, NPrior);
	if ( setNote ) then
		HoloFriends_SetFriendNotes = true;
		SetFriendNotes(infoIndex, list[playerIndex].comment);
	end
end


--[[
  merge comment from list entry with note (return flag for changed note)
  @param entry - table
  @param note - str
  @param notecnt - int (merge count of the note)
  @param MergeNotes - bol
  @param NotesPriority - bol
]]
function HoloFriendsFuncs_MergeComment(entry, note, notecnt, MergeNotes, NotesPriority)
	if ( not entry.comment or (entry.comment == "") ) then
		if ( note and note ~= "" ) then entry.comment = note; end
		return false;
	end

	if ( not note or (note == "") ) then return true; end

	if ( string.sub(entry.comment,1,strlen(note)) == note ) then return false; end

	if ( not entry.mergecnt ) then entry.mergecnt = 0; end
	if ( not notecnt ) then notecnt = 0; end

	if ( MergeNotes ) then
		if ( entry.mergecnt + notecnt <= HF_MaxMergeCount ) then
			if ( NotesPriority ) then
				entry.comment = note.." ; "..entry.comment;
			else
				entry.comment = entry.comment.." ; "..note;
			end
			entry.mergecnt = entry.mergecnt + notecnt + 1;
			return true;
		end
	else
		if ( NotesPriority ) then
			entry.comment = note;
		else
			return true;
		end
	end

	return false;
end


--[[
  add a group to the list
  @param list - Table
  @param group - String
]]
function HoloFriendsFuncs_AddGroup(list, group)
	if (not group or group == "" ) then
		return;
	end

	local groupIndex = HoloFriendsFuncs_ContainsGroup(list, group);
	if ( not groupIndex ) then
		local temp = {};
		temp.name = "1";
		temp.group = group;

		table.insert(list, temp);

		table.sort(list, HoloFriendsFuncs_CompareEntry);
	end
end


--[[
  remove a group from the list
  @param list - Table
  @param index - int
  @param moveToGroup - String
]]
function HoloFriendsFuncs_RemoveGroup(list, index, moveToGroup)
	local group = HoloFriendsLists_GetGroup(list, index);
	if ( group == moveToGroup or group == FRIENDS or group == IGNORE or group == UNKNOWN ) then
		return;
	end

	table.remove(list, index);

	-- move all players belonging to this group
	for idx, entry in pairs(list) do
		if ( entry.group == group ) then
			entry.group = moveToGroup;
		end
	end

	table.sort(list, HoloFriendsFuncs_CompareEntry);
end


--[[
  renames the given group from the list
  @param list - Table
  @param index - int
  @param name - String
]]
function HoloFriendsFuncs_RenameGroup(list, index, name)
	local group = HoloFriendsLists_GetGroup(list, index);
	-- who want's a group 'friends' on his igno-list? It's a cleaner API without any extra-argument
	if ( group == FRIENDS or group == IGNORE or group == UNKNOWN ) then
		return;
	end

	-- move all players belonging to this group
	for idx, entry in pairs(list) do
		if ( entry.group == group ) then
			entry.group = name;
		end
	end
end


--[[
  returns the index of given group name, nil if we do not have name in our list
  @param list - Table
  @param group - String
]]
function HoloFriendsFuncs_ContainsGroup(list, group)
	if ( not group ) then
		return nil;
	end

	local maxi = table.getn(list);
	for index = 1, maxi do
		if ( HoloFriendsFuncs_IsGroup(list, index) and
		     HoloFriendsLists_GetGroup(list, index) == group)
		then
			return index;
		end
	end
	return nil;
end


--[[
  returns nil or the index of name on the list
  @param list - Table
  @param name - String
]]
function HoloFriendsFuncs_ContainsPlayer(list, name)
	if ( not name or name == "" ) then
		return nil;
	end

	-- check the name in lowercase letters, because in our list the lettercase could be different
	-- (WoW change the first letter always to uppercase and the others to lowercase)
	lname = string.lower(name);

	local maxi = table.getn(list);
	for index = 1, maxi do
		local iname = HoloFriendsLists_GetName(list, index);
		if ( HoloFriendsFuncs_IsPlayer(list, index) and
		     string.lower(iname) == lname )
		then
			-- set the list name to the WoW name if different
			if ( iname ~= name ) then list[index].name = name; end
			return index;
		end
	end
	return nil;
end


--[[
  returns true if entry is a group
  @param list - Table
  @param index - int
]]
function HoloFriendsFuncs_IsGroup(list, index)
	return ( HoloFriendsFuncs_IsListIndexValid(list, index) and 
		 (list[index].name == "0" or list[index].name == "1")
	);
end


--[[
  returns true if entry is a player
  @param list - Table
  @param index - int
]]
function HoloFriendsFuncs_IsPlayer(list, index)
	return ( HoloFriendsFuncs_IsListIndexValid(list, index) and 
		 not (list[index].name == "0" or list[index].name == "1")
	);
end


--[[
  check if index is valid
  @param list - Table
  @param index - int
]]
function HoloFriendsFuncs_IsListIndexValid(list, index)
	return ( index and (index > 0) and (index <= table.getn(list)));
end


--[[
  sorts players ascending after group and name
  @param a - Table (must have 'group' and 'name')
  @param b - Table (must have 'group' and 'name')
]]
function HoloFriendsFuncs_CompareEntry(a, b)
	local len = math.max(string.len(a.group), string.len(b.group));
	local sa = string.format('%-'..len..'s',a.group)..a.name;
	local sb = string.format('%-'..len..'s',b.group)..b.name;
	return sa < sb;
end


--[[
  loads data from the char 'player' on the current realm
  @param completeList - Table
  @param player - String, name of character
  @param FLinit - bol (flag for initialisation of friends list)
]]
function HoloFriendsFuncs_LoadList(completeList, player, FLinit)
	local listVersion = completeList.version;
	local realm = GetCVar("realmName");
	local faction = UnitFactionGroup("player");
	local result = nil;
  
	if ( listVersion and listVersion > HF_Version ) then
		HoloFriendsFuncs_SystemMessage(format(HOLOFRIENDS_INITINVALIDLISTVERSION, listVersion));
		-- HOLOFRIENDS_LIST or HOLOIGNORE_LIST won't be used
		completeList = {};
		completeList.version = HF_Version;

		completeList[realm] = {};
		completeList[realm][player] = {};
		result = completeList[realm][player];
	else
		-- set version to latest
		completeList.version = HF_Version;

		-- get realm list
		result = completeList[realm];
		-- new realm?
		if ( not result ) then
			completeList[realm] = {};
			result = completeList[realm];
		end
		-- get player list
		result = completeList[realm][player];
		-- check temporary state of list (friends list only)
		local is_temp_list = false;
		local remove_temp_list = false;
		if ( result and FLinit ) then
			local idx = HoloFriendsFuncs_ContainsGroup(result, FRIENDS);
			if ( result[idx].temporary ) then is_temp_list = true; end
			if ( result[idx].remove ) then remove_temp_list = true; end
			-- if still temporary list, but no remove flag, show dialog again
			if ( is_temp_list and not remove_temp_list ) then
				StaticPopup_Show("HOLOFRIENDS_LOADFACTIONSFRIENDSLIST");
			end
			-- if remove flag is set, remove this list
			if ( is_temp_list and remove_temp_list ) then
				result = nil;
				completeList[realm][player] = nil;
			end
		end
		-- load faction wide friends list if existing
		if ( not result and FLinit ) then
			-- no char data -> look for char in factions list
			local clist = HoloFriendsFuncs_RealmGetAllFactionChars(true);
			local newchar = true;
			for idx = 1, table.getn(clist) do
				if ( clist[idx] == player ) then newchar = false; end
			end
			-- char exist in faction list -> try to load faction data (friends list only)
			if ( not newchar ) then result = completeList[realm][faction]; end
			-- if remove_temp_list is set, user accepted the load of the faction wide friends list
			if ( result and not remove_temp_list ) then
				-- check if this faction data were used the last HF_MaxSecondsOfUnusedFactionsFriendsList (friends list only)
				local idx = HoloFriendsFuncs_ContainsGroup(result, FRIENDS);
				if ( idx ) then
					if ( result[idx].lastuse ) then
						local dt = time() - result[idx].lastuse;
						if ( dt > HF_MaxSecondsOfUnusedFactionsFriendsList ) then
							-- use empty list
							completeList[realm][player] = {};
							result = completeList[realm][player];
							-- mark as temporary in the FRIENDS-group entry
							local temp = {};
							temp.name = "1";
							temp.group = FRIENDS;
							temp.temporary = 1;
							table.insert(result, temp);
							-- start dialog to ask user if factions friends list should be used
							StaticPopup_Show("HOLOFRIENDS_LOADFACTIONSFRIENDSLIST");
						end
					end
				end
			end
		end
		-- start with an empty friends list for this char
		if ( not result ) then
			completeList[realm][player] = {};
			result = completeList[realm][player];
		end
	end

	return result;
end


function HoloFriendsFuncs_IsDataAvailable(list, charName)
	local realm = GetCVar("realmName");
	local result = list[realm][charName];

	if ( result ) then
		return true;
	else
		return false;
	end
end


--[[
  returns all own chars from the current realm (no reference to the original data)
]]
function HoloFriendsFuncs_RealmGetOwnChars()
	local realm = GetCVar("realmName");
	local result = {};

	-- get realm list
	local list = HOLOFRIENDS_LIST[realm];
	for key, entry in pairs(list) do
		table.insert(result, key);
	end

	-- sort the result
	table.sort(result, function (a,b) return a < b; end);

	return result;
end


--[[
  returns all chars of the players faction from the current realm (no reference to the original data)
]]
function HoloFriendsFuncs_RealmGetAllFactionChars(WithUnknown)
	local realm = GetCVar("realmName");
	local faction = UnitFactionGroup("player");
	local result = {};
	local list;

	if ( not HOLOFRIENDS_FACTIONS or not HOLOFRIENDS_FACTIONS[realm] or
	     not HOLOFRIENDS_FACTIONS[realm][faction] ) then return result; end

	-- get faction list of the realm
	list = HOLOFRIENDS_FACTIONS[realm][faction];
	for idx = 1, table.getn(list) do
		table.insert(result, list[idx]);
	end

	if ( WithUnknown ) then
		-- get unknown list of the realm
		list = HOLOFRIENDS_FACTIONS[realm]["unknown"];
		for idx = 1, table.getn(list) do
			table.insert(result, list[idx]);
		end
	end

	-- sort the result
	table.sort(result, function (a,b) return a < b; end);

	return result;
end


local function HFF_FactionListRemove(name)
	local realm = GetCVar("realmName");
	local nlist = nil;
	index = 0;

	nlist = HOLOFRIENDS_FACTIONS[realm]["unknown"];
	index = 0;
	for idx = 1, table.getn(nlist) do
		if (nlist[idx] == name) then index = idx; end
	end
	if (index ~= 0) then table.remove(nlist, index); end

	nlist = HOLOFRIENDS_FACTIONS[realm]["Horde"];
	index = 0;
	for idx = 1, table.getn(nlist) do
		if (nlist[idx] == name) then index = idx; end
	end
	if (index ~= 0) then table.remove(nlist, index); end

	nlist = HOLOFRIENDS_FACTIONS[realm]["Alliance"];
	index = 0;
	for idx = 1, table.getn(nlist) do
		if (nlist[idx] == name) then index = idx; end
	end
	if (index ~= 0) then table.remove(nlist, index); end

	return;
end


function HoloFriendsFuncs_DeleteChar(charstring)
	if ( not charstring ) then return; end
	local realm;

	local char, atrealm = HoloFriends_SeparateFirstString(charstring);
	if ( char == "" ) then return; end
	local at, realm = HoloFriends_SeparateFirstString(atrealm);
	if ( (at ~= "at") or (realm == "") or (realm == " ") ) then realm = GetCVar("realmName"); end

	local faction, faction_lc = UnitFactionGroup("player");
	local fList = HOLOFRIENDS_LIST;
	local iList = HOLOIGNORE_LIST;

	local name = char;
	if ( name == faction ) then name = faction_lc; end

	local exists = fList[realm][char];
	if ( exists == nil ) then
		HoloFriendsFuncs_SystemMessage(format(HOLOFRIENDS_MSGDELETECHARNOTFOUND, name));
		return;
	end

	fList[realm][char] = nil;
	iList[realm][char] = nil;

	-- deleted yourself ...
	if ( UnitName("player") == char ) then
		HoloFriends_List_Update();
		HoloIgnore_List_Update();
	end

	HFF_FactionListRemove(char);

	-- if realm has no chars left, delete realm
	local count = 0;
	for _,_ in pairs(HOLOFRIENDS_LIST[realm]) do count = count + 1; end;
	if ( count == 0 ) then
		fList[realm] = nil;
		iList[realm] = nil;
		HOLOFRIENDS_FACTIONS[realm] = nil;
		if ( realm == GetCVar("realmName") ) then
			HoloFriends_LoadList();
			HoloFriends_List_Update();
		end
	end

	--Share visible?
	if ( HoloFriends_ShareFrame:IsShown() ) then
		HoloFriendsShare_OnShow();
	end

	HoloFriendsFuncs_SystemMessage(format(HOLOFRIENDS_MSGDELETECHARDONE, name));
end


--[[
  Return the date in the requested dateFormat
  @param isoDate - a date in iso format: %Y-%m-%d %H:%M %w
  @param dateFormat -  format of the return value (i.e. %a %d.%m.%Y %H:%M)
--]]
function HoloFriendsFuncs_GetDateString(isoDate, dateFormat)
	local Y = string.sub(isoDate, 1, 4);
	local m = string.sub(isoDate, 6, 7);
	local d = string.sub(isoDate, 9, 10);
	local H = string.sub(isoDate, 12, 13);
	local M = string.sub(isoDate, 15, 16);
	local w = string.sub(isoDate, 18, 18);

	local I = H;
	local p = "am";
	if ( tonumber(I) > 12 ) then
		I = tostring(tonumber(I) - 12);
		p = "pm";
	end
	local A = "";
	if ( w ) then
		if ( w == "1" ) then A = WEEKDAY_MONDAY; end
		if ( w == "2" ) then A = WEEKDAY_TUESDAY; end
		if ( w == "3" ) then A = WEEKDAY_WEDNESDAY; end
		if ( w == "4" ) then A = WEEKDAY_THURSDAY; end
		if ( w == "5" ) then A = WEEKDAY_FRIDAY; end
		if ( w == "6" ) then A = WEEKDAY_SATURDAY; end
		if ( w == "0" ) then A = WEEKDAY_SUNDAY; end
	end

	local retStr = dateFormat;
	retStr = string.gsub(retStr, "%%Y", Y);
	retStr = string.gsub(retStr, "%%m", m);
	retStr = string.gsub(retStr, "%%d", d);
	retStr = string.gsub(retStr, "%%H", H);
	retStr = string.gsub(retStr, "%%M", M);
	retStr = string.gsub(retStr, "%%I", I);
	retStr = string.gsub(retStr, "%%p", p);
	retStr = string.gsub(retStr, "%%A", A);

	return retStr;
end


--[[
  Return true if date1 < date2
  @param date1 - a date in iso format: %Y-%m-%d %H:%M %w
  @param date2 - a date in iso format: %Y-%m-%d %H:%M %w
--]]
function HoloFriendsFuncs_Date1ltDate2(date1, date2)
	if ( date1 == UNKNOWN ) then
		return true;
	else
		local lastseen = HoloFriendsFuncs_GetDateString(date1, "%Y%m%d%H%M");
		local lastseen1 = tonumber(lastseen);
		if ( date2 ~= UNKNOWN ) then
			lastseen = HoloFriendsFuncs_GetDateString(date2, "%Y%m%d%H%M");
			local lastseen2 = tonumber(lastseen);
			if ( lastseen1 < lastseen2 ) then return true; end
		end
	end
	return false;
end


--[[
  returns a table that lists all visible entries from the given list
  @param list - list to check all entries for visibility
  @param Displayed_Names - number of shown entries
  @param HFlist - flag for the FriendsList (use HOLOFRIENDS_SHOW_OFFLINE_FRIENDS)
--]]
function HoloFriendsFuncs_ShownListTab(list, Displayed_Names, HFlist)
	local n_list = table.getn(list);
	local ShownIndexTable = {};
	local expanded = true;
	local visible = true;
	local index = 0;
	local compact = ( not (HOLOFRIENDS_SHOW_OFFLINE_FRIENDS == 1) and HFlist and not HOLOFRIENDS_OPTIONS.ShowGroups );

	if ( compact ) then
		local group_idx, connected;
		local maxi = table.getn(list);
		for index = 1, maxi do
			if ( (list[index].name == "1") or (list[index].name == "0") ) then
				if ( group_idx ) then
					if ( connected ) then
						list[group_idx].connected = true;
					else
						list[group_idx].connected = nil;
					end
				end
				group_idx = index;
				connected = false;
			else
				if ( list[index].connected ) then connected = true; end
			end
		end
		if ( group_idx ) then
			if ( connected ) then
				list[group_idx].connected = true;
			else
				list[group_idx].connected = nil;
			end
		end
	end

	for k, entry in pairs(list) do
		index = index + 1;

		if ( not expanded ) then visible = false; end

		-- entry = group?
		if ( entry.name == "1" ) then
			expanded = true;
			visible = true;
			if ( compact and not entry.connected ) then
				visible = false;
			end
			entry.connected = nil;
		elseif ( entry.name == "0" ) then
			expanded = false;
			visible = true;
			if ( compact and not entry.connected ) then
				visible = false;
			end
			entry.connected = nil;
		-- entry = player .. on- or offline?
		elseif ( not (HOLOFRIENDS_SHOW_OFFLINE_FRIENDS == 1) and HFlist ) then
			if ( expanded and entry.connected ) then
				visible = true;
			else
				visible = false;
			end
		end

		if ( visible ) then table.insert(ShownIndexTable, index); end
	end

	-- fillup table
	local visibleCnt = table.getn(ShownIndexTable);
	if ( visibleCnt < Displayed_Names ) then
		for ii = visibleCnt + 1, Displayed_Names do
			table.insert(ShownIndexTable, n_list + 1);
		end
	end

	return ShownIndexTable;
end
