ZombiePrograms = ZombiePrograms or {}
require("Bandits/Bandit");
require("Bandits/BanditBrain");
require("Bandits/BanditPrograms");
require("Bandits/BanditUtils");

ZombiePrograms.Defeat = {}
ZombiePrograms.Defeat.Stages = {}

local function BWOFallbackAction(bandit)
    local tasks = {}
    local action = ZombRand(50)

    if action == 0 then
        local task = {action="Time", anim="ShiftWeight", time=200}
        table.insert(tasks, task)
    elseif action == 1 then
        local task = {action="Time", anim="ChewNails", time=200}
        table.insert(tasks, task)
    elseif action == 2 then
        local task = {action="Time", anim="Smoke", time=200}
        table.insert(tasks, task)
        table.insert(tasks, task)
        table.insert(tasks, task)
    elseif action == 3 then
        local task = {action="Time", anim="PullAtCollar", time=200}
        table.insert(tasks, task)
    elseif action == 4 then
        local task = {action="Time", anim="Sneeze", time=200}
        table.insert(tasks, task)
        addSound(getPlayer(), bandit:getX(), bandit:getY(), bandit:getZ(), 7, 60)
    elseif action == 5 then
        local task = {action="Time", anim="WipeBrow", time=200}
        table.insert(tasks, task)
    elseif action == 6 then
        local task = {action="Time", anim="WipeHead", time=200}
        table.insert(tasks, task)
    end

    return tasks
end

ZombiePrograms.Defeat.Init = function(bandit)
end

ZombiePrograms.Defeat.Prepare = function(bandit)
	--print("prepare is called.");
    local tasks = {}
    local world = getWorld()
    local cell = getCell()
    local cm = world:getClimateManager()
    local dls = cm:getDayLightStrength()

    Bandit.ForceStationary(bandit, false)
	--[[
	Bandit.SetWeapons(bandit, Bandit.GetWeapons(bandit))		---disable if used with melee.true

	local primary = Bandit.GetBestWeapon(bandit)		---disable if used with melee.true

    local secondary
    if SandboxVars.Bandits.General_CarryTorches and dls < 0.3 then
        secondary = "Base.HandTorch"
    end

    local task = {action="Equip", itemPrimary=primary, itemSecondary=secondary}
    table.insert(tasks, task)
	]]
	return {status=true, next="Follow", tasks=tasks}
end

ZombiePrograms.Defeat.Follow = function(bandit)
	--print("follow is called.");
    local tasks = {}

	if bandit:getModData().isSubdued or bandit:getModData().ZomboWinSexScene or bandit:getModData().activeScene then
		--print("subdue catch is called.");
		return {status=true, next="Subdued", tasks=tasks}
	end
    -- update walk type
    local world = getWorld()
    local cell = getCell()
    local cm = world:getClimateManager()
    local dls = cm:getDayLightStrength()
    local weapons = Bandit.GetWeapons(bandit)
    local hands = bandit:getVariableString("BanditPrimaryType")
	local isSubdued = bandit:getModData().isSubdued
	local isRapist = bandit:getModData().isRapist
	if not isRapist and not isSubdued then bandit:getModData().isRapist = true; end	---- because being here means rapist ----
 
    local walkType = "Run"
    local endurance = -0.06
    local secondary
	
    if dls < 0.3 then
        if SandboxVars.Bandits.General_SneakAtNight then
            if Bandit.IsDNA(bandit, "sneak") then
                walkType = "SneakWalk"
                endurance = 0
            end
        end
    end

    local health = bandit:getHealth()
    if health < 0.8 then
        walkType = "Limp"
        endurance = 0
    end 
 
    local handweapon = bandit:getVariableString("BanditWeapon") 
    
    local healthMin = 0.7
