--[[-- Gui Module - Rocket Info
    - Adds a rocket infomation gui which shows general stats, milestones and build progress of rockets
    @gui Rocket-Info
    @alias rocket_info
]]

local ExpUtil = require("modules/exp_util")
local Gui = require("modules/exp_gui")
local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles
local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event
local config = require("modules.exp_legacy.config.gui.rockets") --- @dep config.gui.rockets
local Colors = require("modules/exp_util/include/color")
local Rockets = require("modules.exp_legacy.modules.control.rockets") --- @dep modules.control.rockets

local time_formats = {
    caption = ExpUtil.format_time_factory_locale{ format = "short", minutes = true, seconds = true },
    caption_hours = ExpUtil.format_time_factory_locale{ format = "short", hours = true, minutes = true },
    tooltip = ExpUtil.format_time_factory_locale{ format = "long", minutes = true, seconds = true },
    tooltip_hours = ExpUtil.format_time_factory_locale{ format = "long", hours = true, minutes = true, seconds = true },
}

--- Check if a player is allowed to use certain interactions
local function check_player_permissions(player, action)
    if not config.progress["allow_" .. action] then
        return false
    end

    if config.progress[action .. "_admins_only"] and not player.admin then
        return false
    end

    if config.progress[action .. "_role_permission"]
    and not Roles.player_allowed(player, config.progress[action .. "_role_permission"]) then
        return false
    end

    return true
end

--- Button to toggle the auto launch on a rocket silo
-- @element toggle_launch
local toggle_launch = Gui.define("toggle_launch")
    :draw{
        type = "sprite-button",
        sprite = "utility/play",
        tooltip = { "rocket-info.toggle-rocket-tooltip" },
        name = Gui.from_name,
    }
    :style(Gui.styles.sprite{
        size = 16,
    })
    :on_click(function(def, player, element)
        local rocket_silo_name = element.parent.name:sub(8)
        local rocket_silo = Rockets.get_silo_entity(rocket_silo_name)
        if rocket_silo.auto_launch then
            element.sprite = "utility/play"
            element.tooltip = { "rocket-info.toggle-rocket-tooltip" }
            rocket_silo.auto_launch = false
        else
            element.sprite = "utility/stop"
            element.tooltip = { "rocket-info.toggle-rocket-tooltip-disabled" }
            rocket_silo.auto_launch = true
        end
    end)

--- XY cords that allow zoom to map when pressed
-- @element silo_cords
local silo_cords = Gui.define("silo_cords")
    :draw(function(definition, parent, silo_data)
        local silo_name = silo_data.silo_name
        local pos = silo_data.position
        local tooltip = config.progress.allow_zoom_to_map and { "rocket-info.progress-label-tooltip" } or nil

        -- Add the x cord flow
        local flow_x = parent.add{
            type = "flow",
            name = "label-x-" .. silo_name,
            caption = silo_name,
        }
        flow_x.style.padding = { 0, 2, 0, 1 }

        -- Add the x cord label
        local label_x = flow_x.add{
            type = "label",
            caption = { "rocket-info.progress-x-pos", pos.x },
            tooltip = tooltip,
        }

        -- Add the y cord flow
        local flow_y = parent.add{
            type = "flow",
            name = "label-y-" .. silo_name,
            caption = silo_name,
        }
        flow_y.style.padding = { 0, 2, 0, 1 }

        -- Add the y cord label
        local label_y = flow_y.add{
            type = "label",
            caption = { "rocket-info.progress-y-pos", pos.y },
            tooltip = tooltip,
        }

        if config.progress.allow_zoom_to_map then
            definition:link_element(label_x)
            definition:link_element(label_y)
        end

        return Gui.no_return()
    end)
    :on_click(function(def, player, element)
        local rocket_silo_name = element.parent.caption
        local rocket_silo = Rockets.get_silo_entity(rocket_silo_name)
        player.set_controller{ type = defines.controllers.remote, position = rocket_silo.position, surface = rocket_silo.surface }
    end)

