-- Dark Portal data collector

-- SavedVariables
dp_mobs = {};
dp_items = {};
dp_profile = {};
dp_errors = {};
dp_settings = {};
dp_quests = {};

DP_VERSION = "1.0.201 BETA";

local dp_magic = {"holy", "fire", "nature", "frost", "shadow", "arcane"};
local dp_rating = {"weapon", "defense", "dodge", "parry", "block", "hitmelee", "hitranged", "hitspell", "critmelee", "critranged", "critspell", "hittakenmelee", "hittakenranged", "hittakenspell", "crittakenmelee", "crittakenranged", "crittakenspell", "hastemelee", "hasteranged", "hastespell"};
local dp_stat = {"strength", "agility", "stamina", "intellect", "spirit"};
local dp_sex = {"u", "m", "f"};
local dp_slot = {"head", "neck", "shoulder", "shirt", "chest", "waist", "legs", "feet", "wrist", "hands", "finger1", "finger2", "trinket1", "trinket2", "back", "mainhand", "offhand", "ranged", "tabard", "bag1", "bag2", "bag3", "bag4"};
dp_slot[0] = "ammo";
local dp_mode = {"heavy", "light"};
local dp_scans = {
	["SELFKILLOTHER"] = SELFKILLOTHER,
	["PARTYKILLOTHER"] = PARTYKILLOTHER,
	["COMBATHITCRITOTHERSELF"] = COMBATHITCRITOTHERSELF,
	["COMBATHITCRITSCHOOLOTHERSELF"] = COMBATHITCRITSCHOOLOTHERSELF,
	["COMBATHITOTHERSELF"] = COMBATHITOTHERSELF,
	["COMBATHITSCHOOLOTHERSELF"] = COMBATHITSCHOOLOTHERSELF,
	["LOOT_ITEM_CREATED_SELF"] = LOOT_ITEM_CREATED_SELF,
	["LOOT_ITEM_CREATED_SELF_MULTIPLE"] = LOOT_ITEM_CREATED_SELF_MULTIPLE,
	["LOOT_ITEM_PUSHED_SELF"] = LOOT_ITEM_PUSHED_SELF,
	["LOOT_ITEM_PUSHED_SELF_MULTIPLE"] = LOOT_ITEM_PUSHED_SELF_MULTIPLE,
	["LOOT_ITEM_SELF"] = LOOT_ITEM_SELF,
	["LOOT_ITEM_SELF_MULTIPLE"] = LOOT_ITEM_SELF_MULTIPLE,
	["LOOT_ROLL_YOU_WON"] = LOOT_ROLL_YOU_WON
};

local dp_character;
local dp_realm;
local dp_locale;
local dp_player;
local dp_oldLogout, dp_oldQuit, dp_oldError;
local dp_lastmob = {
	["time"] = 0,
	["msg"] = "",
};
local dp_sessionStart = 0;
local dp_session = 0;
local dp_lastunit = "";

-- EVENTS

function dp_PLAYER_LOGIN()
	dp_Hook();
	dp_realm = GetCVar("realmName");
	dp_player, _ = UnitName("player");
	dp_locale = GetLocale();
	dp_character = dp_Concat(dp_realm, dp_player);
	if (not dp_profile[dp_character]) then
		dp_profile[dp_character] = {};
	end
	if (not dp_profile[dp_character]["stats"]) then
		dp_profile[dp_character]["stats"] = {};
	end
	if (not dp_settings) then
		dp_settings = {};
	end
	if (not dp_settings["mode"]) then
		dp_settings["mode"] = 2;
	end
	dp_RequestTimePlayed();
end

function dp_PLAYER_LOGOUT()
	dp_Unhook();
--	RequestTimePlayed();
--	dp_Inspect("player");
end

function dp_PLAYER_LEVEL_UP(level)
	if (dp_character) then
		if (not dp_profile[dp_character]["levels"]) then
			dp_profile[dp_character]["levels"] = {};
		end
		dp_profile[dp_character]["levels"][level] = dp_Concat(dp_Played(), time());
	end
end

function dp_TIME_PLAYED_MSG(tot, cur)
	if (dp_sessionStart == 0) then
		dp_sessionStart = tot;
	end
end

