local Gui = require 'utils.gui'
local Global = require 'utils.global'
local Event = require 'utils.event'
local Donator = require 'features.donator'
local Rank = require 'features.rank_system'
local PlayerRewards = require 'utils.player_rewards'
local Server = require 'features.server'
local Token = require 'utils.token'
local Color = require 'resources.color_presets'

local format = string.format

local config = global.config
local config_mapinfo = config.map_info
local config_prewards = config.player_rewards

local normal_color = Color.white
local focus_color = Color.dark_orange
local unfocus_color = Color.black

local reward_amount = 2
local reward_token = PlayerRewards.get_reward()
local info_tab_flags = {
    0x1, -- welcome
    0x2, -- rules
    0x4, -- map_info
    0x8, -- scenario_mods
    0x10 -- whats_new
}
local flags_sum = 0
for _, v in pairs(info_tab_flags) do
    flags_sum = flags_sum + v
end

local map_name_key = 1
local map_description_key = 2
local map_extra_info_key = 3
local new_info_key = 4
local extra_rules_key = 5

local rewarded_players = {}
local primitives = {map_extra_info_lock = nil, info_edited = nil}

local editable_info = {
    [map_name_key] = config_mapinfo.map_name_key,
    [map_description_key] = config_mapinfo.map_description_key,
    [map_extra_info_key] = config_mapinfo.map_extra_info_key,
    [new_info_key] = config_mapinfo.new_info_key,
    [extra_rules_key] = {}
}

Global.register({rewarded_players = rewarded_players, editable_info = editable_info, primitives = primitives},
    function(tbl)
        rewarded_players = tbl.rewarded_players
        editable_info = tbl.editable_info
        primitives = tbl.primitives
    end)

--- Sets the "new info" according to the changelog located on the server
local function process_changelog(data)
    local key = data.key
    if key ~= 'changelog' then
        return
    end

    local value = data.value -- will be nil if no data
    if value then
        editable_info[new_info_key] = value
    end
end

local changelog_callback = Token.register(process_changelog)

--- Uploads the contents of new info tab to the server.
-- Is triggered on closing the info window by clicking the close button or by pressing escape.
local function upload_changelog(player)
    if not player or not player.valid or not player.admin then
        return
    end

    if editable_info[new_info_key] ~= config_mapinfo.new_info_key and primitives.info_edited then
        Server.set_data('misc', 'changelog', editable_info[new_info_key])
        primitives.info_edited = nil
    end
end

--- Tries to download the latest changelog
local function download_changelog()
    Server.try_get_data('misc', 'changelog', changelog_callback)
end

local function prepare_title()
    local welcome_title = [[
111111  1111111 111111  111    111 1111111 11     11
11   11 11      11   11 1111  1111 11      11     11
111111  11111   11   11 11 1111 11 11111   11  1  11
11   11 11      11   11 11  11  11 11      11 111 11
11   11 1111111 111111  11      11 1111111  111 111
]]

    local row = {}
    local welcome_title2 = {row}
    local row_index = 1
    local column_index = 1

    local max = 0
    for i = 1, #welcome_title do
        local char = welcome_title:sub(i, i)
        if char == '\n' then
            row_index = row_index + 1
            row = {}
            welcome_title2[row_index] = row

            max = math.max(max, column_index - 1)

            column_index = 1
        elseif char == '1' then
            row[column_index] = true
            column_index = column_index + 1
        elseif char == ' ' then
            row[column_index] = false
            column_index = column_index + 1
        end
    end

    return welcome_title2, max
end

local title, title_max = prepare_title()

local main_button_name = Gui.uid_name()
local main_frame_name = Gui.uid_name()
local tab_button_name = Gui.uid_name()
local editable_textbox_name = Gui.uid_name()

local function line_bar(parent)
    local bar = parent.add {type = 'progressbar', value = 1}
    local style = bar.style
    style.color = normal_color
    style.horizontally_stretchable = true
end

local function centered_label(parent, string)
    local flow = parent.add {type = 'flow'}
    local flow_style = flow.style
    flow_style.horizontal_align = 'center'
    flow_style.horizontally_stretchable = true

    local label = flow.add {type = 'label', caption = string}
    local label_style = label.style
    label_style.horizontal_align = 'center'
    label_style.single_line = false

    return label
