SH_SMARTHEAL_VERSION= SH_SMARTHEAL.." "..SMARTHEAL_CURRENT_VERSION

function SmartHeal:Init()

	-- Hook the UseAction function and backup original UseAction
	SMARTHEAL_ORIG_USEACTION=UseAction;
	UseAction = function(slot,arg1,onself)	SmartHeal:UseAction(slot,arg1,onself);	end;

	-- set slash command
	SlashCmdList["SMARTHEAL"] = function(arg1) SmartHeal:SlashCmd(arg1); end;
	SLASH_SMARTHEAL1 = "/smartheal";
	SLASH_SMARTHEAL2 = "/smh";
	
	-- get realm and player name
	SmartHeal.realmName=GetRealmName();
	SmartHeal.playerName=UnitName("player");
	
	
	--local _,_,large_version,small_version,release_version=string.find(SmartHeal_Version,"^(%d+)%.(%d+)%.?(%d*)");
	
	--if(not SmartHeal_Config['version'] or tonumber(SmartHeal_Config['version'])<tonumber(SMARTHEAL_CURRENT_VERSION)) then
	--	SmartHeal_Config={}
	--end
	
	SmartHeal_Config['version']=SMARTHEAL_CURRENT_VERSION
	
	-- set realm record in save file
	if (not SmartHeal_Config[SmartHeal.realmName]) then
		SmartHeal_Config[SmartHeal.realmName]={};
	end

	-- set default settings for player if no record in save file
	if (not SmartHeal_Config[SmartHeal.realmName][SmartHeal.playerName]) then
		SmartHeal_Config[SmartHeal.realmName][SmartHeal.playerName]={};
		SmartHeal.InitializeSave=1
	end

end

function SmartHeal:InitMiniMapButton()

	local MinimapDefaultSettings=SmartHeal.default['minimapbutton']
	
	MinimapDefaultSettings.left = function() SH_OptionsFrame:Show() end
	MinimapDefaultSettings.right = function() ScriptErrors:Hide() end
	
	MyMinimapButton:Create('SmartHeal',SmartHeal_Config[SmartHeal.realmName][SmartHeal.playerName]['minimapbutton'],MinimapDefaultSettings)

end

function SmartHeal:isActive()
	return SmartHeal.active;
end

function SmartHeal:setDefault(defaultOption)

	if(not defaultOption) then
		for option in SmartHeal.default do
			
			if (type(SmartHeal.default[option])=="table") then
				
				local module=option
				for option2,value in SmartHeal.default[module] do
					SmartHeal:setConfig(option2,value,module);
				end
			
			else
				SmartHeal:setConfig(option,SmartHeal.default[option]);
			end
			
		end
	else
		for i,option in defaultOption do
			
			if (type(SmartHeal.default[option])=="table") then
				
				local module=option
				for option2,value in SmartHeal.default[module] do
					SmartHeal:setConfig(option2,value,module);
				end
			
			else
				SmartHeal:setConfig(option,SmartHeal.default[option]);
			end
		end
	
	end

end

function SmartHeal:setConfig(option,value,module)

	if(not SmartHeal:isActive()) then return end

	if (value==nil) then value=false end
	
	if (module and module~="" and option and option~="") then
		
		if (not SmartHeal_Config[SmartHeal.realmName][SmartHeal.playerName][module]) then
			SmartHeal_Config[SmartHeal.realmName][SmartHeal.playerName][module]={}
		end
		
		SmartHeal_Config[SmartHeal.realmName][SmartHeal.playerName][module][option]=value;
			

	else 
		if (option and option~="") then
			SmartHeal_Config[SmartHeal.realmName][SmartHeal.playerName][option]=value;
		end
	end

end

function SmartHeal:getConfig(option,module)
	
	if(not SmartHeal:isActive()) then return end
	
	if (module) then
		
		local moduleConfig=SmartHeal:getConfig(module)
		if(moduleConfig) then
			return moduleConfig[option]
		end
	
	else	
		if(SmartHeal_Config[SmartHeal.realmName][SmartHeal.playerName][option]~=nil) then
			return SmartHeal_Config[SmartHeal.realmName][SmartHeal.playerName][option];
		else
			return
		end
	end