function dp_CHAT_MSG(msg, trusted)
	for id, enchant, subId, name in string.gmatch(msg, "|c%x+|Hitem:(%d+):(%d+):(%d+):%d+:%d+:%d+:%d+:%d+|h%[(.+)%]|h|r") do
		if (id and name and name ~= "") then
			id = tonumber(id);
			if (trusted or not dp_items[id]) then
				dp_Item(itemId);
				if (string.find(msg, dp_scans["LOOT_ITEM_CREATED_SELF"]) or string.find(msg, dp_scans["LOOT_ITEM_CREATED_SELF_MULTIPLE"]) or string.find(msg, dp_scans["LOOT_ITEM_PUSHED_SELF"]) or string.find(msg, dp_scans["LOOT_ITEM_PUSHED_SELF_MULTIPLE"]) or string.find(msg, dp_scans["LOOT_ITEM_SELF"]) or string.find(msg, dp_scans["LOOT_ITEM_SELF_MULTIPLE"]) or string.find(msg, dp_scans["LOOT_ROLL_YOU_WON"])) then
					if (not dp_profile[dp_character]["loot"]) then
						dp_profile[dp_character]["loot"] = {};
					end
					dp_profile[dp_character]["loot"][id] = time();
				end
			end
		end
	end
end

function dp_LOOT_OPENED()
  if (not UnitName("target")) then
    return;
  end
  if (not UnitIsDead("target")) then
    return;
  end
	if (UnitIsPlayer("target")) then
		return;
	end
	local i, id, subId, enchant, name, texture;
	for i = 1, GetNumLootItems() do
		if (LootSlotIsItem(i)) then
			id, subId, enchant, name = dp_ItemLink(GetLootSlotLink(i));
			dp_Item(id);
		end
	end
end

function dp_QUEST_LOG_UPDATE()
	dp_profileQuests();
end

function dp_PLAYER_TARGET_CHANGED(target) -- for mobs/npc
	if (not target or (target ~= "target" and target ~= "mouseover")) then
		return;
	end
	if (UnitExists(target)) then
		local name = UnitName(target);
		local level = UnitLevel(target);
		if (name == dp_lastunit or name == UNKNOWNOBJECT or name == UNKNOWNBEING or UnitIsPlayer(target) or UnitIsDeadOrGhost(target) or UnitPlayerControlled(target) or UnitIsUnit(target, "pet") or level == 0) then
			return;
		end
		if (not dp_mobs) then
			dp_mobs = {};
		end
		dp_lastunit = name;
		if (not dp_mobs[name]) then
			local class = UnitClass(target);
			dp_mobs[name] = {
				["desc"] = dp_Concat(UnitClassification(target), class, UnitCreatureType(target), UnitCreatureFamily(target)),
				["min"] = level,
				["max"] = level,
			};
		else
			if (level > 0 and level > dp_mobs[name]["max"]) then
				dp_mobs[name]["max"] = level;
			end
			if (level > 0 and (level < dp_mobs[name]["min"] or dp_mobs[name]["min"] < 0)) then
				dp_mobs[name]["min"] = level;
			end
		end
	end
end

function dp_UPDATE_MOUSEOVER_UNIT(target) -- for players' items
	if (UnitIsPlayer(target)) then
		for i, _ in pairs(dp_slot) do
			link = GetInventoryItemLink(target, i);
			if (link) then
				local itemId, subId, enchant, itemName = dp_ItemLink(link);
				dp_Item(itemId);
  	 	end
		end
	end
end

function dp_CHAT_MSG_COMBAT_HOSTILE_DEATH(msg)
	local name;
	if (not dp_profile[dp_character]["stats"]["kills"]) then
		dp_profile[dp_character]["stats"]["kills"] = 1;
	else
		dp_profile[dp_character]["stats"]["kills"] = dp_profile[dp_character]["stats"]["kills"] + 1;
	end
	_, _, name = string.find(msg, dp_scans["SELFKILLOTHER"]);
	if (not name) then
		_, _, name = string.find(msg, dp_scans["PARTYKILLOTHER"]);
	end
	if (name) then
		if (not dp_mobs[name]) then
			return;
		end
		if (not dp_profile[dp_character]["killed"]) then
			dp_profile[dp_character]["killed"] = {};
		end
		if (not dp_profile[dp_character]["killed"][name]) then
			dp_profile[dp_character]["killed"][name] = 1;
		else
			dp_profile[dp_character]["killed"][name] = dp_profile[dp_character]["killed"][name] + 1;
		end
	end