end

local function header_label(parent, string)
    local flow = parent.add {type = 'flow'}
    local flow_style = flow.style
    flow_style.horizontal_align = 'center'
    flow_style.horizontally_stretchable = true

    local label = flow.add {type = 'label', caption = string}
    local label_style = label.style
    label_style.horizontal_align = 'center'
    label_style.single_line = false
    label_style.font = 'default-dialog-button'

    return label
end

local pages = {
    {
        tab_button = function(parent)
            local button = parent.add {type = 'button', name = tab_button_name, caption = 'Welcome'}
            return button
        end,
        content = function(parent)
            local parent_style = parent.style
            parent_style.right_padding = 0
            parent_style.left_padding = 0
            parent_style.top_padding = 1

            parent = parent.add {
                type = 'scroll-pane',
                vertical_scroll_policy = 'auto-and-reserve-space',
                horizontal_scroll_policy = 'never'
            }
            parent_style = parent.style
            parent_style.vertically_stretchable = false

            header_label(parent, {'info.welcome_header'})
            centered_label(parent, {'info.welcome_text'})

            header_label(parent, {'info.chatting_header'})
            centered_label(parent, {
                'info.chatting_text',
                {'gui-menu.settings'},
                {'gui-menu.controls'},
                {'controls.toggle-console'}
            })

            if config_prewards.enabled and config_prewards.info_player_reward then
                header_label(parent, {'info.free_coin_header'})
                centered_label(parent, {'info.free_coin_text', reward_amount, reward_token, reward_amount, reward_token})
            end

            header_label(parent, {'info.links_header'})
            centered_label(parent, {'info.links_discord'})
            local discord_textbox_flow = parent.add {type = 'flow'}
            local discord_textbox_flow_style = discord_textbox_flow.style
            discord_textbox_flow_style.horizontal_align = 'center'
            discord_textbox_flow_style.horizontally_stretchable = true
            discord_textbox_flow.add({type = 'label', caption = 'Discord: '}).style.font = 'default-bold'
            local discord_textbox = discord_textbox_flow.add {type = 'text-box', text = 'https://redmew.com/discord '}
            discord_textbox.read_only = true
            discord_textbox.style.width = 235
            discord_textbox.style.height = 28
            centered_label(parent, {'info.links_patreon'})
            local patreon_flow = parent.add {type = 'flow', direction = 'horizontal'}
            local patreon_flow_style = patreon_flow.style
            patreon_flow_style.horizontal_align = 'center'
            patreon_flow_style.horizontally_stretchable = true
            patreon_flow.add({type = 'label', caption = 'Patreon:'}).style.font = 'default-bold'
            local patreon_textbox = patreon_flow.add {type = 'text-box', text = 'https://www.patreon.com/redmew '}
            patreon_textbox.read_only = true
            patreon_textbox.style.width = 235
            patreon_textbox.style.height = 28
            centered_label(parent, {'info.links_saves'})
            local save_textbox_flow = parent.add {type = 'flow'}
            local save_textbox_flow_style = save_textbox_flow.style
            save_textbox_flow_style.horizontal_align = 'center'
            save_textbox_flow_style.horizontally_stretchable = true
            save_textbox_flow.add({type = 'label', caption = 'Saves: '}).style.font = 'default-bold'
            local save_textbox = save_textbox_flow.add {type = 'text-box', text = 'http://redmew.com/saves/ '}
            save_textbox.read_only = true
            save_textbox.style.width = 235
            save_textbox.style.height = 28
            centered_label(parent, {'info.links_factoriomaps'})
            local maps_textbox_flow = parent.add {type = 'flow'}
            local maps_textbox_flow_style = maps_textbox_flow.style
            maps_textbox_flow_style.horizontal_align = 'center'
            maps_textbox_flow_style.horizontally_stretchable = true
            maps_textbox_flow.add({type = 'label', caption = 'Maps: '}).style.font = 'default-bold'
            local maps_textbox = maps_textbox_flow.add {
                type = 'text-box',
                text = 'https://factoriomaps.com/browse/redmew.html '
            }
            maps_textbox.read_only = true
            maps_textbox.style.width = 315
            maps_textbox.style.height = 28

            parent.add({type = 'flow'}).style.height = 24
        end
    },
    {
        tab_button = function(parent)
            local button = parent.add {type = 'button', name = tab_button_name, caption = 'Rules'}
            return button
        end,
        content = function(parent)
            local parent_style = parent.style
            parent_style.right_padding = 0
            parent_style.left_padding = 0
            parent_style.top_padding = 1

            parent = parent.add {
                type = 'scroll-pane',
                vertical_scroll_policy = 'auto-and-reserve-space',
                horizontal_scroll_policy = 'never'
            }
            parent_style = parent.style
            parent_style.right_padding = 8
            parent_style.left_padding = 8
            parent_style.vertically_stretchable = true

            header_label(parent, {'info.rules_header'})

            centered_label(parent, {'info.rules_text'})

            local rules = editable_info[extra_rules_key]
            for _, rule in pairs(rules) do
                parent.add({type = 'flow'}).style.height = 16
                centered_label(parent, rule)
            end

            parent.add({type = 'flow'}).style.height = 24
        end
    },
    {
        tab_button = function(parent)
            local button = parent.add {type = 'button', name = tab_button_name, caption = {'info.map_info_button'}}
            return button
        end,
        content = function(parent, player)
            local read_only = not player.admin

            local parent_style = parent.style
            parent_style.right_padding = 8
            parent_style.left_padding = 8
            parent_style.top_padding = 1

            local top_flow = parent.add {type = 'flow'}
            local top_flow_style = top_flow.style
            top_flow_style.horizontal_align = 'center'
            top_flow_style.horizontally_stretchable = true

            local top_label = top_flow.add {type = 'label', caption = {'info.map_info_header'}}
            local top_label_style = top_label.style
            top_label_style.font = 'default-dialog-button'

            local grid = parent.add {type = 'table', column_count = 2}
            local grid_style = grid.style
            grid_style.horizontally_stretchable = true

            grid.add {type = 'label', caption = {'info.map_name_label'}}
            local map_name_textbox = grid.add({type = 'flow'}).add {
                type = 'text-box',
                name = editable_textbox_name,
                text = editable_info[map_name_key]
            }
            map_name_textbox.read_only = read_only

            local map_name_textbox_style = map_name_textbox.style
            map_name_textbox_style.width = 490
            map_name_textbox_style.maximal_height = 30

            Gui.set_data(map_name_textbox, map_name_key)

            grid.add {type = 'label', caption = {'info.map_desc_label'}}
            local map_description_textbox = grid.add({type = 'flow'}).add {
                type = 'text-box',
                name = editable_textbox_name,
                text = editable_info[map_description_key]
            }
            map_description_textbox.read_only = read_only
            map_description_textbox.word_wrap = true

            local map_description_textbox_style = map_description_textbox.style
            map_description_textbox_style.width = 490
            map_description_textbox_style.minimal_height = 80
            map_description_textbox_style.vertically_stretchable = true
            map_description_textbox_style.maximal_height = 100

            Gui.set_data(map_description_textbox, map_description_key)

            grid.add {type = 'label', caption = {'info.map_extra_info_label'}}
            local map_extra_info_textbox = grid.add({type = 'flow'}).add {
                type = 'text-box',
                name = editable_textbox_name,
                text = editable_info[map_extra_info_key]
            }
            map_extra_info_textbox.read_only = read_only
            map_extra_info_textbox.word_wrap = true

            local map_extra_info_textbox_style = map_extra_info_textbox.style
            map_extra_info_textbox_style.width = 490
            map_extra_info_textbox_style.height = 248

            Gui.set_data(map_extra_info_textbox, map_extra_info_key)
        end
    },
    {
        tab_button = function(parent)
            local button = parent.add {type = 'button', name = tab_button_name, caption = {'info.softmods_button'}}
            return button
        end,
        content = function(parent, player)
            local parent_style = parent.style
            parent_style.right_padding = 0
            parent_style.left_padding = 0
            parent_style.top_padding = 1

            parent = parent.add {
                type = 'scroll-pane',
                vertical_scroll_policy = 'auto-and-reserve-space',
                horizontal_scroll_policy = 'never'
            }
            parent_style = parent.style
            parent_style.right_padding = 8
            parent_style.left_padding = 8
            parent_style.vertically_stretchable = true

            header_label(parent, {'info.softmods_header'})

            local grid = parent.add {type = 'table', column_count = 3}
            local grid_style = grid.style
            grid_style.vertical_spacing = 24
            grid_style.horizontal_spacing = 8
            grid_style.top_padding = 8
            grid_style.bottom_padding = 16

            grid.add {type = 'label'}
            local ranks = grid.add {type = 'label', caption = 'Ranks'}
            ranks.style.font = 'default-listbox'
            local ranks_flow = grid.add {type = 'flow', direction = 'vertical'}
            local ranks_label = ranks_flow.add {type = 'label', caption = {'info.softmods_rank_text'}}
            local ranks_label_style = ranks_label.style
            ranks_label_style.single_line = false
            local player_rank_flow = ranks_flow.add {type = 'flow', direction = 'horizontal'}
            player_rank_flow.add {type = 'label', caption = {'info.softmods_rank_is'}}
            local player_name = player.name

            local rank = Rank.get_player_rank(player_name)
            local rank_label = player_rank_flow.add {type = 'label', caption = Rank.get_rank_name(rank)}
            rank_label.style.font_color = Rank.get_rank_color(rank)

            if Donator.is_donator(player_name) then
                local donator_label = player_rank_flow.add {type = 'label', caption = {'ranks.donator'}}
                donator_label.style.font_color = Color.donator
            end

            grid.add {type = 'sprite', sprite = 'entity/market'}
            local market = grid.add {type = 'label', caption = {'info.softmods_market_label'}}
            market.style.font = 'default-listbox'
            local market_label = grid.add {type = 'label', caption = {'info.softmods_market_text'}}
            market_label.style.single_line = false

            grid.add {type = 'sprite', sprite = 'item/player-port'}
            local train_savior = grid.add {type = 'label', caption = {'info.softmods_saviour_label'}}
            local train_savior_style = train_savior.style
            train_savior_style.font = 'default-listbox'
            train_savior_style.single_line = false
            local train_savior_label = grid.add {type = 'label', caption = {'info.softmods_saviour_text'}}
            train_savior_label.style.single_line = false

            if config.player_list.enabled then
                grid.add {type = 'sprite', sprite = 'entity/character'}
                local player_list = grid.add {type = 'label', caption = {'info.softmods_plist_label'}}
                player_list.style.font = 'default-listbox'
                player_list.style.single_line = false
                local player_list_label = grid.add {type = 'label', caption = {'info.softmods_plist_text'}}
                player_list_label.style.single_line = false
            end
            if config.poll.enabled then
                grid.add {type = 'sprite', sprite = 'item/programmable-speaker'}
                local poll = grid.add {type = 'label', caption = {'info.softmods_polls_label'}}
                poll.style.font = 'default-listbox'
                local poll_label = grid.add {type = 'label', caption = {'info.softmods_polls_text'}}
                poll_label.style.single_line = false
            end

            if config.tag_group.enabled then
                local tag_button = grid.add {type = 'label', caption = 'tag'}
                local tag_button_style = tag_button.style
                tag_button_style.font = 'default-listbox'
                tag_button_style.font_color = Color.black
                local tag = grid.add {type = 'label', caption = {'info.softmods_tags_label'}}
                tag.style.font = 'default-listbox'
                local tag_label = grid.add {type = 'label', caption = {'info.softmods_tags_text'}}
                tag_label.style.single_line = false
            end

            if config.tasklist.enabled then
                grid.add {type = 'sprite', sprite = 'item/repair-pack'}
                local task = grid.add {type = 'label', caption = {'info.softmods_tasks_label'}}
                task.style.font = 'default-listbox'
                local task_label = grid.add {type = 'label', caption = {'info.softmods_tasks_text'}}
                task_label.style.single_line = false
            end

            if config.score.enabled then
                grid.add {type = 'sprite', sprite = 'item/rocket-silo'}
                local score = grid.add {type = 'label', caption = {'info.softmods_score_label'}}
                score.style.font = 'default-listbox'
                local score_label = grid.add {type = 'label', caption = {'info.softmods_score_text'}}
                score_label.style.single_line = false
            end
        end
    },
    {
        tab_button = function(parent)
            local button = parent.add {type = 'button', name = tab_button_name, caption = {'info.whats_new_button'}}
            return button
        end,
        content = function(parent, player)
            local read_only = not player.admin

            local parent_style = parent.style
            parent_style.right_padding = 8
            parent_style.left_padding = 8
            parent_style.top_padding = 1

            header_label(parent, 'New Features')

            local new_info_flow = parent.add {name = 'whatsNew_new_info_flow', type = 'flow'}
            new_info_flow.style.horizontal_align = 'center'

            local new_info_textbox = new_info_flow.add {
                type = 'text-box',
                name = editable_textbox_name,
                text = editable_info[new_info_key]
            }
            new_info_textbox.read_only = read_only

            local new_info_textbox_style = new_info_textbox.style
            new_info_textbox_style.width = 590
            new_info_textbox_style.height = 360
            new_info_textbox_style.left_margin = 2

            Gui.set_data(new_info_textbox, new_info_key)
        end
    }
}

