----------------------------------------------------------------------------
--	Ranked Matchmaking AI v1.1 NewStructure
--	Author: adamqqq		Email:adamqqq@163.com
----------------------------------------------------------------------------
--------------------------------------
-- General Initialization
--------------------------------------
local utility = require(GetScriptDirectory() .. "/utility")
local ability_item_usage_generic = require(GetScriptDirectory() .. "/ability_item_usage_generic")
local AbilityExtensions = require(GetScriptDirectory() .. "/util/AbilityAbstraction")
local A = require(GetScriptDirectory() .. "/util/MiraDota")

local debugmode = false
local npcBot = GetBot()
if npcBot == nil or npcBot:IsIllusion() then
	return
end

local Talents = {}
local Abilities = {}
local AbilitiesReal = {}

ability_item_usage_generic.InitAbility(Abilities, AbilitiesReal, Talents)

local AbilityToLevelUp =
{
	Abilities[3],
	Abilities[1],
	Abilities[1],
	Abilities[2],
	Abilities[1],
	Abilities[2],
	Abilities[1],
	Abilities[2],
	Abilities[4],
	"talent",
	Abilities[2],
	Abilities[4],
	Abilities[3],
	Abilities[3],
	"talent",
	Abilities[3],
	"nil",
	Abilities[4],
	"nil",
	"talent",
	"nil",
	"nil",
	"nil",
	"nil",
	"talent",
}

local TalentTree = {
	function()
		return Talents[1]
	end,
	function()
		return Talents[3]
	end,
	function()
		return Talents[6]
	end,
	function()
		return Talents[8]
	end
}

-- check skill build vs current level
utility.CheckAbilityBuild(AbilityToLevelUp)

function BuybackUsageThink()
	ability_item_usage_generic.BuybackUsageThink();
end

function CourierUsageThink()
	ability_item_usage_generic.CourierUsageThink();
end

function AbilityLevelUpThink()
	ability_item_usage_generic.AbilityLevelUpThink2(AbilityToLevelUp, TalentTree)
end

--------------------------------------
-- Ability Usage Thinking
--------------------------------------
local cast = {}
cast.Desire = {}
cast.Target = {}
cast.Type = {}
local Consider = {}
local CanCast = { utility.NCanCast, utility.NCanCast, utility.NCanCast, utility.UCanCast, utility.UCanCast }
local enemyDisabled = utility.enemyDisabled

function GetComboDamage()
	return ability_item_usage_generic.GetComboDamage(AbilitiesReal)
end

function GetComboMana()
	return ability_item_usage_generic.GetComboMana(AbilitiesReal)
end

