--[[
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 manages the friendslist, but also the initialisation of the addon.

]]

-- default config settings
HOLOFRIENDS_SHOW_OFFLINE_FRIENDS = 1;
HOLOFRIENDS_TURNINFO = 0;

HOLOFRIENDS_LIST = {};
HOLOFRIENDS_FACTIONS = {};

-- flags to skip HoloFriends_List_Update()
HoloFriends_SetFriendNotes = false;
HoloFriends_SetNotifyOn = false;
HoloFriends_SetNotifyOff = false;
local HF_GetServerList = false;
HoloFriends_FriendListMSGEvent = false;
HoloFriends_FriendListUPDATEEvent = false;

-- timer to run the list update only once
HoloFriends_ListUpdateRefTime = time();
HoloFriends_ListUpdateStartTime = HoloFriends_ListUpdateRefTime;
HoloFriends_ListUpdateMaxSec = 3;

local HF_Max_Server_Friends = 100;
local HF_Displayed_Names = 18;
local HF_Friends_Remote_Add_Delay = 3;

local HF_Loaded = false;
local HF_Requested_List_Update = false;
local HF_IsGroupSelected = false;

local HF_SelectedEntry = -1;
local HF_LastClicked = -1;

local HF_list = {};
local HF_Moving_Friend = nil;
local HF_Target_Friend = nil;

local HF_List_Online_Template = "%s - |cffffffff%s|r";
local HF_List_Offline_Template = "|cff999999%s|r - |cff999999%s|r";
local HF_Level_Template = "|cff999999"..string.gsub(FRIENDS_LEVEL_TEMPLATE, "%%1?%$?d", "%%1%$s").."|r";
local HF_Limit_Alert = string.gsub(HOLOFRIENDS_MSGFRIENDLIMITALERT, "%%d", HF_Max_Server_Friends);

local HF_Class_Icon_Tcoords = {
	["WARRIOR"]     = {0, 0.25, 0, 0.25},
	["MAGE"]        = {0.25, 0.49609375, 0, 0.25},
	["ROGUE"]       = {0.49609375, 0.7421875, 0, 0.25},
	["DRUID"]       = {0.7421875, 0.98828125, 0, 0.25},
	["HUNTER"]      = {0, 0.25, 0.25, 0.5},
	["SHAMAN"]      = {0.25, 0.49609375, 0.25, 0.5},
	["PRIEST"]      = {0.49609375, 0.7421875, 0.25, 0.5},
	["WARLOCK"]     = {0.7421875, 0.98828125, 0.25, 0.5},
	["PALADIN"]     = {0, 0.25, 0.5, 0.75},
	["DEATHKNIGHT"] = {0.25, 0.5, 0.5, 0.75}
};


function HoloFriends_SlashCommand(msg)
	if ( not HF_Loaded ) then return; end

	local cmd, subCmd = HoloFriends_SeparateFirstString(msg);

	if ( cmd == "delete" ) then
		local dialog = StaticPopup_Show("HOLOFRIENDS_DELETECHARDATA", subCmd);
		if ( dialog ) then
			dialog.data = subCmd;
		end
	end
end


-- check the faction list
local function HFF_CheckFactionList()
	local realm = GetCVar("realmName");
	local faction = UnitFactionGroup("player");
	local name = UnitName("player");

	local result = nil;
	local nlist = nil;
	local index = 0;

	-- create list if not existing
	result = HOLOFRIENDS_FACTIONS[realm];
	if ( not result ) then HOLOFRIENDS_FACTIONS[realm] = {}; end

	-- add faction if not existing
	result = HOLOFRIENDS_FACTIONS[realm][faction];
	if ( not result ) then HOLOFRIENDS_FACTIONS[realm][faction] = {}; end

	-- add list for chars with unknown faction if not existing
	result = HOLOFRIENDS_FACTIONS[realm]["unknown"];
	if ( not result ) then
		HOLOFRIENDS_FACTIONS[realm]["unknown"] = {};
		nlist = HOLOFRIENDS_FACTIONS[realm]["unknown"];
		-- add all chars of this realm to the "unknown" list
		local charList = HoloFriendsFuncs_RealmGetOwnChars(); 
		for key, pname in pairs(charList) do
			table.insert(nlist, pname);
		end
		table.sort(nlist, function (a,b) return a < b; end);
	end

	-- remove this char from "unknown" list, if included
	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

	-- add this char to faction list, if not included
	nlist = HOLOFRIENDS_FACTIONS[realm][faction];
	index = 0;
	for idx = 1, table.getn(nlist) do
		if (nlist[idx] == name) then index = idx; end
	end
	if (index == 0) then
		table.insert(nlist, name);
		table.sort(nlist, function (a,b) return a < b; end);
	end
end


