--[[
	Author: Dennis Werner Garske (DWG)
	License: MIT License
]]
local _G = _G or getfenv(0)
local Roids = _G.Roids or {}

-- Validates that the given target is either friend (if [help]) or foe (if [harm])
-- target: The unit id to check
-- help: Optional. If set to 1 then the target must be friendly. If set to 0 it must be an enemy.
-- remarks: Will always return true if help is not given
-- returns: Whether or not the given target can either be attacked or supported, depending on help
function Roids.CheckHelp(target, help)
	if help then
		if help == 1 then
            return UnitCanAssist("player", target);
		else
			return UnitCanAttack("player", target);
		end
	end
	return true;
end

-- Ensures the validity of the given target
-- target: The unit id to check
-- help: Optional. If set to 1 then the target must be friendly. If set to 0 it must be an enemy
-- returns: Whether or not the target is a viable target
function Roids.IsValidTarget(target, help)    
	if target ~= "mouseover" then
		if not Roids.CheckHelp(target, help) or not UnitExists(target) then
			return false;
		end
		return true;
	end
	
	if (not Roids.mouseoverUnit) and not UnitName("mouseover") then
		return false;
	end
    
	return Roids.CheckHelp(target, help);
end

-- Returns the current shapeshift / stance index
-- returns: The index of the current shapeshift form / stance. 0 if in no shapeshift form / stance
function Roids.GetCurrentShapeshiftIndex()
    for i=1, GetNumShapeshiftForms() do
        _, _, active = GetShapeshiftFormInfo(i);
        if active then
            return i; 
        end
    end
    
    return 0;
end

-- Checks whether or not the given buffName is present on the given unit's buff bar
-- buffName: The name of the buff
-- unit: The UnitID of the unit to check
-- returns: True if the buffName can be found, false otherwhise
function Roids.HasBuffName(buffName, unit)
    if not buffName or not unit then
        return false;
    end
    
    local text = getglobal(RoidsTooltip:GetName().."TextLeft1");
	for i=1, 32 do
		RoidsTooltip:SetOwner(UIParent, "ANCHOR_NONE");
		RoidsTooltip:SetUnitBuff(unit, i);
		name = text:GetText();
		RoidsTooltip:Hide();
        buffName = string.gsub(buffName, "_", " ");
		if ( name and string.find(name, buffName) ) then
			return true;
		end
    end
    
    return false;
end

-- Checks whether or not the given buffName is present on the given unit's debuff bar
-- buffName: The name of the debuff
-- unit: The UnitID of the unit to check
-- returns: True if the buffName can be found, false otherwhise
function Roids.HasDeBuffName(buffName, unit)
    if not buffName or not unit then
        return false;
    end
    
    local text = getglobal(RoidsTooltip:GetName().."TextLeft1");
	for i=1, 16 do
		RoidsTooltip:SetOwner(UIParent, "ANCHOR_NONE");
		RoidsTooltip:SetUnitDebuff(unit, i);
		name = text:GetText();
		RoidsTooltip:Hide();
        buffName = string.gsub(buffName, "_", " ");
		if ( name and string.find(name, buffName) ) then
			return true;
		end
    end
    
    return false;
end

-- Checks whether or not the given textureName is present in the current player's buff bar
-- textureName: The full name (including path) of the texture
-- returns: True if the texture can be found, false otherwhise
function Roids.HasBuff(textureName)
    for i = 1, 16 do
        if UnitBuff("player", i) == textureName then
            return true;
        end
    end
    
    return false;
end

-- Maps easy to use weapon type names (e.g. Axes, Shields) to their inventory slot name and their localized tooltip name
Roids.WeaponTypeNames = {
    Daggers = { slot = "MainHandSlot", name = Roids.Localized.Dagger },
    Fists =  { slot = "MainHandSlot", name = Roids.Localized.FistWeapon },
    Axes =  { slot = "MainHandSlot", name = Roids.Localized.Axe },
    Swords =  { slot = "MainHandSlot", name = Roids.Localized.Sword },
    Staffs =  { slot = "MainHandSlot", name = Roids.Localized.Staff },
    Maces =  { slot = "MainHandSlot", name = Roids.Localized.Mace },
    Polearms =  { slot = "MainHandSlot", name = Roids.Localized.Polearm },
    Shields = { slot = "SecondaryHandSlot", name = Roids.Localized.Shield },
    Guns = { slot = "RangedSlot", name = Roids.Localized.Gun },
    Crossbows = { slot = "RangedSlot", name = Roids.Localized.Crossbow },
    Bows = { slot = "RangedSlot", name = Roids.Localized.Bow },
    Thrown = { slot = "RangedSlot", name = Roids.Localized.Thrown },
    Wands = { slot = "RangedSlot", name = Roids.Localized.Wand },
};