local function draw_main_frame(center, player)
    local frame = center.add {type = 'frame', name = main_frame_name, direction = 'vertical'}
    local frame_style = frame.style
    frame_style.height = 600
    frame_style.width = 650
    frame_style.left_padding = 16
    frame_style.right_padding = 16
    frame_style.top_padding = 16

    local top_flow = frame.add {type = 'flow'}
    local top_flow_style = top_flow.style
    top_flow_style.horizontal_align = 'center'
    top_flow_style.top_padding = 8
    top_flow_style.horizontally_stretchable = true

    local title_grid = top_flow.add {type = 'table', column_count = title_max}
    for _, row in ipairs(title) do
        for _, char in ipairs(row) do
            local ele
            if char then
                ele = title_grid.add {type = 'sprite', sprite = 'virtual-signal/signal-red'}
                ele.style.stretch_image_to_widget_size = true
            else
                ele = title_grid.add {type = 'label', caption = ' '}
            end

            local ele_style = ele.style
            ele_style.height = 10
            ele_style.width = 10
        end
    end

    local title_grid_style = title_grid.style
    title_grid_style.vertical_spacing = 0
    title_grid_style.horizontal_spacing = 0
    title_grid_style.bottom_padding = 8

    line_bar(frame)

    local tab_buttons = {}
    local active_tab = 1
    local data = {tab_buttons = tab_buttons, active_tab = active_tab}

    local tab_flow = frame.add {type = 'flow', direction = 'horizontal'}
    local tab_flow_style = tab_flow.style
    tab_flow_style.horizontal_align = 'center'
    tab_flow_style.horizontally_stretchable = true

    for index, page in ipairs(pages) do
        local button_flow = tab_flow.add {type = 'flow'}
        local button = page.tab_button(button_flow, player)

        local button_style = button.style
        button_style.left_padding = 3
        button_style.right_padding = 3

        Gui.set_data(button, {index = index, data = data})

        tab_buttons[index] = button
    end

    tab_buttons[active_tab].style.font_color = focus_color

    line_bar(frame)

    local content = frame.add {type = 'frame', direction = 'vertical', style = 'inside_shallow_frame'}
    local content_style = content.style
    content_style.horizontally_stretchable = true
    content_style.vertically_stretchable = true
    content_style.left_padding = 8
    content_style.right_padding = 8
    content_style.top_padding = 4

    pages[active_tab].content(content, player)

    data.content = content

    local bottom_flow = frame.add {type = 'flow'}
    local bottom_flow_style = bottom_flow.style
    bottom_flow_style.horizontal_align = 'center'
    bottom_flow_style.top_padding = 8
    bottom_flow_style.horizontally_stretchable = true

    Gui.make_close_button(bottom_flow, main_button_name)

    player.opened = frame