end

function dp_CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS(msg)
	dp_lastmob = { ["time"] = time(), ["msg"] = msg };
end

function dp_PLAYER_DEAD()
	local name;
	if (not dp_profile[dp_character]["stats"]["died"]) then
		dp_profile[dp_character]["stats"]["died"] = 1;
	else
		dp_profile[dp_character]["stats"]["died"] = dp_profile[dp_character]["stats"]["died"] + 1;
	end
	if (not dp_profile[dp_character]["deaths"]) then
		dp_profile[dp_character]["deaths"] = {};
	end
	if (dp_lastmob["msg"] ~= "" and (time() - dp_lastmob["time"] < 15)) then
		_, _, name = string.find(dp_lastmob["msg"], dp_scans["COMBATHITOTHERSELF"]);
		if (not name) then
			_, _, name = string.find(dp_lastmob["msg"], dp_scans["COMBATHITSCHOOLOTHERSELF"])
		end
		if (not name) then
			_, _, name = string.find(dp_lastmob["msg"], dp_scans["COMBATHITCRITOTHERSELF"]);
		end
		if (not name) then
			_, _, name = string.find(dp_lastmob["msg"], dp_scans["COMBATHITCRITSCHOOLOTHERSELF"]);
		end
		if (name) then
			if (dp_mobs[name]) then
				dp_profile[dp_character]["deaths"][time()] = name;
			end
		end
	end
end

function dp_BANKFRAME_CLOSED()
	dp_profile[dp_character]["bank"] = {};
	for _, bag in pairs({-1, 5, 6, 7, 8, 9, 10, 11}) do
		dp_Bag(bag, "bank");
	end
end

function dp_MERCHANT_SHOW()
	if (not UnitName("target")) then
		return;
	end
	for i = 1, GetMerchantNumItems() do
		local name, texture, price, count, available, usable = GetMerchantItemInfo(i);
		if (name) then
			local link = GetMerchantItemLink(i);
			if (link) then
				local id, subId, enchant, name = dp_ItemLink(link);
				dp_Item(id);
			end
		end
	end
end

function dp_TRADE_SKILL_SHOW()
	if (not	dp_profile[dp_character]["tradeskills"]) then
		dp_profile[dp_character]["tradeskills"] = {};
	end
	local header = "temp"
	for i = 1, GetNumTradeSkills() do
		local name, type, _, isExpanded = GetTradeSkillInfo(i);
		if (name) then
			if (type == "header") then
				header = name;
				if (not isExpanded) then
--					ExpandSkillHeader(i);
				end
			else
				icon = GetTradeSkillIcon(i);
				skill = GetTradeSkillLine(i);
				itemId, _ = dp_ItemLink(GetTradeSkillItemLink(i));
				dp_Item(itemId);
				if (not dp_profile[dp_character]["tradeskills"][skill]) then
					dp_profile[dp_character]["tradeskills"][skill] = {};
				end
				dp_profile[dp_character]["tradeskills"][skill][name] = dp_Concat(header, icon, itemId);
			end
		end
	end
end

function dp_CRAFT_SHOW()
	if (not	dp_profile[dp_character]["crafting"]) then
		dp_profile[dp_character]["crafting"] = {};
	end
	local header = "temp";
	for i = 1, GetNumCrafts() do
		local name, subspell, type, _, isExpanded = GetCraftInfo(i);
		if (name) then
			if (type == "header") then
				header = name;
				if (not isExpanded) then
--					ExpandSkillHeader(i);
				end
			else
				craft = GetCraftName();
				desc = GetCraftDescription(i);
				icon = GetCraftIcon(i);
				craft = GetCraftSkillLine(i);
				itemId, _ = dp_ItemLink(GetCraftItemLink(i));
				dp_Item(itemId);
				if (not dp_profile[dp_character]["crafting"][craft]) then
					dp_profile[dp_character]["crafting"][craft] = {};
				end
				dp_profile[dp_character]["crafting"][craft][name] = dp_Concat(desc, icon, itemId, subspell);
			end
		end
	end
