vipWindow = nil
vipButton = nil
addVipWindow = nil
editVipWindow = nil
vipInfo = {}

-- @ Groups
addGroupWindow = nil
vipGroups = {}
maxVipGroups = 5
editableGroupCount = 1
-- @

local globalSettings = {
    showGrouped = false,
    hideOfflineVips = false,
    vipSortOrder = {}
}

controllerVip = Controller:new()
function controllerVip:onInit()

    Keybind.new("Windows", "Show/hide VIP list", "Ctrl+P", "")
    Keybind.bind("Windows", "Show/hide VIP list", {
      {
        type = KEY_DOWN,
        callback = toggle,
      }
    })
    vipButton = modules.game_mainpanel.addToggleButton('vipListButton', tr('VIP List') .. ' (Ctrl+P)',
                                                                '/images/options/button_vip', toggle, false, 3)
    vipWindow = g_ui.loadUI('viplist')
    controllerVip:registerEvents(g_game, {
        onAddVip = onAddVip,
        onVipStateChange = onVipStateChange,
        onVipGroupChange = onVipGroupChange
    })

    local settings = g_settings.getNode('VipList')
    if settings then
       globalSettings.showGrouped = settings['Grouped'] or false
       globalSettings.hideOfflineVips = settings['OfflineVips'] or false
       globalSettings.vipSortOrder = {}
       if settings['vipSortOrder'] then
           for k, v in pairs(settings['vipSortOrder']) do
               globalSettings.vipSortOrder[tonumber(k)] = v
           end
       end
    end
    refresh()
    vipWindow:setup()
    if g_game.isOnline() then
        vipWindow:setupOnStart()
    end
end

function controllerVip:onTerminate()
    Keybind.delete("Windows", "Show/hide VIP list")
    local ArrayWidgets = {addVipWindow, editVipWindow, vipWindow, vipButton, addGroupWindow}
    for _, widget in ipairs(ArrayWidgets) do
        if widget ~= nil or widget then
            widget:destroy()
            widget = nil
        end
    end
    vipInfo = {}
end

function controllerVip:onGameStart()
    if not g_game.getFeature(GameAdditionalVipInfo) then
        loadVipInfo()
    end
    if not g_game.getFeature(GameVipGroups) then
        vipWindow.miniborder:hide()
        globalSettings.showGrouped = false
    else
        vipInfo = {}
    end
    vipWindow:setupOnStart() -- load character window configuration
    refresh()
    vipButton:setOn(vipButton:isOn())
end

function controllerVip:onGameEnd()
    local settings = {}
    settings['Grouped'] = globalSettings.showGrouped or false
    settings['OfflineVips'] = globalSettings.hideOfflineVips or false
    settings['vipSortOrder'] = globalSettings.vipSortOrder or {}
    if not g_game.getFeature(GameVipGroups) then
        saveVipInfo()
    end
    g_settings.mergeNode('VipList', settings)

    vipWindow:setParent(nil, true)
    clear()
    if editVipWindow then
        editVipWindow:destroy()
        editVipWindow = nil
    end
    if addGroupWindow then
        addGroupWindow:destroy()
        addGroupWindow = nil
    end
end

function loadVipInfo()
    local settings = g_settings.getNode('VipList')
    if not settings then
        vipInfo = {}
        return
    end
    vipInfo = settings['VipInfo'] or {}
end

function saveVipInfo()
    if not g_game.getFeature(GameAdditionalVipInfo) then
        if not g_settings.getNode('VipList') then
            g_settings.setNode('VipList', {})
        end
        local settings = {}
        settings['VipInfo'] = vipInfo
        g_settings.mergeNode('VipList', settings)
    end
end

function refresh()
    clear()
    for id, vip in pairs(g_game.getVips()) do
        onAddVip(id, unpack(vip))
    end

    vipWindow:setContentMinimumHeight(38)
end