end

local function close_main_frame(frame, player)
    upload_changelog(player)
    Gui.destroy(frame)
    player.gui.top[main_button_name].style = 'slot_button'
end

local function reward_player(player, index, message)
    if not config_prewards.enabled or not config_prewards.info_player_reward then
        return
    end

    local player_index = player.index
    if not rewarded_players[player_index] then
        error('Player with no entry in rewarded_players table')
        return false
    end
    local tab_flag = info_tab_flags[index]

    if bit32.band(rewarded_players[player_index], tab_flag) == tab_flag then
        return
    else
        PlayerRewards.give_reward(player, reward_amount, message)
        rewarded_players[player_index] = rewarded_players[player_index] + tab_flag
        if rewarded_players[player_index] == flags_sum then
            rewarded_players[player_index] = nil
        end
    end
end

local function toggle(event)
    local player = event.player
    local gui = player.gui
    local center = gui.center
    local main_frame = center[main_frame_name]
    local main_button = gui.top[main_button_name]

    if main_frame then
        close_main_frame(main_frame, player)
    else
        main_button.style = 'highlighted_tool_button'
        local style = main_button.style
        style.width = 40
        style.height = 40
        style.padding = 0

        draw_main_frame(center, player)
    end
end

local function player_created(event)
    local player = game.get_player(event.player_index)
    if not player or not player.valid then
        return
    end

    player.gui.top.add({
        type = 'sprite-button',
        name = main_button_name,
        sprite = 'virtual-signal/signal-info',
        tooltip = {'info.tooltip'}
    })

    rewarded_players[player.index] = 0
    reward_player(player, info_tab_flags[1])