-- this one hooks our functions and gets the realm list
local function HFF_OnLoad(self)
	-- setup of the options
	HoloFriendsOptions_VariablesLoaded();

	-- hook functions
	HoloFriendsHooks_OnLoad();

	-- insert the drop down menu
	HoloFriends_InsertDropDown();

	-- hide old friends frame
	FriendsListFrame:Hide();
	-- and replace with new
	FriendsListFrame = HoloFriendsFrame;

	-- modify the ADD_FRIEND popup
	StaticPopupDialogs["ADD_FRIEND"].OnAccept = function(self)
		HoloFriends_AddFriend(self.editBox:GetText());
	end
	StaticPopupDialogs["ADD_FRIEND"].EditBoxOnEnterPressed = function(self)
		HoloFriends_AddFriend(self:GetParent().editBox:GetText());
		self:GetParent():Hide();
	end

	-- hide the button on the drag item
	HoloFriendsNameButtonDrag:SetNormalTexture("");
	HoloFriendsNameButtonDragClassIcon:Hide();
	HoloFriendsNameButtonDragServer:Hide();
	HoloFriendsNameButtonDragName:SetPoint("TOPLEFT", "HoloFriendsNameButtonDrag", "TOPLEFT", 20, 0);
	HoloFriendsNameButtonDrag:Hide();

	-- load the friends list
	HoloFriends_LoadList();

	HF_Loaded = true;

	-- init the faction list
	HFF_CheckFactionList();

	self:RegisterEvent("FRIENDLIST_UPDATE");
	self:RegisterEvent("WHO_LIST_UPDATE");
	self:RegisterEvent("CHAT_MSG_SYSTEM");

	SlashCmdList["HOLOFRIENDS"] = HoloFriends_SlashCommand;
	SLASH_HOLOFRIENDS1 = "/holofriends";
	SLASH_HOLOFRIENDS2 = "/hfriends";

	if ( DEFAULT_CHAT_FRAME ) then
		local msg = format(HOLOFRIENDS_INITADDONLOADED, HoloFriendsFuncs_Version());
		HoloFriendsFuncs_SystemMessage(msg);
	end
end


-- create and insert a new friend entry
local function HFF_InsertNewEntry(name, group, level, class, lc_class, area, note, connected, onstate, notify)
	if ( not name or name == "" ) then
		return nil;
	end

	local temp = {};
	temp.name = name;
	if ( not group or group == "" ) then
		temp.group = FRIENDS;
	else
		temp.group = group;
	end
	if ( level    and (level ~= 0) )          then temp.level    = level;    end
	if ( class    and (class ~= UNKNOWN) )    then temp.class    = class;    end
	if ( lc_class and (lc_class ~= UNKNOWN) ) then temp.lc_class = lc_class; end
	if ( area     and (area ~= UNKNOWN) )     then temp.area     = area;     end
	if ( connected ) then temp.connected = connected; end
	if ( connected == 1 ) then temp.onstate = onstate; end
	if ( notify ) then temp.notify = notify; end

	-- get comment from ignore list if there is any
	local wasIgnore = HoloFriendsFuncs_ContainsPlayer(HoloIgnore_GetList(), name);
	local ignore_note = "";
	if ( wasIgnore ) then
		ignore_note = HoloFriendsLists_GetComment(HoloIgnore_GetList(), wasIgnore);
	end

	-- set comment from ignore list if no note
	temp.comment = note;
	HoloFriendsFuncs_MergeComment(temp, ignore_note, 0, true, false);

	table.insert(HF_list, temp);
	table.sort(HF_list, HoloFriendsFuncs_CompareEntry);
end


local function HFF_GetClassFromLCClass(lc_class, noclass)
	if ( not lc_class ) then return noclass; end

	local realm = GetCVar("realmName");
	local charList = HoloFriendsFuncs_RealmGetOwnChars();
	for key, pname in pairs(charList) do
		local plist = HOLOFRIENDS_LIST[realm][pname];
		local maxi = table.getn(plist);
		for i = 1, maxi, 1 do
			local i_lc_class = plist[i].lc_class;
			local i_class    = plist[i].class;
			if ( i_class and i_lc_class and 
			     (i_class ~= UNKNOWN) and (i_lc_class ~= UNKNOWN) and
			     (i_lc_class == lc_class))
			then
				-- check if the class entry is valid
				if ( HF_Class_Icon_Tcoords[i_class] ) then
					return i_class;
				end
			end
		end
	end
	return noclass;
end


local function HFF_GetClassFromWho(name)
	local class = UNKNOWN;
	if ( HoloFriends_interceptWhoResults == 0 ) then
		SetWhoToUI(0);
		HoloFriends_interceptWhoResults = GetTime();
		SendWho("n-"..name);
		local numWho = GetNumWhoResults();
		if ( numWho and (numWho ~= 0) ) then
			for inum = 1, numWho do
				local charname, charclass;
				charname, _, _, _, _, _, charclass = GetWhoInfo(inum);
				if ( charclass and (name == charname) ) then
					class = charclass;
				end
			end
		end
	end
	return class;
end


-- event handling