end

function SmartHeal:SlashCmd(arg1)

	if (not SmartHeal:isActive()) then
		SmartHeal:ErrorMsg(SH_IS_NOT_ACTIVE_CLASS)
		return;
	end;

	if(not SH_OptionsFrame:IsShown()) then
		SH_OptionsFrame:Show()
	end
	
end

function SmartHeal:doCast(spell,rank)

	if (not spell) then return; end;
	
	local spell_with_rank=spell;
	SmartHeal.CastedSpell=spell;
	
	if (rank and rank~="") then
		spell_with_rank=spell.."("..SH_RANK.." "..rank..")"
		SmartHeal.CastedRank=rank;
	end
	
	SmartHeal.CacheUnitId=SmartHeal:TargetUnitId()
	
	if (SmartHeal.selfCast==1) then SmartHeal.CacheUnitId="player" end
	
	CastSpellByName(spell_with_rank,SmartHeal.selfCast);
	
	SmartHeal.selfCast=nil
	
end

function SmartHeal:TargetUnitId()

	local targetunitId

	if (not UnitExists("target")) then 
		return
	end

	if(UnitInRaid("target")) then
	
		for i=1,40 do
			if UnitIsUnit("raid"..i,"target") then
				targetunitId="raid"..i
				break
			end
		end
		
	elseif(UnitInParty("target") and not UnitIsUnit("player","target")) then
	
		for i=1,4 do
			if UnitIsUnit("party"..i,"target") then
				targetunitId="party"..i
				break
			end
		end
	
	elseif (UnitIsUnit("target","player")) then
		
		targetunitId="player"
	
	end
	
	return targetunitId

end

function SmartHeal:PlayerHasSpell(spell) 
	
	local HasSpell=false
	local i = 1
	while true do
		local sName , sRank = GetSpellName(i, BOOKTYPE_SPELL)
		if not sName then
			do break end
		else
			if (sName==spell) then
				HasSpell=true
				break
			end
		end

	i = i + 1
	end

	return HasSpell;
end

function SmartHeal:Cast(spell_fullname,ForceTarget)


	local _,_,spell,rankCap=string.find(spell_fullname,"(.+)%("..SH_RANK.." (%d+)%)")
	
	if (not spell) then
		spell=spell_fullname
	end
	
	if (not SmartHeal:isActive()) then
		SmartHeal:ErrorMsg(SH_IS_NOT_ACTIVE_CLASS)
		return
	end;

	if (	not SmartHeal:getConfig("enable") or spell=="" or not spell) then
		return
	end

	if (not SmartHeal.selfCast) then
		SmartHeal.selfCast=SmartHeal:isSelfCast(spell)
		if (IsAltKeyDown() and SmartHeal:getConfig("altselfcast")) then
		   	SmartHeal.selfCast=1
		elseif(SmartHeal.HotkeyMouseButton=="RightButton" and SmartHeal:getConfig("RClickHotKeySelfCast")) then
			SmartHeal.selfCast=1
		elseif (
			--SmartHeal.selfCast==1 and not SmartHeal:getConfig("autoselfcast") or 
			((UnitCanAttack("player","target") and ForceTarget))
		
		) then
			SmartHeal.selfCast=0
			SmartHeal:doCast(spell,rankCap)
			return
		end
	end
	
	local rank
	
	if (SmartHeal.buffList[spell]~=nil) then
		SmartHeal.isBuff=true
		SmartHeal.isHeal=false
		rank=SmartHeal:buffRank(spell)
	elseif (SmartHeal.spellList[spell]~=nil) then
		SmartHeal.isBuff=false
		SmartHeal.isHeal=true
		rank=SmartHeal:healRank(spell)
	else
		SmartHeal:doCast(spell,rankCap)
	end
	
	if (spell==SH_LESSER_GREATER_HEALS) then
		if (rank<=3) then
			spell=SH_LESSER_HEAL
		elseif (rank<=7) then
			spell=SH_HEAL
			rank=rank-3
		else
			spell=SH_GREATER_HEAL
			rank=rank-7
		end
	end
	
	if(rankCap) then
		rank=min(rank,tonumber(rankCap))
	end
	
	SmartHeal:doCast(spell,rank);