-- Checks whether or not the given weaponType is currently equipped
-- weaponType: The name of the weapon's type (e.g. Axe, Shield, etc.)
-- returns: True when equipped, otherwhise false
function Roids.HasWeaponEquipped(weaponType)
    if not Roids.WeaponTypeNames[weaponType] then
        return false;
    end
    
	RoidsTooltip:SetOwner(UIParent, "ANCHOR_NONE");
    
    local slotName = Roids.WeaponTypeNames[weaponType].slot;
    local localizedName = Roids.WeaponTypeNames[weaponType].name;

    local slotId = GetInventorySlotInfo(slotName);
    hasItem = RoidsTooltip:SetInventoryItem("player", slotId);
    if not hasItem then
        return false;
    end
    
	local lines = RoidsTooltip:NumLines();
	for i = 1, lines do
		local label = getglobal("RoidsTooltipTextLeft"..i);
		if label:GetText() then
			if label:GetText() == localizedName then
                return true;
            end
		end
        
		label = getglobal("RoidsTooltipTextRight"..i);
		if label:GetText() then
			if label:GetText() == localizedName then
                return true;
            end
		end
    end
    
    return false;
end

-- Checks whether or not the given UnitId is in your party or your raid
-- target: The UnitId of the target to check
-- groupType: The name of the group type your target has to be in ("party" or "raid")
-- returns: True when the given target is in the given groupType, otherwhise false
function Roids.IsTargetInGroupType(target, groupType)
    local upperBound = 5;
    if groupType == "raid" then
        upperBound = 40;
    end
    
    for i = 1, upperBound do
        if UnitName(groupType..i) == UnitName(target) then
            return true;
        end
    end
    
    return false;
end

-- Checks whether or not we're currently casting a channeled spell
function Roids.CheckChanneled(conditionals)
    -- Remove the "(Rank X)" part from the spells name in order to allow downranking
    local spellName = string.gsub(Roids.CurrentSpell.spellName, "%(.-%)%s*", "");
    local channeled = string.gsub(conditionals.checkchanneled, "%(.-%)%s*", "");
    
    if Roids.CurrentSpell.type == "channeled" and spellName == channeled then
        return false;
    end
    
    if channeled == Roids.Localized.Attack then
        return not Roids.CurrentSpell.autoAttack;
    end
    
    if channeled == Roids.Localized.AutoShot then
        return not Roids.CurrentSpell.autoShot;
    end
    
    if channeled == Roids.Localized.Shoot then
        return not Roids.CurrentSpell.wand;
    end
    
    Roids.CurrentSpell.spellName = channeled;
    return true;
end

-- Checks whether or not the given unit has more or less power in percent than the given amount
-- unit: The unit we're checking
-- bigger: 1 if the percentage needs to be bigger, 0 if it needs to be lower
-- amount: The required amount
-- returns: True or false
function Roids.ValidatePower(unit, bigger, amount)
    local powerPercent = 100 / UnitManaMax(unit) * UnitMana(unit);
    if bigger == 0 then
        return powerPercent < tonumber(amount);
    end
    
    return powerPercent > tonumber(amount);
end

-- Checks whether or not the given unit has more or less total power than the given amount
-- unit: The unit we're checking
-- bigger: 1 if the raw power needs to be bigger, 0 if it needs to be less
-- amount: The required amount
-- returns: True or false
function Roids.ValidateRawPower(unit, bigger, amount)
    local power = UnitMana(unit);
    if bigger == 0 then
        return power < tonumber(amount);
    end
    
    return power > tonumber(amount);
end

-- Checks whether or not the given unit has more or less hp in percent than the given amount
-- unit: The unit we're checking
-- bigger: 1 if the percentage needs to be bigger, 0 if it needs to be lower
-- amount: The required amount
-- returns: True or false
function Roids.ValidateHp(unit, bigger, amount)
    local powerPercent = 100 / UnitHealthMax(unit) * UnitHealth(unit);
    if bigger == 0 then
        return powerPercent < tonumber(amount);
    end
    
    return powerPercent > tonumber(amount);