Consider[1] = function() --Location AOE Example

	local abilityNumber = 1
	--------------------------------------
	-- Generic Variable Setting
	--------------------------------------
	local ability = AbilitiesReal[abilityNumber];

	if not ability:IsFullyCastable() then
		return BOT_ACTION_DESIRE_NONE, 0;
	end

	local CastRange = ability:GetCastRange();
	local Damage = ability:GetAbilityDamage();
	local Radius = ability:GetAOERadius()
	local CastPoint = ability:GetCastPoint();

	local allys = npcBot:GetNearbyHeroes(1200, false, BOT_MODE_NONE);
	local enemys = npcBot:GetNearbyHeroes(CastRange + 300, true, BOT_MODE_NONE)
	local WeakestEnemy, HeroHealth = utility.GetWeakestUnit(enemys)
	local creeps = npcBot:GetNearbyCreeps(CastRange + 300, true)
	local WeakestCreep, CreepHealth = utility.GetWeakestUnit(creeps)

	local mana = npcBot:GetMana()
	local maxMana = npcBot:GetMaxMana()
	local abilityLevel = ability:GetLevel()
	local radius = ability:GetAOERadius() - 100
	local castPoint = ability:GetCastPoint()
	local manaCost = ability:GetManaCost()
	local duration = ability:GetDuration()
	local damage = ability:GetAbilityDamage()
	local castRange = ability:GetCastRange()

	--------------------------------------
	-- Global high-priorty usage
	--------------------------------------
	--try to kill enemy hero
	if (npcBot:GetActiveMode() ~= BOT_MODE_RETREAT)
	then
		if (WeakestEnemy ~= nil)
		then
			if (CanCast[abilityNumber](WeakestEnemy))
			then
				if (
					HeroHealth <= WeakestEnemy:GetActualIncomingDamage(Damage, DAMAGE_TYPE_MAGICAL) or
						(
						HeroHealth <= WeakestEnemy:GetActualIncomingDamage(GetComboDamage(), DAMAGE_TYPE_MAGICAL) and
							npcBot:GetMana() > ComboMana))
				then
					return BOT_ACTION_DESIRE_HIGH, WeakestEnemy:GetExtrapolatedLocation(CastPoint);
				end
			end
		end
	end
	--------------------------------------
	-- Mode based usage
	--------------------------------------
	-- If we're farming and can kill 3+ creeps with LSA
	if npcBot:GetActiveMode() == BOT_MODE_FARM and mana > maxMana * 0.55 + manaCost then
		local locationAoE = npcBot:FindAoELocation(true, false, npcBot:GetLocation(), CastRange, Radius, 0, Damage);

		if (locationAoE.count >= 3) then
			return BOT_ACTION_DESIRE_LOW, locationAoE.targetloc;
		end
	end

	-- If my mana is enough,use it at enemy
	if npcBot:GetActiveMode() == BOT_MODE_LANING and mana > maxMana * 0.5 + manaCost
	then
		local locationAoE = npcBot:FindAoELocation(true, false, npcBot:GetLocation(), CastRange, Radius, 0, Damage);
		if (locationAoE.count >= 4) then
			return BOT_ACTION_DESIRE_LOW, locationAoE.targetloc
		end
	end

	-- If my mana is enough,use it at enemy
	if (npcBot:GetActiveMode() == BOT_MODE_LANING)
	then
		if (ManaPercentage > 0.6 and ability:GetLevel() >= 2)
		then
			local locationAoE = npcBot:FindAoELocation(true, true, npcBot:GetLocation(), CastRange, Radius, 0, Damage);
			if (locationAoE.count >= 2) then
				return BOT_ACTION_DESIRE_LOW, locationAoE.targetloc;
			end

			if (WeakestEnemy ~= nil)
			then
				if (CanCast[abilityNumber](WeakestEnemy))
				then
					return BOT_ACTION_DESIRE_LOW, WeakestEnemy:GetExtrapolatedLocation(CastPoint);
				end
			end
		end
	end

	-- If we're pushing or defending a lane and can hit 4+ creeps, go for it
	if (npcBot:GetActiveMode() == BOT_MODE_PUSH_TOWER_TOP or
		npcBot:GetActiveMode() == BOT_MODE_PUSH_TOWER_MID or
		npcBot:GetActiveMode() == BOT_MODE_PUSH_TOWER_BOT or
		npcBot:GetActiveMode() == BOT_MODE_DEFEND_TOWER_TOP or
		npcBot:GetActiveMode() == BOT_MODE_DEFEND_TOWER_MID or
		npcBot:GetActiveMode() == BOT_MODE_DEFEND_TOWER_BOT)
	then
		if mana > maxMana * 0.5 + manaCost then
			local locationAoE = npcBot:FindAoELocation(true, false, npcBot:GetLocation(), CastRange, Radius, 0, 0)

			if (locationAoE.count >= 4)
			then
				return BOT_ACTION_DESIRE_LOW, locationAoE.targetloc;
			end
		end
	end

	-- If we're going after someone
	if (npcBot:GetActiveMode() == BOT_MODE_ROAM or
		npcBot:GetActiveMode() == BOT_MODE_TEAM_ROAM or
		npcBot:GetActiveMode() == BOT_MODE_DEFEND_ALLY or
		npcBot:GetActiveMode() == BOT_MODE_ATTACK)
	then
		local locationAoE = npcBot:FindAoELocation(true, true, npcBot:GetLocation(), CastRange, Radius, 0, 0);
		if (locationAoE.count >= 2) then
			return BOT_ACTION_DESIRE_LOW, locationAoE.targetloc;
		end

		local npcEnemy = npcBot:GetTarget();

		if (npcEnemy ~= nil)
		then
			if (CanCast[abilityNumber](npcEnemy))
			then
				return BOT_ACTION_DESIRE_HIGH, npcEnemy:GetExtrapolatedLocation(CastPoint);
			end
		end
	end

	return BOT_ACTION_DESIRE_NONE, 0;