--[[	---bandits often run around like chicken with heads cut off
    if SandboxVars.Bandits.General_RunAway and health < healthMin then
		print("end of follow: escape");
        return {status=true, next="Escape", tasks=tasks}
    end
]]
    local target = {}
	local config = {}
	config.mustSee = true
	config.hearDist = 7
	
    local closestZombie = BanditUtils.GetClosestZombieLocation(bandit)
    local closestBandit = BanditUtils.GetClosestEnemyBanditLocation(bandit)
    local closestPlayer = BanditUtils.GetClosestPlayerLocation(bandit, config)

    target = closestZombie
    if closestBandit.dist < closestZombie.dist then
        target = closestBandit
        enemy = BanditZombie.GetInstanceById(target.id)
		if enemy then
			Bandit.SetProgram(bandit, "Bandit", {})
			--print("end of follow: prepare");
			return {status=true, next="Prepare", tasks=tasks}
		end
    end

    if not Bandit.IsHostile(bandit) and closestPlayer.dist < closestBandit.dist then
        target = closestPlayer
        enemy = BanditPlayer.GetPlayerById(target.id)
		if enemy:getModData().ZomboWinSexScene and not bandit:getModData().activeScene then
			target = closestZombie or closestBandit;
		end
    end
	--[[	---bandit sometimes attacks the player
	if target == closestZombie or target == closestBandit then
		Bandit.SetProgram(bandit, "Bandit", {})
		return {status=true, next="Prepare", tasks=tasks}
	end
	]]
    local closeSlow = false

    if target.x and target.y and target.z then

        local targetSquare = cell:getGridSquare(target.x, target.y, target.z)
        local banditSquare = bandit:getSquare()
        if targetSquare and banditSquare then
            local targetBuilding = targetSquare:getBuilding()
            local banditBuilding = banditSquare:getBuilding()
            local x = 100

            if targetBuilding and not banditBuilding then
                Bandit.Say(bandit, "INSIDE")
            end
            if not targetBuilding and banditBuilding then
                Bandit.Say(bandit, "OUTSIDE")
            end
            if targetBuilding and banditBuilding then
                if bandit:getZ() < target.z then
                    Bandit.Say(bandit, "UPSTAIRS")
                else
                    local room = targetSquare:getRoom()
                    if room then
                        local roomName = room:getName()
                        if roomName == "kitchen" then
                            Bandit.Say(bandit, "ROOM_KITCHEN")
                        end
                        if roomName == "bathroom" then
                            Bandit.Say(bandit, "ROOM_BATHROOM")
                        end
                    end
                end
            end
        end

        -- out of ammo, get close
        local minDist = 0.5		--- default 2.0f
		
        if target.dist > minDist then

            -- detect water to build a bridge
            if SandboxVars.Bandits.General_BuildBridge then 
                local path = BanditUtils.Bresenham(math.floor(bandit:getX() + 0.0), math.floor(bandit:getY() + 0.0), math.floor(target.x + 0.0), math.floor(target.y + 0.0))
                local last = {}
                for _, coords in pairs(path) do
                    local square = cell:getGridSquare(coords.x, coords.y, 0)
                    if square then
                        if BanditUtils.IsWater(square) then
                            -- local as = AdjacentFreeTileFinder.Find(square, bandit)
                            if last.x and last.y then
                                -- print ("go build bridge from x: " .. last.x .. " y: " .. last.y .. " to x:" .. coords.x .. " y:" .. coords.y)
                                table.insert(tasks, BanditUtils.GetMoveTask(endurance, last.x, last.y, 0, walkType, target.dist, false))

                                if math.floor(bandit:getX()) == last.x and math.floor(bandit:getY()) == last.y then
                                    -- in position
                                    return {status=true, next="BuildBridge", tasks=tasks}
                                else
                                    --not there yet
                                    return {status=true, next="Follow", tasks=tasks}
                                end
                            end
                        else
                            last = coords
                        end
                        
                    end
                end
            end

            -- must be deterministic, not random (same for all clients)
            local id = BanditUtils.GetCharacterID(bandit)

            local dx = 0
            local dy = 0
            local dxf = ((id % 10) - 5) / 10
            local dyf = ((id % 11) - 5) / 10


            table.insert(tasks, BanditUtils.GetMoveTask(endurance, target.x+dx+dxf, target.y+dy+dyf, target.z, walkType, target.dist, closeSlow))
        end
    else
		if not bandit:getModData().activeScene and isRapist then
			local chance = ZombRand(6)
			if chance == 0 then
				local task = {action="Time", anim="Smoke", time=200}
				table.insert(tasks, task)
			else
				local walkType = "Walk"
				local endurance = 0
				local config = {}
				config.mustSee = true
				config.hearDist = 7

				local closestPlayer = BanditUtils.GetClosestPlayerLocation(bandit, config)

				if closestPlayer.x and closestPlayer.y and closestPlayer.z then

					-- calculate random escape direction
					local deltaX = 1 + ZombRand(4)
					local deltaY = 1 + ZombRand(4)

					local rx = ZombRand(2)
					local ry = ZombRand(2)
					if rx == 1 then deltaX = -deltaX end
					if ry == 1 then deltaY = -deltaY end

					table.insert(tasks, BanditUtils.GetMoveTask(endurance, closestPlayer.x+deltaX, closestPlayer.y+deltaY, 0, walkType, 12, false))
				end
				--print("end of follow: target");
				return {status=true, next="Follow", tasks=tasks}
			end
		end
    end
	
	--print("end of follow");
    return {status=true, next="Follow", tasks=tasks}