end

-- NORMAL FUNCTIONS

function dp_Inspect(unit)
	if (unit == "player") then
		-- basic stats
		if (not dp_profile[dp_character]["stats"]) then
			dp_profile[dp_character]["stats"] = {};
		end
		for i = 1, table.getn(dp_stat) do
			dp_profile[dp_character]["stats"][dp_stat[i]] = dp_Concat(UnitStat("player", i));
		end
		dp_profile[dp_character]["locale"] = dp_locale;
		dp_profile[dp_character]["stats"]["faction"], _ = UnitFactionGroup("player");
		dp_profile[dp_character]["stats"]["race"] = UnitRace("player");
		dp_profile[dp_character]["stats"]["class"] = UnitClass("player");
		dp_profile[dp_character]["stats"]["sex"] = dp_sex[UnitSex("player")];
		dp_profile[dp_character]["stats"]["armor"] = dp_Concat(UnitArmor("player"));
		dp_profile[dp_character]["stats"]["defense"] = dp_Concat(UnitDefense("player"));
		dp_profile[dp_character]["stats"]["attack"] = dp_Concat(UnitAttackBothHands("player"));
		dp_profile[dp_character]["stats"]["ap"] = dp_Concat(UnitAttackPower("player"));
		dp_profile[dp_character]["stats"]["damage"] = dp_Concat(UnitDamage("player"));
		dp_profile[dp_character]["stats"]["rangedattack"] = dp_Concat(UnitRangedAttack("player"));
		dp_profile[dp_character]["stats"]["rangedap"] = dp_Concat(UnitRangedAttackPower("player"));
		dp_profile[dp_character]["stats"]["rangeddamage"] = dp_Concat(UnitRangedDamage("player"));
		dp_profile[dp_character]["stats"]["dodge"] = GetDodgeChance();
		dp_profile[dp_character]["stats"]["block"] = GetBlockChance();
		dp_profile[dp_character]["stats"]["parry"] = GetParryChance();
		dp_profile[dp_character]["stats"]["crit"] = GetCritChance();
		dp_profile[dp_character]["stats"]["rangedcrit"] = GetRangedCritChance();
		dp_profile[dp_character]["stats"]["experience"] = dp_Concat(UnitXP("player"), UnitXPMax("player"));
		dp_profile[dp_character]["stats"]["health"] = UnitHealthMax("player");
		dp_profile[dp_character]["stats"]["mana"] = UnitManaMax("player");
		dp_profile[dp_character]["stats"]["money"] = GetMoney();
		dp_profile[dp_character]["stats"]["level"] = UnitLevel("player");
		dp_profile[dp_character]["stats"]["played"] = dp_Played();
		dp_profile[dp_character]["stats"]["resists"] = {};
		dp_profile[dp_character]["stats"]["spellcrit"] = {};
		for i = 1, table.getn(dp_magic) do
			local base, resistance, pos, neg = UnitResistance("player", i);
			dp_profile[dp_character]["stats"]["resists"][dp_magic[i]] = dp_Concat(base, resistance, pos, neg);
			dp_profile[dp_character]["stats"]["spellcrit"][dp_magic[i]] = GetSpellCritChance(i);
		end
		dp_profile[dp_character]["stats"]["hitrating"] = {};
		for i = 1, table.getn(dp_rating) do
			dp_profile[dp_character]["stats"]["hitrating"][dp_rating[i]] = dp_Concat(GetCombatRating(i), GetCombatRatingBonus(i));
		end
		if (IsInGuild()) then
			dp_profile[dp_character]["stats"]["guild"] = dp_Concat(GetGuildInfo("player"));
		else
			dp_profile[dp_character]["stats"]["guild"] = dp_Concat(0, 0, 0);
		end
		dp_profile[dp_character]["build"] = dp_Concat(GetBuildInfo());

		dp_profileSkills();
		dp_profileTalents();
		dp_profileSpells();
		dp_profileReputation();
  	dp_profileQuests();
		dp_profileEquipment();
		dp_profileInventory();
	end
end