end

Consider[2] = function() --Location AOE Example

	local abilityNumber = 2
	--------------------------------------
	-- Generic Variable Setting
	--------------------------------------
	local ability = AbilitiesReal[abilityNumber];

	if not ability:IsFullyCastable() then
		return BOT_ACTION_DESIRE_NONE, 0;
	end

	local CastRange = ability:GetCastRange();
	local Damage = ability:GetAbilityDamage();
	local Radius = ability:GetAOERadius()
	local CastPoint = ability:GetCastPoint();

	local allys = npcBot:GetNearbyHeroes(1200, false, BOT_MODE_NONE);
	local enemys = npcBot:GetNearbyHeroes(CastRange + 300, true, BOT_MODE_NONE)
	local WeakestEnemy, HeroHealth = utility.GetWeakestUnit(enemys)
	local creeps = npcBot:GetNearbyCreeps(CastRange + 300, true)
	local WeakestCreep, CreepHealth = utility.GetWeakestUnit(creeps)

	--------------------------------------
	-- Global high-priorty usage
	--------------------------------------
	-- Check for a channeling enemy
	local channelingEnemy = A.Linq.First(enemys, function(t) return A.Hero.IsTeleporting(t) and CanCast[2](t) end)
	if channelingEnemy then
		return BOT_ACTION_DESIRE_HIGH, npcEnemy:GetLocation()
	end

	--try to kill enemy hero
	if (npcBot:GetActiveMode() ~= BOT_MODE_RETREAT)
	then
		if (WeakestEnemy ~= nil)
		then
			if (CanCast[abilityNumber](WeakestEnemy))
			then
				if (
					HeroHealth <= WeakestEnemy:GetActualIncomingDamage(Damage, DAMAGE_TYPE_MAGICAL) or
						(
						HeroHealth <= WeakestEnemy:GetActualIncomingDamage(GetComboDamage(), DAMAGE_TYPE_MAGICAL) and
							npcBot:GetMana() > ComboMana))
				then
					return BOT_ACTION_DESIRE_HIGH, WeakestEnemy:GetExtrapolatedLocation(CastPoint);
				end
			end
		end
	end

	-- If we're seriously retreating, see if we can land a stun on someone who's damaged us recently
	if (npcBot:GetActiveMode() == BOT_MODE_RETREAT and npcBot:GetActiveModeDesire() >= BOT_MODE_DESIRE_HIGH)
	then
		for _, npcEnemy in pairs(enemys) do
			if (npcBot:WasRecentlyDamagedByHero(npcEnemy, 2.0))
			then
				if (CanCast[abilityNumber](npcEnemy))
				then
					return BOT_ACTION_DESIRE_MODERATE, npcEnemy:GetExtrapolatedLocation(CastPoint);
				end
			end
		end
	end

	-- If we're going after someone
	if (npcBot:GetActiveMode() == BOT_MODE_ROAM or
		npcBot:GetActiveMode() == BOT_MODE_TEAM_ROAM or
		npcBot:GetActiveMode() == BOT_MODE_DEFEND_ALLY or
		npcBot:GetActiveMode() == BOT_MODE_ATTACK)
	then
		local locationAoE = npcBot:FindAoELocation(true, true, npcBot:GetLocation(), CastRange, Radius, 0, 0);
		if (locationAoE.count >= 2) then
			return BOT_ACTION_DESIRE_LOW, locationAoE.targetloc;
		end

		local npcEnemy = npcBot:GetTarget();

		if (npcEnemy ~= nil)
		then
			if (CanCast[abilityNumber](npcEnemy))
			then
				return BOT_ACTION_DESIRE_HIGH, npcEnemy:GetExtrapolatedLocation(CastPoint);
			end
		end
	end

	return BOT_ACTION_DESIRE_NONE, 0;