end

ZombiePrograms.Defeat.Subdued = function(bandit)
	local tasks = {}
	local bx = bandit:getX();
	local by = bandit:getY();
	local bz = bandit:getZ();
	local brain = BanditBrain.Get(bandit)
	local isRapist = bandit:getModData().isRapist
--[[
	print("subdue start");
	print("bSexScene: " .. tostring(bandit:getModData().ZomboWinSexScene));
	print("bActiveScene: " .. tostring(bandit:getModData().activeScene));
	print("bisSubdued: " .. tostring(bandit:getModData().isSubdued));
	print("bisRapist: " .. tostring(isRapist));
]]
	--Play ZW anims
	if bandit:getModData().ZomboWinSexScene then
		bandit:getModData().ZomboWinSexScene = false;
		bandit:getModData().activeScene = true;
		--if bandit:isSitAgainstWall() then bandit:setSitAgainstWall(false); end	--no sitting
		local task = {action="Forn", time=2000};
		
        table.insert(brain.tasks, 1, task);
		return {status=true, next="Subdued", tasks=tasks};
	end
	--- subdued only: from ZLMain Rope
	if bandit:getModData().isSubdued and not bandit:getModData().ZomboWinSexScene and not bandit:getModData().activeScene then
		Bandit.ForceStationary(bandit, true);
		--- \Bandits\42\media\AnimSets\zombie\bumped\ ---
		local task = {action="Time", anim="Surrender", time=200}
        table.insert(tasks, task)
		return {status=true, next="Subdued", tasks=tasks}
	end
	
	if not bandit:getModData().isSubdued and not bandit:getModData().ZomboWinSexScene and not bandit:getModData().activeScene and isRapist then
		--print("rapist follow is called.");
		return {status=true, next="Follow", tasks=tasks};
--[[	--- code makes friendlies skip rapist logic and becomes rapists ---
	elseif not isRapist then
		print("a nil rapist follow is called.");
		return {status=true, next="Follow", tasks=tasks};
]]
	end
	
	-- fallback
	if not bandit:getModData().isSubdued and not bandit:getModData().ZomboWinSexScene and not bandit:getModData().activeScene then
		local subTasks = BWOFallbackAction(bandit)
		if #subTasks > 0 then
			for _, subTask in pairs(subTasks) do
				table.insert(tasks, subTask)
			end
			return {status=true, next="Subdued", tasks=tasks};	
		end
	end
