--luacheck: ignore
local Functions = require 'modules.fjei.functions'
local Gui = require 'utils.gui'
local math_ceil = math.ceil
local math_abs = math.abs
local table_remove = table.remove
local table_insert = table.insert
local main_window_width = 228
local recipe_window_width = 480
local recipe_window_amount_width = 40
local recipe_window_item_name_width = 128
local recipe_window_position = 'screen'
local column_count = 5
local line_count = 5
local items_per_page = column_count * line_count
local Public = {}

local function save_window_location(player, frame)
    if not storage.fjei.player_data then
        return
    end
    if not storage.fjei.player_data[player.index] then
        return
    end
    storage.fjei.player_data[player.index].gui_position = { frame.location.x, frame.location.y }
end

local function get_total_page_count(player)
    if not storage.fjei.player_data[player.index].size_of_filtered_list then
        Functions.set_filtered_list(player)
    end
    local count = math_ceil(storage.fjei.player_data[player.index].size_of_filtered_list / items_per_page)
    if count < 1 then
        count = 1
    end
    return count
end

local function set_page_count_caption(player)
    if not storage.fjei.player_data[player.index].filtered_list then
        Functions.set_filtered_list(player)
    end
    local active_page = storage.fjei.player_data[player.index].active_page
    local element = player.gui.left.fjei_main_window.fjei_main_window_control_table.fjei_main_window_page_counter
    local caption = active_page .. '/' .. get_total_page_count(player)
    element.caption = caption

    --set textfield width
    local style = player.gui.left.fjei_main_window.fjei_main_window_control_table.fjei_main_window_search_textfield.style
    local width = 112 - string.len(caption) * 6
    style.minimal_width = width
    style.maximal_width = width
end

local function get_formatted_amount(amount)
    if amount < 1000 then
        amount = amount .. ' x '
        return amount
    end
    if amount >= 1000000 then
        amount = math.floor(amount * 0.00001) * 0.1 .. 'm '
        return amount
    end
    if amount >= 1000 then
        amount = math.floor(amount * 0.01) * 0.1 .. 'k '
        return amount
    end
end

local function get_localised_name(name)
    local item = prototypes.item[name]
    if item then
        return item.localised_name
    end
    local fluid = prototypes.fluid[name]
    if fluid then
        return fluid.localised_name
    end
    local recipe = prototypes.recipe[name]
    if recipe then
        return recipe.localised_name
    end
    return name
end

local function get_sprite_type(name)
    if prototypes.item[name] then
        return 'item'
    end
    if prototypes.fluid[name] then
        return 'fluid'
    end
    if prototypes.entity[name] then
        return 'entity'
    end
    if prototypes.recipe[name] then
        return 'recipe'
    end
    return false
end

local function get_active_recipe_name(player)
    local fjei_recipe_window = player.gui[recipe_window_position]['fjei_recipe_window']
    if not fjei_recipe_window then
        return
    end
    if not fjei_recipe_window.recipe_container then
        return
    end
    local container = fjei_recipe_window.recipe_container.children[1]
    if not container then
        return
    end
    return container.children[1].name
end

local function add_choose_elem_button(element, name, is_recipe)
    local elem_type
    if is_recipe then
        elem_type = 'recipe'
    else
        elem_type = get_sprite_type(name)
    end
    if not elem_type then
        return
    end

    local elem_type = get_sprite_type(name)
    local choose_elem_button = element.add({ type = 'choose-elem-button', name = name, elem_type = elem_type })
    choose_elem_button.locked = true
    choose_elem_button.elem_value = name

    choose_elem_button.style.minimal_width = 36
    choose_elem_button.style.minimal_height = 36
    choose_elem_button.style.maximal_width = 36
    choose_elem_button.style.maximal_height = 36
    choose_elem_button.style.margin = -1
    choose_elem_button.style.right_margin = 2
    choose_elem_button.style.padding = 2
end

