library AI
	import TerrainData
	import Players
	import Units
	import Statistics

	native UnitAlive takes unit u returns boolean

	constant integer STATE_AGGRESSIVE = 0
	constant integer STATE_RETREAT    = 1

	constant integer BEAST_ID   = 'h002'
	constant integer THUNDER_CD = 10

	player tempPlayer
	unit tempUnit
	group grp = CreateGroup()

	unit hunterLeader = null

	function hunterElection takes nothing returns nothing
		integer index = 0

		loop
			exitwhen index >= Players.FIRST_BEAST

			if UnitAlive(Units.hero[index]) and not Players.aiControlled[index] then
				hunterLeader = Units.hero[index]
				return
			end

			index++
		end

		index = 0
		loop
			exitwhen index >= Players.FIRST_BEAST

			if UnitAlive(Units.hero[index]) then
				hunterLeader = Units.hero[index]
				return
			end

			index++
		end
	end

	function randomizeSitePreferences takes nothing returns nothing
		integer index = 0

		loop
			exitwhen index >= Players.FIRST_BEAST

			Units.sitePref.saveInt(Units.hero[index].getHandleId(), GetRandomInt(0, 1))

			index++
		end
	end

	function proceedObjective takes nothing returns nothing
		if UnitHasItem(tempUnit, Units.artifact) then
			// Head home.
			IssuePointOrder(tempUnit, "move", TerrainData.RUNE_RETURN_X, TerrainData.RUNE_RETURN_Y)
		else
			// Pick an artifact based on objective.
			item artifact

			int objective = Units.sitePref.loadInt(Units.hero[tempUnit.getOwner().getId()].getHandleId())
			if objective == 0 then
				artifact = Units.artifact
			else
				artifact = Units.artifact2
			end

			// Head to artifact.
			if IsUnitInRangeXY(tempUnit, GetItemX(artifact), GetItemY(artifact), 200.) then
				IssueTargetOrder(tempUnit, "smart", artifact)
			else
				IssuePointOrder(tempUnit, "move", artifact.getX(), artifact.getY())
			end
		end
	end

	function parseHunter takes nothing returns nothing
		unit iter
		real d = 1200.
		unit selected = null
		player owner = GetOwningPlayer(tempUnit)
		real hunterX
		real hunterY
		real tempD
		real ang

		if not UnitAlive(hunterLeader) then
			hunterElection()
		end

		hunterX = GetUnitX(tempUnit)
		hunterY = GetUnitY(tempUnit)

		// Cache closest enemy if one is in range.
		GroupEnumUnitsInRange(grp, GetUnitX(tempUnit), GetUnitY(tempUnit), d, null)
		loop
			iter = FirstOfGroup(grp)
			exitwhen iter == null

			if UnitAlive(iter) and IsUnitInRange(iter, tempUnit, d) and IsUnitEnemy(iter, owner) and IsUnitVisible(iter, owner) then
				selected = iter
				d = TerrainData.distanceBetweenXY(GetUnitX(iter), hunterX, GetUnitY(iter), hunterY)
			end

			GroupRemoveUnit(grp, iter)
		end

		// Fight if in range.
		if selected != null then
			if IsUnitInRange(tempUnit, selected, 900.) then
				tempUnit.issuePointOrder("channel", vec2(GetUnitX(selected), GetUnitY(selected)))
			else
				IssuePointOrder(tempUnit, "move", GetUnitX(selected), GetUnitY(selected))
			end
		else
			// Proceed; no enemy is available.
			if tempUnit == hunterLeader then
				// I am the leader.

				// Check how far away buddies are.
				GroupEnumUnitsInRange(grp, GetUnitX(tempUnit), GetUnitY(tempUnit), 1400., null)
				d = 0.
				loop
					iter = FirstOfGroup(grp)
					exitwhen iter == null
					tempD = TerrainData.distanceBetweenXY(GetUnitX(iter), hunterX, GetUnitY(iter), hunterY)

					if UnitAlive(iter) and iter.getOwner().getId() < Players.FIRST_BEAST and tempD > d then
						d = tempD
					end

					GroupRemoveUnit(grp, iter)
				end

				if d > 1000. then
					// Buddies are a bit slow, wait.
					IssueImmediateOrder(tempUnit, "stop")
					SetUnitFacingTimed(tempUnit, GetUnitFacing(tempUnit) - 90., 1.25)
				else
					// Proceed depending on objective.
					ExecuteFunc("proceedObjective")
				end
			else
				// I am supportive.
				ang = TerrainData.radiansBetweenXY(hunterX, GetUnitX(hunterLeader), hunterY, GetUnitY(hunterLeader)) + bj_PI
				IssuePointOrder(tempUnit, "move", GetUnitX(hunterLeader) + 200. * Cos(ang), GetUnitY(hunterLeader) + 200. * Sin(ang))
			end
		end
	end

	function parseBeast takes nothing returns nothing
		unit iter
		real d = 99999999.
		unit selected = null
		real safeX
		real safeY
		real ang

		GroupEnumUnitsInRect(grp, bj_mapInitialPlayableArea, null)
		loop
			iter = FirstOfGroup(grp)
			exitwhen iter == null

			if IsUnitEnemy(iter, tempPlayer) and UnitAlive(iter) and IsUnitInRangeXY(iter, GetUnitX(tempUnit), GetUnitY(tempUnit), d) and GetUnitAbilityLevel(iter, 'Binv') < 1 then
				selected = iter
				d = TerrainData.distanceBetweenXY(GetUnitX(iter), GetUnitX(tempUnit), GetUnitY(iter), GetUnitY(tempUnit))
			end

			GroupRemoveUnit(grp, iter)
		end

		if Units.beastState.loadInt(tempUnit.getHandleId()) == STATE_AGGRESSIVE then
			// Aggressive.

			// Change state if low.
			if GetWidgetLife(tempUnit) < GetUnitState(tempUnit, UNIT_STATE_MAX_LIFE) * .5 then
				Units.beastState.saveInt(tempUnit.getHandleId(), STATE_RETREAT)
			end

			// Become passive if fighting without lightning.
			if Units.countBalls(tempUnit) < 2 and IsUnitInRange(tempUnit, selected, 1200.) then
				Units.beastState.saveInt(tempUnit.getHandleId(), STATE_RETREAT)
			end

			// Go fight.
			if selected != null and GetUnitCurrentOrder(tempUnit) != OrderId("attack") then
				// Try lightning.
				if IsUnitInRange(tempUnit, selected, 140.) and (Statistics.secondsElapsed - Units.thunderCd.loadInt(tempUnit.getHandleId()) > THUNDER_CD) then
					IssueImmediateOrder(tempUnit, "berserk")
					Units.thunderCd.saveInt(tempUnit.getHandleId(), Statistics.secondsElapsed)
				else
					IssueTargetOrder(tempUnit, "attack", selected)
				end
			end
		else
			// Retreat.

			// Change state if high.
			if GetWidgetLife(tempUnit) > GetUnitState(tempUnit, UNIT_STATE_MAX_LIFE) * .8 then
				Units.beastState.saveInt(tempUnit.getHandleId(), STATE_AGGRESSIVE)
			end

			// Run away.
			if GetItemX(Units.artifact) > 0. and GetItemY(Units.artifact) < 0. then
				// Defend base area.
				safeX =  3350.
				safeY = -3700.
			else
				// Defend castle area.
				safeX = -2150.
				safeY =  3180.
			end

			if selected != null and IsUnitInRange(tempUnit, selected, 1200.) then
				ang = TerrainData.radiansBetweenXY(GetUnitX(selected), safeX, GetUnitY(selected), safeY)
				IssuePointOrder(tempUnit, "move", GetUnitX(selected) + 1400. * Cos(ang), GetUnitY(selected) + 1400. * Sin(ang))
			else
				SetUnitFacingTimed(tempUnit, GetUnitFacing(tempUnit) + 90., 1.)
			end
		end
	end

	function parsePlayer takes nothing returns nothing
		unit hero = Units.hero[GetPlayerId(tempPlayer)]

		tempUnit = hero

		if not UnitAlive(hero) then
			return
		end

		if GetUnitTypeId(hero) == BEAST_ID then
			ExecuteFunc("parseBeast")
		else
			ExecuteFunc("parseHunter")
		end
	end

	public struct AI
		static constant real CLOCK_PERIOD = 1./2.

		static player array players
		static integer count = -1

		static timer clock = CreateTimer()

		static method periodic takes nothing returns nothing
			integer index = 0
			loop
				exitwhen index > count

				tempPlayer = players[index]
				ExecuteFunc("parsePlayer")

				index++
			end
		end

		static method resetState takes nothing returns nothing
			integer index = Players.FIRST_BEAST

			// Elect new hunter leader.
			hunterElection()
			randomizeSitePreferences()

			loop
				exitwhen index > Players.LAST_BEAST

				// Reset beast states to aggressive.
				Units.beastState.saveInt(Units.hero[index].getHandleId(), STATE_AGGRESSIVE)

				// Reset thunder cooldown.
				Units.thunderCd.saveInt(Units.hero[index].getHandleId(), 0)

				index++
			end
		end

		static method addPlayer takes player p returns nothing
			count++
			players[count] = p

			if count == 0 then
				TimerStart(clock, CLOCK_PERIOD, true, function periodic)
			end
		end
	end
endlibrary