function dp_Hook()
	for i, scan in pairs(dp_scans) do
		scan = string.gsub(scan, "%(", "%%%(")
		scan = string.gsub(scan, "%)", "%%%)")
		scan = string.gsub(scan, "(%%s)", "%(%.%+%)")
		scan = string.gsub(scan, "(%%%d$s)", "%(%.%+%)")
		scan = string.gsub(scan, "(%%d)", "%(%%%d%+%)")
		scan = string.gsub(scan, "(%%%d$d)", "%(%%%d%+%)")
		dp_scans[i] = scan
	end
	dp_oldLogout = Logout;
	Logout = dp_Logout;
	dp_oldQuit = Quit;
	Quit = dp_Quit;
--	dp_oldError = geterrorhandler();
--	seterrorhandler(dp_Error);
end

function dp_Unhook()
	Logout = dp_oldLogout;
	Quit = dp_oldQuit;
--	seterrorhandler(dp_oldError);
end

function dp_Logout(...)
	dp_RequestTimePlayed();
	dp_Inspect("player");
	dp_oldLogout({...});
end

function dp_Quit(...)
	dp_RequestTimePlayed();
	dp_Inspect("player");
	dp_oldQuit({...});
end

function dp_OnLoad()
	this:RegisterEvent("PLAYER_LOGIN");
	this:RegisterEvent("PLAYER_LOGOUT");
	-- Mob
	this:RegisterEvent("PLAYER_TARGET_CHANGED");
	this:RegisterEvent("UPDATE_MOUSEOVER_UNIT");
	this:RegisterEvent("CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS");
	this:RegisterEvent("CHAT_MSG_COMBAT_HOSTILE_DEATH");
	-- Quest
	this:RegisterEvent("QUEST_LOG_UPDATE");
	-- Items
	this:RegisterEvent("MERCHANT_SHOW");
	this:RegisterEvent("LOOT_OPENED");
	this:RegisterEvent("BANKFRAME_CLOSED");
	-- Auction
--	this:RegisterEvent("AUCTION_ITEM_LIST_UPDATE");
	-- Chat
	this:RegisterEvent("CHAT_MSG_CHANNEL");
	this:RegisterEvent("CHAT_MSG_PARTY");
	this:RegisterEvent("CHAT_MSG_RAID");
	this:RegisterEvent("CHAT_MSG_SAY");
	this:RegisterEvent("CHAT_MSG_WHISPER");
	this:RegisterEvent("CHAT_MSG_GUILD");
	this:RegisterEvent("CHAT_MSG_YELL");
	this:RegisterEvent("CHAT_MSG_LOOT");
	-- Profile
	this:RegisterEvent("TIME_PLAYED_MSG");
	this:RegisterEvent("PLAYER_LEVEL_UP");
	this:RegisterEvent("PLAYER_DEAD");
	this:RegisterEvent("CRAFT_SHOW");
	this:RegisterEvent("TRADE_SKILL_SHOW");

	SLASH_DARKPORTAL1 = "/dp";
	SLASH_DARKPORTAL2 = "/darkportal";
	SlashCmdList["DARKPORTAL"] = dp_Command;

	DEFAULT_CHAT_FRAME:AddMessage("|c" .. DP_COLOR_DP .. DP_SCRIPTNAME .. " " .. DP_VERSION .. " |cffffffff" .. DP_MSG_LOADED);
	DEFAULT_CHAT_FRAME:AddMessage(DP_MSG_COMMAND);
end