function HoloFriends_OnEvent(self, event, ...)
	if ( event == "VARIABLES_LOADED" ) then
		HFF_OnLoad(self);
		self:UnregisterEvent("VARIABLES_LOADED");
	end

	if ( event == "FRIENDLIST_UPDATE" ) then
		if ( HoloFriends_SetFriendNotes or HoloFriends_SetNotifyOn or HoloFriends_SetNotifyOff ) then
			HoloFriends_SetFriendNotes = false;

			if ( HoloFriends_RunListUpdate and (HoloFriends_RunListUpdate ~= "") ) then
				HoloFriends_FriendListUPDATEEvent = true;
			else
				HoloFriends_SetNotifyOn = false;
				HoloFriends_SetNotifyOff = false;
			end

			if ( HoloFriends_FriendListMSGEvent and HoloFriends_FriendListUPDATEEvent ) then
				HoloFriends_SetNotifyOn = false;
				HoloFriends_SetNotifyOff = false;
				HoloFriends_FriendListMSGEvent = false;
				HoloFriends_FriendListUPDATEEvent = false;
				if ( HoloFriends_RunListUpdate == "RunUpdate" ) then
					HoloFriends_RunListUpdate = "";
					HoloFriends_List_Update();
				end
				if ( (difftime(time(), HoloFriends_ListUpdateStartTime) > HoloFriends_ListUpdateMaxSec) ) then
					HoloFriends_RunListUpdate = "";
				end
				if ( HoloFriends_RunListUpdate == "RunLocal" ) then
					HoloFriends_RunListUpdate = "";
					HoloFriends_CheckLocalList();
				end
				if ( HoloFriends_RunListUpdate == "RunServer" ) then
					HoloFriends_RunListUpdate = "";
					HoloFriends_CheckServerList();
				end
			end
		else
			if ( HF_GetServerList ) then
				HF_GetServerList = false;
			else
				HoloFriends_List_Update();
			end
		end
	end

	if ( event == "WHO_LIST_UPDATE" ) then
		HoloFriendsScan_CheckWhoListResult(HF_list);
	end

	if ( event == "CHAT_MSG_SYSTEM" ) then
		local msg = ...;
		if ( string.match(msg, HoloFriends_WHO_NUM_RESULTS) ) then
			HoloFriendsScan_CheckWhoListResult(HF_list);
		end
	end
end


function HoloFriends_OnUpdate()
	if( HF_Moving_Friend ) then
		local slot;
		HF_Target_Friend = nil;
		for index = 1, HF_Displayed_Names, 1 do
			slot = getglobal("HoloFriendsNameButton"..index);
			if ( MouseIsOver(slot) ) then
				slot:LockHighlight();
				HF_Target_Friend = slot;
				local pos = HoloFriendsScrollFrame:GetVerticalScroll();
				if ( index == 1 and pos >= FRIENDS_FRAME_IGNORE_HEIGHT ) then
					HoloFriendsScrollFrameScrollBar:SetValue(pos - FRIENDS_FRAME_IGNORE_HEIGHT);
				end
				if ( index == HF_Displayed_Names and
				     (pos < FRIENDS_FRAME_IGNORE_HEIGHT*(table.getn(HF_list) - HF_Displayed_Names)) )
				then
					HoloFriendsScrollFrameScrollBar:SetValue(pos + FRIENDS_FRAME_IGNORE_HEIGHT);
				end
			else
				slot:UnlockHighlight();
			end
		end
		HF_Moving_Friend:UnlockHighlight();
	end
end


-- add a friend
function HoloFriends_AddFriend(name)
	if ( name and name ~= "" ) then
		if ( not HoloFriendsFuncs_ContainsPlayer(HF_list, name) ) then
			HFF_InsertNewEntry(name);
			if ( GetNumFriends() < HF_Max_Server_Friends ) then
				local index = HoloFriendsFuncs_ContainsPlayer(HF_list, name);
				HoloFriendsLists_SetNotify(HF_list, index, true, false, "RunUpdate");
			else
				local msg = format(HOLOFRIENDS_MSGFRIENDONLINEDISABLED, name);
				HoloFriendsFuncs_SystemMessage(msg);
				HoloFriends_List_Update();
			end
		end
		if ( HoloFriends_ShareFrame:IsShown() ) then
			HoloFriendsShare_OnShow();
		end
	elseif ( UnitIsPlayer("target") and UnitCanCooperate("player", "target") ) then
		local tname = UnitName("target");
		
		if ( not HoloFriendsFuncs_ContainsPlayer(HF_list, tname) ) then
			HFF_InsertNewEntry(tname);
			if ( GetNumFriends() < HF_Max_Server_Friends ) then
				local index = HoloFriendsFuncs_ContainsPlayer(HF_list, tname);
				HoloFriendsLists_SetNotify(HF_list, index, true, false, "RunUpdate");
			else
				local msg = format(HOLOFRIENDS_MSGFRIENDONLINEDISABLED, tname);
				HoloFriendsFuncs_SystemMessage(msg);
				HoloFriends_List_Update();
			end
		end
		-- update share window if open
		if ( HoloFriends_ShareFrame:IsShown() ) then
			HoloFriendsShare_OnShow();
		end
	else
		StaticPopup_Show("ADD_FRIEND");
	end
