-- this script adds a group button to create groups for your players --

local Gui = require 'utils.gui'
local Global = require 'utils.global'
local SpamProtection = require 'utils.spam_protection'
local Event = require 'utils.event'
local Token = require 'utils.token'
local Color = require 'utils.color_presets'

local module_name = Gui.uid_name()

local this =
{
    player_group = {},
    join_spam_protection = {},
    tag_groups =
    {
        ['Miner'] = { name = 'Miner', founder = 'script', description = '[img=item/electric-mining-drill]', static = true },
        ['Smeltery'] = { name = 'Smeltery', founder = 'script', description = '[img=item/stone-furnace]', static = true },
        ['Power'] = { name = 'Power', founder = 'script', description = '[img=item/big-electric-pole]', static = true },
        ['Production'] = { name = 'Production', founder = 'script', description = '[img=item/assembling-machine-1]', static = true },
        ['Science'] = { name = 'Science', founder = 'script', description = '[img=item/chemical-science-pack]', static = true },
        ['Trainman'] = { name = 'Trainman', founder = 'script', description = '[img=item/locomotive]', static = true },
        ['Oil processing'] = { name = 'Oil Processing', founder = 'script', description = '[img=fluid/crude-oil]', static = true },
        ['Trooper'] = { name = 'Trooper', founder = 'script', description = '[img=item/submachine-gun]', static = true },
        ['Fortifications'] = { name = 'Fortifications', founder = 'script', description = '[img=item/stone-wall]', static = true },
        ['Support'] = { name = 'Support', founder = 'script', description = '[img=item/repair-pack]', static = true }
    },
    alphanumeric = true
}

Global.register(
    this,
    function (t)
        this = t
    end
)

local Public = {}

local function build_group_gui(data)
    local player = data.player
    local frame = data.frame
    local group_name_width = 150
    local description_width = 240
    local members_width = 90
    local member_columns = 2
    local actions_width = 130
    local total_height = (frame.style.minimal_height or 500) - 60

    frame.clear()

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

    local headings =
    {
        { { 'gui.title' }, group_name_width },
        { { 'gui.description' }, description_width },
        { { 'gui.members' }, members_width * member_columns },
        { '', actions_width }
    }
    for _, h in pairs(headings) do
        local l = t.add({ type = 'label', caption = h[1] })
        l.style.font_color = { r = 0.98, g = 0.66, b = 0.22 }
        l.style.font = 'heading-2'
        l.style.top_padding = 6
        l.style.minimal_height = 40
        l.style.minimal_width = h[2]
        l.style.maximal_width = h[2]
        l.style.horizontal_align = 'center'
    end

    local scroll_pane =
        frame.add(
            {
                type = 'scroll-pane',
                name = 'scroll_pane',
                direction = 'vertical',
                horizontal_scroll_policy = 'never',
                vertical_scroll_policy = 'auto'
            }
        )
    scroll_pane.style.maximal_height = total_height - 50
    scroll_pane.style.minimal_height = total_height - 50

    t = scroll_pane.add({ type = 'table', name = 'groups_table', column_count = 4 })
    for _, h in pairs(headings) do
        local l = t.add({ type = 'label', caption = '' })
        l.style.minimal_width = h[2]
        l.style.maximal_width = h[2]
    end

    for _, group in pairs(this.tag_groups) do
        if (group.name and group.founder and group.description) then
            local l = t.add({ type = 'label', caption = group.name })
            l.style.top_padding = 16
            l.style.bottom_padding = 16
            l.style.minimal_width = group_name_width
            l.style.maximal_width = group_name_width
            l.style.font = 'default-semibold'
            l.style.horizontal_align = 'center'
            local color
            if game.players[group.founder] and game.players[group.founder].color then
                color = game.players[group.founder].color
            else
                color = { r = 0.90, g = 0.90, b = 0.90 }
            end
            color = { r = color.r * 0.6 + 0.4, g = color.g * 0.6 + 0.4, b = color.b * 0.6 + 0.4, a = 1 }
            l.style.font_color = color
            l.style.single_line = false
            l = t.add({ type = 'label', caption = group.description })
            l.style.top_padding = 16
            l.style.bottom_padding = 16
            l.style.minimal_width = description_width
            l.style.maximal_width = description_width
            l.style.font_color = { r = 0.90, g = 0.90, b = 0.90 }
            l.style.single_line = false
            l.style.font = 'default-semibold'
            l.style.horizontal_align = 'center'

            local tt = t.add({ type = 'table', column_count = 2 })
            local flow = tt.add({ type = 'flow' })
            flow.style.left_padding = 65
            local ttt = tt.add({ type = 'table', column_count = member_columns })
            ttt.style.minimal_width = members_width * 2 - 25
            for _, p in pairs(game.connected_players) do
                if group.name == this.player_group[p.name] then
                    l = ttt.add({ type = 'label', caption = p.name })
                    color =
                    {
                        r = p.color.r * 0.6 + 0.4,
                        g = p.color.g * 0.6 + 0.4,
                        b = p.color.b * 0.6 + 0.4,
                        a = 1
                    }
                    l.style.font_color = color
                    l.style.maximal_width = members_width * 2 - 60
                    l.style.single_line = false
                    l.style.font = 'default-semibold'
                    l.style.horizontal_align = 'center'
                end
            end

            tt = t.add({ type = 'table', name = group.name, column_count = 1 })
            if group.name ~= this.player_group[player.name] then
                local b = tt.add({ type = 'button', caption = { 'gui.join' } })
                b.style.font = 'default-bold'
                b.style.minimal_width = actions_width
                b.style.maximal_width = actions_width
            else
                local b = tt.add({ type = 'button', caption = { 'gui.leave' } })
                b.style.font = 'default-bold'
                b.style.minimal_width = actions_width
                b.style.maximal_width = actions_width
            end
            if (player.admin == true or group.founder == player.name) and not group.static then
                local b = tt.add({ type = 'button', caption = { 'gui.delete' } })
                b.style.font = 'default-bold'
                b.style.minimal_width = actions_width
                b.style.maximal_width = actions_width
            else
                local b = tt.add({ type = 'button', caption = { 'gui.delete' } })
                b.enabled = false
                b.style.font = 'default-bold'
                b.style.minimal_width = actions_width
                b.style.maximal_width = actions_width
            end
            tt.add { type = 'line' }
        end
    end

    local frame2 = frame.add({ type = 'frame', name = 'frame2' })
    t = frame2.add({ type = 'table', name = 'group_table', column_count = 4 })
    local textfield = t.add({ type = 'textfield', name = 'new_group_name', text = 'Name' })
    textfield.style.minimal_width = 200
    textfield = t.add({ type = 'textfield', name = 'new_group_description', text = 'Description' })
    textfield.style.minimal_width = 400
    local b = t.add({ type = 'button', name = 'create_new_group', caption = { 'gui.create' } })
    b.style.minimal_width = 150
    b.style.font = 'default-bold'