end

function SmartHeal:healRank(spell)

	local SpellStats=SmartHeal.spellList[spell];

	local target="target";
	if(SmartHeal.selfCast==1) then
		target="player";
	end
	
	local ignoreMana=false
	if (UnitIsDead("player")) then ignoreMana=true end
	
	local maxrank=SmartHeal:SpellMaxRank(spell,ignoreMana)
	if (not maxrank) then return end
	
	-- target max acceptable rank or optimal rank whichever is lower
	local rank = min(SmartHeal:targetRank(target,spell),maxrank)

	if (	SpellStats.HoT==1 
		or (not UnitInParty(target) and not UnitInRaid(target) and not UnitIsUnit("player",target)) 
		or (SmartHeal:ClickHeal_IsOverdrive()) -- overdrive mode ignore hp deficit
	) then
		
		if(SpellStats.HoT~=1) then
			SmartHeal.HealedValue=SmartHeal:AdjustHealValue(spell,rank)
		end
		
		return rank
	end
	
	-- find the best spell rank for the health deficit
	local hpDeficit=0;
	local TotalHeal=0
	local rank2=1
	if (SpellStats.group==1) then
		for i=1,4 do
			hpDeficit=max(hpDeficit,UnitHealthMax("party"..i)-UnitHealth("party"..i));
		end
		hpDeficit=max(hpDeficit, (UnitHealthMax("player")-UnitHealth("player")) )
	else
		hpDeficit=UnitHealthMax(target)-UnitHealth(target);
	end

	
	for r=1,table.getn(SpellStats.value) do

		rank2=r
		TotalHeal=SmartHeal:AdjustHealValue(spell,r)
		if (TotalHeal>hpDeficit*SmartHeal:getConfig('overheal')/100) then
			SmartHeal.HealedValue=TotalHeal
			break
		end

	end
	
	return min(rank,rank2)

end

function SmartHeal:isSelfCast(spell)

	if (not UnitExists("target") or not UnitIsFriend("target","player")) then
		return 1
	else
		return 0
	end

end

function SmartHeal:targetRank(target,spell)

	local targetRank=1;
	local SpellStats

	
	if (SmartHeal.isBuff) then
		SpellStats=SmartHeal.buffList[spell];
	elseif (SmartHeal.isHeal) then
		SpellStats=SmartHeal.spellList[spell];
	end
	
	-- find target acceptable rank
	for i=table.getn(SpellStats.level),1,-1 do
		if (UnitLevel(target)>=SpellStats.level[i]-10) then
			targetRank=i;
			break;
			
		end
	end

	return targetRank
end

function SmartHeal:AdjustHealValue(spell,rank)

	-- calculate bonus offsets
	local EquipmentBonus=0
	if (BonusScanner) then
		EquipmentBonus=tonumber(BonusScanner:GetBonus("HEAL"))
	end
	
	-- Adjust Talent
	local talent_multiplier,talent_adder=SmartHeal:TalentAdjust(spell)
	
	local healvalue=(SmartHeal.spellList[spell].value[rank]*talent_multiplier)+talent_adder+(EquipmentBonus*SmartHeal:bonusAdjust(spell,rank));
	
	return healvalue or 0

end