end


-- remove the selected friend
function HoloFriends_RemoveFriend()
	if ( not HF_IsGroupSelected and HoloFriends_IsSelectedEntryValid() ) then
		local notify = HoloFriendsLists_GetNotify(HF_list, HF_SelectedEntry);
		local name = HF_list[HF_SelectedEntry].name;

		-- remove from our list
		table.remove(HF_list, HF_SelectedEntry);
		HoloFriends_DeselectEntry();

		if ( notify ) then
			-- remove from original list
			RemoveFriend(name);
		else
			HoloFriends_List_Update();
		end
		-- update share window if open
		if ( HoloFriends_ShareFrame:IsShown() ) then
			HoloFriendsShare_OnShow();
		end
	end
end


-- BEGIN OF GETTERS

-- returns the id of the last clicked entry
function HoloFriends_GetLastClickedIndex()
	return HF_LastClicked;
end


-- returns the selected entry
function HoloFriends_GetSelectedEntry()
	return HF_SelectedEntry;
end


-- returns true if the selected entry is valid
function HoloFriends_IsSelectedEntryValid()
	if ( not HF_Loaded ) then return false; end

	return HoloFriendsFuncs_IsListIndexValid(HF_list, HF_SelectedEntry);
end


function HoloFriends_GetList()
	if ( not HF_Loaded ) then
		local list = {};
		return list;
	end

	return HF_list;
end


function HoloFriends_LoadList()
	-- get the correct list
	HF_list = HoloFriendsFuncs_LoadList(HOLOFRIENDS_LIST, UnitName("player"), 1);

	-- check if group "friends" is in our list
	if ( not HoloFriendsFuncs_ContainsGroup(HF_list, FRIENDS) ) then
		HFF_InsertNewEntry("1", FRIENDS);
	end

	-- set actual date in seconds to the friends group (indicator of last use)
	local idx = HoloFriendsFuncs_ContainsGroup(HF_list, FRIENDS);
	HF_list[idx].lastuse = time();

	-- check class in list (changed list entry with version 0.4)
	local maxi = table.getn(HF_list);
	for index = 1, maxi do
		local lc_class = HF_list[index].lc_class;
		local    class = HF_list[index].class;
		if ( class and not lc_class ) then
			HF_list[index].lc_class = class;
			HF_list[index].class = nil;
		end
	end
end

-- END OF GETTER


-- BEGIN OF ITEM TOOLTIP

function HoloFriends_NameButton_SetTooltip(self)
	local index = self:GetID();

	GameTooltip:SetOwner(self, "ANCHOR_BOTTOMRIGHT");
	GameTooltip:SetText(HoloFriendsLists_GetName(HF_list, index), 1.0, 1.0, 1.0);
	if ( not HoloFriendsFuncs_IsGroup(HF_list, index) ) then
		local level = HoloFriendsLists_GetLevel(HF_list, index);
		local lc_class = HoloFriendsLists_GetLCClass(HF_list, index);
		local info = format(TEXT(HF_Level_Template), level, lc_class);
		GameTooltip:AddLine(info, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 0);
	end
	if ( HoloFriendsLists_GetComment(HF_list, index) ~= "" ) then
		local note = HoloFriendsLists_GetComment(HF_list, index);
		GameTooltip:AddLine(note, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1);
	end
	if ( not HoloFriendsFuncs_IsGroup(HF_list, index) ) then
		local lastSeen = HoloFriendsLists_GetLastSeen(HF_list, index);
		if ( lastSeen == UNKNOWN ) then
			lastSeen = HOLOFRIENDS_TOOLTIPNEVERSEEN;
		else
			lastSeen = HoloFriendsFuncs_GetDateString(lastSeen, HOLOFRIENDS_TOOLTIPDATEFORMAT);
			lastSeen = HOLOFRIENDS_TOOLTIPLASTSEEN..": "..lastSeen
		end
		GameTooltip:AddLine(lastSeen, 1.0, 1.0, 1.0, 0);
	end
	GameTooltip:Show();
end

-- END OF ITEM TOOLTIP


-- START OF GUI FUNCTIONS

-- drag and drop function
function HoloFriends_NameButton_OnDragStart(self)
	if ( HoloFriendsFuncs_IsGroup(HF_list, self:GetID()) ) then
		return;
	end
	HF_list[self:GetID()].remove = nil;

	local cursorX, cursorY = GetCursorPosition();
	cursorX = cursorX / self:GetScale();
	cursorY = cursorY / self:GetScale();

	HF_Moving_Friend = HoloFriendsNameButtonDrag;
	HF_Moving_Friend:SetID(self:GetID());
	HoloFriendsNameButtonDragName:SetText(getglobal(self:GetName().."Name"):GetText());
	HF_Moving_Friend:ClearAllPoints();
	HF_Moving_Friend:SetPoint("TOPLEFT", "UIParent", "BOTTOMLEFT", cursorX+5, cursorY);
	HF_Moving_Friend:Show();
	HF_Moving_Friend:StartMoving();