end

local build_group_gui_token = Token.register(build_group_gui)

local function refresh_gui()
    local players = game.connected_players
    for i = 1, #players do
        local player = players[i]
        local frame = Gui.get_player_active_frame(player)
        if frame then
            if frame.frame2 and frame.frame2.valid then
                local new_group_name = frame.frame2.group_table.new_group_name.text
                local new_group_description = frame.frame2.group_table.new_group_description.text

                if new_group_name:len() > 30 then
                    new_group_name = ''
                end

                if new_group_description:len() > 60 then
                    new_group_description = ''
                end

                local data = { player = player, frame = frame }
                build_group_gui(data)

                frame = Gui.get_player_active_frame(player)
                frame.frame2.group_table.new_group_name.text = new_group_name
                frame.frame2.group_table.new_group_description.text = new_group_description
            end
        end
    end
end

local function on_player_joined_game(event)
    local player = game.players[event.player_index]

    if not this.player_group[player.name] then
        this.player_group[player.name] = '[Group]'
    end

    if not this.join_spam_protection[player.name] then
        this.join_spam_protection[player.name] = game.tick
    end
end

local function on_gui_text_changed(event)
    local element = event.element
    if not element or not element.valid then
        return
    end

    local player = game.get_player(event.player_index)

    local name = element.name
    local text = element.text

    if name == 'new_group_name' then
        local is_spamming = SpamProtection.is_spamming(player, nil, 'Groups new_group_name')
        if is_spamming then
            return
        end
        if text:len() > 30 then
            element.text = ''
        end
    end
    if name == 'new_group_description' then
        local is_spamming = SpamProtection.is_spamming(player, nil, 'Groups new_group_desc')
        if is_spamming then
            return
        end
        if text:len() > 60 then
            element.text = ''
        end
    end
end

local function alphanumeric(str)
    return (string.match(str, '[^%w%s%p]') ~= nil)