function SmartHeal:UseAction(slot, arg2, onself)

	-- Hack to get the spell name
	SmartHealSpellTooltip:SetAction(slot)
	local spell = getglobal("SmartHealSpellTooltipTextLeft1"):GetText()
	local rankTemp = getglobal("SmartHealSpellTooltipTextRight1"):GetText()
	local _,rankCap
	if(rankTemp) then
		_,_,rankCap=string.find(rankTemp,"^"..SH_RANK.." (%d+)")
	end
	
	SmartHeal.HotkeyMouseButton=arg1
	
	if ( (SmartHeal.spellList[spell]~=nil or SmartHeal.buffList[spell]~=nil)
		and SmartHeal:getConfig("override") and SmartHeal:getConfig("enable")) then
		
		if (rankCap) then
			SmartHeal:Cast(spell.."("..SH_RANK.." "..rankCap..")")
		else
			SmartHeal:Cast(spell)
		end
	
	else
		
		SmartHeal.selfCast=onself
			
		-- Check for alt self cast
		if (IsAltKeyDown() and SmartHeal:getConfig("altselfcast")) then
			SmartHeal.selfCast=1
		
		elseif(SmartHeal.HotkeyMouseButton=="RightButton" and SmartHeal:getConfig("RClickHotKeySelfCast")) then
			
			SmartHeal.selfCast=1
		
		elseif(	SmartHeal:isSelfCast(spell)==1 and SmartHeal:getConfig("autoselfcast") and
			(SmartHeal.spellList[spell]~=nil or SmartHeal.buffList[spell]~=nil)
		
		) then
			SmartHeal.selfCast=1
		end
		
		SmartHeal.CacheUnitId=SmartHeal:TargetUnitId()

		if (rankCap and SmartHeal.spellList[spell]~=nil and SmartHeal.spellList[spell].HoT~=1) then
			SmartHeal.HealedValue=SmartHeal:AdjustHealValue(spell,tonumber(rankCap))
		end
		
		-- do default button action if the spell is not listed in the healing spells list
		SMARTHEAL_ORIG_USEACTION(slot, arg2, SmartHeal.selfCast)
		
		SmartHeal.selfCast=nil
	end
	

end

function SmartHeal:SpellMaxRank(spellname,ignoreMana)
	local maxRank=1;
	local manatooltip;
	local spells={[1]=spellname}
	
	if(spellname==SH_LESSER_GREATER_HEALS) then
		spells={[1]=SH_LESSER_HEAL, [4]=SH_HEAL, [8]=SH_GREATER_HEAL,}
	end
	
	
	for r,spell in spells do
		local i = 1
		while true do
			local sName , sRank = GetSpellName(i, BOOKTYPE_SPELL)
			if not sName then
				do break end
			elseif (sName==spells[r]) then
				local _,_,rankNumber=string.find(sRank,"^"..SH_RANK.." (%d+)")
				if (rankNumber and rankNumber~="") then
					rankNumber=tonumber(rankNumber)
					SmartHealSpellTooltip:SetSpell(i,BOOKTYPE_SPELL)
					manatooltip = getglobal("SmartHealSpellTooltipTextLeft2"):GetText()
					local _,_,mana=string.find(manatooltip,"^(%d+) "..SH_MANA);	
					if ((mana and UnitMana('player')>tonumber(mana)) or not mana or ignoreMana) then
						maxRank=max(maxRank,rankNumber+(r-1))
					end
				else
					return
				end
			end
			
			i = i + 1
		end -- end of while loop
	end -- end of for loop
	return maxRank
end