end


-- drag and drop function
function HoloFriends_NameButton_OnDragStop()
	if ( not HF_Moving_Friend ) then
		return;
	end

	HF_Moving_Friend:StopMovingOrSizing();
	HF_Moving_Friend:Hide();
	HF_Moving_Friend:ClearAllPoints();

	if ( not HF_Target_Friend ) then
		return;
	end

	local targetIndex = HF_Target_Friend:GetID();
	if ( targetIndex > table.getn(HF_list) ) then
		targetIndex = table.getn(HF_list);
	end
	HF_list[HF_Moving_Friend:GetID()].group = HF_list[targetIndex].group;
	HF_Moving_Friend = nil;
	HoloFriends_List_Update();
	-- update share window if open
	if ( HoloFriends_ShareFrame:IsShown() ) then
		HoloFriendsShare_OnShow();
	end
end


-- if we clicked on a header, toggle state and select header, otherwise just select entry
function HoloFriends_NameButton_OnClick(self, button)
	HF_LastClicked = self:GetID();
	if ( not HF_LastClicked ) then
		return;
	end

	if ( button == "LeftButton" ) then
		HF_SelectedEntry = HF_LastClicked;
		-- group selected
		if ( HF_list[HF_SelectedEntry].name == "0" ) then
			HF_IsGroupSelected = true;
			HF_list[HF_SelectedEntry].name = "1";
		elseif ( HF_list[HF_SelectedEntry].name == "1" ) then
			HF_IsGroupSelected = true;
			HF_list[HF_SelectedEntry].name = "0";
		else
			-- player selected
			HF_IsGroupSelected = false;
			if ( not HoloFriendsLists_GetNotify(HF_list, HF_SelectedEntry) ) then
				SetWhoToUI(0);
				HoloFriends_interceptWhoResults = GetTime()
				SendWho("n-"..HoloFriendsLists_GetName(HF_list, HF_SelectedEntry));
                                -- this is broken code.  GetNumWhoResults() won't be ready until later.  Hence the need 
                                -- to click on a disconnected user twice for it to actually disappear -- ithilyn 2007/06/06
				local numWho = GetNumWhoResults();
				if ( numWho == 0 ) then
					HF_list[HF_SelectedEntry].connected = false;
				end
			end
		end
		HoloFriends_List_Update();
	else
		HoloFriends_ShowListDropdown(HF_LastClicked);
	end
end


function HoloFriends_CheckBox_OnClick(self)
	if ( self:GetChecked() and GetNumFriends() == HF_Max_Server_Friends ) then
		self:SetChecked(nil);
		HoloFriendsFuncs_SystemMessage(HF_Limit_Alert);
	else
		if ( self:GetChecked() ) then
			PlaySound("igMainMenuOptionCheckBoxOff");
		else
			PlaySound("igMainMenuOptionCheckBoxOn");
		end
		local index = self:GetParent():GetID();
		HF_list[index].remove = nil;
		HoloFriendsLists_SetNotify(HF_list, index, self:GetChecked(), false, "RunUpdate");
	end
end


-- start friends list update and check that it runs only once at time
function HoloFriends_List_Update()
	if ( not HF_Loaded ) then return; end

	if ( (HoloFriends_ListUpdateStartTime ~= HoloFriends_ListUpdateRefTime) and
	     (difftime(time(), HoloFriends_ListUpdateStartTime) < HoloFriends_ListUpdateMaxSec) ) then
		HF_Requested_List_Update = true;
		return;
	end

	HoloFriends_CheckLocalList();
end