end

local function FindNearbyTeleportTarget(target)
	local isEnemy = not AbilityExtensions:IsOnSameTeam(npcBot, target)
	local castDelay = AbilitiesReal[4]:GetSpecialValueInt("cast_delay")
	local creeps = AbilityExtensions:GetNearbyCreeps(target, 600, isEnemy)
		:Filter(function(t) return not t:WasRecentlyDamagedByAnyHero(3) and
				#AbilityExtensions:GetNearbyHeroes(t, 500, true, BOT_MODE_NONE) == 0
		end)
		:Max(function(t) return t:GetHealth() end)
	local buildings = AbilityExtensions:Concat(target:GetNearbyTowers(700, isEnemy), target:GetNearbyBarracks(700, isEnemy))
	buildings = AbilityExtensions:Filter(buildings,
		function(t) return t:HasModifier("modifier_backdoor_protection_active") and t:GetHealth() >= 100 or
				t:GetHealth() >= 300 and not t:WasRecentlyDamagedByAnyHero(castDelay - 2) or t:GetHealth() >= 800 or
				t:HasModifier("modifier_fountain_glyph")
		end)
	buildings = AbilityExtensions:Max(buildings, function(t) return AbilityExtensions:GetBuildingPhysicalHealth(t) end)
	local hero
	if AbilityExtensions:HasScepter(npcBot) then
		local friends = target:GetNearbyHeroes(900, isEnemy, BOT_MODE_NONE)
		friends = AbilityExtensions:Filter(friends,
			function(t) return t:NotRetreating(t) and AbilityExtensions:AllyCanCast(t) and
					AbilityExtensions:GetHealthPercent(t) >= 0.5 and not t:IsSeverelyDisabled(t)
			end)
		friends = AbilityExtensions:FilterNot(friends,
			function(t) return t:IsIllusion() and
					AbilityExtensions:GetModifierRemainingDuration(t, "modifier_illusion") <= castDelay
			end)
		hero = target:Max(friends, function(t) return t:GetHealth() end)
	end
	return buildings or hero or creeps
end

local darkRiftOriginalTarget
local trackOriginalTargetPosition
local darkRiftChosenTarget