local function add_sprite_icon(element, name, is_recipe)
    local sprite_type
    if is_recipe then
        sprite_type = 'recipe'
    else
        sprite_type = get_sprite_type(name)
    end

    if not sprite_type then
        return
    end

    local sprite = element.add({ type = 'sprite', name = name, sprite = sprite_type .. '/' .. name, tooltip = get_localised_name(name) })
    sprite.style.minimal_width = 32
    sprite.style.minimal_height = 32
    sprite.style.maximal_width = 32
    sprite.style.maximal_height = 32
    sprite.style.margin = 4
    sprite.style.padding = 0
end

local function display_item_list(player)
    if not player.gui.left.fjei_main_window then
        return
    end
    if not storage.fjei.player_data[player.index].filtered_list then
        Functions.set_filtered_list(player)
    end

    local active_page = storage.fjei.player_data[player.index].active_page
    local starting_index = 1 + (active_page - 1) * items_per_page
    local sorted_item_list = storage.fjei.sorted_item_list
    local filtered_list = storage.fjei.player_data[player.index].filtered_list
    local item_list_table = player.gui.left.fjei_main_window.fjei_main_window_item_list_table
    item_list_table.clear()

    for i = starting_index, starting_index + items_per_page - 1, 1 do
        if not filtered_list[i] then
            return
        end
        add_sprite_icon(item_list_table, sorted_item_list[filtered_list[i]])
    end
end

local function display_history(player)
    if not player.gui.left.fjei_main_window then
        return
    end
    local player_data = storage.fjei.player_data[player.index]
    local history = player_data.history
    if not history then
        return
    end
    local history_table = player.gui.left.fjei_main_window.fjei_main_window_history_table
    history_table.clear()

    for i = player_data.size_of_history - column_count, player_data.size_of_history, 1 do
        local name = history[i]
        if name then
            add_sprite_icon(history_table, name)
        end
    end
end

function Public.refresh_main_window(player)
    if not player.gui.left.fjei_main_window then
        return
    end
    set_page_count_caption(player)
    display_item_list(player)
    display_history(player)
end

local function draw_main_window(player)
    if player.gui.left['fjei_main_window'] then
        player.gui.left['fjei_main_window'].destroy()
    end
    local frame = player.gui.left.add({ type = 'frame', name = 'fjei_main_window', direction = 'vertical' })
    frame.style.minimal_width = main_window_width
    frame.style.maximal_width = main_window_width
    frame.style.padding = 4
    frame.style.margin = 2

    local t = frame.add({ type = 'table', name = 'fjei_main_window_control_table', column_count = 4 })
    local element = t.add({ type = 'label', name = 'fjei_main_window_page_counter', caption = ' ' })
    element.style.font = 'heading-2'
    element.style.font_color = { 222, 222, 222 }
    element.style.right_margin = 4

    local element = t.add({ type = 'sprite-button', name = 'fjei_main_window_previous_page', caption = '←' })
    element.style.font = 'heading-1'
    element.style.maximal_height = 32
    element.style.maximal_width = 32
    local element = t.add({ type = 'sprite-button', name = 'fjei_main_window_next_page', caption = '→' })
    element.style.font = 'heading-1'
    element.style.maximal_height = 32
    element.style.maximal_width = 32
    local text = storage.fjei.player_data[player.index].active_filter
    if not text then
        text = ''
    end
    local textfield = t.add({ type = 'textfield', name = 'fjei_main_window_search_textfield', text = text })
    textfield.style.left_margin = 4

    frame.add({ type = 'line' })

    local t = frame.add({ type = 'table', name = 'fjei_main_window_item_list_table', column_count = column_count })

    frame.add({ type = 'line' })

    local t = frame.add({ type = 'table', name = 'fjei_main_window_history_table', column_count = column_count })

    Public.refresh_main_window(player)
end

local function refresh_recipe_bar(player, selected_recipe)
    local old_recipe_name = get_active_recipe_name(player)
    if not old_recipe_name then
        return
    end

    if not player.gui[recipe_window_position]['fjei_recipe_window'] then
        return
    end
    if not player.gui[recipe_window_position]['fjei_recipe_window'].header_container then
        return
    end
    local fjei_recipe_window_select_table = player.gui[recipe_window_position]['fjei_recipe_window'].header_container.header_table.scroll_pane.fjei_recipe_window_select_table

    local container = fjei_recipe_window_select_table[old_recipe_name]
    container.clear()
    add_sprite_icon(container, old_recipe_name)

    local container = fjei_recipe_window_select_table[selected_recipe]
    container.clear()
    local element = container.add({ type = 'frame', name = 'fjei_recipe_window_selected_recipe' })
    element.style.margin = 0
    element.style.padding = -4
    add_sprite_icon(element, selected_recipe)
