-- This module allows to handle getting damaged locally and redirects that damage to host.
-- Also handles shared health system in general.
-- Also recalculates percentage-based damage.

local rpc = net.new_rpc_namespace()

local module = {}

module.recent_damage = 0
module.recent_message = "unknown"
module.last_damage_message = "unknown"

ModLuaFileAppend("data/scripts/game_helpers.lua", "mods/quant.ew/files/system/damage/append/game_helpers.lua")
ModTextFileSetContent(
    "data/entities/misc/effect_hearty.xml",
    ModTextFileGetContent("mods/quant.ew/files/system/damage/append/hearty_effect.xml")
)

local function damage_received(damage, message, entity_id, add_healing_effect)
    local was_my_player = entity_id == nil or ctx.my_player.entity == entity_id
    if not was_my_player then
        return
    end

    module.recent_damage = module.recent_damage + damage
    if message ~= nil then
        module.recent_message = message
        module.last_damage_message = GameTextGetTranslatedOrNot(message) .. " from " .. ctx.my_player.name
    end
    if ctx.is_host then
        module.inflict_damage(damage)
    end
    if add_healing_effect then
        rpc.healing_effect()
    end
end

util.add_cross_call("ew_ds_damaged", damage_received)

local function do_game_over(message)
    net.proxy_notify_game_over()
    ctx.run_ended = true

    local damage_model = EntityGetFirstComponentIncludingDisabled(ctx.my_player.entity, "DamageModelComponent")
    if damage_model ~= nil then
        ComponentSetValue2(damage_model, "wait_for_kill_flag_on_death", false)
        ComponentSetValue2(damage_model, "ui_report_damage", false)
        ComponentSetValue2(damage_model, "hp", 2 ^ -38)
    end
    EntityInflictDamage(ctx.my_player.entity, 1000000, "DAMAGE_CURSE", message, "NONE", 0, 0, GameGetWorldStateEntity())
    GameTriggerGameOver()
    EntityKill(ctx.my_player.entity)
end

function module.on_local_player_spawn(my_player)
    local damage_model = EntityGetFirstComponentIncludingDisabled(my_player.entity, "DamageModelComponent")
    if ctx.is_host then
        util.ensure_component_present(my_player.entity, "LuaComponent", "ew_player_damage", {
            script_damage_received = "mods/quant.ew/files/system/damage/cbs/host_adjust_received_damage.lua",
        })
    else
        util.ensure_component_present(my_player.entity, "LuaComponent", "ew_player_damage", {
            script_damage_received = "mods/quant.ew/files/system/damage/cbs/send_damage_to_host.lua",
        })
    end
    ComponentSetValue2(damage_model, "wait_for_kill_flag_on_death", true)
end

function module.on_world_update_client()
    if module.recent_damage ~= 0 and GameGetFrameNum() % 15 == 2 then
        if not ctx.run_ended then
            rpc.deal_damage(module.recent_damage, module.recent_message)
            module.recent_damage = 0
            module.recent_message = "unknown"
        end
    end
end

local last_health

local function do_health_diff(hp, max_hp)
    local current_hp = util.get_ent_health(ctx.my_player.entity)
    util.set_ent_health(ctx.my_player.entity, { hp, max_hp })
    if last_health ~= nil then
        local diff = last_health - current_hp
        if diff ~= 0 then
            damage_received(diff, nil)
        end
    end
    last_health = util.get_ent_health(ctx.my_player.entity)
end

function module.on_world_update_host()
    if GameGetFrameNum() % 4 == 3 then
        local hp, max_hp = module.health(), module.max_health()
        if not ctx.my_player.currently_polymorphed then
            do_health_diff(hp, max_hp)
        end
        rpc.update_shared_health(hp, max_hp)
        if hp <= 0 and not ctx.run_ended then
            local message = module.last_damage_message
            do_game_over(message)
            rpc.trigger_game_over(message)
        end
    end
end

function module.on_new_player_seen(new_playerdata, player_count)
    local hp = ctx.proxy_opt.health_per_player / 25
    module.set_max_health(module.max_health() + hp)
    module.set_health(module.health() + hp)
end