function dp_OnEvent()
	if (event == "PLAYER_LOGIN") then
		dp_PLAYER_LOGIN();
	elseif (event == "PLAYER_LOGOUT") then
		dp_PLAYER_LOGOUT();
	elseif (event == "TIME_PLAYED_MSG") then
		dp_TIME_PLAYED_MSG(arg1, arg2);
	elseif (event == "PLAYER_LEVEL_UP") then
		dp_RequestTimePlayed();
		dp_PLAYER_LEVEL_UP(arg1);
	elseif (event == "LOOT_OPENED") then
		dp_LOOT_OPENED();
	elseif (event == "BANKFRAME_CLOSED") then
		dp_BANKFRAME_CLOSED();
	elseif (event == "MERCHANT_SHOW") then
		dp_MERCHANT_SHOW();
	elseif (event == "QUEST_LOG_UPDATE") then
		dp_QUEST_LOG_UPDATE();
	elseif (event == "PLAYER_TARGET_CHANGED") then
		dp_PLAYER_TARGET_CHANGED("target");
	elseif (event == "TRADE_SKILL_SHOW") then
		dp_TRADE_SKILL_SHOW();
	elseif (event == "CRAFT_SHOW") then
		dp_CRAFT_SHOW();
	elseif (event == "CHAT_MSG_COMBAT_HOSTILE_DEATH") then
		dp_CHAT_MSG_COMBAT_HOSTILE_DEATH(arg1);
	elseif (event == "CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS") then
		dp_CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS(arg1);
	elseif (event == "PLAYER_DEAD") then
		dp_PLAYER_DEAD();
	elseif (event == "CHAT_MSG_LOOT") then
		dp_CHAT_MSG(arg1, true);
	elseif (dp_settings["mode"] == 1) then
		if (event == "CHAT_MSG_CHANNEL" or event == "CHAT_MSG_PARTY" or event == "CHAT_MSG_RAID" or event == "CHAT_MSG_SAY" or event == "CHAT_MSG_WHISPER" or event == "CHAT_MSG_GUILD" or event == "CHAT_MSG_YELL") then
			dp_CHAT_MSG(arg1, false);
		elseif (event == "UPDATE_MOUSEOVER_UNIT") then
			if (UnitIsPlayer("mouseover")) then
				dp_UPDATE_MOUSEOVER_UNIT("mouseover");
			else
				dp_PLAYER_TARGET_CHANGED("mouseover");
			end
		end
	end
end

function dp_OnUpdate()
	dp_session = dp_session + arg1;
end

function dp_Command(str)
	if (dp_settings["mode"] == 2) then
		dp_settings["mode"] = 1;
	else
		dp_settings["mode"] = 2;
	end
	DEFAULT_CHAT_FRAME:AddMessage(DP_MSG_MODE .. " " .. dp_mode[dp_settings["mode"]]);
end

function dp_profileInventory()
	dp_profile[dp_character]["bags"] = {};
	for _, bag in pairs({-2, 0, 1, 2, 3, 4}) do
		dp_Bag(bag, "bags");
	end
end

function dp_profileEquipment()
	dp_profile[dp_character]["equipment"] = {};
	for i, slot in pairs(dp_slot) do
		link = GetInventoryItemLink("player", i);
		quality = GetInventoryItemQuality("player", i);
		if (link) then
			local itemId, subId, enchant, itemName = dp_ItemLink(link);
			dp_Item(itemId);
     	dp_profile[dp_character]["equipment"][i] = dp_Concat(itemId, subId, enchant, 1);
   	else
      dp_profile[dp_character]["equipment"][i] = dp_Concat(0, 0, 0, 0);
   	end
  end
end

function dp_profileSkills()
	dp_profile[dp_character]["skills"] = {};
	local header = ""
	for i = 1, GetNumSkillLines() do
		local skill, isHeader, isExpanded, rank, _, modifier, rankmax = GetSkillLineInfo(i);
		if (skill) then
			if (isHeader) then
				header = skill;
				dp_profile[dp_character]["skills"][header] = {};
				if (not isExpanded) then
					ExpandSkillHeader(i);
				end
			else
				if (not dp_profile[dp_character]["skills"][header]) then
					dp_profile[dp_character]["skills"][header] = {};
				end
				dp_profile[dp_character]["skills"][header][skill] = dp_Concat(rank, rankmax, modifier);
			end
		end
	end
end

function dp_profileTalents()
	dp_profile[dp_character]["talents"] = {};
	for tab = 1, GetNumTalentTabs() do
		local tabName, _, _, _ = GetTalentTabInfo(tab);
		dp_profile[dp_character]["talents"][tabName] = {};
		for i = 1, GetNumTalents(tab) do
			local talent, texture, posY, posX, rank, maxrank = GetTalentInfo(tab, i);
			if (rank > 0) then
				dp_profile[dp_character]["talents"][tabName][talent] = dp_Concat(rank, maxrank, posX, posY, texture);
			end
		end
	end
end