end

-- Checks whether the given creatureType is the same as the target's creature type
-- creatureType: The type to check
-- target: The target's unitID
-- returns: True or false
-- remarks: Allows for both localized and unlocalized type names
function Roids.ValidateCreatureType(creatureType, target)
    local targetType = UnitCreatureType(target);
    local englishType = Roids.Localized.CreatureTypes[targetType];
    return creatureType == targetType or creatureType == englishType;
end

-- Returns the cooldown of the given spellName or nil if no such spell was found
function Roids.GetSpellCooldownByName(spellName)
    local checkFor = function(bookType)
        local i = 1
        while true do
            local name, spellRank = GetSpellName(i, bookType);
            
            if not name then
                break;
            end
            
            if name == spellName then
                local _, duration = GetSpellCooldown(i, bookType);
                return duration;
            end
            
            i = i + 1
        end
        return nil;
    end
    
    
    local cd = checkFor(BOOKTYPE_PET);
    if not cd then cd = checkFor(BOOKTYPE_SPELL); end
    
    return cd;
end

-- Returns the cooldown of the given equipped itemName or nil if no such item was found
function Roids.GetInventoryCooldownByName(itemName)
    RoidsTooltip:SetOwner(UIParent, "ANCHOR_NONE");
    for i=0, 19 do
        RoidsTooltip:ClearLines();
        hasItem = RoidsTooltip:SetInventoryItem("player", i);
        
        if hasItem then
            local lines = RoidsTooltip:NumLines();
            
            local label = getglobal("RoidsTooltipTextLeft1");
            
            if label:GetText() == itemName then
                local _, duration, _ = GetInventoryItemCooldown("player", i);
                return duration;
            end
        end
    end
    
    return nil;
end

-- Returns the cooldown of the given itemName in the player's bags or nil if no such item was found
function Roids.GetContainerItemCooldownByName(itemName)
    RoidsTooltip:SetOwner(WorldFrame, "ANCHOR_NONE");
    
    for i = 0, 4 do
        for j = 1, GetContainerNumSlots(i) do
            RoidsTooltip:ClearLines();
            RoidsTooltip:SetBagItem(i, j);
            if RoidsTooltipTextLeft1:GetText() == itemName then
                local _, duration, _ = GetContainerItemCooldown(i, j);
                return duration;
            end
        end
    end
    
    return nil;
end