function clear()
    local vipList = vipWindow:getChildById('contentsPanel')
    vipList:destroyChildren()
    if not g_game.isOnline() then
        if g_game.getFeature(GameAdditionalVipInfo) then
            vipInfo = {}
            vipGroups = {}
        end
    end
    if editVipWindow ~= nil or editVipWindow then
        editVipWindow:hide()
    end
end

function toggle()
    if vipButton:isOn() then
        vipWindow:close()
        vipButton:setOn(false)
    else
        if not vipWindow:getParent() then
            local panel = modules.game_interface.findContentPanelAvailable(vipWindow, vipWindow:getMinimumHeight())
            if not panel then
                return
            end

            panel:addChild(vipWindow)
        end
        vipWindow:open()
        vipButton:setOn(true)
    end
end

function onMiniWindowOpen()
    vipButton:setOn(true)
end

function onMiniWindowClose()
    vipButton:setOn(false)
end

function createAddWindow()
    if not addVipWindow then
        addVipWindow = g_ui.displayUI('addvip')
    end
end

function createEditWindow(widget)
    if editVipWindow then
        return
    end

    editVipWindow = g_ui.displayUI('editvip')

    local name = widget:getText()
    local id = widget:getId():sub(4)
    -- @Groups
    if not g_game.getFeature(GameVipGroups) then
        editVipWindow:setText('Edit VIP')
        editVipWindow:setSize('272 170')
    else
        editVipWindow:setHeight(350 + 2 * (#vipGroups))
    end
    editVipWindow.groups:destroyChildren()
    table.sort(vipGroups, function(a, b)
        return a[1] > b[1]
    end)
    for _, group in ipairs(vipGroups) do
        local groupBox = g_ui.createWidget('VipGroupBox', editVipWindow.groups)
        groupBox:setText(group[2])
        groupBox.id = group[1]
        groupBox:setChecked(checkPlayerGroup(name, group[1]))
    end
    -- @
    local okButton = editVipWindow:getChildById('buttonOK')
    local cancelButton = editVipWindow:getChildById('buttonCancel')

    local nameLabel = editVipWindow:getChildById('nameLabel')
    nameLabel:setText(name)

    local descriptionText = editVipWindow:getChildById('descriptionText')
    descriptionText:appendText(widget:getTooltip())

    local notifyCheckBox = editVipWindow:recursiveGetChildById('checkBoxNotify')
    notifyCheckBox:setChecked(widget.notifyLogin)

    local iconRadioGroup = UIRadioGroup.create()
    for i = VipIconFirst, VipIconLast do
        iconRadioGroup:addWidget(editVipWindow:recursiveGetChildById('icon' .. i))
    end
    if not widget.iconId then
        widget.iconId = 0
    end
    iconRadioGroup:selectWidget(editVipWindow:recursiveGetChildById('icon' .. widget.iconId))

    local cancelFunction = function()
        editVipWindow:destroy()
        iconRadioGroup:destroy()
        editVipWindow = nil
    end

    local saveFunction = function()
        local vipList = vipWindow:getChildById('contentsPanel')
        if not widget then
            cancelFunction()
            return
        end

        local name = widget:getText()
        local state = widget.vipState
        local description = descriptionText:getText()
        local iconId = tonumber(iconRadioGroup:getSelectedWidget():getId():sub(5))
        if not iconId then
            iconId = 0
        end
        local notify = notifyCheckBox:isChecked()
        local groups = {}
        for _, child in pairs(editVipWindow.groups:getChildren()) do
            if child:isChecked() then
                table.insert(groups, child.id)
            end
        end
        if g_game.getFeature(GameAdditionalVipInfo) then
            g_game.editVip(id, description, iconId, notify, groups)
        else
            if notify ~= false or #description > 0 or iconId > 0 then
                vipInfo[name] = {
                    description = description,
                    iconId = iconId,
                    notifyLogin = notify
                }
            else
                vipInfo[name] = nil
            end
        end

        widget:destroy()
        onAddVip(id, name, state, description, iconId, notify, groups, nil)
        
        if iconRadioGroup then
            iconRadioGroup:destroy()
            iconRadioGroup = nil
        end
        if editVipWindow then
            editVipWindow:destroy()
            editVipWindow = nil
        end
    end

    cancelButton.onClick = cancelFunction
    okButton.onClick = saveFunction

    editVipWindow.onEscape = cancelFunction
    editVipWindow.onEnter = saveFunction
end

function destroyAddWindow()
    if addVipWindow then
        addVipWindow:destroy()
        addVipWindow = nil
    end
end

function addVip()
    g_game.addVip(addVipWindow:getChildById('name'):getText())
    destroyAddWindow()
end

function removeVip(widgetOrName)
    if not widgetOrName then
        return
    end

    local widget
    local vipList = vipWindow:getChildById('contentsPanel')
    if type(widgetOrName) == 'string' then
        local entries = vipList:getChildren()
        for i = 1, #entries do
            if entries[i]:getText():lower() == widgetOrName:lower() then
                widget = entries[i]
                break
            end
        end
        if not widget then
            return
        end
    else
        widget = widgetOrName
    end

    if widget then
        local id = widget:getId():sub(4)
        local name = widget:getText()
        g_game.removeVip(id)
        if g_game.getFeature(GameVipGroups) and  globalSettings.showGrouped then
            widget:getParent():removeChild(widget)
        else
            vipList:removeChild(widget)
        end
        if vipInfo[name:lower()] and g_game.getFeature(GameAdditionalVipInfo) then
            vipInfo[name:lower()] = nil
        end
        refresh()
    end
end

function hideOffline(state)
    globalSettings.hideOfflineVips = state
    refresh()
end

function isHiddingOffline()
    local settings = g_settings.getNode('VipList')
    if not settings then
        return false
    end
    return settings['hideOffline']
end

function getSortedBy()
    if g_game.getFeature(GameAdditionalVipInfo) then 
        if not globalSettings.vipSortOrder then
            return ''
        end
        return globalSettings.vipSortOrder[1]
    else
        local settings = g_settings.getNode('VipList')
        if not settings or not settings['sortedBy'] then
            return 'status'
        end
        return settings['sortedBy']
    end
end

function sortBy(state)
    if not g_game.getFeature(GameAdditionalVipInfo) then
        local settings = {}
        settings['sortedBy'] = state
        g_settings.mergeNode('VipList', settings)
    end
    for i, v in ipairs(globalSettings.vipSortOrder) do
        if v == state then
            table.remove(globalSettings.vipSortOrder, i)
            break
        end
    end
    table.insert(globalSettings.vipSortOrder, 1, state)
    local contentPanel = vipWindow:getChildById('contentsPanel')
    if g_game.getFeature(GameVipGroups) and globalSettings.showGrouped then
        for _, groupWidget in ipairs(contentPanel:getChildren()) do
            if groupWidget:getId():find('group-') == 1 then
                local groupPanel = groupWidget:getChildById('panel')
                local children = groupPanel:getChildren()
                table.sort(children, compareVips)
                for i, child in ipairs(children) do
                    groupPanel:moveChildToIndex(child, i)
                end
            end
        end
    else
        local children = contentPanel:getChildren()
        table.sort(children, compareVips)
        for i, child in ipairs(children) do
            contentPanel:moveChildToIndex(child, i)
        end
    end
    contentPanel:updateLayout()
end

function compareVips(a, b)
    for _, orderType in ipairs(globalSettings.vipSortOrder) do
        if orderType == 'byState' or orderType == 'status' then
            if a.vipState ~= nil and b.vipState ~= nil then
                if a.vipState ~= b.vipState then
                    return a.vipState > b.vipState
                end
            end
        elseif orderType == 'byName' or orderType == 'name' then
            if a:getText() ~= nil and b:getText() ~= nil then
                if a:getText():lower() ~= b:getText():lower() then
                    return a:getText():lower() < b:getText():lower()
                end
            end
        elseif orderType == 'byType' or orderType == 'type' then
            if a.iconId ~= nil and b.iconId ~= nil then
                if a.iconId ~= b.iconId then
                    return a.iconId > b.iconId
                end
            end
        end
    end
    return false
end

function onAddVip(id, name, state, description, iconId, notify, groupID, bool)
    if g_game.getFeature(GameAdditionalVipInfo) then
        vipInfo[name] = {
            playerId = id,
            playerName = name,
            vipState = state,
            vipDesc = description,
            icon = iconId,
            hasNotify = notify,
            vipGroups = groupID
        }
        if globalSettings.showGrouped then
            showGroups()
            return
        end
    end
    local vipList = vipWindow:getChildById('contentsPanel')
    local childrenContentPanel = vipList:getChildCount()

    if bool then
        for i = 1, childrenContentPanel do
            local vipName = vipList:getChildByIndex(i)

            if vipName:getText() == name then
                setVipState(vipName, state)
                if state == VipState.Online then
                    vipName:setVisible(true)
                elseif state == VipState.Offline and globalSettings.hideOfflineVips then
                    vipName:setVisible(false)
                end
                return
            end
        end
    end

    for j = 1, childrenContentPanel do
        if vipList:getChildByIndex(j):getText() == name then
            return
        end
    end

    local label = g_ui.createWidget('VipListLabel')
    label.onMousePress = onVipListLabelMousePress
    label:setId('vip' .. id)
    label:setText(name)

    if not g_game.getFeature(GameAdditionalVipInfo) then
        local tmpVipInfo = vipInfo[name]
        label.iconId = 0
        label.notifyLogin = false
        if tmpVipInfo then
            if tmpVipInfo.iconId then
                label:setImageClip(torect((tmpVipInfo.iconId * 12) .. ' 0 12 12'))
                label.iconId = tmpVipInfo.iconId
            end
            if tmpVipInfo.description then
                label:setTooltip(tmpVipInfo.description)
            end
            label.notifyLogin = tmpVipInfo.notifyLogin or false
        end
    else
        label:setTooltip(description)
        label:setImageClip(torect((iconId * 12) .. ' 0 12 12'))
        label.iconId = iconId
        label.notifyLogin = notify
    end

    setVipState(label, state)

    label.vipState = state

    label:setPhantom(false)
    connect(label, {
        onDoubleClick = function()
            g_game.openPrivateChannel(label:getText())
            return true
        end
    })

    if state == VipState.Offline and globalSettings.hideOfflineVips then
        label:setVisible(false)
    end

    local nameLower = name:lower()
    local childrenCount = vipList:getChildCount()

    for i = 1, childrenCount do
        local child = vipList:getChildByIndex(i)
        if (state == VipState.Online and child.vipState ~= VipState.Online and getSortedBy() == 'status') or
            (label.iconId > child.iconId and getSortedBy() == 'type') then
            vipList:insertChild(i, label)
            return
        end

        if (((state ~= VipState.Online and child.vipState ~= VipState.Online) or
            (state == VipState.Online and child.vipState == VipState.Online)) and getSortedBy() == 'status') or
            (label.iconId == child.iconId and getSortedBy() == 'type') or getSortedBy() == 'name' then

            local childText = child:getText():lower()
            local length = math.min(childText:len(), nameLower:len())

            for j = 1, length do
                if nameLower:byte(j) < childText:byte(j) then
                    vipList:insertChild(i, label)
                    return
                elseif nameLower:byte(j) > childText:byte(j) then
                    break
                elseif j == nameLower:len() then -- We are at the end of nameLower, and its shorter than childText, thus insert before
                    vipList:insertChild(i, label)
                    return
                end
            end
        end
    end

    vipList:insertChild(childrenCount + 1, label)
end

function onVipStateChange(id, state, groupID)
    if g_game.getFeature(GameVipGroups) and globalSettings.showGrouped then
        local name, description, iconId, notify = searchPlayerbyId(id)
        onAddVip(id, name, state, description, iconId, notify, groupID, true)
    else
        local vipList = vipWindow:getChildById('contentsPanel')
        local label = vipList:getChildById('vip' .. id)
        local name = label:getText()
        local description = label:getTooltip()
        local iconId = label.iconId
        local notify = label.notifyLogin
        label:destroy()
    
        onAddVip(id, name, state, description, iconId, notify)
    end

    if notify and state ~= VipState.Pending then
        modules.game_textmessage.displayFailureMessage(state == VipState.Online and tr('%s has logged in.', name) or
                                                           tr('%s has logged out.', name))
    end
end

function onVipListMousePress(widget, mousePos, mouseButton)
    if mouseButton ~= MouseRightButton then
        return
    end

    local vipList = vipWindow:getChildById('contentsPanel')

    local menu = g_ui.createWidget('PopupMenu')
    menu:setGameMenu(true)
    menu:addOption(tr('Add new VIP'), function()
        createAddWindow()
    end)

    menu:addSeparator()
    if not globalSettings.hideOfflineVips then
        menu:addOption(tr('Hide Offline'), function()
            hideOffline(true)
        end)
    else
        menu:addOption(tr('Show Offline'), function()
            hideOffline(false)
        end)
    end

    if g_game.getFeature(GameVipGroups) then
        menu:addOption(tr('Add new group'), function()
            createAddGroupWindow()
        end)
    end

    menu:addSeparator()
    if not (getSortedBy() == 'name') then
        menu:addOption(tr('Sort by name'), function()
            sortBy('name')
        end)
    end

    if not (getSortedBy() == 'status') then
        menu:addOption(tr('Sort by status'), function()
            sortBy('status')
        end)
    end

    if not (getSortedBy() == 'type') then
        menu:addOption(tr('Sort by type'), function()
            sortBy('type')
        end)
    end
    if g_game.getFeature(GameVipGroups) then
        if not globalSettings.showGrouped then
            menu:addOption(tr('Show groups'), function()
                globalSettings.showGrouped = true
                showGroups()
            end)
        else
            menu:addOption(tr('Hide groups'), function()
                globalSettings.showGrouped = false
                refresh()
            end)
        end
    end
    menu:display(mousePos)

    return true
end

function onVipListLabelMousePress(widget, mousePos, mouseButton)
    if mouseButton ~= MouseRightButton then
        return
    end

    local vipList = vipWindow:getChildById('contentsPanel')
    local isGroup = string.find(widget:getId(), 'group')
    local isVip = string.find(widget:getId(), 'vip')

    local menu = g_ui.createWidget('PopupMenu')
    menu:setGameMenu(true)
    if not isGroup then
        menu:addOption(tr('Edit %s', widget:getText()), function()
            if widget then
                createEditWindow(widget)
            end
        end)
        menu:addOption(tr('Remove %s', widget:getText()), function()
            if widget then
                removeVip(widget)
            end
        end)

        if isVip and widget.vipState == VipState.Online then
            menu:addOption(tr('Message to %s', widget:getText()), function()
                g_game.openPrivateChannel(widget:getText())
            end)
        end
    end

    menu:addOption(tr('Add new VIP'), function()
        createAddWindow()
    end)
    if modules.game_console.getOwnPrivateTab() then
        menu:addSeparator()
        menu:addOption(tr('Invite to private chat'), function()
            g_game.inviteToOwnChannel(widget:getText())
        end)
        menu:addOption(tr('Exclude from private chat'), function()
            g_game.excludeFromOwnChannel(widget:getText())
        end)
    end
    if g_game.getFeature(GameVipGroups) then
        menu:addSeparator()
        if isGroup and widget.editable then
            local groupName = widget:getTooltip() and widget:getTooltip() or widget.group:getText()

            menu:addOption(tr('Edit group %s', groupName), function()
                createEditGroupWindow(groupName, widget.groupId)
            end)
            menu:addOption(tr('Remove group %s', groupName), function()
                g_game.editVipGroups(3, widget.groupId, '')
            end)
        end

        menu:addOption(tr('Add new group'), function()
            createAddGroupWindow()
        end)
    end

    menu:addSeparator()
    if not globalSettings.hideOfflineVips then
        menu:addOption(tr('Hide Offline'), function()
            hideOffline(true)
        end)
    else
        menu:addOption(tr('Show Offline'), function()
            hideOffline(false)
        end)
    end

    menu:addOption(tr('Sort by name'), function()
        sortBy('byName')
    end)
    menu:addOption(tr('Sort by type'), function()
        sortBy('byType')
    end)
    menu:addOption(tr('Sort by status'), function()
        sortBy('byState')
    end)

    if g_game.getFeature(GameVipGroups) then
        if not globalSettings.showGrouped then
            menu:addOption(tr('Show groups'), function()
                globalSettings.showGrouped = true
                showGroups()
            end)
        else
            menu:addOption(tr('Hide groups'), function()
                globalSettings.showGrouped = false
                refresh()
            end)
        end
    end

    if not isGroup then
        menu:addSeparator()
        menu:addOption(tr('Copy Name'), function()
            g_window.setClipboardText(widget:getText())
        end)
    end

    menu:display(mousePos)

    return true
end

-- @ groups
function onVipGroupChange(vipGroupsArray, groupsAmountLeft)
    vipGroups = vipGroupsArray
    maxVipGroups = groupsAmountLeft
    editableGroupCount = groupsAmountLeft
    refresh()
end

function createAddGroupWindow()
    if maxVipGroups < 1 then
        displayInfoBox(tr('Maximum of User-Created Groups Reached'),
            'You have already reached the maximum of groups you can create yourself.')
        return
    end
    if not addGroupWindow then
        addGroupWindow = g_ui.displayUI('addgroup')
        addGroupWindow:setText(tr(addGroupWindow:getText(), maxVipGroups))
    end
end

function createEditGroupWindow(groupName, groupId)
    if not addGroupWindow then
        addGroupWindow = g_ui.displayUI('addgroup')
        addGroupWindow:setText('Edit VIP group')
        addGroupWindow.header:setText('Please enter a group name:')
        addGroupWindow.name:setText(groupName)
        function addGroupWindow.onEnter()
            editGroup(groupId)
        end
        function addGroupWindow.okButton.onClick()
            editGroup(groupId)
        end
    end
end

function addGroup()
    g_game.editVipGroups(1, 0, addGroupWindow:getChildById('name'):getText())
    if addGroupWindow then
        addGroupWindow:destroy()
        addGroupWindow = nil
    end
end

function editGroup(groupId)
    g_game.editVipGroups(2, groupId, addGroupWindow:getChildById('name'):getText())
    if addGroupWindow then
        addGroupWindow:destroy()
        addGroupWindow = nil
    end
end

function getPlayersByGroup(groupId)
    local playerFromGroupID = {}
    for id, data in pairs(vipInfo) do
        if data.vipGroups and table.contains(data.vipGroups, groupId) then
            table.insert(playerFromGroupID, data)
        end
    end
    return playerFromGroupID
end

function getPlayersNoGroup()
    local playerFromNoGroup = {}
    for id, data in pairs(vipInfo) do
        if not data.vipGroups or #data.vipGroups == 0 then
            table.insert(playerFromNoGroup, data)
        end
    end
    return playerFromNoGroup
end

function showGroups(sortType)
    local contentsPanel = vipWindow:getChildById('contentsPanel')
    contentsPanel:destroyChildren()

    table.sort(vipGroups, function(a, b)
        return a[2] < b[2]
    end)

    local function createPlayerWidget(group, player)
        if not player.playerId then -- prevent error cache config.otml
            return
        end
        local playerWidget = g_ui.createWidget('VipListLabel', group.panel)
        playerWidget.onMousePress = onVipListLabelMousePress
        playerWidget:setId('vip' .. player.playerId)
        playerWidget:setText(player.playerName)
        playerWidget:setImageClip(torect(player.icon * 12 .. ' 0 12 12'))
        playerWidget.iconId = player.icon
        playerWidget.notifyLogin = player.hasNotify
        playerWidget.vipState = player.vipState

        setVipState(playerWidget, player.vipState)
        playerWidget:setPhantom(false)

        connect(playerWidget, {
            onDoubleClick = function()
                g_game.openPrivateChannel(playerWidget:getText())
                return true
            end
        })
        return playerWidget
    end

    for _, group in ipairs(vipGroups) do
        local groupId, groupName, isEditable = group[1], group[2], group[3]
        local playersInGroup = getPlayersByGroup(groupId)

        if #playersInGroup > 0 then
            local groupWidget = g_ui.createWidget('VipGroupList', contentsPanel)
            groupWidget.group:setText(groupName)
            if #groupName >= 18 then
                groupWidget:setTooltip(groupName)
            end
            groupWidget:setId('group-' .. groupId)
            groupWidget.onMousePress = onVipListLabelMousePress
            groupWidget.groupId = groupId
            groupWidget.editable = isEditable

            local visiblePlayers = 0
            for _, player in ipairs(playersInGroup) do
                if player.icon == nil then
                    break
                end
                local playerWidget = createPlayerWidget(groupWidget, player)
                if player.vipState == VipState.Offline and globalSettings.hideOfflineVips then
                    playerWidget:setVisible(false)
                else
                    visiblePlayers = visiblePlayers + 1
                end
            end

            if visiblePlayers == 0 then
                groupWidget:hide()
            else
                groupWidget:setSize('156 ' .. (16 * visiblePlayers + groupWidget:getHeight()))
            end
        end
    end

    local playersNoGroup = getPlayersNoGroup()
    if #playersNoGroup > 0 then
        local noGroupWidget = g_ui.createWidget('VipGroupList', contentsPanel)
        noGroupWidget.onMousePress = onVipListLabelMousePress
        noGroupWidget:setId('group')

        local visiblePlayers = 0
        for _, player in ipairs(playersNoGroup) do
            local playerWidget = createPlayerWidget(noGroupWidget, player)
            if player.vipState == VipState.Offline and globalSettings.hideOfflineVips then
                playerWidget:setVisible(false)
            else
                visiblePlayers = visiblePlayers + 1
            end
        end

        if visiblePlayers == 0 then
            noGroupWidget:hide()
        else
            noGroupWidget:setSize('156 ' .. (15 * visiblePlayers + noGroupWidget:getHeight()))
        end
    end
    -- contentsPanel:updateLayout()
    sortBy(getSortedBy())
end

function setVipState(widget, vipState)
    if vipState == VipState.Online then
        widget:setColor('#5ff75f')
    end
    if vipState == VipState.Pending then
        widget:setColor('#ffca38')
    elseif vipState == VipState.Offline then
        widget:setColor('#f75f5f')
    elseif vipState == VipState.Training then
        widget:setColor('#f75f5f')
    end
end

function getPlayerGroups(playerName)
    local playerGroups = {}
    for id, vip in pairs(g_game.getVips()) do
        if vip[1] == playerName then
            playerGroups = vip[6]
            break
        end
    end
    return playerGroups
end

function checkPlayerGroup(playerName, groupId)
    local vips = g_game.getVips()
    for _, vip in pairs(vips) do
        if vip[1] == playerName then
            local playerGroups = vip[6]
            for _, playerGroup in ipairs(playerGroups) do
                if playerGroup == groupId then
                    return true
                end
            end
            return false
        end
    end
    return false
end

function searchPlayerbyId(playerId)
    for key, idCache in pairs(vipInfo) do
        if tonumber(idCache.playerId) == playerId then
            local name = idCache.playerName
            local description = idCache.vipDesc
            local iconId = idCache.icon
            local notify = idCache.hasNotify

            return name, description, iconId, notify
        end
    end
end
