

function GHU_Talent(_name,_icon,f)
	local class = GHClass("GHU_Document");
	
	-- local variables
	local tabs = {};
	local unspentPoints = 0;
	local local_SHOW_INSCRIPTION_LEVEL = SHOW_INSCRIPTION_LEVEL;
	local reqPerTier = 5;
	local previewTalents = GetCVarBool("previewTalents");
	local name = _name;
	local orig = {};
	local isHookedAfterLoad,learnFunction;
	
	local shown;
	
	assert(type(f)=="function","Not a function as argument for learn function");
	learnFunction = f;
	
	local Hook = function(funcName)
		assert(type(class[funcName])=="function","No function found for "..funcName);
		orig[funcName] = _G[funcName];
		_G[funcName] = class[funcName];		
	end
	
	
	-- ======================== Internal API ========================
	local GetIndex = function(tabRefID,refID)
		assert(type(tabRefID)=="number" or type(tabRefID)=="string","Illigal tab ref");
		for i=1,#(tabs) do
			if tabs[i].tabRefID == tabRefID then
				if refID then
					for j=1,#(tabs[i].talents) do
						if tabs[i].talents[j].refID == refID then
							return i,j;
						end
					end
				end
				return i;
			end
		end
		return nil;
	end
	
	local SetTooltip = function(b)
		local i = b:GetID();
		local t = PanelTemplates_GetSelectedTab(PlayerTalentFrame);
		local tal = tabs[t].talents[i];
		
		GameTooltip:SetOwner(b, "ANCHOR_RIGHT");
		GameTooltip:AddLine(tal.name,1,1,1);  -- GameTooltipHeader?
		
		-- rank
		GameTooltip:AddLine(format(TOOLTIP_TALENT_RANK,tal.currentRank,tal.maxRank),1,1,1);
		
		-- reqiorement from prereq (TOOLTIP_TALENT_PREREQ)
		if tal.preReqRefID then
			local tabI2,talentI2 = GetIndex(tabs[t].tabRefID,tal.preReqRefID);
			assert(tabI2 == t,"Found in other tab incorrectly.");
			if talentI2 then
				local tal2 = tabs[tabI2].talents[talentI2];
				if not(tal2.currentRank == tal2.maxRank) then
					GameTooltip:AddLine(format(TOOLTIP_TALENT_PREREQ,tal2.maxRank,tal2.name),1,0,0,1,1);
				end
			end
		end
		
		-- requirement tier  ( TOOLTIP_TALENT_TIER_POINTS)
		if not((tabs[t].pointsSpent or 0) >= (tal.tier-1) * (reqPerTier or 5)) then
			GameTooltip:AddLine(format(TOOLTIP_TALENT_TIER_POINTS,(tal.tier-1) * 5,tabs[t].name),1,0,0,1,1);
		end
		
		
		-- white text
		for i,p in pairs(tal.whiteText) do
			if type(p)=="table" then
				GameTooltip:AddDoubleLine(p[1] or "",p[2] or "",1,1,1,1,1,1,1,1,1,1);
			end
		end
		
		-- description
		GameTooltip:AddLine(tal.description,1, 0.82, 0,  1, 1);
	
		GameTooltip:Show();
		
	end
	
	local UpdateEvtTooltip = function()
		if shown then
			if GameTooltip:GetOwner() and GameTooltip:GetOwner():GetName() then
				if string.sub(GameTooltip:GetOwner():GetName() or "",0,23) == "PlayerTalentFrameTalent" then
					SetTooltip(GameTooltip:GetOwner())
				end
			end
		end
	end
	
	-- Functions 
	
	-- ======================== Show / Hide ========================
	class.InitHooksAfterLoad = function()
		if not(isHookedAfterLoad) then
					
			Hook("PlayerTalentFrameTalent_OnEnter")
			Hook("PlayerTalentFrameTalent_OnClick")
			
			-- setup talent buttons
			local button;
			for i = 1, MAX_NUM_TALENTS do
				button = _G["PlayerTalentFrameTalent"..i];
				if ( button ) then
					button:SetScript("OnClick", PlayerTalentFrameTalent_OnClick);
					button:SetScript("OnEvent", PlayerTalentFrameTalent_OnEvent);
					button:SetScript("OnEnter", PlayerTalentFrameTalent_OnEnter);
				end
			end
							
			isHookedAfterLoad = true;
		end
	end
	
	class.Show = function()
		shown = 1;
		-- set glyph level
		SHOW_INSCRIPTION_LEVEL = 255;
		PLAYER_TALENTS_PER_TIER = reqPerTier;
		SetCVar("previewTalents",nil);
		
		if PlayerTalentFrame_OnShow then 
			PlayerTalentFrame_OnShow(); 
			PlayerTalentFrameTitleText:SetText(name);
			PlayerTalentFrameStatusFrame:Hide();
		end
	end
	
	class.Hide = function()
		shown = nil;
		-- set glyph back
		SHOW_INSCRIPTION_LEVEL = local_SHOW_INSCRIPTION_LEVEL;
		PLAYER_TALENTS_PER_TIER = 5;
		SetCVar("previewTalents",previewTalents);
			
		if PlayerTalentFrame_OnShow then PlayerTalentFrame_OnShow(); end
	end
	
	-- ======================== Hooks ========================
	-- GetNumTalentTabs([inspect[,pet]])
	class.GetNumTalentTabs = function(inspect,pet)
		if shown and not(inspect) and not(pet) and not(GHU_Tabs.settingTalentTabs) then
			return #(tabs);
		end
		return orig.GetNumTalentTabs(inspect,pet);
	end
	
	-- GetTalentTabInfo(tabIndex[,inspect][,isPet][, groupIndex])
	-- name, iconTexture, pointsSpent, background, previewPointsSpent
	class.GetTalentTabInfo = function(tabIndex,inspect,pet,groupIndex) -- problem. This is both used for the side tabs, but also to get the name.
		if shown and not(inspect) and not(pet) and not(GHU_Tabs.settingTalentTabs) then
			local t = tabs[tabIndex];
			if type(t) == "table" then
				return t.name,"",t.pointsSpent,t.background,0;
			end
			return;
		end
		return orig.GetTalentTabInfo(tabIndex,inspect,pet,groupIndex);
	end
	
	-- GetNumTalents(tabIndex[,inspect[,pet]])
	class.GetNumTalents = function(tabIndex,inspect,pet)
		if shown and not(inspect) and not(pet) then
			if type(tabs[tabIndex])=="table" then
				return #(tabs[tabIndex].talents);
			end
			return 0;
		end
		return orig.GetNumTalents(tabIndex,inspect,pet);
	end
	
	-- GetTalentInfo(tabIndex[,inspect][,isPet][, groupIndex])
	-- return name, iconTexture, tier, column, rank, maxRank, isExceptional, meetsPrereq.
	class.GetTalentInfo = function(tabIndex,talentIndex,inspect,pet, groupIndex)
		if shown and not(inspect) and not(pet) then
			if type(tabs[tabIndex])=="table" then
				local t = tabs[tabIndex].talents[talentIndex];
				if type(t)=="table" then
					local a,_,meetsPrereq = class.GetTalentPrereqs(tabIndex,talentIndex);
					if not(a) then meetsPrereq = true; end
					
					return t.name, t.icon, t.tier, t.column, t.currentRank, t.maxRank, 1, meetsPrereq,t.currentRank,meetsPrereq;
				end			
			end
			return nil;
		end
		return orig.GetTalentInfo(tabIndex,talentIndex,inspect,pet, groupIndex);
	end
	
	-- GetTalentPrereqs(tabIndex,talentIndex[,inspect] [, groupIndex])
	--return tier, column, isLearnable
	class.GetTalentPrereqs = function(tabIndex,talentIndex,inspect, groupIndex)
		if shown and not(inspect) then
			if type(tabs[tabIndex])=="table" then
				local t = tabs[tabIndex].talents[talentIndex];
				if t then
					local tabI2,talentI2 = GetIndex(tabs[tabIndex].tabRefID,t.preReqRefID);
					assert(tabI2 == tabIndex,"Found in other tab incorrectly.");
					if talentI2 then
						local t2 = tabs[tabI2].talents[talentI2];
						return t2.tier,t2.column,(t2.currentRank == t2.maxRank and 1 or nil);
					end
				end
			end
			return;
		end
		return orig.GetTalentPrereqs(tabIndex,talentIndex,inspect, groupIndex);
	end
	
	-- LearnTalent(tabIndex,talentIndex) 
	class.LearnTalent = function(tabIndex,talentIndex)
		if shown then
			if learnFunction and type(tabs[tabIndex])=="table" and type(tabs[tabIndex].talents[talentIndex])=="table" then
				learnFunction(tabs[tabIndex].tabRefID,tabs[tabIndex].talents[talentIndex].refID);
			end
			return;
		end
		return orig.LearnTalent(tabIndex,talentIndex);
	end
	
	-- todo: hook the update frame to insert a custom background.
	class.TalentFrame_Update = function(TalentFrame)
		orig.TalentFrame_Update(TalentFrame);
		
		
		-- set up custom background
		if shown then
			PlayerTalentFrameTitleText:SetText(name);
						
			local selectedTab = PanelTemplates_GetSelectedTab(TalentFrame);
			local talentFrameName = TalentFrame:GetName();
			local name, icon, pointsSpent, background, previewPointsSpent = GetTalentTabInfo(selectedTab, TalentFrame.inspect, TalentFrame.pet,TalentFrame.talentGroup);
			if background and string.sub(string.upper(background),0,9) == "INTERFACE" then -- a custom background
				base = background.."-";
				local backgroundPiece = _G[talentFrameName.."BackgroundTopLeft"];
				backgroundPiece:SetTexture(base.."TopLeft");
				SetDesaturation(backgroundPiece, false);
				backgroundPiece = _G[talentFrameName.."BackgroundTopRight"];
				backgroundPiece:SetTexture(base.."TopRight");
				SetDesaturation(backgroundPiece, false);
				backgroundPiece = _G[talentFrameName.."BackgroundBottomLeft"];
				backgroundPiece:SetTexture(base.."BottomLeft");
				SetDesaturation(backgroundPiece, false);
				backgroundPiece = _G[talentFrameName.."BackgroundBottomRight"];
				backgroundPiece:SetTexture(base.."BottomRight");
				SetDesaturation(backgroundPiece, false);
	
			end
		end
	end
	
	
	class.GetActiveTalentGroup = function(inspect,pet)
		if shown and not(inspect) and not(pet) then
			if PlayerTalentFrame then
				return PlayerTalentFrame.talentGroup
			end
			return 1;
		end
		return orig.GetActiveTalentGroup(inspect,pet);
	end
	
	class.GetUnspentTalentPoints = function(inspect,pet,group)
		if shown and not(inspect) and not(pet) then
			return unspentPoints or 0;
		end
		return orig.GetUnspentTalentPoints(inspect,pet,group);
	end
	
	class.GetGroupPreviewTalentPointsSpent = function(pet,group)
		if shown and not(pet) then
			return 0;
		end
		return orig.GetGroupPreviewTalentPointsSpent(pet,group);
	end
	
	class.PlayerTalentFrameTalent_OnEnter = function(b)
		if shown and not(PlayerTalentFrame.inspect) and not(PlayerTalentFrame.pet) then
			SetTooltip(b);
			return;	
		end
		return orig.PlayerTalentFrameTalent_OnEnter(b);
	end
	
	class.PlayerTalentFrameTalent_OnClick = function(s, button)
		if shown and not(PlayerTalentFrame.inspect) and not(PlayerTalentFrame.pet) then
			if button == "LeftButton" then
				local talI = s:GetID();
				local tabI = PanelTemplates_GetSelectedTab(PlayerTalentFrame);
				local tal = tabs[tabI].talents[talI];
				
				if tal.preReqRefID then
					local tabI2,talentI2 = GetIndex(tabs[tabI].tabRefID,tal.preReqRefID);
					assert(tabI2 == tabI,"Found in other tab incorrectly.");
					if talentI2 then
						local tal2 = tabs[tabI2].talents[talentI2];
						if not(tal2.currentRank == tal2.maxRank) then
							return;
						end
					end
				end
				
				if not((tabs[tabI].pointsSpent or 0) >= (tal.tier-1) * (reqPerTier or 5)) then
					return;
				end
				
				if learnFunction then
					learnFunction(tabs[tabI].tabRefID,tabs[tabI].talents[talI].refID);
				end
			end
			return
		end
		return orig.PlayerTalentFrameTalent_OnClick(s, button);
	end
	
	-- ======================== Set API ========================

	class.AddTab = function(tabRefID,name,background)
		-- validate input
		assert(type(tabRefID)=="string" or type(tabRefID)=="number","Illigal tabRefID");
		assert(type(name)=="string","Illigal name.");
		assert(type(background)=="string","Illigal background. Got "..type(background)..".");
		-- insert
		local t = {
			["tabRefID"] = tabRefID,
			["name"] = name,
			["background"] = background,
			["pointsSpent"] = 0;
			["talents"] = {};
		}
		table.insert(tabs,t);
	end
	
	class.AddTalent = function(tabRefID,refID,name,icon,tier,column,maxRank,preReqRefID)
		-- validate input
		assert(type(refID)=="string" or type(refID)=="number","Illigal refID");
		assert(type(tabRefID)=="string" or type(tabRefID)=="number","Illigal tabRefID");
		assert(type(name)=="string","Illigal name.");
		assert(type(icon)=="string","Illigal icon.");
		assert(type(tier)=="number","Illigal tier.");
		assert(type(column)=="number" and column > 0 and column < 5,"Illigal column.");
		assert(type(maxRank)=="number","Illigal maxRank.");
		
		local tabI,talentI = GetIndex(tabRefID,refID);
		assert(tabI,"Tab not found.");
		assert(not(talentI),"Talent already inserted");
		-- insert
		local t = {
			["refID"] = refID,
			["name"] = name,
			["icon"] = icon,
			["currentRank"] = 0,
			["maxRank"] = maxRank,
			["preReqRefID"] = preReqRefID,
			["tier"] = tier,
			["column"] = column,
			["description"] = "",
			["whiteText"] = {},
		}
		table.insert(tabs[tabI].talents,t);
	end
	
	class.SetUnspentPoints = function(points)
		assert(type(points)=="number","Unspend points must be a number");
		unspentPoints = points;
	end
	
	class.SetSpentPoints = function(tabRefID,points)
		assert(type(tabRefID)=="string" or type(tabRefID)=="number","Illigal tabRefID");
		assert(type(points)=="number","Spent points must be a number");
		local tabI = GetIndex(tabRefID);
		assert(tabI,"Tab not found.");
		tabs[tabI].pointsSpent = points;
	end
	
	class.SetCurrentRank = function(tabRefID,refID,points)
		assert(type(tabRefID)=="string" or type(tabRefID)=="number","Illigal tabRefID");
		assert(type(refID)=="string" or type(refID)=="number","Illigal refID");
		assert(type(points)=="number","Points must be a number");
		local tabI,talentI = GetIndex(tabRefID,refID);
		assert(tabI and talentI,"Talent dosent exsist");
		tabs[tabI].talents[talentI].currentRank = points;
		
		local newTotal = 0;
		for i,talent in pairs(tabs[tabI].talents) do
			newTotal = newTotal + (talent.currentRank or 0);
		end
		class.SetSpentPoints(tabRefID,newTotal);
		TalentFrame_Update(PlayerTalentFrame);
		if PlayerTalentFrameTitleText then
			PlayerTalentFrameTitleText:SetText(name);
		end
		UpdateEvtTooltip();
	end
	
	class.SetTalentDetails = function(tabRefID,refID,description,...)
		assert(type(refID)=="string" or type(refID)=="number","Illigal refID");
		assert(type(tabRefID)=="string" or type(tabRefID)=="number","Illigal tabRefID");
		assert(type(description)=="string","Illigal description.");
		
		local tabI,talentI = GetIndex(tabRefID,refID);
		assert(tabI,"Tab not found.");
		assert(talentI,"Talent not found.");
		
		local t = {...};
		local whiteText = {};
		local i = 1;
		while i <= #(t) do
			table.insert(whiteText,{t[i],t[i+1]});
			i = i + 2;
		end
		
		tabs[tabI].talents[talentI].description = description;
		tabs[tabI].talents[talentI].whiteText = whiteText;
	end
	
	class.Clear = function()
		if shown then
			class.Hide();
		end
		tabs = {};
		unspentPoints = 0;
	end
	
	class.SetRequiredTalentsPerTier = function(num)
		assert(type(num)=="number","Required talents per tier must bea  number, got "..type(num));
		reqPerTier = num;
	end
	
	function class.Save()
		return {
			tabs = tabs,
			unspentPoints = unspentPoints,
			reqPerTier = reqPerTier,
		};
	end

	function class.Load(data)
		data = data or {};
		tabs = data.tabs or {};
		unspentPoints = data.unspentPoints or {};
		reqPerTier = data.reqPerTier or 5;
	end
	
	
	-- setup
	GHU_Tabs:NewTab("Talent",class,name,_icon)
	
	-- hooks
	Hook("GetNumTalentTabs")
	Hook("GetTalentTabInfo")
	Hook("GetNumTalents")
	Hook("GetTalentInfo")
	Hook("GetTalentPrereqs")
	Hook("LearnTalent")
	Hook("TalentFrame_Update")
	Hook("GetActiveTalentGroup")
	Hook("GetUnspentTalentPoints")
	Hook("GetGroupPreviewTalentPointsSpent")
	
	-- hooks after load of bliz talent UI
	class:RegisterEvent("VARIABLES_LOADED");
	class:SetScript("OnEvent",InitHooksAfterLoad);
	
	
	
	return class;
end