--[[
	print("subdue done");
	print("bSexScened: " .. tostring(bandit:getModData().ZomboWinSexScene));
	print("bActiveScened: " .. tostring(bandit:getModData().activeScene));
	print("bisSubduedd: " .. tostring(bandit:getModData().isSubdued));
	print("bisRapistd: " .. tostring(isRapist));
]]
	return {status=true, next="Subdued", tasks=tasks}
end

ZombiePrograms.Defeat.Escape = function(bandit)
    local tasks = {}
    local weapons = Bandit.GetWeapons(bandit)

    local health = bandit:getHealth()

    if SandboxVars.Bandits.General_Surrender and health < 0.16 then
        bandit:setPrimaryHandItem(nil)
        if weapons.melee then
            local item = InventoryItemFactory.CreateItem(weapons.melee)
            if item then
                bandit:getSquare():AddWorldInventoryItem(item, ZombRandFloat(0.2, 0.8), ZombRandFloat(0.2, 0.8), 0)
                weapons.melee = nil
            end
        end
        if weapons.primary and weapons.primary.name then
            local item = InventoryItemFactory.CreateItem(weapons.primary.name)
            if item then
                bandit:getSquare():AddWorldInventoryItem(item, ZombRandFloat(0.2, 0.8), ZombRandFloat(0.2, 0.8), 0)
                weapons.primary = nil
            end
        end
        if weapons.secondary and weapons.secondary.name then
            local item = InventoryItemFactory.CreateItem(weapons.secondary.name)
            if item then
                bandit:getSquare():AddWorldInventoryItem(item, ZombRandFloat(0.2, 0.8), ZombRandFloat(0.2, 0.8), 0)
                weapons.secondary = nil
            end
        end
        Bandit.SetWeapons(bandit, weapons)
        return {status=true, next="Surrender", tasks=tasks}
    end

    local endurance = -0.06
    local walkType = "Run"
    if health < 0.8 then
        walkType = "Limp"
        endurance = 0
    end
	local config = {}
	config.mustSee = true
	config.hearDist = 7
    --local handweapon = bandit:getVariableString("BanditWeapon")

    local closestPlayer = BanditUtils.GetClosestPlayerLocation(bandit, config)

    if closestPlayer.x and closestPlayer.y and closestPlayer.z then

        -- calculate random escape direction
        local deltaX = 100 + ZombRand(100)
        local deltaY = 100 + ZombRand(100)

        local rx = ZombRand(2)
        local ry = ZombRand(2)
        if rx == 1 then deltaX = -deltaX end
        if ry == 1 then deltaY = -deltaY end

        table.insert(tasks, BanditUtils.GetMoveTask(endurance, closestPlayer.x+deltaX, closestPlayer.y+deltaY, 0, walkType, 12, false))
    end
    return {status=true, next="Escape", tasks=tasks}
end

ZombiePrograms.Defeat.Surrender = function(bandit)
    local tasks = {}

    if ZombRand(2) == 0 then
        local task = {action="Time", anim="Surrender", time=40}
        table.insert(tasks, task)
    else
        local task = {action="Time", anim="Scramble", time=40}
        table.insert(tasks, task)
    end

    return {status=true, next="Surrender", tasks=tasks}
end

ZombiePrograms.Defeat.BuildBridge = function(bandit)
    local tasks = {}

    for dx = -1, 1 do
        for dy = -1, 1 do
            local square = getCell():getGridSquare(bandit:getX() + dx, bandit:getY() + dy, bandit:getZ())
            if square then
                if BanditUtils.IsWater(square) then
                    local task = {action="Equip", itemPrimary="Base.Hammer", itemSecondary=nil}
                    table.insert(tasks, task)
                
                    local task = {action="BuildFloor", anim="HammerLow", sound="Hammering", x=square:getX(), y=square:getY(), time=500}
                    table.insert(tasks, task)
                    return {status=true, next="BuildBridge", tasks=tasks}
                end
            end
        end
    end
    return {status=true, next="Follow", tasks=tasks}
end