function SmartHeal:TalentAdjust(spell)

	local _, playerClass = UnitClass("player");
	local rank
	local multiplier_factor=1
	local additional_factor=0;

	if (playerClass=="PRIEST") then

		-- Improved Renew (renew only) 5% per point
		_, _, _, _, rank,_= GetTalentInfo(2,2);
		if (spell==SH_RENEW) then multiplier_factor=multiplier_factor*(1+(rank*0.05)); end;

		-- Spiritual Healing (all healing) 2% per point
		_, _, _, _, rank,_= GetTalentInfo(2,15);
		multiplier_factor=multiplier_factor*(1+(rank*0.02));

		-- Spiritual Guidance (all healing) 5% per point, based on total spirit
		_, _, _, _, rank,_= GetTalentInfo(2,14);
		
		local spirit=0;
		if (BonusScanner) then
			spirit=tonumber(BonusScanner:GetBonus("SPI"));	
		end
		
		additional_factor=additional_factor+spirit*(rank*0.05)

	elseif (playerClass=="PALADIN") then

		-- Healing Light (healing light and flash light) 4% per point
		_, _, _, _, rank,_= GetTalentInfo(1,5);
		if (spell==SH_HEALING_LIGHT or spell==SH_FLASH_OF_LIGHT) then multiplier_factor=multiplier_factor*(1+(rank*0.04)); end;

	elseif (playerClass=="DRUID") then

		-- Improved Rejuvenation (rejuvenation only) 5% per point
		_, _, _, _, rank,_= GetTalentInfo(3,10);
		if (spell==SH_REJUVENATION) then multiplier_factor=multiplier_factor*(1+(rank*0.05)); end;

		-- Gift of Nature (all healing spells) 2% per point
		_, _, _, _, rank,_= GetTalentInfo(3,12);
		multiplier_factor=multiplier_factor*(1+(rank*0.02));

	elseif (playerClass=="SHAMAN") then

		-- Purification 2% per point
		_, _, _, _, rank,_= GetTalentInfo(3,10);
		multiplier_factor=multiplier_factor*(1+(rank*0.02));

	end

	return multiplier_factor,additional_factor

end

function SmartHeal:bonusAdjust(spell,rank)

	local SpellStats=SmartHeal.spellList[spell];
	local bonus=1;
	local castTimeBonus=1;
	local lowlevelBonus=1;
	local HoTBonus=1;

	-- cast time discount,  ActualBenefit = AdvertisedBenefit * (CastingTime / 3.5)
	if (SpellStats.castTime[rank]<3.5 and SpellStats.value[rank]>0) then
		castTimeBonus=SpellStats.castTime[rank]/3.5;
	end

	-- Low level discount , EffectiveBonus = (1-((20-LevelLearnt)*0.0375))*AdvertisedBonus
	if (SpellStats.level[rank]<20) then
		lowlevelBonus=(1-((20-SpellStats.level[rank])*0.0375))
	end

	-- Heal+HoT discount
	if (SpellStats.HoTvalue~=nil) then
		if (SpellStats.value[rank]>0 and SpellStats.HoTvalue[rank]>0) then
			HoTBonus=SpellStats.value[rank]/(SpellStats.value[rank]+SpellStats.HoTvalue[rank])
		end
	end
	bonus=bonus*castTimeBonus*lowlevelBonus*HoTBonus;

	return bonus;
end

function SmartHeal:buffRank(spell)

	local BuffStats=SmartHeal.buffList[spell];
	local targetRank=1;
	local target="target";

	if(SmartHeal.selfCast==1) then
		target="player";
	end

	local maxrank=SmartHeal:SpellMaxRank(spell)
	
	if(not maxrank) then return end
	
	if (BuffStats.group==0) then -- target buff
		targetRank=SmartHeal:targetRank(target,spell);

	elseif (BuffStats.group==1) then -- group member buff

		for i=1,4 do
			targetRank=max(targetRank,SmartHeal:targetRank("party"..i,spell));
		end

		targetRank=max(targetRank,SmartHeal:targetRank("player",spell));

	elseif (BuffStats.group==2) then -- paladin unit class blessings

		local _, targetClass = UnitClass(target);
		local thisUnitClass;

		if SmartHeal:inRaid() then

			for i=1,40 do

				_, thisUnitClass = UnitClass("raid"..i);
				if (thisUnitClass==targetClass) then
					targetRank=max(targetRank,SmartHeal:targetRank("raid"..i,spell));
				end

			end

		elseif SmartHeal:inParty() then

			for i=1,4 do

				_, thisUnitClass = UnitClass("party"..i);
				if (thisUnitClass==targetClass) then
					targetRank=max(targetRank,SmartHeal:targetRank("party"..i,spell));
				end

			end
			targetRank=max(targetRank,SmartHeal:targetRank("player",spell));

		end

	else
		targetRank=SmartHeal:targetRank(target,spell); -- treat as target only buff
	end
	
	-- target max acceptable rank or player max rank whichever is lower
	return min(targetRank,maxrank);

