-- hunger module by mewmew --
local Public = {}
local P = require 'utils.player_modifiers'

local starve_messages = { ' ran out of foodstamps.', ' starved.', ' should not have skipped breakfast today.' }

local overfeed_messages = {
    ' ate too much and exploded.',
    ' needs to work on their bad eating habbits.',
    ' should have skipped dinner today.',
    ' forgot to count them calories.'
}

local player_hunger_fish_food_value = 10
local player_hunger_spawn_value = 80
local player_hunger_stages = {}
for x = 1, 200, 1 do
    if x <= 200 then
        player_hunger_stages[x] = 'Obese'
    end
    if x <= 179 then
        player_hunger_stages[x] = 'Stuffed'
    end
    if x <= 150 then
        player_hunger_stages[x] = 'Bloated'
    end
    if x <= 130 then
        player_hunger_stages[x] = 'Sated'
    end
    if x <= 110 then
        player_hunger_stages[x] = 'Well Fed'
    end
    if x <= 89 then
        player_hunger_stages[x] = 'Nourished'
    end
    if x <= 70 then
        player_hunger_stages[x] = 'Hungry'
    end
    if x <= 35 then
        player_hunger_stages[x] = 'Starving'
    end
end

local player_hunger_color_list = {}
for x = 1, 50, 1 do
    player_hunger_color_list[x] = { r = 0.5 + x * 0.01, g = x * 0.01, b = x * 0.005 }
    player_hunger_color_list[50 + x] = { r = 1 - x * 0.02, g = 0.5 + x * 0.01, b = 0.25 }
    player_hunger_color_list[100 + x] = { r = 0 + x * 0.02, g = 1 - x * 0.01, b = 0.25 }
    player_hunger_color_list[150 + x] = { r = 1 - x * 0.01, g = 0.5 - x * 0.01, b = 0.25 - x * 0.005 }
end

local player_hunger_buff = {}
local max_buff = 0.50
local max_buff_high = 110
local max_buff_low = 89
local max_debuff = -0.85
local max_debuff_high = 180
local max_debuff_low = 20

for x = 1, max_debuff_low, 1 do
    player_hunger_buff[x] = max_debuff
end
for x = max_debuff_high, 200, 1 do
    player_hunger_buff[x] = max_debuff
end
for x = max_buff_low, max_buff_high, 1 do
    player_hunger_buff[x] = max_buff
end

for x = max_debuff_low, max_buff_low, 1 do
    local step = (max_buff - max_debuff) / (max_buff_low - max_debuff_low)
    player_hunger_buff[x] = math.round(max_debuff + (x - max_debuff_low) * step, 2)
end

for x = max_buff_high, max_debuff_high, 1 do
    local step = (max_buff - max_debuff) / (max_debuff_high - max_buff_high)
    player_hunger_buff[x] = math.round(max_buff - (x - max_buff_high) * step, 2)
end

local function create_hunger_gui(player)
    if player.gui.top['hunger_frame'] then
        player.gui.top['hunger_frame'].destroy()
    end
    local element = player.gui.top.add { type = 'sprite-button', name = 'hunger_frame', caption = ' ' }
    element.style.font = 'default-bold'
    element.style.minimal_height = 38
    element.style.minimal_width = 128
    element.style.maximal_height = 38
    element.style.padding = 0
    element.style.margin = 0
    element.style.vertical_align = 'center'
    element.style.horizontal_align = 'center'
end

local function update_hunger_gui(player)
    if not player.gui.top['hunger_frame'] then
        create_hunger_gui(player)
    end
    local str = tostring(storage.player_hunger[player.name])
    str = str .. '% '
    str = str .. player_hunger_stages[storage.player_hunger[player.name]]
    player.gui.top['hunger_frame'].caption = str
    player.gui.top['hunger_frame'].style.font_color = player_hunger_color_list[storage.player_hunger[player.name]]
end