--- Base element for each rocket in the progress list
-- @element rocket_entry
local rocket_entry = Gui.define("rocket_entry")
    :draw(function(_, parent, silo_data)
        local silo_name = silo_data.silo_name
        local player = Gui.get_player(parent)

        -- Add the toggle auto launch if the player is allowed it
        -- Auto launch was removed from the api and no 2.0 equivalent was added
        -- https://forums.factorio.com/viewtopic.php?f=28&t=118065&p=656502
        if check_player_permissions(player, "toggle_active") then
            parent.add{ type = "flow" }
            --[[local flow = parent.add{ type = "flow", name = "toggle-" .. silo_name }
            local button = toggle_launch(flow)
            button.tooltip = silo_data.toggle_tooltip
            button.sprite = silo_data.toggle_sprite]]
        end

        -- Draw the silo cords element
        silo_cords(parent, silo_data)

        -- Add a progress label
        local alignment = Gui.elements.aligned_flow(parent, { name = silo_name })
        local element =
            alignment.add{
                type = "label",
                name = "label",
                caption = silo_data.progress_caption,
                tooltip = silo_data.progress_tooltip,
            }

        -- Return the progress label
        return element
    end)

--- Data label which contains a name and a value label pair
-- @element data_label
local data_label = Gui.define("data_label")
    :draw(function(_, parent, label_data)
        local data_name = label_data.name
        local data_subname = label_data.subname
        local data_fullname = data_subname and data_name .. data_subname or data_name

        -- Add the name label
        local name_label = parent.add{
            type = "label",
            name = data_fullname .. "-label",
            caption = { "rocket-info.data-caption-" .. data_name, data_subname },
            tooltip = { "rocket-info.data-tooltip-" .. data_name, data_subname },
        }
        name_label.style.padding = { 0, 2 }

        --- Right aligned label to store the data
        local alignment = Gui.elements.aligned_flow(parent, { name = data_fullname })
        local element =
            alignment.add{
                type = "label",
                name = "label",
                caption = label_data.value,
                tooltip = label_data.tooltip,
            }
        element.style.padding = { 0, 2 }

        return element
    end)

-- Used to update the captions and tooltips on the data labels
local function update_data_labels(parent, data_label_data)
    for _, label_data in ipairs(data_label_data) do
        local data_name = label_data.subname and label_data.name .. label_data.subname or label_data.name
        if not parent[data_name] then
            data_label(parent, label_data)
        else
            local data_label_element = parent[data_name].label
            data_label_element.tooltip = label_data.tooltip
            data_label_element.caption = label_data.value
        end
    end
end

local function get_progress_data(force_name)
    local force_silos = Rockets.get_silos(force_name)
    local progress_data = {}

    for _, silo_data in pairs(force_silos) do
        local rocket_silo = silo_data.entity
        if not rocket_silo or not rocket_silo.valid then
            -- Remove from list if not valid
            force_silos[silo_data.name] = nil
            table.insert(progress_data, {
                silo_name = silo_data.name,
                remove = true,
            })
        else
            -- Get the progress caption and tooltip
            local progress_color = Colors.white
            local progress_caption = { "rocket-info.progress-caption", rocket_silo.rocket_parts }
            local progress_tooltip = { "rocket-info.progress-tooltip", silo_data.launched or 0 }
            local status = rocket_silo.status == defines.entity_status.waiting_to_launch_rocket
            if status and silo_data.awaiting_reset then
                progress_caption = { "rocket-info.progress-launched" }
                progress_color = Colors.green
            elseif status then
                progress_caption = { "rocket-info.progress-caption", 100 }
                progress_color = Colors.cyan
            else
                silo_data.awaiting_reset = false
            end

            -- Get the toggle button data
            local toggle_tooltip = { "rocket-info.toggle-rocket-tooltip-disabled" }
            local toggle_sprite = "utility/play"
            if false --[[rocket_silo.auto_launch]] then
                toggle_tooltip = { "rocket-info.toggle-rocket-tooltip" }
                toggle_sprite = "utility/stop"
            end

            -- Insert the gui data
            table.insert(progress_data, {
                silo_name = silo_data.name,
                position = rocket_silo.position,
                allow_launch = not silo_data.awaiting_reset and status or false,
                progress_color = progress_color,
                progress_caption = progress_caption,
                progress_tooltip = progress_tooltip,
                toggle_tooltip = toggle_tooltip,
                toggle_sprite = toggle_sprite,
            })
        end
    end

    return progress_data