-- 7.31 rework fiends_gate
Consider[4] = function()
	local abilityNumber = 4
	--------------------------------------
	-- Generic Variable Setting
	--------------------------------------
	local ability = AbilitiesReal[abilityNumber];

	if not ability:IsFullyCastable() or npcBot:HasModifier("modifier_abyssal_underlord_dark_rift") then
		return BOT_ACTION_DESIRE_NONE, 0;
	end

	local CastRange = ability:GetCastRange();
	local Damage = ability:GetAbilityDamage();
	local Radius = ability:GetAOERadius()
	local CastPoint = ability:GetCastPoint();

	local allys = npcBot:GetNearbyHeroes(Radius, false, BOT_MODE_NONE);
	local enemys = npcBot:GetNearbyHeroes(CastRange + 300, true, BOT_MODE_NONE)
	local WeakestEnemy, HeroHealth = utility.GetWeakestUnit(enemys)
	local creeps = npcBot:GetNearbyCreeps(CastRange + 300, true)
	local WeakestCreep, CreepHealth = utility.GetWeakestUnit(creeps)

	if (npcBot:GetActiveMode() == BOT_MODE_RETREAT and npcBot:GetActiveModeDesire() >= BOT_MODE_DESIRE_HIGH)
	then
		if (npcBot:WasRecentlyDamagedByAnyHero(2.0) and AbilityExtensions:GetHealthPercent(npcBot) >= 0.25)
		then
			return BOT_ACTION_DESIRE_MODERATE, GetAncient(GetTeam()):GetLocation(), "Location"
		end
	end

	-- If we're going after someone
	if (npcBot:GetActiveMode() == BOT_MODE_ROAM or
		npcBot:GetActiveMode() == BOT_MODE_TEAM_ROAM or
		npcBot:GetActiveMode() == BOT_MODE_DEFEND_ALLY or
		npcBot:GetActiveMode() == BOT_MODE_ATTACK)
	then
		local npcEnemy = AbilityExtensions:GetTargetIfGood(npcBot)

		if (npcEnemy ~= nil)
		then
			if (#enemys == 0 and #allys >= 2 and GetUnitToUnitDistance(npcEnemy, npcBot) > 3000)
			then
				local target = FindNearbyTeleportTarget(npcEnemy)
				if target ~= nil then
					darkRiftOriginalTarget = npcEnemy
					return BOT_ACTION_DESIRE_HIGH, target:GetLocation(), "Location"
				end
			end
		end
	end

	return BOT_ACTION_DESIRE_NONE, 0;
end

local darkRiftCancelRange = 1200 ^ 2
Consider[5] = function()
	local ability = AbilitiesReal[5]
	if not ability:IsFullyCastable() or ability:IsHidden() then
		return 0
	end
	local target = AbilityExtensions:GetTargetIfGood(npcBot)
	if darkRiftChosenTarget then
		if darkRiftOriginalTarget:IsAlive() then
			if GetUnitToUnitDistanceSqr(darkRiftChosenTarget, darkRiftOriginalTarget) >= darkRiftCancelRange then
				coroutine.yield(BOT_ACTION_DESIRE_MODERATE)
			end
		else
			if GetUnitToLocationDistanceSqr(darkRiftChosenTarget, trackOriginalTargetPosition) >= darkRiftCancelRange and
				(
				target == nil or
					target:CanBeSeen() and GetUnitToLocationDistanceSqr(darkRiftChosenTarget, target) >= darkRiftCancelRange) then
				coroutine.yield(BOT_ACTION_DESIRE_HIGH)
			elseif AbilityExtensions:IsRetreating(npcBot) then
				coroutine.yield(BOT_ACTION_DESIRE_MODERATE)
			end
		end
	end
	return 0
end
Consider[5] = A.Dota.EmptyDesireFun

AbilityExtensions:AutoModifyConsiderFunction(npcBot, Consider, AbilitiesReal)
function AbilityUsageThink()
	if npcBot:HasModifier("modifier_abyssal_underlord_dark_rift") then
		if darkRiftOriginalTarget and darkRiftOriginalTarget:IsAlive() and darkRiftOriginalTarget:CanBeSeen() then
			trackOriginalTargetPosition = darkRiftOriginalTarget:GetLocation()
		end
	end
	-- Check if we're already using an ability
	if (npcBot:IsUsingAbility() or npcBot:IsChanneling() or npcBot:IsSilenced())
	then
		return
	end

	ComboMana = GetComboMana()
	AttackRange = npcBot:GetAttackRange()
	ManaPercentage = npcBot:GetMana() / npcBot:GetMaxMana()
	HealthPercentage = npcBot:GetHealth() / npcBot:GetMaxHealth()

	cast = ability_item_usage_generic.ConsiderAbility(AbilitiesReal, Consider)
	---------------------------------debug--------------------------------------------
	if (debugmode == true)
	then
		ability_item_usage_generic.PrintDebugInfo(AbilitiesReal, cast)
	end
	ability_item_usage_generic.UseAbility(AbilitiesReal, cast)
end

function CourierUsageThink()
	ability_item_usage_generic.CourierUsageThink()
end