function Public.hunger_update(player, food_value)
    if not player.character then
        return
    end
    if food_value == -1 and player.character.driving == true then
        return
    end

    local past_hunger = storage.player_hunger[player.name]
    storage.player_hunger[player.name] = storage.player_hunger[player.name] + food_value
    if storage.player_hunger[player.name] > 200 then
        storage.player_hunger[player.name] = 200
    end

    if past_hunger == 200 and storage.player_hunger[player.name] + food_value > 200 then
        storage.player_hunger[player.name] = player_hunger_spawn_value
        player.surface.create_entity({ name = 'big-artillery-explosion', position = player.character.position })
        player.character.die('player')
        game.print(player.name .. overfeed_messages[math.random(1, #overfeed_messages)], { r = 0.75, g = 0.0, b = 0.0 })
    end

    if storage.player_hunger[player.name] < 1 then
        storage.player_hunger[player.name] = player_hunger_spawn_value
        player.character.die('player')
        game.print(player.name .. starve_messages[math.random(1, #starve_messages)], { r = 0.75, g = 0.0, b = 0.0 })
    end

    if player.character then
        if player_hunger_stages[storage.player_hunger[player.name]] ~= player_hunger_stages[past_hunger] then
            local print_message = 'You are ' .. player_hunger_stages[storage.player_hunger[player.name]] .. '.'
            if player_hunger_stages[storage.player_hunger[player.name]] == 'Obese' then
                print_message = 'You have become ' .. player_hunger_stages[storage.player_hunger[player.name]] .. '.'
            end
            if player_hunger_stages[storage.player_hunger[player.name]] == 'Starving' then
                print_message = 'You are starving!'
                game.print(player.name .. ' is starving!', player_hunger_color_list[storage.player_hunger[player.name]])
            end
            player.print(print_message, player_hunger_color_list[storage.player_hunger[player.name]])
        end
    end

    if not player.character then
        return
    end

    if player_hunger_buff[storage.player_hunger[player.name]] < 0 then
        P.update_single_modifier(player, 'character_running_speed_modifier', 'hunger', player_hunger_buff[storage.player_hunger[player.name]] * 0.75)
    else
        P.update_single_modifier(player, 'character_running_speed_modifier', 'hunger', player_hunger_buff[storage.player_hunger[player.name]] * 0.15)
    end
    P.update_single_modifier(player, 'character_mining_speed_modifier', 'hunger', player_hunger_buff[storage.player_hunger[player.name]])
    P.update_player_modifiers(player)

    update_hunger_gui(player)
end

local function on_player_joined_game(event)
    local player = game.players[event.player_index]
    if not storage.player_hunger then
        storage.player_hunger = {}
    end
    if player.online_time == 0 then
        storage.player_hunger[player.name] = player_hunger_spawn_value
        Public.hunger_update(player, 0)
    end
    update_hunger_gui(player)
end

local function on_player_used_capsule(event)
    if event.item.name == 'raw-fish' then
        local player = game.players[event.player_index]
        if player.character.health < player.character.max_health + player.character_health_bonus + player.force.character_health_bonus then
            return
        end
        Public.hunger_update(player, player_hunger_fish_food_value)
        player.play_sound { path = 'utility/armor_insert', volume_modifier = 0.9 }
    end
end

local function on_player_respawned(event)
    local player = game.players[event.player_index]
    storage.player_hunger[player.name] = player_hunger_spawn_value
    Public.hunger_update(player, 0)
end

local function on_tick()
    for _, player in pairs(game.connected_players) do
        if player.afk_time < 18000 then
            Public.hunger_update(player, -1)
        end
    end
end

local event = require 'utils.event'
event.on_nth_tick(3600, on_tick)
event.add(defines.events.on_player_respawned, on_player_respawned)
event.add(defines.events.on_player_used_capsule, on_player_used_capsule)
event.add(defines.events.on_player_joined_game, on_player_joined_game)

return Public