--			-- Go through each valid target to see if within field of view and if there is
--			-- clear line of sight. Field of view treated as wedge in front of character.
--			for _, character in pairs(characters) do
--				local toTarget = (character.HumanoidRootPart.Position - model.HumanoidRootPart.Position)
--				toTarget = Vector3.new(toTarget.X, 0, toTarget.Z)
--				local angle = math.acos(toTarget:Dot(model.HumanoidRootPart.CFrame.lookVector)/toTarget.magnitude)
--				if math.deg(angle) < configs["FieldOfView"]/2 then	
--					ZombieTarget = character
--					-- raycast to see if target is actually visible
--					local toTarget = Ray.new(model.HumanoidRootPart.Position, (ZombieTarget.HumanoidRootPart.Position - model.HumanoidRootPart.Position))
--					local part, position = game.Workspace:FindPartOnRayWithIgnoreList(toTarget, zombies)
--					if part and part.Parent == ZombieTarget then
--						return true
--					end		
--					ZombieTarget = nil						
--				end
--			end
		end
		return false
	end
	CanSeeTarget.TransitionState = PursueState
	
	-- TargetDead: Check if target is dead.
	local TargetDead = StateMachine.NewCondition()
	TargetDead.Name = "TargetDead"
	TargetDead.Evaluate = function()
		if ZombieTarget and ZombieTarget.Humanoid then
			return ZombieTarget.Humanoid.Health <= 0
		end
		return true
	end
	TargetDead.TransitionState = IdleState
	
	-- GotDamaged: Check if NPC has taken damage
	local lastHealth = model.Humanoid.Health
	local GotDamaged = StateMachine.NewCondition()
	GotDamaged.Name = "GotDamaged"
	GotDamaged.Evaluate = function()
		if model then
			if lastHealth > model.Humanoid.Health then
				return true
			end
		end
		return false
	end	
	GotDamaged.TransitionState = SearchState
	
	-- GotBored: Used to provide random state change.
	local GotBored = StateMachine.NewCondition()
	GotBored.Name = "GotBored"
	GotBored.Evaluate = function()
		local now = os.time()
		if now - lastBored > configs["BoredomDuration"] then
			local roll = math.random()
			if roll < configs["ChanceOfBoredom"] then
				lastBored = now
				if GotBored.TransitionState == SearchState then
					GotBored.TransitionState = IdleState
				else
					GotBored.TransitionState = SearchState
				end
				return true
			end
		end
		return false
	end
	GotBored.TransitionState = IdleState
	
	-- LostTarget: Checks clear line of sight
	local LostTarget = StateMachine.NewCondition()
	LostTarget.Name = "LostTarget"
	LostTarget.Evaluate = function()
		if true then return false end
		if ZombieTarget then
			if (ZombieTarget.HumanoidRootPart.Position - model.HumanoidRootPart.Position).magnitude > 10 then
				local toTarget = Ray.new(model.HumanoidRootPart.Position, (ZombieTarget.HumanoidRootPart.Position - model.HumanoidRootPart.Position))
				local part, position = game.Workspace:FindPartOnRay(toTarget, model)
				if not part or part.Parent ~= ZombieTarget  then
					--print("Lost target!")
					ZombieTargetLastLocation = ZombieTarget.HumanoidRootPart.Position
					ZombieTarget = nil
					return true
				end
			end
		end
		return false
	end
	LostTarget.TransitionState = HuntState
	
	local WithinRange = StateMachine.NewCondition()
	WithinRange.Name = "WithinRange"
	WithinRange.Evaluate = function()
		if ZombieTarget then
			if model:FindFirstChild("HumanoidRootPart") then
				local distance = (model.HumanoidRootPart.Position - ZombieTarget.HumanoidRootPart.Position).magnitude
				if distance < configs["AttackRange"] then
					--print("Within attack range!")
					return true
				end
			end
		end
		return false
	end
	WithinRange.TransitionState = AttackState
	
	local OutsideRange = StateMachine.NewCondition()
	OutsideRange.Name = "OutsideRange"
	OutsideRange.Evaluate = function()
		if ZombieTarget then
			if model:FindFirstChild("HumanoidRootPart") and ZombieTarget:FindFirstChild("HumanoidRootPart") then
				local distance = (model.HumanoidRootPart.Position - ZombieTarget.HumanoidRootPart.Position).magnitude
				if distance > configs["AttackRange"] then
					--print("Outside attack range!")
					return true
				end
			end
		end
		return false
	end
	OutsideRange.TransitionState = PursueState
	
	table.insert(IdleState.Conditions, CanSeeTarget)
	table.insert(IdleState.Conditions, GotDamaged)
	table.insert(IdleState.Conditions, GotBored)
	
	table.insert(SearchState.Conditions, GotBored)
	table.insert(SearchState.Conditions, CanSeeTarget)
	
	table.insert(PursueState.Conditions, LostTarget)
	table.insert(PursueState.Conditions, WithinRange)
	table.insert(PursueState.Conditions, TargetDead)
	
	table.insert(AttackState.Conditions, OutsideRange)
	table.insert(AttackState.Conditions, TargetDead)
	
	table.insert(HuntState.Conditions, GotBored)
	table.insert(HuntState.Conditions, CanSeeTarget)
	
	-- Setup arms damage
	local canHit = true
	local lastHit = os.time()
	local function handleHit(other, zombieArm)
		if canHit then
			if other and other.Parent and other.Parent.Name ~= "Zombie" and other.Parent:FindFirstChild("Humanoid") then
				local enemy = other.Parent						
				if enemy.Humanoid.WalkSpeed > 0 then
					local shield = enemy:FindFirstChild("Shield")
					if shield then
						model:BreakJoints()
					end
					local killBehaviour = require(game.ServerScriptService.Server_Main.Behaviours.Kill)
					killBehaviour:ExecuteBehaviour(zombieArm, enemy)
				end
			end
		else
			local now = os.time()
			if now - lastHit > configs["DamageCooldown"] then
				lastHit = now
				canHit = true
			end
		end
	end	
	local leftHitConnect, rightHitConnect
	leftHitConnect = model:FindFirstChild("Left Arm").Touched:connect(function(other) handleHit(other, model:FindFirstChild("Left Arm")) end)
	rightHitConnect = model:FindFirstChild("Right Arm").Touched:connect(function(other) handleHit(other, model:FindFirstChild("Right Arm")) end)
	
	--ZombieAI.Animate(model)
	--updateDisplay()
	--updateDisplay(model.BillboardGui.TextLabel, StateMachine.CurrentState)
	local thread = coroutine.create(function()
		while true do
			wait()
			-- calculate repulsion force
			if model == nil then
				break
			end
			
			if model:FindFirstChild("HumanoidRootPart") == nil then
				break
			end
						
			local humanoids = HumanoidList:GetCurrent()
			local localZombies = {}
			for _, humanoid in pairs(humanoids) do
				if humanoid and humanoid ~= model.Humanoid and humanoid.Parent and humanoid.Parent:FindFirstChild("HumanoidRootPart") then
					local HumanoidRootPart = humanoid.Parent:FindFirstChild("HumanoidRootPart")
					if HumanoidRootPart ~= nil and model ~= nil then
						if model:FindFirstChild("HumanoidRootPart") then
							local distance = (model.HumanoidRootPart.Position - HumanoidRootPart.Position).magnitude
							if distance <= 2.5 then
								table.insert(localZombies, HumanoidRootPart.Position)
							end
						end
					end
				end
			end
			local repulsionDirection = AIUtilities:GetRepulsionVector(model.HumanoidRootPart.Position, localZombies)
			if repulsionDirection.magnitude > 0 then
				--print("replusion direction: " .. tostring(repulsionDirection))
			end
			model.HumanoidRootPart.RepulsionForce.force = repulsionDirection
			
			if StateMachine.CurrentState and model.Configurations.Debug.Value then
				model.BillboardGui.TextLabel.Visible = true
				model.BillboardGui.TextLabel.Text = StateMachine.CurrentState.Name
			end
			if not model.Configurations.Debug.Value then
				model.BillboardGui.TextLabel.Visible = false
			end
		end
	end)
	coroutine.resume(thread)	
	
	StateMachine.SwitchState(IdleState)
	
	zombie.Stop = function()
		StateMachine.SwitchState(nil)
	end	
	
	return zombie
end

return ZombieAI