function module.on_client_spawned(peer_id, playerdata)
    EntityAddComponent2(
        playerdata.entity,
        "LuaComponent",
        { script_damage_received = "mods/quant.ew/files/system/damage/cbs/send_damage_to_client.lua" }
    )
    if not ctx.is_host then
        EntityAddComponent2(playerdata.entity, "LuaComponent", {
            _tags = "ew_immortal",
            script_damage_about_to_be_received = "mods/quant.ew/files/resource/cbs/immortal.lua",
        })
    end
end

function module.health()
    return tonumber(GlobalsGetValue("ew_shared_hp", "4"))
end

function module.max_health()
    return tonumber(GlobalsGetValue("ew_shared_max_hp", "4"))
end

function module.set_health(hp)
    GlobalsSetValue("ew_shared_hp", tostring(hp))
end

function module.set_max_health(hp)
    GlobalsSetValue("ew_shared_max_hp", tostring(hp))
end

function module.inflict_damage(dmg)
    local hp = module.health()
    module.set_health(math.min(math.max(hp - dmg, 0), module.max_health()))
end

-- Provides health capability
ctx.cap.health = {
    health = module.health,
    max_health = module.max_health,
    set_health = module.set_health,
    set_max_health = module.set_max_health,
    inflict_damage = module.inflict_damage,
    do_game_over = function(message)
        do_game_over(message)
        rpc.trigger_game_over(message)
    end,
    on_poly_death = function(message)
        do_game_over(message)
        rpc.trigger_game_over(message)
    end,
}

rpc.opts_reliable()
function rpc.deal_damage(damage, message)
    local message_n = GameTextGetTranslatedOrNot(message) .. " (" .. ctx.rpc_player_data.name .. ")"
    module.last_damage_message = message_n
    if ctx.is_host then
        local host_entity_id = ctx.my_player.entity
        local protection_component_id = GameGetGameEffect(host_entity_id, "PROTECTION_ALL")
        if protection_component_id ~= 0 then
            EntitySetComponentIsEnabled(host_entity_id, protection_component_id, false)
        end

        module.inflict_damage(damage)
        if protection_component_id ~= 0 then
            EntitySetComponentIsEnabled(host_entity_id, protection_component_id, true)
        end
    end
    GamePrint(string.format("Got %.2f damage: %s", damage * 25, message_n))
end

function rpc.update_shared_health(hp, max_hp)
    if not ctx.my_player.currently_polymorphed then
        do_health_diff(hp, max_hp)
    end
end

rpc.opts_reliable()
function rpc.trigger_game_over(message)
    do_game_over(message)
end

function rpc.healing_effect()
    local entity_id = ctx.rpc_player_data.entity
    local x, y = EntityGetTransform(entity_id)
    local entity_fx = EntityLoad("data/entities/particles/heal_effect.xml", x, y)
    EntityAddChild(entity_id, entity_fx)
end

rpc.opts_reliable()
rpc.opts_everywhere()
function rpc.effect_hearty(applied)
    if not ctx.is_host then
        return
    end
    local hearty_applied_count = tonumber(GlobalsGetValue("ew_effect_hearty", "0"))
    if applied then
        -- The effect was added
        if module.max_health() <= 0.4 then
            return
        end

        module.set_health(math.max(module.health() * 0.5, 0.04))
        module.set_max_health(module.max_health() * 0.5)
        hearty_applied_count = hearty_applied_count + 1
    else
        -- The effect was removed
        if hearty_applied_count <= 0 then
            return
        end
        module.set_max_health(module.max_health() * 2)
        module.set_health(module.health() * 2)
        hearty_applied_count = hearty_applied_count - 1
    end
    GlobalsSetValue("ew_effect_hearty", tostring(hearty_applied_count))
end

util.add_cross_call("ew_ds_effect_hearty", rpc.effect_hearty)

rpc.opts_reliable()
function rpc.melee_damage_client(target_peer, damage, message)
    if ctx.my_player.peer_id == target_peer then
        EntityInflictDamage(ctx.my_player.entity, damage, "DAMAGE_MELEE", message, "NONE", 0, 0, 0)
    end
end
util.add_cross_call("ew_ds_client_damaged", rpc.melee_damage_client)

return module