function dp_profileSpells()
	dp_profile[dp_character]["spells"] = {};
	for tab = 1, GetNumSpellTabs() do
		local tabName, _, offset, spells = GetSpellTabInfo(tab);
		dp_profile[dp_character]["spells"][tabName] = {};
		for i = offset + 1, offset + spells do
			local name, rank = GetSpellName(i, BOOKTYPE_SPELL);
			if (not rank or rank == "") then
				rank = "";
			end
			dp_profile[dp_character]["spells"][tabName][i] = dp_Concat(name, rank, GetSpellTexture(i, BOOKTYPE_SPELL));
		end
	end
end

function dp_profileReputation()
	dp_profile[dp_character]["reputation"] = {};
	local header = "";
	for i = 1, GetNumFactions() do
		local faction, _, _, _, _, rep, war, _, isHeader, isCollapsed = GetFactionInfo(i);
		if (faction) then
			if (isHeader) then
				header = faction;
				if (isCollapsed) then
					ExpandFactionHeader(i);
				end
			else
				if (not dp_profile[dp_character]["reputation"][header]) then
					dp_profile[dp_character]["reputation"][header] = {};
				end
				dp_profile[dp_character]["reputation"][header][faction] = dp_Concat(rep, war);
			end
		end
	end
end

function dp_profileQuests()
	this:UnregisterEvent("QUEST_LOG_UPDATE");
	local oldQuest = GetQuestLogSelection();
	dp_profile[dp_character]["quests"] = {};
	if (not dp_profile[dp_character]["completed"]) then
		dp_profile[dp_character]["completed"] = {};
	end
 	local collapsed = {};
	local header = "";
	for i = 1, GetNumQuestLogEntries() do
		local title, level, tag, isHeader, isCollapsed, isComplete = GetQuestLogTitle(i);
		if (title) then
			if (isHeader) then
				header = title;
				dp_profile[dp_character]["quests"][header] = {};
				if (not dp_quests[header]) then
					dp_quests[header] = {};
				end
				collapsed[title] = isCollapsed;
				if (isCollapsed) then
					ExpandQuestHeader(i);
				end
			else
				if (not dp_quests[header][title]) then
					SelectQuestLogEntry(i);
					local desc, obj = GetQuestLogQuestText();
					for j = 1, GetNumQuestLogRewards() do
						local link = GetQuestLogItemLink("reward", j);
						local itemId, subId, enchant, itemName = dp_ItemLink(link);
						dp_Item(itemId);
					end
					for j = 1, GetNumQuestLogChoices() do
						local link = GetQuestLogItemLink("choice", j);
						local itemId, subId, enchant, itemName = dp_ItemLink(link);
						dp_Item(itemId);
					end
					dp_quests[header][title] = dp_Concat(tag, level, dp_QuestText(desc), dp_QuestText(obj));
				end
				if (isComplete == 1) then
					dp_profile[dp_character]["quests"][header][title] = 1;
					if (not dp_profile[dp_character]["completed"][title]) then
						dp_profile[dp_character]["completed"][title] = time();
					end
				elseif (isComplete == -1) then
					dp_profile[dp_character]["quests"][header][title] = -1;
				else
					dp_profile[dp_character]["quests"][header][title] = 0;
				end
			end
		end
	end
	for i = 1, GetNumQuestLogEntries() do
		title, _, _, isHeader, isCollapsed = GetQuestLogTitle(i);
		if (title and isHeader and collapsed[title]) then
			CollapseQuestHeader(i);
		end
	end
	SelectQuestLogEntry(oldQuest);
	this:RegisterEvent("QUEST_LOG_UPDATE");
end

function dp_Bag(bag, type)
	local slots, itemId, enchang, subId, itemName;
	local texture, num, locked, quality, readable;
	if (bag == KEYRING_CONTAINER) then
		slots = GetKeyRingSize();
	else
		slots = GetContainerNumSlots(bag);
	end
	if (not dp_profile[dp_character][type]) then
		dp_profile[dp_character][type] = {};
	end
	dp_profile[dp_character][type][bag] = {};
	if (slots > 0) then
		for slot = 1, slots do
			link = GetContainerItemLink(bag, slot);
			if (link) then
				itemId, subId, enchant, itemName = dp_ItemLink(link);
				texture, num, locked, quality, readable = GetContainerItemInfo(bag, slot);
				if ((not num) or (num < 1)) then
					num = 1;
				end
				dp_Item(itemId);
				dp_profile[dp_character][type][bag][slot] = dp_Concat(itemId, subId, enchant, num);
			else
				dp_profile[dp_character][type][bag][slot] = dp_Concat(0, 0, 0, 0);
			end
		end
	end