end

function SmartHeal:inRaid()
	if GetNumRaidMembers()>0 then
		return true
	else
		return false
	end
end

function SmartHeal:inParty()
	if GetNumPartyMembers()>0 then
		return true
	else
		return false
	end
end

function SmartHeal:ErrorMsg(msg,r,g,b)
	if (not r and not g and not b) then
		r=1;g=0;b=0;
	end
	DEFAULT_CHAT_FRAME:AddMessage(msg,r,g,b);
end

function SmartHeal:doDebug()

	SmartHeal:ErrorMsg("SmartHeal Casted: "..SmartHeal.CastedSpell.."(Rank "..SmartHeal.CastedRank..")")
	
	if (SMARTHEAL_HEALTABLE[SmartHeal.playerClass] ~= nil) then
		SmartHeal_DebugMsg['talent']=SmartHeal:getAllTalent()
	end

end

function SmartHeal:AlertHealer(arg1)
	
	if (not UnitExists("target") or not UnitIsFriend("player","target")) then 
		return 
	end
	
	local current_targetName=UnitName("target")
	
	local _,_,casterName,spell=string.find(arg1,"(.+) "..SH_BEGINS_TO_CAST.." (.+).")
	if(not casterName or not spell) then return end
	
	TargetByName(casterName)
	
	if (UnitName("target")==casterName and UnitName("targettarget")==current_targetName) then
		
		local _, casterClass = UnitClass("target");
		
		if (UnitName("target")~= current_targetName) then
			TargetLastTarget()
		end
		
		local alert_msg=string.format(SH_ALERT_HEALER_MSG, casterName, spell, current_targetName);
		local holdtime=UIERRORS_HOLD_TIME;
		local casterspellList=SMARTHEAL_HEALTABLE[casterClass];
		
		if (casterspellList) then
			local spellStats=casterspellList[spell];
			if (spellStats) then
				if (spellStats.castTime[1]) then
					holdtime=spellStats.castTime[1]; -- take the rank 1 cast time as holdtime
				end
		
				UIErrorsFrame:AddMessage(alert_msg, 0, 1.0, 0, 1.0, holdtime);
			end
			
		end
	
	else
		
		if (UnitName("target")~= current_targetName) then
			TargetLastTarget()
		end
	end
	
end

-- cannot stop cast onupdate after 1.10 , using warning alert instead 
function SmartHeal:StopCasting_OnUpdate(arg1)
	
	--if (not SmartHeal.SpellIsCasting and not SmartHeal.SpellIsChanneling) then return end

	if (not SmartHeal.HealUnitId or not SmartHeal.DoExcessHealAlert or not SmartHeal:getConfig("excesshealalert")) then 
		return
	end

	if (SmartHeal.SpellIsCasting) then
		SmartHeal.timer=SmartHeal.timer-(arg1*1000)
	end
	
	if(SmartHeal.timer<1000) then
	
		local hp_ratio=UnitHealth(SmartHeal.HealUnitId)/UnitHealthMax(SmartHeal.HealUnitId)*100
			
		if (hp_ratio>=SmartHeal:getConfig("excesshealalerttrigger")) then
			UIErrorsFrame:AddMessage(SH_EXCESSIVE_HEALING_ALERT_MSG.." "..UnitName(SmartHeal.HealUnitId).."!!!", 1, 0, 1, 1.0,3);
		end

		SmartHeal.DoExcessHealAlert=nil
	
	end
	
end

-- for debug purposes, use when talent tree is changed by Blizzard
function SmartHeal:getAllTalent()

	local _, playerClass = UnitClass("player");
	local numTabs = GetNumTalentTabs();
	local talent={}
	talent[playerClass]={}
	for t=1, numTabs do
		local numTalents = GetNumTalents(t);
		talent[playerClass][t]={}
		for i=1, numTalents do
			local nameTalent, _, _, _, currRank, maxRank= GetTalentInfo(t,i);
			talent[playerClass][t][i]={name=nameTalent, rank=currRank,max=maxRank}
		end
	end

	return talent
end