end

--- Sets editable_info[map_extra_info_key] outright or adds info to it.
-- Forbids map_extra_info being explicitly set twice
local function create_map_extra_info(value, set)
    if primitives.map_extra_info_lock and set then
        error('Cannot set extra info twice, use add instead')
        return
    elseif primitives.map_extra_info_lock then
        return
    elseif set then
        editable_info[map_extra_info_key] = value
        primitives.map_extra_info_lock = true
    elseif editable_info[map_extra_info_key] == config_mapinfo.map_extra_info_key then
        editable_info[map_extra_info_key] = value
    else
        editable_info[map_extra_info_key] = format('%s\n%s', editable_info[map_extra_info_key], value)
    end
end

Event.add(defines.events.on_player_created, player_created)

Event.add(Server.events.on_server_started, download_changelog)

Server.on_data_set_changed('misc', process_changelog)

Gui.on_click(main_button_name, toggle)

Gui.on_click(tab_button_name, function(event)
    local button = event.element
    local player = event.player

    local button_data = Gui.get_data(button)
    local index = button_data.index
    local data = button_data.data
    local active_tab = data.active_tab

    if active_tab == index then
        return
    end

    local tab_buttons = data.tab_buttons
    local old_button = tab_buttons[active_tab]

    old_button.style.font_color = unfocus_color
    button.style.font_color = focus_color

    data.active_tab = index

    local content = data.content
    Gui.clear(content)

    pages[index].content(content, player)
    if rewarded_players[player.index] then
        reward_player(player, index, {'info.free_coin_print', reward_amount, reward_token})
    end
end)