end

--- Update the build progress section
local function update_build_progress(parent, progress_data)
    local show_message = true
    for _, silo_data in ipairs(progress_data) do
        parent.parent.no_silos.visible = false
        parent.visible = true
        local silo_name = silo_data.silo_name
        local progress_label = parent[silo_name]
        if silo_data.remove then
            -- Remove the rocket from the list
            Gui.destroy_if_valid(parent["toggle-" .. silo_name])
            Gui.destroy_if_valid(parent["launch-" .. silo_name])
            Gui.destroy_if_valid(parent["label-x-" .. silo_name])
            Gui.destroy_if_valid(parent["label-y-" .. silo_name])
            Gui.destroy_if_valid(parent[silo_name])
        elseif not progress_label then
            -- Add the rocket to the list
            show_message = false
            rocket_entry(parent, silo_data)
        else
            show_message = false
            -- Update the existing labels
            progress_label = progress_label.label
            progress_label.caption = silo_data.progress_caption
            progress_label.tooltip = silo_data.progress_tooltip
            progress_label.style.font_color = silo_data.progress_color

            -- Update the toggle button
            local toggle_button = parent["toggle-" .. silo_name]
            if toggle_button then
                toggle_button = toggle_button[toggle_launch.name]
                toggle_button.tooltip = silo_data.toggle_tooltip
                toggle_button.sprite = silo_data.toggle_sprite
            end
        end
    end

    if show_message then
        parent.parent.no_silos.visible = true
        parent.visible = false
    end
end

--- Gets the label data for all the different stats
local function get_stats_data(force_name)
    local force_rockets = Rockets.get_rocket_count(force_name)
    local stats = Rockets.get_stats(force_name)
    local stats_data = {}

    -- Format the first launch data
    if config.stats.show_first_rocket then
        local value = stats.first_launch or 0
        table.insert(stats_data, {
            name = "first-launch",
            value = time_formats.caption_hours(value),
            tooltip = time_formats.tooltip_hours(value),
        })
    end

    -- Format the last launch data
    if config.stats.show_last_rocket then
        local value = stats.last_launch or 0
        table.insert(stats_data, {
            name = "last-launch",
            value = time_formats.caption_hours(value),
            tooltip = time_formats.tooltip_hours(value),
        })
    end

    -- Format fastest launch data
    if config.stats.show_fastest_rocket then
        local value = stats.fastest_launch or 0
        table.insert(stats_data, {
            name = "fastest-launch",
            value = time_formats.caption_hours(value),
            tooltip = time_formats.tooltip_hours(value),
        })
    end

    -- Format total rocket data
    if config.stats.show_total_rockets then
        local total_rockets = Rockets.get_game_rocket_count()
        total_rockets = total_rockets == 0 and 1 or total_rockets
        local percentage = math.round(force_rockets / total_rockets, 3) * 100
        table.insert(stats_data, {
            name = "total-rockets",
            value = force_rockets,
            tooltip = { "rocket-info.value-tooltip-total-rockets", percentage },
        })
    end

    -- Format game avg data
    if config.stats.show_game_avg then
        local avg = force_rockets > 0 and math.floor(game.tick / force_rockets) or 0
        table.insert(stats_data, {
            name = "avg-launch",
            value = time_formats.caption(avg),
            tooltip = time_formats.tooltip(avg),
        })
    end

    -- Format rolling avg data
    for _, avg_over in pairs(config.stats.rolling_avg) do
        local avg = Rockets.get_rolling_average(force_name, avg_over)
        table.insert(stats_data, {
            name = "avg-launch-n",
            subname = avg_over,
            value = time_formats.caption(avg),
            tooltip = time_formats.tooltip(avg),
        })
    end

    -- Return formated data
    return stats_data