end

local function draw_recipe_window_header(player, container, item_name, recipes, mode, recipe_name)
    if mode == 1 then
        container.parent.caption = 'Product of recipe: '
    else
        container.parent.caption = 'Ingredient in recipe: '
    end

    local t = container.add({ type = 'table', name = 'header_table', column_count = 3 })

    local element = t.add({ type = 'label', name = item_name, caption = ' ' })
    element.visible = false

    local scroll_pane = t.add({ type = 'scroll-pane', name = 'scroll_pane', horizontal_scroll_policy = 'always', vertical_scroll_policy = 'never' })
    scroll_pane.style.minimal_width = recipe_window_width - 66
    scroll_pane.style.maximal_width = recipe_window_width - 66
    scroll_pane.style.minimal_height = 60
    scroll_pane.style.maximal_height = 60
    local tt = scroll_pane.add({ type = 'table', name = 'fjei_recipe_window_select_table', column_count = 8192 })

    for key, name in pairs(recipes) do
        if not tt[name] then
            local ttt = tt.add({ type = 'table', name = name, column_count = 1 })
            if recipe_name == name then
                local element = ttt.add({ type = 'frame', name = 'fjei_recipe_window_selected_recipe' })
                element.style.margin = 0
                element.style.padding = -4
                add_sprite_icon(element, name)
            else
                add_sprite_icon(ttt, name)
            end
        end
    end

    local element = t.add { type = 'sprite-button', sprite = 'utility/close_fat', name = 'fjei_close_recipe_window' }
    element.style.left_margin = 5

    local element = container.add({ type = 'line' })
    element.style.right_margin = 6
end

