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

local messenger_next_waypoint = wesnoth.require "ai/micro_ais/cas/ca_messenger_f_next_waypoint.lua"

local ca_messenger_move = {}

function ca_messenger_move:evaluation(ai, cfg)
    -- Move the messenger toward goal, potentially attack adjacent unit

    local messenger = AH.get_units_with_moves { id = cfg.id }[1]

    if messenger then return cfg.ca_score end
    return 0
end

function ca_messenger_move:execution(ai, cfg)
    local messenger = AH.get_units_with_moves { id = cfg.id }[1]

    local x, y = messenger_next_waypoint(messenger, cfg)

    if (messenger.x ~= x) or (messenger.y ~= y) then
        local wp = AH.get_closest_location(
            { x, y },
            { { "not", { { "filter", { { "not", { side = wesnoth.current.side } } } } } } },
            messenger
        )
        x, y = wp[1], wp[2]
    end

    local next_hop = AH.next_hop(messenger, x, y, { ignore_own_units = true } )
    if (not next_hop) then next_hop = { messenger.x, messenger.y } end

    -- Compare this to the "ideal path"
    local path = wesnoth.find_path(messenger, x, y, { ignore_units = 'yes' })
    local optimum_hop, optimum_cost = { messenger.x, messenger.y }, 0
    for _,step in ipairs(path) do
        local sub_path, sub_cost = wesnoth.find_path(messenger, step[1], step[2])
        if sub_cost > messenger.moves then
            break
        else
            local unit_in_way = wesnoth.get_unit(step[1], step[2])

            if unit_in_way and (unit_in_way.side == messenger.side) then
                local reach = AH.get_reachable_unocc(unit_in_way)
                if (reach:size() > 1) then unit_in_way = nil end
            end

            if not unit_in_way then
                optimum_hop, nh_cost = step, sub_cost
            end
        end
    end

    -- Now compare how long it would take from the end of both of these options
    local x_current, y_current = messenger.x, messenger.y

    local unit_in_way = wesnoth.get_unit(next_hop[1], next_hop[2])
    if (unit_in_way == messenger) then unit_in_way = nil end
    if unit_in_way then wesnoth.extract_unit(unit_in_way) end

    wesnoth.put_unit(next_hop[1], next_hop[2], messenger)
    local _, cost1 = wesnoth.find_path(messenger, x, y, { ignore_units = 'yes' })

    local unit_in_way2 = wesnoth.get_unit(optimum_hop[1], optimum_hop[2])
    if (unit_in_way2 == messenger) then unit_in_way2 = nil end
    if unit_in_way2 then wesnoth.extract_unit(unit_in_way2) end

    wesnoth.put_unit(optimum_hop[1], optimum_hop[2], messenger)
    local _, cost2 = wesnoth.find_path(messenger, x, y, { ignore_units = 'yes' })

    wesnoth.put_unit(x_current, y_current, messenger)
    if unit_in_way then wesnoth.put_unit(unit_in_way) end
    if unit_in_way2 then wesnoth.put_unit(unit_in_way2) end

    -- If cost2 is significantly less, that means that the optimum path might
    -- overall be faster even though it is currently blocked
    if (cost2 + messenger.max_moves/2 < cost1) then next_hop = optimum_hop end

    if next_hop and ((next_hop[1] ~= messenger.x) or (next_hop[2] ~= messenger.y)) then
        local unit_in_way = wesnoth.get_unit(next_hop[1], next_hop[2])
        if unit_in_way then AH.move_unit_out_of_way(ai, unit_in_way) end
        if (not messenger) or (not messenger.valid) then return end

        AH.checked_move(ai, messenger, next_hop[1], next_hop[2])
    else
        AH.checked_stopunit_moves(ai, messenger)
    end
    if (not messenger) or (not messenger.valid) then return end

    -- Test whether an attack without retaliation or with little damage is possible
    if (messenger.attacks_left <= 0) then return end
    if (not H.get_child(messenger.__cfg, 'attack')) then return end

    local targets = wesnoth.get_units {
        { "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
        { "filter_adjacent", { id = cfg.id } }
    }

    local max_rating, best_target, best_weapon = -9e99
    for _,target in ipairs(targets) do
        local n_weapon = 0
        for weapon in H.child_range(messenger.__cfg, "attack") do
            n_weapon = n_weapon + 1

            local att_stats, def_stats = wesnoth.simulate_combat(messenger, n_weapon, target)

            local rating = -9e99
            -- This is an acceptable attack if:
            -- 1. There is no counter attack
            -- 2. Probability of death is >=67% for enemy, 0% for attacker (default values)

            local enemy_death_chance = cfg.enemy_death_chance or 0.67
            local messenger_death_chance = cfg.messenger_death_chance or 0

            if (att_stats.hp_chance[messenger.hitpoints] == 1)
                or (def_stats.hp_chance[0] >= tonumber(enemy_death_chance)) and (att_stats.hp_chance[0] <= tonumber(messenger_death_chance))
            then
                rating = target.max_hitpoints + def_stats.hp_chance[0]*100 + att_stats.average_hp - def_stats.average_hp
            end

            if (rating > max_rating) then
                max_rating, best_target, best_weapon = rating, target, n_weapon
            end
        end
    end

    if best_target then
        AH.checked_attack(ai, messenger, best_target, best_weapon)
    else
        -- Always attack enemy on last waypoint
        local waypoint_x = AH.split(cfg.waypoint_x, ",")
        local waypoint_y = AH.split(cfg.waypoint_y, ",")
        local target = wesnoth.get_units {
            x = tonumber(waypoint_x[#waypoint_x]),
            y = tonumber(waypoint_y[#waypoint_y]),
            { "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
            { "filter_adjacent", { id = cfg.id } }
        }[1]

        if target then
            AH.checked_attack(ai, messenger, target)
        end
    end
    if (not messenger) or (not messenger.valid) then return end

    AH.checked_stopunit_attacks(ai, messenger)
end

return ca_messenger_move