-- check the local list
function HoloFriends_CheckLocalList()
	HoloFriends_ListUpdateStartTime = time();
	local index = next(HF_list);
	while ( index ) do
		if ( not HF_list[index].name ) then
			table.remove(HF_list, index);
		else
			if ( HF_list[index].imported ) then
				HF_list[index].imported = nil;
				local notify = (GetNumFriends() < HF_Max_Server_Friends);
				HoloFriendsLists_SetNotify(HF_list, index, notify, false, "RunLocal");
				return;
			end

			if ( HF_list[index].setnote ) then
				HF_list[index].setnote = nil;
				local name = HF_list[index].name;
				local comment = HF_list[index].comment;
				local numNotifyFriends = GetNumFriends();
				local infoIndex;
				for inum = 1, numNotifyFriends do
					local iname = GetFriendInfo(inum);
					if ( iname == name ) then
						infoIndex = inum;
					end
				end
				local MergeNotes = HOLOFRIENDS_OPTIONS.MergeNotes;
				local NotesPriority = HOLOFRIENDS_OPTIONS.NotesPriority;
				if ( infoIndex and not (not MergeNotes and NotesPriority) ) then
					HoloFriends_SetFriendNotes = true;
					SetFriendNotes(infoIndex, comment);
				end
			end

			-- remove wrong level entries
			local level = HF_list[index].level;
			if ( level and (level == UNKNOWN or level == 0 or level == "") ) then
				HF_list[index].level = nil;
			end

			-- remove empty and UNKNOWN fields (some time area is an empty string and one can share old data)
			if ( HF_list[index].area == "" ) then HF_list[index].area = nil; end
			if ( HF_list[index].area == UNKNOWN ) then HF_list[index].area = nil; end
			if ( HF_list[index].lc_class == "" ) then HF_list[index].lc_class = nil; end
			if ( HF_list[index].lc_class == UNKNOWN ) then HF_list[index].lc_class = nil; end
			if ( HF_list[index].class == "" ) then HF_list[index].class = nil; end
			if ( HF_list[index].class == UNKNOWN ) then HF_list[index].class = nil; end
			-- check if the class entry is valid
			if ( HF_list[index].class ) then
				if ( not HF_Class_Icon_Tcoords[HF_list[index].class] ) then
					HF_list[index].class = nil;
					HF_list[index].lc_class = nil;
				end
			end

			if ( HF_list[index].lc_class and not HF_list[index].class ) then
				HF_list[index].class = HFF_GetClassFromLCClass(HF_list[index].lc_class);
			end

			-- remove list entry of friend, because it was only a temporary add
			local tmpadd = HF_list[index].tmpadd;
			local tmpremove = false;
			if ( tmpadd and not HF_list[index].notify ) then
				if ( time() - tmpadd > HF_Friends_Remote_Add_Delay ) then tmpremove = true; end
			end
			if ( tmpremove ) then
				table.remove(HF_list, index);
			else
				index = next(HF_list, index);
			end
		end
	end

	-- check if group "friends" is in our list
	if ( not HoloFriendsFuncs_ContainsGroup(HF_list, FRIENDS) ) then
		HFF_InsertNewEntry("1", FRIENDS);
	end

	-- set actual date in seconds to the friends group (indicator of last use)
	local index = HoloFriendsFuncs_ContainsGroup(HF_list, FRIENDS)
	HF_list[index].lastuse = time();

	HoloFriends_CheckServerList();
end