local function draw_recipe(player, container, recipe_name)
    local recipe = prototypes.recipe[recipe_name]

    local t = container.add({ type = 'table', column_count = 2 })
    add_choose_elem_button(t, recipe.name, true)

    local tt = t.add({ type = 'table', column_count = 1 })
    local ttt = tt.add({ type = 'table', column_count = 2 })
    local element_1 = ttt.add({ type = 'label', caption = recipe.localised_name })
    element_1.style.font = 'heading-1'
    element_1.style.single_line = false
    local element_2 = ttt.add({ type = 'label', caption = ' ' })
    element_2.style.font = 'heading-1'
    if not player.force.recipes[recipe_name].enabled then
        element_2.style.font_color = { 200, 0, 0 }
        element_2.caption = '*'
        local str = 'Further research is required to unlock this recipe.'
        element_2.tooltip = str
        element_1.tooltip = str
    end

    local element = tt.add({ type = 'label', caption = '◷ ' .. math.round(recipe.energy, 2) .. ' seconds crafting time' })
    element.style.font = 'default'
    element.style.font_color = { 215, 215, 215 }
    element.style.top_margin = -4

    container.add({ type = 'line' })

    local element = container.add({ type = 'label', caption = 'Products:' })
    element.style.font = 'heading-2'
    local t = container.add({ type = 'table', column_count = 2 })

    for _, product in pairs(recipe.products) do
        local tt = t.add({ type = 'table', column_count = 3 })

        local amount = product.amount
        if not amount then
            amount = math.round((product.amount_min + product.amount_max) * 0.5, 2)
        end
        amount = amount * product.probability

        local element = tt.add({ type = 'label', caption = get_formatted_amount(amount) })
        element.style.minimal_width = recipe_window_amount_width
        element.style.maximal_width = recipe_window_amount_width
        element.style.single_line = false
        element.style.horizontal_align = 'right'
        add_choose_elem_button(tt, product.name)
        if product.temperature then
            local ttt = tt.add({ type = 'table', column_count = 1 })
            local element = ttt.add({ type = 'label', caption = get_localised_name(product.name) })
            element.style.minimal_width = recipe_window_item_name_width
            element.style.maximal_width = recipe_window_item_name_width
            element.style.single_line = false
            element.style.font = 'default'
            element.style.bottom_margin = -3
            local element = ttt.add({ type = 'label', caption = product.temperature .. ' °C' })
            element.style.top_margin = -3
        else
            local element = tt.add({ type = 'label', caption = get_localised_name(product.name) })
            element.style.minimal_width = recipe_window_item_name_width
            element.style.maximal_width = recipe_window_item_name_width
            element.style.single_line = false
            element.style.font = 'default'
        end
        if not product.amount or not product.probability or product.probability ~= 1 then
            element.tooltip = 'This number is an approximate value.'
            element.style.font_color = { 255, 255, 0 }
        end
    end

    local element = container.add({ type = 'label', caption = 'Ingredients:' })
    element.style.font = 'heading-2'
    local t = container.add({ type = 'table', column_count = 2 })

    for key, ingredient in pairs(recipe.ingredients) do
        local tt = t.add({ type = 'table', column_count = 3 })
        local element = tt.add({ type = 'label', caption = get_formatted_amount(ingredient.amount) })
        element.style.minimal_width = recipe_window_amount_width
        element.style.maximal_width = recipe_window_amount_width
        element.style.single_line = false
        element.style.horizontal_align = 'right'

        add_choose_elem_button(tt, ingredient.name)
        if ingredient.minimum_temperature and math_abs(ingredient.minimum_temperature) < 4294967296 then
            local ttt = tt.add({ type = 'table', column_count = 1 })
            local element = ttt.add({ type = 'label', caption = get_localised_name(ingredient.name) })
            element.style.minimal_width = recipe_window_item_name_width
            element.style.maximal_width = recipe_window_item_name_width
            element.style.single_line = false
            element.style.font = 'default'
            element.style.bottom_margin = -3
            local element = ttt.add({ type = 'label', caption = ingredient.minimum_temperature .. ' °C' })
            element.style.top_margin = -3
        else
            local element = tt.add({ type = 'label', caption = get_localised_name(ingredient.name) })
            element.style.minimal_width = recipe_window_item_name_width
            element.style.maximal_width = recipe_window_item_name_width
            element.style.single_line = false
            element.style.font = 'default'
        end
    end

    local machines = Functions.get_crafting_machines_for_recipe(player.force.name, recipe)

    local element = container.add({ type = 'line' })
    element.style.top_margin = 2
    element.style.bottom_margin = 2

    if #machines == 0 then
        local t = container.add({ type = 'table', column_count = 10 })
        local element = t.add({ type = 'label', caption = 'Made by:' })
        element.style.font = 'heading-2'
        local element = t.add({ type = 'label', caption = 'Crafting method unknown.' })
        element.style.font = 'heading-2'
        element.style.font_color = { 150, 150, 150 }
        return
    end

    local t = container.add({ type = 'table', column_count = 10 })
    local element = t.add({ type = 'label', caption = 'Made by:' })
    element.style.right_margin = 2
    element.style.font = 'heading-2'

    for key, machine in pairs(machines) do
        local prototype = prototypes.entity[machine]
        if prototype then
            add_choose_elem_button(t, machine)
        end
    end
end