Gui.on_text_changed(editable_textbox_name, function(event)
    local textbox = event.element
    local key = Gui.get_data(textbox)

    editable_info[key] = textbox.text
    primitives.info_edited = true
end)

Gui.on_custom_close(main_frame_name, function(event)
    close_main_frame(event.element, event.player)
end)

Gui.allow_player_to_toggle_top_element_visibility(main_button_name)

local Public = {}

function Public.show_info(player)
    toggle({player = player})
end

function Public.get_map_name()
    return editable_info[map_name_key]
end

function Public.set_map_name(value)
    editable_info[map_name_key] = value
end

function Public.get_map_description()
    return editable_info[map_description_key]
end

function Public.set_map_description(value)
    editable_info[map_description_key] = value
end

function Public.get_map_extra_info()
    return editable_info[map_extra_info_key]
end

--- Adds to existing map_extra_info. Removes default text if it is the only text in place.
function Public.add_map_extra_info(value)
    create_map_extra_info(value, false)
end

--- Overrides all info added via add_map_extra_info.
-- This should only be used in maps, never in features/modules.
-- Use case: for maps that know exactly what features they're using and
-- want full control over the info presented.
function Public.set_map_extra_info(value)
    create_map_extra_info(value, true)
end

function Public.get_new_info()
    return editable_info[new_info_key]
end

function Public.set_new_info(value)
    editable_info[new_info_key] = value
end

function Public.add_extra_rule(rule)
    local rules = editable_info[extra_rules_key]
    table.insert(rules, rule)
end

return Public
