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 MAIUV = wesnoth.require "ai/micro_ais/micro_ai_unit_variables.lua"
local MAISD = wesnoth.require "ai/micro_ais/micro_ai_self_data.lua"

local function get_hang_out_units(cfg)
    local units = AH.get_units_with_moves {
        side = wesnoth.current.side,
        { "and", cfg.filter }
    }
    return units
end

local ca_hang_out = {}

function ca_hang_out:evaluation(ai, cfg, self)
    -- Return 0 if the mobilize condition has previously been met
    if MAISD.get_mai_self_data(self.data, cfg.ai_id, "mobilize_units") then
        return 0
    end

    -- Otherwise check if any of the mobilize conditions are now met
    if (cfg.mobilize_condition and wesnoth.eval_conditional(cfg.mobilize_condition))
        or (cfg.mobilize_on_gold_less_than and (wesnoth.sides[wesnoth.current.side].gold < cfg.mobilize_on_gold_less_than))
    then
        MAISD.insert_mai_self_data(self.data, cfg.ai_id, { mobilize_units = true })

        -- Need to unmark all units also (all units, with and without moves)
        local units = wesnoth.get_units { side = wesnoth.current.side, { "and", cfg.filter } }
        for _,unit in ipairs(units) do
            MAIUV.delete_mai_unit_variables(unit, cfg.ai_id)
        end

        return 0
    end

    if get_hang_out_units(cfg)[1] then return cfg.ca_score end
    return 0
end

function ca_hang_out:execution(ai, cfg, self)
    local units = get_hang_out_units(cfg)

    -- Get the locations close to which the units should hang out
    -- cfg.filter_location defaults to the location of the side leader(s)
    local filter_location = cfg.filter_location or {
        { "filter", { side = wesnoth.current.side, canrecruit = "yes" } }
    }

    local width, height = wesnoth.get_map_size()
    local locs = wesnoth.get_locations {
        x = '1-' .. width,
        y = '1-' .. height,
        { "and", filter_location }
    }

    -- Get map of locations to be avoided.
    -- Use [micro_ai][avoid] tag with priority over [ai][avoid].
    -- If neither is given, default to all castle terrain.
    -- ai.get_avoid() cannot be used as it always returns an array, even when the aspect is not set,
    -- and an empty array could also mean that no hexes match the filter
    local avoid_tag
    if cfg.avoid then
        avoid_tag = cfg.avoid
    else
        local ai_tag = H.get_child(wesnoth.sides[wesnoth.current.side].__cfg, 'ai')
        for aspect in H.child_range(ai_tag, 'aspect') do
            if (aspect.id == 'avoid') then
                local facet = H.get_child(aspect, 'facet')
                if facet then
                    avoid_tag = H.get_child(facet, 'value')
                else
                    avoid_tag = { terrain = 'C*,C*^*,*^C*' }
                end
            end
        end
    end
    local avoid_map = LS.of_pairs(wesnoth.get_locations(avoid_tag))

    local max_rating, best_hex, best_unit = -9e99
    for _,unit in ipairs(units) do
        -- Only consider units that have not been marked yet
        if (not MAIUV.get_mai_unit_variables(unit, cfg.ai_id, "moved")) then
            local max_rating_unit, best_hex_unit = -9e99

            -- Check out all unoccupied hexes the unit can reach
            local reach_map = AH.get_reachable_unocc(unit)
            reach_map:iter( function(x, y, v)
                if (not avoid_map:get(x, y)) then
                    for _,loc in ipairs(locs) do
                        -- Main rating is the distance from any of the goal hexes
                        local rating = -H.distance_between(x, y, loc[1], loc[2])

                        -- Fastest unit moves first
                        rating = rating + unit.max_moves / 100.

                        -- Minor penalty for distance from current position of unit
                        -- so that there's not too much shuffling around
                        local rating = rating - H.distance_between(x, y, unit.x, unit.y) / 1000.

                        if (rating > max_rating_unit) then
                            max_rating_unit = rating
                            best_hex_unit = { x, y }
                        end
                    end
                end
            end)

            -- Only consider a unit if the best hex found for it is not its current location
            if best_hex_unit and ((best_hex_unit[1] ~= unit.x) or (best_hex_unit[2] ~= unit.y)) then
                if (max_rating_unit > max_rating) then
                    max_rating = max_rating_unit
                    best_hex, best_unit = best_hex_unit, unit
                end
            end
        end
    end

    -- If no valid locations/units were found or all units are in their
    -- respective best locations already, we take moves away from all units
    if (not best_unit) then
        for _,unit in ipairs(units) do
            AH.checked_stopunit_moves(ai, unit)

            -- Also remove the markers
            if unit and unit.valid then MAIUV.delete_mai_unit_variables(unit, cfg.ai_id) end
        end
    else
        -- Otherwise move unit (partial move only) and mark as having been used
        AH.checked_move(ai, best_unit, best_hex[1], best_hex[2])

        if best_unit and best_unit.valid then
            MAIUV.set_mai_unit_variables(best_unit, cfg.ai_id, { moved = true })
        end
    end
end

return ca_hang_out