local function create_recipe_window(item_name, player, button, selected_recipe)
    local mode
    if button == defines.mouse_button_type.left then
        mode = 1
    else
        mode = 2
    end

    if selected_recipe and player.gui[recipe_window_position]['fjei_recipe_window'] then
        refresh_recipe_bar(player, selected_recipe)
        local container = player.gui[recipe_window_position]['fjei_recipe_window'].recipe_container
        container.clear()
        draw_recipe(player, container, selected_recipe)
        return
    end

    if not storage.fjei.item_list[item_name] then
        return
    end

    --shift researched recipes forward in the list
    local recipes = {}
    local size_of_recipes = 0
    local researched_recipes = {}
    local size_of_researched_recipes = 0
    local unknown_recipes = {}
    local size_of_unknown_recipes = 0
    local force_recipes = player.force.recipes

    for k, recipe_name in pairs(storage.fjei.item_list[item_name][mode]) do
        if k > 512 then
            break
        end
        if force_recipes[recipe_name] then
            if force_recipes[recipe_name].enabled then
                size_of_researched_recipes = size_of_researched_recipes + 1
                researched_recipes[size_of_researched_recipes] = recipe_name
            else
                size_of_unknown_recipes = size_of_unknown_recipes + 1
                unknown_recipes[size_of_unknown_recipes] = recipe_name
            end
        end
    end

    for k, recipe_name in pairs(researched_recipes) do
        size_of_recipes = size_of_recipes + 1
        recipes[size_of_recipes] = recipe_name
    end
    for k, recipe_name in pairs(unknown_recipes) do
        size_of_recipes = size_of_recipes + 1
        recipes[size_of_recipes] = recipe_name
    end

    if size_of_recipes == 0 then
        return
    end

    if not selected_recipe then
        for key, recipe_name in pairs(recipes) do
            if #Functions.get_crafting_machines_for_recipe(player.force.name, prototypes.recipe[recipe_name]) > 0 then
                selected_recipe = recipe_name
                break
            end
            if key > 16 then
                break
            end
        end
    end

    local recipe_name = recipes[1]
    if selected_recipe then
        for k, v in pairs(recipes) do
            if v == selected_recipe then
                recipe_name = recipes[k]
            end
        end
    end

    if player.gui[recipe_window_position]['fjei_recipe_window'] then
        save_window_location(player, player.gui[recipe_window_position]['fjei_recipe_window'])
        player.gui[recipe_window_position]['fjei_recipe_window'].destroy()
    end
    local frame = player.gui[recipe_window_position].add({ type = 'frame', name = 'fjei_recipe_window', direction = 'vertical', caption = ' ' })
    frame.style.minimal_width = recipe_window_width
    frame.style.maximal_width = recipe_window_width
    frame.style.padding = 4
    frame.style.left_padding = 6
    frame.style.right_padding = 6

    local player_data = storage.fjei.player_data
    local position
    if player_data[player.index] and player_data[player.index].gui_position then
        position = { x = player_data[player.index].gui_position[1], y = player_data[player.index].gui_position[2] }
    else
        position = { x = 2, y = 370 }
    end
    frame.location = position

    local container = frame.add({ type = 'table', name = 'header_container', column_count = 1 })
    draw_recipe_window_header(player, container, item_name, recipes, mode, recipe_name)

    local container = frame.add({ type = 'table', name = 'recipe_container', column_count = 1 })
    draw_recipe(player, container, recipe_name)

    return true
end

local function add_to_history(item_name, player)
    if not prototypes.item[item_name] and not prototypes.fluid[item_name] then
        return
    end

    local player_data = storage.fjei.player_data[player.index]
    if not player_data.history then
        player_data.history = { item_name }
        player_data.size_of_history = 1
        return
    end

    --shift existing item forward
    for k, v in pairs(player_data.history) do
        if v == item_name then
            table_remove(player_data.history, k)
            table_insert(player_data.history, item_name)
            return
        end
    end

    --remove first item, add new item
    if player_data.size_of_history >= column_count then
        table_remove(player_data.history, 1)
        player_data.history[column_count] = item_name
        return
    end

    player_data.size_of_history = player_data.size_of_history + 1
    player_data.history[player_data.size_of_history] = item_name
end

local function show_cursor_stack_item(element, player, button)
    local cursor_stack = player.cursor_stack
    if not cursor_stack then
        return
    end
    if not cursor_stack.valid_for_read then
        return
    end
    if not storage.fjei then
        Functions.build_tables()
    end
    if not storage.fjei.player_data[player.index] then
        storage.fjei.player_data[player.index] = {}
    end
    if not storage.fjei.item_list[cursor_stack.name] then
        return
    end
    add_to_history(cursor_stack.name, player)
    draw_main_window(player)
    create_recipe_window(cursor_stack.name, player, button)
    return true
end

local function toggle_main_window(element, player, button)
    if show_cursor_stack_item(element, player, button) then
        return true
    end
    if player.gui.left.fjei_main_window then
        player.gui.left.fjei_main_window.destroy()
        if player.gui[recipe_window_position].fjei_recipe_window then
            player.gui[recipe_window_position].fjei_recipe_window.destroy()
        end
    else
        Functions.handle_translations_fetch(player)
        draw_main_window(player)
    end
    return true
