local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local LS = wesnoth.require "lua/location_set.lua"

local function get_guardian(cfg)
    local filter = cfg.filter or { id = cfg.id }
    local guardian = AH.get_units_with_moves {
        side = wesnoth.current.side,
        { "and", filter }
    }[1]
    return guardian
end

local ca_zone_guardian = {}

function ca_zone_guardian:evaluation(ai, cfg)
    if get_guardian(cfg) then return cfg.ca_score end
    return 0
end

function ca_zone_guardian:execution(ai, cfg)
    local guardian = get_guardian(cfg)
    local reach = wesnoth.find_reach(guardian)

    local zone_enemy = cfg.filter_location_enemy or cfg.filter_location
    local enemies = wesnoth.get_units {
            { "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
            { "filter_location", zone_enemy }
        }
    if enemies[1] then
        local min_dist, target = 9e99
        for _,enemy in ipairs(enemies) do
            local dist = H.distance_between(guardian.x, guardian.y, enemy.x, enemy.y)
            if (dist < min_dist) then
                target, min_dist = enemy, dist
            end
        end

        -- If a valid target was found, guardian attacks this target, or moves toward it
        if target then
            -- Find tiles adjacent to the target
            -- Save the one with the highest defense rating that guardian can reach
            local best_defense, attack_loc = -9e99
            for xa,ya in H.adjacent_tiles(target.x, target.y) do
                -- Only consider unoccupied hexes
                local occ_hex = wesnoth.get_units { x = xa, y = ya, { "not", { id = guardian.id } } }[1]
                if not occ_hex then
                    local defense = 100 - wesnoth.unit_defense(guardian, wesnoth.get_terrain(xa, ya))
                    local nh = AH.next_hop(guardian, xa, ya)
                    if nh then
                        if (nh[1] == xa) and (nh[2] == ya) and (defense > best_defense) then
                            best_defense, attack_loc = defense, { xa, ya }
                        end
                    end
                end
            end

            -- If a valid hex was found: move there and attack
            if attack_loc then
                AH.movefull_stopunit(ai, guardian, attack_loc)
                if (not guardian) or (not guardian.valid) then return end
                if (not target) or (not target.valid) then return end

                AH.checked_attack(ai, guardian, target)
            else  -- Otherwise move toward that enemy
                local reach = wesnoth.find_reach(guardian)

                -- Go through all hexes the guardian can reach, find closest to target
                -- Cannot use next_hop here since target hex is occupied by enemy
                local min_dist, nh = 9e99
                for _,hex in ipairs(reach) do
                    -- Only consider unoccupied hexes
                    local occ_hex = wesnoth.get_units { x = hex[1], y = hex[2], { "not", { id = guardian.id } } }[1]
                    if not occ_hex then
                        local dist = H.distance_between(hex[1], hex[2], target.x, target.y)
                        if (dist < min_dist) then
                            min_dist, nh = dist, { hex[1], hex[2] }
                        end
                    end
                end

                AH.movefull_stopunit(ai, guardian, nh)
            end
        end

    -- If no enemy around or within the zone, move toward station or zone
    else
        local newpos
        -- If cfg.station_x/y are given, move toward that location
        if cfg.station_x and cfg.station_y then
            newpos = { cfg.station_x, cfg.station_y }
        -- Otherwise choose one randomly from those given in filter_location
        else
            local width, height = wesnoth.get_map_size()
            local locs_map = LS.of_pairs(wesnoth.get_locations {
                x = '1-' .. width,
                y = '1-' .. height,
                { "and", cfg.filter_location }
            })

            -- Check out which of those hexes the guardian can reach
            local reach_map = LS.of_pairs(wesnoth.find_reach(guardian))
            reach_map:inter(locs_map)

            -- If it can reach some hexes, use only reachable locations,
            -- otherwise move toward any (random) one from the entire set
            if (reach_map:size() > 0) then
                locs_map = reach_map
            end

            local locs = locs_map:to_pairs()

            if (#locs > 0) then
                local newind = math.random(#locs)
                newpos = { locs[newind][1], locs[newind][2] }
            else
                newpos = { guardian.x, guardian.y }
            end
        end

        -- Next hop toward that position
        local nh = AH.next_hop(guardian, newpos[1], newpos[2])
        if nh then
            AH.movefull_stopunit(ai, guardian, nh)
        end
    end
    if (not guardian) or (not guardian.valid) then return end

    AH.checked_stopunit_moves(ai, guardian)
    -- If there are attacks left and guardian ended up next to an enemy, we'll leave this to RCA AI
end

return ca_zone_guardian