end

--- Gets the label data for the milestones
local function get_milestone_data(force_name)
    local force_rockets = Rockets.get_rocket_count(force_name)
    local milestone_data = {}

    for _, milestone in ipairs(config.milestones) do
        if milestone <= force_rockets then
            local time = Rockets.get_rocket_time(force_name, milestone)
            table.insert(milestone_data, {
                name = "milestone-n",
                subname = milestone,
                value = time_formats.caption_hours(time),
                tooltip = time_formats.tooltip_hours(time),
            })
        else
            table.insert(milestone_data, {
                name = "milestone-n",
                subname = milestone,
                value = { "rocket-info.data-caption-milestone-next" },
                tooltip = { "rocket-info.data-tooltip-milestone-next" },
            })
            break
        end
    end

    return milestone_data
end

-- Button to toggle a section dropdown
-- @element toggle_section
local toggle_section = Gui.define("rocket_info_toggle_section")
    :draw{
        type = "sprite-button",
        sprite = "utility/expand",
        hovered_sprite = "utility/expand",
        tooltip = { "rocket-info.toggle-section-tooltip" },
        style = "frame_action_button",
        name = Gui.from_name,
    }
    :style(Gui.styles.sprite{
        size = 20,
    })
    :on_click(function(def, player, element)
        local header_flow = assert(element.parent)
        local flow_name = header_flow.caption
        local flow = header_flow.parent.parent[flow_name]
        if Gui.toggle_visible_state(flow) then
            element.sprite = "utility/collapse"
            element.tooltip = { "rocket-info.toggle-section-collapse-tooltip" }
        else
            element.sprite = "utility/expand"
            element.tooltip = { "rocket-info.toggle-section-tooltip" }
        end
    end)

-- Draw a section header and main scroll
-- @element rocket_list_container
local section = Gui.define("rocket_info_section")
    :draw(function(definition, parent, section_name, table_size)
        -- Draw the header for the section
        local header = Gui.elements.header(parent, {
            name = section_name .. "-header",
            caption = { "rocket-info.section-caption-" .. section_name },
            tooltip = { "rocket-info.section-tooltip-" .. section_name },
        })

        definition:link_element(header.label)

        -- Right aligned button to toggle the section
        header.caption = section_name
        toggle_section(header)

        -- Table used to store the data
        local scroll_table = Gui.elements.scroll_table(parent, 215, table_size, section_name)
        scroll_table.parent.visible = false

        -- Return the flow table
        return definition:unlink_element(scroll_table)
    end)
    :on_click(function(def, player, element, event)
        event.element = element.parent.flow[toggle_section.name]
        toggle_section:raise_event(event)
    end)

--- Main gui container for the left flow
-- @element rocket_list_container
local rocket_list_container = Gui.define("rocket_list_container")
    :draw(function(definition, parent)
        -- Draw the internal container
        local container = Gui.elements.container(parent, 200)

        -- Set the container style
        local style = container.style
        style.padding = 0

        local player = Gui.get_player(parent)
        local force_name = player.force.name
        -- Draw stats section
        if config.stats.show_stats then
            update_data_labels(section(container, "stats", 2), get_stats_data(force_name))
        end

        -- Draw milestones section
        if config.milestones.show_milestones then
            update_data_labels(section(container, "milestones", 2), get_milestone_data(force_name))
        end

        -- Draw build progress list
        if config.progress.show_progress then
            local col_count = 3
            if check_player_permissions(player, "toggle_active") then col_count = col_count + 1 end
            local progress = section(container, "progress", col_count)
            -- Label used when there are no active silos
            local no_silos = progress.parent.add{
                type = "label",
                name = "no_silos",
                caption = { "rocket-info.progress-no-silos" },
            }
            no_silos.style.padding = { 1, 2 }
            update_build_progress(progress, get_progress_data(force_name))
        end

        -- Return the external container
        return container.parent
    end)