end

local function main_window_next_page(element, player, button)
    local player_data = storage.fjei.player_data[player.index]
    if button == defines.mouse_button_type.right then
        for _ = 1, 5, 1 do
            if player_data.active_page == get_total_page_count(player) then
                player_data.active_page = 1
            else
                player_data.active_page = player_data.active_page + 1
            end
        end
    else
        if player_data.active_page == get_total_page_count(player) then
            player_data.active_page = 1
        else
            player_data.active_page = player_data.active_page + 1
        end
    end
    Public.refresh_main_window(player)
    return true
end

local function main_window_previous_page(element, player, button)
    local player_data = storage.fjei.player_data[player.index]
    if button == defines.mouse_button_type.right then
        for _ = 1, 5, 1 do
            if player_data.active_page == 1 then
                player_data.active_page = get_total_page_count(player)
            else
                player_data.active_page = player_data.active_page - 1
            end
        end
    else
        if player_data.active_page == 1 then
            player_data.active_page = get_total_page_count(player)
        else
            player_data.active_page = player_data.active_page - 1
        end
    end
    Public.refresh_main_window(player)
    return true
end

local function clear_search_textfield(element, player, button)
    if show_cursor_stack_item(element, player, button) then
        return true
    end
    if button ~= defines.mouse_button_type.right then
        return
    end
    storage.fjei.player_data[player.index].active_filter = false
    element.text = ''
    Functions.set_filtered_list(player)
    Public.refresh_main_window(player)
    return true
end

local function close_recipe_window(element, player, button)
    local recipe_window = player.gui[recipe_window_position]['fjei_recipe_window']
    save_window_location(player, recipe_window)
    if recipe_window then
        recipe_window.destroy()
    end
    return true
end

local gui_actions = {
    ['fjei_toggle_button'] = toggle_main_window,
    ['fjei_main_window_next_page'] = main_window_next_page,
    ['fjei_main_window_previous_page'] = main_window_previous_page,
    ['fjei_main_window_search_textfield'] = clear_search_textfield,
    ['fjei_close_recipe_window'] = close_recipe_window
}

function Public.gui_click_actions(element, player, button)
    if not gui_actions[element.name] then
        return
    end
    if not storage.fjei then
        Functions.build_tables()
    end
    if not storage.fjei.player_data[player.index] then
        storage.fjei.player_data[player.index] = {}
    end
    gui_actions[element.name](element, player, button)
    return true
end

function Public.draw_top_toggle_button(player)
    local button_flow = Gui.mod_button(player)
    if button_flow.fjei_toggle_button then
        return
    end
    button_flow.add { type = 'sprite-button', name = 'fjei_toggle_button', caption = 'FJEI' }
    button_flow.style.minimal_height = 38
    button_flow.style.maximal_height = 38
    button_flow.style.minimal_width = 40
    button_flow.style.padding = -2
    button_flow.style.font_color = { 222, 222, 222 }
end

function Public.open_recipe(element, player, button)
    if not storage.fjei then
        Functions.build_tables()
    end
    if not storage.fjei.player_data[player.index] then
        storage.fjei.player_data[player.index] = {}
    end

    local item_name = element.name
    local selected_recipe = false

    if element.parent.name == 'fjei_recipe_window_selected_recipe' then
        return
    end
    if element.parent then
        if element.parent.parent then
            if element.parent.parent.name == 'fjei_recipe_window_select_table' then
                selected_recipe = item_name
            end
        end
    end
    if element.parent.name == 'fjei_main_window_item_list_table' or element.parent.name == 'fjei_main_window_history_table' then
        local recipe_window = player.gui[recipe_window_position]['fjei_recipe_window']
        if recipe_window then
            local active_item = recipe_window.header_container.header_table.children[1].name
            if active_item == element.name and storage.fjei.player_data[player.index].last_button == button then
                save_window_location(player, recipe_window)
                recipe_window.destroy()
                return
            end
        end
    end

    add_to_history(item_name, player)
    if not create_recipe_window(item_name, player, button, selected_recipe) then
        return
    end
    display_history(player)

    storage.fjei.player_data[player.index].last_button = button
end

return Public