end

function dp_Item(id)
	if (not id) then
		return;
	end
	local itemName, link, rarity, level, minlevel, itemType, subtype, stack, loc, texture = GetItemInfo(id);
	if (itemName) then
		DarkPortalTooltip:SetOwner(UIParent, "ANCHOR_NONE")
		DarkPortalTooltip:SetHyperlink("item:" .. id .. ":0:0:0:0:0:0:0");
		dp_items[tonumber(id)] = dp_Concat(itemName, rarity, texture, minlevel, level, itemType, subtype, loc, stack, dp_Tooltip());
	end
end

function dp_ItemLink(link)
	if (link) then
		local id, encId, subId, name = string.match(link, "|c%x+|Hitem:(%d+):(%d+):%d+:%d+:%d+:%d+:(%d+):%d+|h%[(.+)%]|h|r")
		if (id) then
			return tonumber(id), tonumber(subId), tonumber(encId), name;
		end
	end
	return 0, 0, 0, "";
end

function dp_Tooltip(name)
	if (name == nil) then 
		name = "DarkPortalTooltip";
	end
	local ttFrame = getglobal(name);
	local ttText = "";
	local ttTextScan = {};
	for i = 1, ttFrame:NumLines() do
		local ttTextBuff = nil;
		ttText = getglobal(name .. "TextLeft" .. i);
		if (ttText and ttText:IsShown()) then
			ttTextBuff = ttText:GetText();
--			ttTextColor = ttText:GetTextColor();
			ttTextColor = "";
			if (ttText) then
--				ttTextBuff = string.gsub(ttTextBuff, "\n", "<br>");
				ttTextBuff = ttTextColor .. string.gsub(ttTextBuff, "\r", "");
			end
		end
		ttText = getglobal(name .. "TextRight" .. i);
		if (ttText and ttText:IsShown()) then
			if (ttText) then
				local ttTextBuffRight = ttText:GetText();
				if (ttTextBuffRight) then
					ttTextBuff = ttTextBuff .. "\t" .. ttTextBuffRight;
				end
			end
		end
		if (ttTextBuff) then
			table.insert(ttTextScan, ttTextBuff);
		end
	end
	ttFrame:ClearLines();
	return table.concat(ttTextScan, "\n");
end

function dp_Error(err)
	err = err .. "\n" .. debugstack();
	local timestamp = time();
	dp_errors[timestamp] = dp_Concat(err);
	_ERRORMESSAGE(err);
end

function dp_RequestTimePlayed()
	-- request only if over 1min since last request
	if (dp_sessionStart == 0) then
		RequestTimePlayed();
	end
end

function dp_Played()
	if (dp_sessionStart > 0) then
		dp_RequestTimePlayed();
	end
	return dp_sessionStart + math.floor(dp_session);
end

function dp_QuestText(text)
	source = string.gsub(text, UnitName("player") .. "([%s%.,:;%-%(%)%!%?'])", "<Name>%1");
	class = UnitClass("player") .. "([%s%.,:;%-%(%)%!%?'])";
	source = string.gsub(source, class, "<Class>%1");
	source = string.gsub(source, string.lower(class), "<Class>%1");
	race = UnitRace("player") .. "([%s%.,:;%-%(%)%!%?'])";
	source = string.gsub(source, race, "<Race>%1");
	source = string.gsub(source, string.lower(race), "<Race>%1");
	return source;
end


-- GENERAL FUNCTIONS

function dp_Concat(...)
	local str = "";
	for k, v in pairs({...}) do
		if k ~= 1 then
			str = str .. "^";
		end
		if v and v ~= nil then
			str = str .. v;
		else
			str = str .. "0";
		end
	end
	return str;
end

function dp_Time()
	return math.floor(GetTime() * 1000);
end