-- check if all entries in server friendslist are in our list
function HoloFriends_CheckServerList()
	HoloFriends_ListUpdateStartTime = time();
	local NotFaction = HoloFriendsFuncs_IsDataAvailable(HOLOFRIENDS_LIST, UnitName("player"));
	local numNotifyFriends;
	local maxi;

	-- cleanup
	maxi = table.getn(HF_list);
	for index = 1, maxi do
		if ( HF_list[index].notify and (HF_list[index].notify ~= 3) ) then HF_list[index].notify = 2; end
	end

	HF_GetServerList = true;
	ShowFriends(); -- reload friends list from server
	numNotifyFriends = GetNumFriends();

	-- data we get from server
	local name, level, lc_class, area, connected;
	local onstate; -- AFK or DND
	local note; -- Friends note
	local playerIndex;

	-- check if all friends are in our list (maybe ShowFriends() needs to be executed to get the actual list from server)
	for index = 1, numNotifyFriends do
		local class = UNKNOWN;
		name, level, lc_class, area, connected, onstate, note = GetFriendInfo(index);

		-- reset timer independet of message hook
		if ( GetTime() > (HoloFriends_interceptWhoResults + HoloFriends_whoCheckInterval) ) then
			HoloFriends_interceptWhoResults = 0;
		end

		playerIndex = HoloFriendsFuncs_ContainsPlayer(HF_list, name);
		if ( playerIndex ) then
			class = HF_list[playerIndex].class;
			if ( not class ) then class = UNKNOWN; end

			if ( not connected ) then lc_class = HF_list[playerIndex].lc_class; end
			-- look for lc_class in list to set class
			if ( lc_class and (lc_class ~= UNKNOWN) and (class == UNKNOWN) ) then
				class = HFF_GetClassFromLCClass(lc_class, UNKNOWN);
			end
			-- get the server class by a who request if still UNKNOWN
			if ( connected and (class == UNKNOWN) ) then
				class = HFF_GetClassFromWho(name)
			end

			if ( level ~= 0 ) then HF_list[playerIndex].level = level; end
			if ( class ~= UNKNOWN ) then HF_list[playerIndex].class = class; end
			if ( lc_class ~= UNKNOWN ) then HF_list[playerIndex].lc_class = lc_class; end
			if ( area ~= UNKNOWN ) then HF_list[playerIndex].area = area; end

			HF_list[playerIndex].connected = connected;
			if ( connected == 1 ) then
				HF_list[playerIndex].onstate = onstate;
				HF_list[playerIndex].lastSeen = date("%Y-%m-%d %H:%M %w");
			else
				HF_list[playerIndex].onstate = nil;
			end

			-- check and sync online note and list comment
			HoloFriendsFuncs_CheckComment(playerIndex, index, note);

			if ( NotFaction ) then
				if ( HF_list[playerIndex].notify and (HF_list[playerIndex].notify == 3) ) then
					HoloFriendsLists_SetNotify(HF_list, playerIndex, nil, true, "RunServer");
					return;
				else
					HF_list[playerIndex].notify = true;
				end

				-- remove player scan mark and add friend permanent
				local tmpadd = HF_list[playerIndex].tmpadd;
				if ( tmpadd ) then
					if ( time() - tmpadd > HF_Friends_Remote_Add_Delay ) then
						HF_list[playerIndex].tmpadd = nil;
					end
				end
			elseif ( not HF_list[playerIndex].notify ) then
				HoloFriendsLists_SetNotify(HF_list, playerIndex, nil, false, "RunServer");
				return;
			else
				HF_list[playerIndex].notify = true;
			end
		else
			if ( NotFaction ) then
				-- lock for lc_class in list to set class
				if ( lc_class and (lc_class ~= UNKNOWN) and (class == UNKNOWN) ) then
					class = HFF_GetClassFromLCClass(lc_class, UNKNOWN);
				end
				-- get the server class by a who request if still UNKNOWN
				if ( connected and (class == UNKNOWN) ) then
					class = HFF_GetClassFromWho(name);
				end

				HFF_InsertNewEntry(name, nil, level, class, lc_class, area, note, connected, onstate, true);

				-- temporary add timestamp in seconds
				-- to allow player scan by adding und removing from friends list by other addons
				playerIndex = HoloFriendsFuncs_ContainsPlayer(HF_list, name);
				if ( playerIndex ) then HF_list[playerIndex].tmpadd = time(); end
			else
				if ( name and (name ~= "") ) then
					-- remove from server list
					HoloFriends_SetNotifyOff = true;
					HoloFriends_ignoreRemoveFriendsSysMsg = true;
					HoloFriends_RunListUpdate = "RunServer";
					RemoveFriend(name);
					local msg = format(HOLOFRIENDS_MSGFRIENDONLINEDISABLED, name);
					HoloFriendsFuncs_SystemMessage(msg);
					return;
				end
			end
		end
	end

	-- check if all online friends are still there
	maxi = table.getn(HF_list);
	for index = 1, maxi do
		if ( HF_list[index].notify and (HF_list[index].notify == 3) ) then
			local msg = format(TEXT(HOLOFRIENDS_MSGFRIENDMISSINGONLINE), HF_list[index].name);
			HoloFriendsFuncs_SystemMessage(msg);
			HF_list[index].notify = false;
		end
		if ( HF_list[index].notify and (HF_list[index].notify == 2) ) then
			local silent = false;
			if ( NotFaction ) then silent = true; end
			HoloFriendsLists_SetNotify(HF_list, index, 3, silent, "RunServer");
			return;
		end
	end

	-- sort the entries
	table.sort(HF_list, HoloFriendsFuncs_CompareEntry);

	-- get some counter
	local onlineCounter = 0;
	local sumCounter = 0;
	maxi = table.getn(HF_list);
	for index = 1, maxi do
		if ( (HF_list[index].name ~= "0") and (HF_list[index].name ~= "1") ) then
			sumCounter = sumCounter + 1;
			if ( HF_list[index].connected ) then
				onlineCounter = onlineCounter + 1;
			end
		end
	end

	HoloFriends_UpdateFriendsList(onlineCounter, sumCounter, numNotifyFriends);
end