end

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

    local name = element.name

    local frame = Gui.get_player_active_frame(player)
    if not frame then
        return
    end
    if frame.name ~= 'Groups' then
        return
    end

    if name == 'create_new_group' then
        local is_spamming = SpamProtection.is_spamming(player, nil, 'Group Click')

        if is_spamming then
            return
        end

        local new_group_name = frame.frame2.group_table.new_group_name.text
        local new_group_description = frame.frame2.group_table.new_group_description.text
        if new_group_name ~= '' and new_group_name ~= 'Name' and new_group_description ~= 'Description' then
            if this.alphanumeric then
                if alphanumeric(new_group_name) then
                    player.print('Group name is not valid.', { color = { r = 0.90, g = 0.0, b = 0.0 } })
                    return
                end

                if alphanumeric(new_group_description) then
                    player.print('Group description is not valid.', { color = { r = 0.90, g = 0.0, b = 0.0 } })
                    return
                end
            end

            if string.len(new_group_name) > 64 then
                player.print('Group name is too long. 64 characters maximum.', { color = { r = 0.90, g = 0.0, b = 0.0 } })
                return
            end

            if string.len(new_group_description) > 128 then
                player.print('Description is too long. 128 characters maximum.', { color = { r = 0.90, g = 0.0, b = 0.0 } })
                return
            end

            this.tag_groups[new_group_name] =
            {
                name = new_group_name,
                description = new_group_description,
                founder = player.name
            }
            local color =
            {
                r = player.color.r * 0.7 + 0.3,
                g = player.color.g * 0.7 + 0.3,
                b = player.color.b * 0.7 + 0.3,
                a = 1
            }
            game.print(player.name .. ' has founded a new group!', color)
            game.print('>> ' .. new_group_name, { r = 0.98, g = 0.66, b = 0.22 })
            game.print(new_group_description, { r = 0.85, g = 0.85, b = 0.85 })

            frame.frame2.group_table.new_group_name.text = 'Name'
            frame.frame2.group_table.new_group_description.text = 'Description'
            refresh_gui()
            return
        end
    end

    local p = element.parent
    if p then
        p = p.parent
    end
    if p then
        if p.name == 'groups_table' then
            local is_spamming = SpamProtection.is_spamming(player, nil, 'Group Click')

            if is_spamming then
                return
            end

            local caption = element.caption and element.caption[1]

            if element.type == 'button' and caption == 'gui.join' then
                this.player_group[player.name] = element.parent.name
                local str = '[' .. element.parent.name
                str = str .. ']'
                player.tag = str
                if game.tick - this.join_spam_protection[player.name] > 600 then
                    local color =
                    {
                        r = player.color.r * 0.7 + 0.3,
                        g = player.color.g * 0.7 + 0.3,
                        b = player.color.b * 0.7 + 0.3,
                        a = 1
                    }
                    game.print(player.name .. ' has joined group "' .. element.parent.name .. '"', color)
                    this.join_spam_protection[player.name] = game.tick
                end
                refresh_gui()
                return
            end

            if element.type == 'button' and caption == 'gui.delete' then
                if this.tag_groups[element.parent.name] and this.tag_groups[element.parent.name].static then
                    player.print('Static groups cannot be deleted.', { color = Color.warning })
                    return
                end
                for _, players in pairs(game.players) do
                    if this.player_group[players.name] then
                        if this.player_group[players.name] == element.parent.name then
                            this.player_group[players.name] = '[Group]'
                            players.tag = ''
                        end
                    end
                end
                game.print(player.name .. ' deleted group "' .. element.parent.name .. '"')
                this.tag_groups[element.parent.name] = nil
                refresh_gui()
                return
            end

            if element.type == 'button' and caption == 'gui.leave' then
                this.player_group[player.name] = '[Group]'
                player.tag = ''
                refresh_gui()
                return
            end
        end
    end
end

function Public.alphanumeric_only(value)
    if value then
        this.alphanumeric = value
    else
        this.alphanumeric = false
    end
end

function Public.reset_groups()
    local players = game.connected_players
    for i = 1, #players do
        local player = players[i]
        this.player_group[player.name] = '[Group]'
        this.join_spam_protection[player.name] = game.tick
    end
    if this.tag_groups and next(this.tag_groups) then
        for name, data in pairs(this.tag_groups) do
            if not data.static then
                this.tag_groups[name] = nil
            end
        end
    end
end

Gui.add_tab_to_gui({ name = module_name, caption = 'Groups', id = build_group_gui_token, admin = false })

Gui.on_click(
    module_name,
    function (event)
        local player = event.player
        Gui.reload_active_tab(player, nil, 'Groups')
    end
)

Event.add(defines.events.on_gui_click, on_gui_click)
Event.add(defines.events.on_player_joined_game, on_player_joined_game)
Event.add(defines.events.on_gui_text_changed, on_gui_text_changed)

return Public