--- Add the element to the left flow with a toolbar button
Gui.add_left_element(rocket_list_container, function(player, element)
    return player.force.rockets_launched > 0 and Roles.player_allowed(player, "gui/rocket-info")
end)
Gui.toolbar.create_button{
    name = "rocket_list_toggle",
    left_element = rocket_list_container,
    sprite = "item/rocket-silo",
    tooltip = { "rocket-info.main-tooltip" },
    visible = function(player, element)
        return Roles.player_allowed(player, "gui/rocket-info")
    end
}

--- Update the gui for all players on a force
local function update_rocket_gui_all(force_name)
    local stats = get_stats_data(force_name)
    local milestones = get_milestone_data(force_name)
    local progress = get_progress_data(force_name)
    for _, player in pairs(game.forces[force_name].players) do
        local container = Gui.get_left_element(rocket_list_container, player)
        local frame = container.frame
        update_data_labels(frame.stats.table, stats)
        update_data_labels(frame.milestones.table, milestones)
        update_build_progress(frame.progress.table, progress)
    end
end

--- Event used to update the stats when a rocket is launched
--- @param event EventData.on_cargo_pod_finished_ascending
Event.add(defines.events.on_cargo_pod_finished_ascending, function(event)
    update_rocket_gui_all(event.cargo_pod.force.name)
end)

--- Update only the progress gui for a force
local function update_rocket_gui_progress(force_name)
    local progress = get_progress_data(force_name)
    for _, player in pairs(game.forces[force_name].connected_players) do
        local container = Gui.get_left_element(rocket_list_container, player)
        local frame = container.frame
        update_build_progress(frame.progress.table, progress)
    end
end

--- Event used to set a rocket silo to be awaiting reset
--- @param event EventData.on_rocket_launch_ordered
Event.add(defines.events.on_rocket_launch_ordered, function(event)
    update_rocket_gui_progress(event.rocket_silo.force.name)
end)

Event.on_nth_tick(150, function()
    for _, force in pairs(game.forces) do
        if #Rockets.get_silos(force.name) > 0 then
            update_rocket_gui_progress(force.name)
        end
    end
end)

--- Adds a silo to the list when it is built
--- @param event EventData.on_built_entity | EventData.on_robot_built_entity | EventData.script_raised_built | EventData.script_raised_revive
local function on_built(event)
    local entity = event.entity
    if entity.valid and entity.name == "rocket-silo" then
        update_rocket_gui_progress(entity.force.name)
    end
end

Event.add(defines.events.on_built_entity, on_built)
Event.add(defines.events.on_robot_built_entity, on_built)
Event.add(defines.events.script_raised_built, on_built)
Event.add(defines.events.script_raised_revive, on_built)

--- Redraw the progress section on role change
local function role_update_event(event)
    if not config.progress.show_progress then return end
    local player = game.players[event.player_index]
    local container = Gui.get_left_element(rocket_list_container, player)
    local progress_scroll = container.frame.progress
    Gui.destroy_if_valid(progress_scroll.table)

    local col_count = 3
    if check_player_permissions(player, "toggle_active") then col_count = col_count + 1 end
    local progress = progress_scroll.add{
        type = "table",
        name = "table",
        column_count = col_count,
    }

    update_build_progress(progress, get_progress_data(player.force.name))
end

Event.add(Roles.events.on_role_assigned, role_update_event)
Event.add(Roles.events.on_role_unassigned, role_update_event)

return rocket_list_container