-- update scrollframe & buttons
function HoloFriends_UpdateFriendsList(onlineCounter, sumCounter, notifyCounter)
	HoloFriends_ListUpdateStartTime = time();
	local visibleCnt;

	-- get visible index table
	local ShownIndexTable = {};
	ShownIndexTable = HoloFriendsFuncs_ShownListTab(HF_list, HF_Displayed_Names, 1);

	-- get counter of visible elements
	local numElementsTable = table.getn(ShownIndexTable);
	local visibleCnt = 0;
	if ( numElementsTable > HF_Displayed_Names ) then
		visibleCnt = numElementsTable;
	else
		local numElementsList = table.getn(HF_list);
		for ii = 1, numElementsTable do
			if ( ShownIndexTable[ii] <= numElementsList ) then
				visibleCnt = visibleCnt + 1;
			end
		end
	end

	-- enable/disable remove-buttons
	if ( HoloFriends_IsSelectedEntryValid() ) then
		if ( HoloFriendsFuncs_IsGroup(HF_list, HF_SelectedEntry) ) then
			if ( HoloFriendsLists_GetGroup(HF_list, HF_SelectedEntry) ~= FRIENDS ) then
				HoloFriendsRemoveGroupButton:Enable();
				HoloFriendsRemoveFriendButton:Disable();
			else
				HoloFriendsRemoveGroupButton:Disable();
				HoloFriendsRemoveFriendButton:Disable();
			end
		else
			HoloFriendsRemoveGroupButton:Disable();
			HoloFriendsRemoveFriendButton:Enable();
		end
	else
		HoloFriendsRemoveGroupButton:Disable();
		HoloFriendsRemoveFriendButton:Disable();
	end

	-- ScrollFrame stuff
	local ScrollBarSize = 0;
	if ( visibleCnt > HF_Displayed_Names ) then ScrollBarSize = 20; end
	FauxScrollFrame_Update(HoloFriendsScrollFrame, visibleCnt, HF_Displayed_Names, 16);
	-- scrollframe offset
	local offset = FauxScrollFrame_GetOffset(HoloFriendsScrollFrame);

	-- button and textures
	local button, buttonText, buttonIcon, buttonCheckBox;

	local entry = {};
	local line = 1;

	-- set values / textures for all buttons
	while ( line <= HF_Displayed_Names ) do
		local index = ShownIndexTable[line + offset];

		entry = HF_list[index];

		button = getglobal("HoloFriendsNameButton"..line);
		button:SetID(index);

		if ( line > visibleCnt ) then
			button:Hide();
		else
			button:Show();
		end

		if ( entry ) then
			if ( index == HF_SelectedEntry ) then
				button:LockHighlight();
			else
				button:UnlockHighlight();
			end

			-- get textfield, icon and checkbox
			buttonText = getglobal("HoloFriendsNameButton"..line.."Name");
			buttonTextRemove = getglobal("HoloFriendsNameButton"..line.."NameRemove");
			buttonIcon = getglobal("HoloFriendsNameButton"..line.."ClassIcon");
			buttonCheckBox = getglobal("HoloFriendsNameButton"..line.."Server");

			-- group entry ?
			if ( entry.name == "1" or entry.name == "0" ) then
				buttonText:SetText(entry.group);
				buttonTextRemove:SetText("");
				buttonText:ClearAllPoints();
				buttonText:SetPoint("TOPLEFT", "HoloFriendsNameButton"..line, "TOPLEFT", 20, 0);
				buttonIcon:Hide();
				buttonCheckBox:Hide();

				if ( entry.name == "1" ) then
					button:SetNormalTexture("Interface\\Buttons\\UI-MinusButton-Up");
				else
					button:SetNormalTexture("Interface\\Buttons\\UI-PlusButton-Up");
				end
			else -- no, we got a player
				local textwidth;
				local name = HoloFriendsLists_GetName(HF_list, index, HOLOFRIENDS_OPTIONS.ShowClassColor);
				local area = HoloFriendsLists_GetArea(HF_list, index, true);
				local note = HoloFriendsLists_GetComment(HF_list, index);
				local info;
				if ( HOLOFRIENDS_TURNINFO == 0 ) then
					info = area;
				else
					info = note;
				end
				if ( entry.remove ) then
					buttonText:SetText(name);
					textwidth = buttonText:GetStringWidth();
				end
				if ( entry.connected ) then
					buttonText:SetText(format(TEXT(HF_List_Online_Template), name, info));
				else
					buttonText:SetText(format(TEXT(HF_List_Offline_Template), name, info));
				end
				if ( entry.remove ) then
					buttonTextRemove:SetText("|cffffffff_____________________________________|r");
					buttonTextRemove:SetWidth(textwidth + 10);
				else
					buttonTextRemove:SetText("");
				end
				button:SetNormalTexture("");
				buttonCheckBox:Show();
				buttonCheckBox:SetChecked(entry.notify);
				local class = entry.class;
				if ( HOLOFRIENDS_OPTIONS.ShowClassIcons ) then
					local coords;
					if ( class and (class ~= UNKNOWN) ) then
						coords = HF_Class_Icon_Tcoords[class];
					end
					if ( coords ) then
						buttonIcon:SetTexCoord(coords[1], coords[2], coords[3], coords[4]);
					else
						buttonIcon:SetTexCoord(0.5, 0.75, 0.5, 0.75);
					end
					buttonIcon:Show();
					buttonText:ClearAllPoints();
					buttonText:SetPoint("LEFT", buttonIcon:GetName(), "RIGHT", 3, 0);
				else
					buttonText:ClearAllPoints();
					buttonText:SetPoint("TOPLEFT", "HoloFriendsNameButton"..line, "TOPLEFT", 36, 0);
					buttonIcon:Hide();
				end
			end

			-- Set the button text with
			button:SetWidth(315 - ScrollBarSize);
			if ( buttonIcon:IsShown() ) then
				buttonText:SetWidth(260 - ScrollBarSize);
			else
				buttonText:SetWidth(276 - ScrollBarSize);
			end
		end

		line = line + 1;
	end

	HoloFriendsOnline:SetText(format("%s %d / %d (%d)", HOLOFRIENDS_WINDOWMAINNUMBERONLINE, onlineCounter, sumCounter, notifyCounter));

	HoloFriends_ListUpdateStartTime = HoloFriends_ListUpdateRefTime;

	if ( HF_Requested_List_Update ) then
		HF_Requested_List_Update = false;
		HoloFriends_List_Update();
	end
end


function HoloFriends_OnHide()
	HoloFriends_DeselectEntry();
end


function HoloFriends_DeselectEntry()
	HF_SelectedEntry = -1;
end

-- END OF GUI FUNCTIONS