-- A list of Conditionals and their functions to validate them
Roids.Keywords = {
    help = function(conditionals)
        return true;
    end,
    
    harm = function(conditionals)
        return true;
    end,
    
    stance = function(conditionals)
        local inStance = false;
        for k,v in pairs(Roids.splitString(conditionals.stance, "/")) do
            if Roids.GetCurrentShapeshiftIndex() == tonumber(v) then
                inStance = true;
                break;
            end
        end
        
        if not inStance then
            return false;
        end
        return true;
    end,
    
    mod = function(conditionals)
        local modifiersPressed = true;
        
        for k,v in pairs(Roids.splitString(conditionals.mod, "/")) do
            if v == "alt" and not IsAltKeyDown() then
                modifiersPressed = false;
                break;
            elseif v == "ctrl" and not IsControlKeyDown() then
                modifiersPressed = false;
                break;
            elseif v == "shift" and not IsShiftKeyDown() then
                modifiersPressed = false;
                break;
            end
        end
        
        return modifiersPressed;
    end,
    
    target = function(conditionals)
        return Roids.IsValidTarget(conditionals.target, conditionals.help);
    end,
    
    combat = function(conditionals)
        return UnitAffectingCombat("player");
    end,
    
    nocombat = function(conditionals)
        return not UnitAffectingCombat("player");
    end,
    
    stealth = function(conditionals)
        return Roids.HasBuff("Interface\\Icons\\Ability_Ambush");
    end,
    
    nostealth = function(conditionals)
        return not Roids.HasBuff("Interface\\Icons\\Ability_Ambush");
    end,
    
    equipped = function(conditionals)
        return Roids.HasWeaponEquipped(conditionals.equipped);
    end,
    
    noequipped = function(conditionals)
        return not Roids.HasWeaponEquipped(conditionals.equipped);
    end,

    worn = equipped,
    noworn = noequipped,
    
    dead = function(conditionals)
        return UnitIsDeadOrGhost(conditionals.target);
    end,
    
    nodead = function(conditionals)
        return not UnitIsDeadOrGhost(conditionals.target);
    end,
    
    party = function(conditionals)
        return Roids.IsTargetInGroupType(conditionals.target, "party");
    end,
    
    raid = function(conditionals)
        return Roids.IsTargetInGroupType(conditionals.target, "raid");
    end,
    
    group = function(conditionals)
        if conditionals.group == "party" then
            return GetNumPartyMembers() > 0;
        elseif conditionals.group == "raid" then
            return GetNumRaidMembers() > 0;
        end
        return false;
    end,
    
    checkchanneled = function(conditionals)
        return Roids.CheckChanneled(conditionals);
    end,
    
    buff = function(conditionals)
        return Roids.HasBuffName(conditionals.buff, conditionals.target);
    end,
    
    nobuff = function(conditionals)
        return not Roids.HasBuffName(conditionals.nobuff, conditionals.target);
    end,
    
    debuff = function(conditionals)
        return Roids.HasDeBuffName(conditionals.debuff, conditionals.target);
    end,
    
    nodebuff = function(conditionals)
        return not Roids.HasDeBuffName(conditionals.nodebuff, conditionals.target);
    end,
    
    mybuff = function(conditionals)
        return Roids.HasBuffName(conditionals.mybuff, "player");
    end,
    
    nomybuff = function(conditionals)
        return not Roids.HasBuffName(conditionals.nomybuff, "player");
    end,
    
    mydebuff = function(conditionals)
        return Roids.HasDeBuffName(conditionals.mydebuff, "player");
    end,
    
    nomydebuff = function(conditionals)
        return not Roids.HasDeBuffName(conditionals.nomydebuff, "player");
    end,
    
    power = function(conditionals)
        return Roids.ValidatePower(conditionals.target, conditionals.power.bigger, conditionals.power.amount);
    end,
    
    mypower = function(conditionals)
        return Roids.ValidatePower("player", conditionals.mypower.bigger, conditionals.mypower.amount);
    end,
    
    rawpower = function(conditionals)
        return Roids.ValidateRawPower(conditionals.target, conditionals.rawpower.bigger, conditionals.rawpower.amount);
    end,
    
    myrawpower = function(conditionals)
        return Roids.ValidateRawPower("player", conditionals.myrawpower.bigger, conditionals.myrawpower.amount);
    end,
    
    hp = function(conditionals)
        return Roids.ValidateHp(conditionals.target, conditionals.hp.bigger, conditionals.hp.amount);
    end,
    
    myhp = function(conditionals)
        return Roids.ValidateHp("player", conditionals.myhp.bigger, conditionals.myhp.amount);
    end,
    
    type = function(conditionals)
        return Roids.ValidateCreatureType(conditionals.type, conditionals.target);
    end,
    
    cooldown = function(conditionals)
        local name = string.gsub(conditionals.cooldown, "_", " ");
        local cd = Roids.GetSpellCooldownByName(name);
        if not cd then cd = Roids.GetInventoryCooldownByName(name); end
        if not cd then cd = Roids.GetContainerItemCooldownByName(name) end
        return cd > 0;
    end,
    
    nocooldown = function(conditionals)
        local name = string.gsub(conditionals.nocooldown, "_", " ");
        local cd = Roids.GetSpellCooldownByName(name);
        if not cd then cd = Roids.GetInventoryCooldownByName(name); end
        if not cd then cd = Roids.GetContainerItemCooldownByName(name) end
        return cd == 0;
    end,
    
    channeled = function(conditionals)
        return Roids.CurrentSpell.spellName ~= "";
    end,
    
    nochanneled = function(conditionals)
        return Roids.CurrentSpell.spellName == "";
    end,
    
    attacks = function(conditionals)
        return UnitIsUnit("targettarget", conditionals.attacks);
    end,
    
    noattacks = function(conditionals)
        return not UnitIsUnit("targettarget", conditionals.noattacks);
    end,
    
    isplayer = function(conditionals)
        return UnitIsPlayer(conditionals.isplayer);
    end,
    
    isnpc = function(conditionals)
        return not UnitIsPlayer(conditionals.isnpc);
    end,
};