/*
===========================================================================
Copyright (C) 2015 the OpenMoHAA team

This file is part of OpenMoHAA source code.

OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

OpenMoHAA source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

#include "cl_ui.h"
#include "../gamespy/goaceng.h"
#include "../gamespy/sv_gamespy.h"
#include "../gamespy/common/gsPlatformSocket.h"

Event EV_FAKKServerList_Connect(
    "connect",
    EV_DEFAULT,
    NULL,
    NULL,
    "Connect to the specified server"
);

Event EV_FAKKServerList_RefreshServerList(
    "refreshserverlist",
    EV_DEFAULT,
    NULL,
    NULL,
    "Refresh the serverlist"
);

Event EV_FAKKServerList_RefreshLANServerList(
    "refreshlanserverlist",
    EV_DEFAULT,
    NULL,
    NULL,
    "Refresh the LAN serverlist"
);

Event EV_FAKKServerList_CancelRefresh(
    "cancelrefresh",
    EV_DEFAULT,
    NULL,
    NULL,
    "Cancel serverlist Refresh"
);

Event EV_FAKKServerList_LANListing(
    "lanlisting",
    EV_DEFAULT,
    NULL,
    NULL,
    "Makes this server list to LAN stuff when there's a choice between Internet & LAN servers"
);

Event EV_FAKKServerList_UpdateServer(
    "updateserver",
    EV_DEFAULT,
    NULL,
    NULL,
    "Update the selected server"
);

struct ServerListInstance {
    int iServerType;
    UIFAKKServerList* serverList;
};

class FAKKServerListItem : public UIListCtrlItem {
    UIFAKKServerList* m_parent; // Added in OPM
    str m_strings[6];
    str m_sVersion;
    bool m_bDifferentVersion;
    bool m_bFavorite;
    bool m_bQueryDone;
    bool m_bQueryFailed;
    int m_iNumPlayers;

public:
    str m_sIP;
    unsigned int m_uiRealIP;
    int m_iPort;
    int m_iGameSpyPort;

public:
    FAKKServerListItem(UIFAKKServerList* parent, str string1, str string2, str string3, str string4, str string5, str string6, str ver);

    griditemtype_t getListItemType(int index) const override;
    int getListItemValue(int i) const override;
    str getListItemString(int i) const override;
    void setListItemString(int i, str sNewString);
    void DrawListItem(int iColumn, const UIRect2D& drawRect, bool bSelected, UIFont* pFont) override;
    qboolean IsHeaderEntry() const override;
    bool IsQueried() const;
    void SetQueried(bool bIsQueried);
    bool IfQueryFailed() const;
    void SetQueryFailed(bool bFailed);
    void SetNumPlayers(int iNum);
    bool IsFavorite() const;
    void SetFavorite(bool bIsFavorite);
    str GetListItemVersion() const;
    void SetListItemVersion(str sNewVer);
    void SetDifferentVersion(bool bIsDifferentVersion);
    bool IsDifferentVersion() const;
};

static int g_iTotalNumPlayers;
qboolean g_bNumericSort = qfalse;
qboolean g_bReverseSort = qfalse;
qboolean g_NeedAdditionalLANSearch = qfalse;
qboolean g_bDoneUpdating[2];
ServerListInstance g_ServerListInst[2];

void UpdateServerListCallBack(GServerList serverlist, int msg, void* instance, void* param1, void* param2);
static void AddFilter(char* filter, const char* value);

FAKKServerListItem::FAKKServerListItem(UIFAKKServerList* parent, str string1, str string2, str string3, str string4, str string5, str string6, str ver)
{
    m_parent = parent;
    m_strings[0] = string1;
    m_strings[1] = string2;
    m_strings[2] = string3;
    m_strings[3] = string4;
    m_strings[4] = string5;
    m_strings[5] = string6;
    m_sVersion = ver;

    m_bFavorite = false;
    m_bQueryDone = false;
    m_bQueryFailed = false;
    m_iNumPlayers = 0;
}

griditemtype_t FAKKServerListItem::getListItemType(int index) const
{
    return griditemtype_t::TYPE_OWNERDRAW;
}

int FAKKServerListItem::getListItemValue(int i) const
{
    return atoi(m_strings[i]);
}

str FAKKServerListItem::getListItemString(int i) const
{
    return m_strings[i];
}

void FAKKServerListItem::setListItemString(int i, str sNewString)
{
    m_strings[i] = sNewString;
}

void FAKKServerListItem::DrawListItem(int iColumn, const UIRect2D& drawRect, bool bSelected, UIFont* pFont)
{
    static cvar_t *pColoringType = Cvar_Get("cl_browserdetailedcolors", "0", CVAR_ARCHIVE);
    UIRect2D newRect = drawRect;
    vec2_t virtualScale = { 1.f, 1.f };

    /*
    if (m_parent->isVirtual()) {
        virtualScale[0] = uid.vidWidth / 640.f;
        virtualScale[1] = uid.vidHeight / 480.f;
        newRect.size.width *= uid.vidWidth / 1024;
        newRect.size.height *= uid.vidHeight / 768;
    }
    */

    virtualScale[0] = m_parent->getHighResScale()[0];
    virtualScale[1] = m_parent->getHighResScale()[1];
    
    if (!pColoringType->integer) {
        if (IfQueryFailed() || (IsDifferentVersion() && IsQueried())) {
            if (bSelected) {
                DrawBox(newRect, UColor(0.2f, 0.0f, 0.0f), 1.0);
                pFont->setColor(UColor(0.9f, 0.0f, 0.0f));
            } else {
                DrawBox(newRect, UColor(0.1f, 0.0f, 0.0f), 1.0);
                pFont->setColor(UColor(0.55f, 0.0f, 0.0f));
            }
        } else if (IsQueried()) {
            if (bSelected) {
                DrawBox(newRect, UColor(0.2f, 0.18f, 0.015f), 1.0);
                pFont->setColor(UColor(0.9f, 0.8f, 0.6f));
            } else {
                DrawBox(newRect, UColor(0.02f, 0.07f, 0.004f), 1.0);
                pFont->setColor(UHudColor);
            }
        } else {
            if (bSelected) {
                DrawBox(newRect, UColor(0.15f, 0.18f, 0.18f), 1.0);
                pFont->setColor(UColor(0.6f, 0.7f, 0.8f));
            } else {
                DrawBox(newRect, UColor(0.005f, 0.07f, 0.02f), 1.0);
                pFont->setColor(UColor(0.05f, 0.5f, 0.6f));
            }
        }

        pFont->Print(
            newRect.pos.x / virtualScale[0] + 1.0,
            newRect.pos.y / virtualScale[1],
            getListItemString(iColumn).c_str(),
            -1,
            virtualScale
        );
    } else {
        if (IsDifferentVersion()) {
            if (IsQueried()) {
                if (bSelected) {
                    DrawBox(newRect, UColor(0.25f, 0.0f, 0.0f), 1.0);
                    pFont->setColor(UColor(0.0f, 0.5f, 0.0f));
                } else {
                    DrawBox(newRect, UColor(0.005f, 0.07f, 0.02f), 1.0);
                    pFont->setColor(UColor(0.0f, 0.35f, 0.0f));
                }
            } else {
                if (bSelected) {
                    DrawBox(newRect, UColor(0.25f, 0.0f, 0.0f), 1.0);
                    pFont->setColor(UColor(0.5f, 0.6f, 0.7f));
                } else {
                    DrawBox(newRect, UColor(0.15f, 0.0f, 0.0f), 1.0);
                    pFont->setColor(UColor(0.05f, 0.4f, 0.5f));
                }
            }
        } else if (IsQueried()) {
            if (bSelected) {
                DrawBox(newRect, UColor(0.2f, 0.18f, 0.015f), 1.0);
                pFont->setColor(UColor(0.9f, 0.8f, 0.6f));
            } else {
                DrawBox(newRect, UColor(0.02f, 0.07f, 0.005f), 1.0);
                pFont->setColor(UHudColor);
            }
        } else {
            if (bSelected) {
                DrawBox(newRect, UColor(0.15f, 0.18f, 0.18f), 1.0);
                pFont->setColor(UColor(0.6f, 0.7f, 0.8f));
            } else {
                DrawBox(newRect, UColor(0.005f, 0.07f, 0.02f), 1.0);
                pFont->setColor(UColor(0.05f, 0.5f, 0.6f));
            }

            if (IfQueryFailed()) {
                DrawBox(
                    UIRect2D(
                        newRect.pos.x,
                        newRect.pos.y + newRect.size.height * 0.5 - 1.0 + newRect.pos.y,
                        newRect.size.width,
                        newRect.size.height * 0.5 - 1.0 + newRect.pos.y
                    ),
                    URed,
                    0.3f
                );
            }

            pFont->Print(
                newRect.pos.x / virtualScale[0] + 1.0,
                newRect.pos.y / virtualScale[1],
                getListItemString(iColumn).c_str(),
                -1,
                virtualScale
            );

            if (IsDifferentVersion()) {
                DrawBox(
                    UIRect2D(
                        newRect.pos.x,
                        newRect.pos.y + newRect.size.height * 0.5 - 1.0 + newRect.pos.y,
                        newRect.size.width,
                        1.0
                    ),
                    URed,
                    0.3f
                );
            }
        }
    }
}

qboolean FAKKServerListItem::IsHeaderEntry() const
{
    return m_bFavorite;
}

bool FAKKServerListItem::IsQueried() const
{
    return m_bQueryDone;
}

void FAKKServerListItem::SetQueried(bool bIsQueried)
{
    m_bQueryDone = bIsQueried;
    if (m_bQueryDone) {
        m_bQueryFailed = false;
    } else {
        SetNumPlayers(0);
    }
}

bool FAKKServerListItem::IfQueryFailed() const
{
    return m_bQueryFailed;
}

void FAKKServerListItem::SetQueryFailed(bool bFailed)
{
    m_bQueryFailed = bFailed;
}

void FAKKServerListItem::SetNumPlayers(int iNum)
{
    if (m_iNumPlayers) {
        g_iTotalNumPlayers -= m_iNumPlayers;
    }

    m_iNumPlayers = iNum;
    g_iTotalNumPlayers += iNum;
    Cvar_Set("dm_playercount", va("%d", g_iTotalNumPlayers));
}

bool FAKKServerListItem::IsFavorite() const
{
    return m_bFavorite;
}

void FAKKServerListItem::SetFavorite(bool bIsFavorite)
{
    m_bFavorite = bIsFavorite;
}

str FAKKServerListItem::GetListItemVersion() const
{
    return m_sVersion;
}

void FAKKServerListItem::SetListItemVersion(str sNewVer)
{
    m_sVersion = sNewVer;
}

void FAKKServerListItem::SetDifferentVersion(bool bIsDifferentVersion)
{
    m_bDifferentVersion = bIsDifferentVersion;
}

bool FAKKServerListItem::IsDifferentVersion() const
{
    return m_bDifferentVersion;
}

CLASS_DECLARATION( UIListCtrl, UIFAKKServerList, NULL )
{
    { &EV_UIListBase_ItemSelected,              &UIFAKKServerList::SelectServer },
    { &EV_UIListBase_ItemDoubleClicked,         &UIFAKKServerList::ConnectServer },
    { &EV_FAKKServerList_RefreshServerList,     &UIFAKKServerList::RefreshServerList },
    { &EV_FAKKServerList_RefreshLANServerList,  &UIFAKKServerList::RefreshLANServerList },
    { &EV_FAKKServerList_CancelRefresh,         &UIFAKKServerList::CancelRefresh },
    { &EV_FAKKServerList_Connect,               &UIFAKKServerList::ConnectServer },
    { &EV_FAKKServerList_LANListing,            &UIFAKKServerList::MakeLANListing },
    { &EV_FAKKServerList_UpdateServer,          &UIFAKKServerList::UpdateServer },
	{ NULL, NULL }
};

UIFAKKServerList::UIFAKKServerList()
{
	setBackgroundColor(UWhite, true);
    Connect(this, EV_UIListBase_ItemDoubleClicked, EV_UIListBase_ItemDoubleClicked);
    Connect(this, EV_UIListBase_ItemSelected, EV_UIListBase_ItemSelected);

	AllowActivate(true);
	setHeaderFont("facfont-20");
	m_serverList[0] = NULL;
	m_serverList[1] = NULL;
	m_bHasList = false;
	m_bGettingList[0] = false;
	m_bGettingList[1] = false;
	m_bUpdatingList = false;
	m_bLANListing = false;
	m_iLastSortColumn = 2;
}

void UIFAKKServerList::SelectServer( Event *ev )
{
}

void UIFAKKServerList::ConnectServer( Event *ev )
{
    const FAKKServerListItem* pItem;

    if (getCurrentItem() <= 0) {
        return;
    }

    pItem = static_cast<const FAKKServerListItem*>(GetItem(getCurrentItem()));
    if (pItem->IsDifferentVersion()) {
        const char* message;
        float neededVersion = com_target_version->value;
        float serverVersion = atof(pItem->GetListItemVersion().c_str());

        // Tolerate patch version
        if (fabs(neededVersion - serverVersion) >= 0.1) {
            UI_SetReturnMenuToCurrent();

            message = va("Server is version %s, you are targeting %s", pItem->GetListItemVersion().c_str(), com_target_version->string);
            Cvar_Set("com_errormessage", message);

            UI_PushMenu("wrongversion");
        } else {
            message = va("Can not connect to v%s server, you are targeting v%s", pItem->GetListItemVersion().c_str(), com_target_version->string);
        
            Cvar_Set("dm_serverstatus", message);
        }
    } else {
        char cmdString[256];

        UI_SetReturnMenuToCurrent();

        Com_sprintf(cmdString, sizeof(cmdString), "connect %s\n", pItem->getListItemString(1).c_str());
        Cbuf_AddText(cmdString);
        ServerListHalt(m_serverList[0]);
    }
}

qboolean UIFAKKServerList::KeyEvent( int key, unsigned int time )
{
    switch (key)
    {
    case K_ENTER:
    case K_KP_ENTER:
        ConnectServer(NULL);
        return qtrue;
    case K_UPARROW:
        if (getCurrentItem() > 1) {
            TrySelectItem(getCurrentItem() - 1);
            SelectServer(NULL);

            return qtrue;
        } else {
            return qfalse;
        }
        break;
    case K_DOWNARROW:
        if (getCurrentItem() < getNumItems()) {
            TrySelectItem(getCurrentItem() + 1);
            SelectServer(NULL);

            return qtrue;
        }
        else {
            return qfalse;
        }
        break;
    case 'u':
    case 'U':
        if (getCurrentItem() > 0)
        {
            const FAKKServerListItem* pItem = static_cast<const FAKKServerListItem*>(GetItem(getCurrentItem()));
            ServerListAuxUpdate(m_serverList[0], pItem->m_sIP.c_str(), pItem->m_iGameSpyPort, true, GQueryType::qt_status);
        }
        return UIListCtrl::KeyEvent(key, time);
    case 'c':
    case 'C':
        ServerListHalt(m_serverList[0]);
        return qtrue;
    case 'i':
    case 'I':
        {
            int i, j;
            const FAKKServerListItem* pServerItem1;
            const FAKKServerListItem* pServerItem2;
            int iNumErrors = 0;

            for (i = 1; i <= getNumItems(); i++) {
                pServerItem1 = static_cast<const FAKKServerListItem*>(GetItem(i));

                for (j = i + 1; j <= getNumItems(); j++) {
                    pServerItem2 = static_cast<const FAKKServerListItem*>(GetItem(j));

                    if (!str::icmp(pServerItem1->m_sIP.c_str(), pServerItem2->m_sIP.c_str())) {
                        if (pServerItem1->m_iPort == pServerItem2->m_iPort) {
                            Com_DPrintf("*#*#* Duplicate server address: %s:%i\n", pServerItem1->m_sIP.c_str(), pServerItem1->m_iPort);
                            iNumErrors++;
                        }

                        if (pServerItem1->m_iGameSpyPort == pServerItem2->m_iGameSpyPort) {
                            Com_DPrintf("*#*#* servers at IP %s sharing GameSpy port %i\n", pServerItem1->m_sIP.c_str(), pServerItem1->m_iGameSpyPort);
                            iNumErrors++;
                        }
                    }
                }
            }

            Com_DPrintf("*#*#* %i problems detected\n", iNumErrors);
        }
        return qtrue;
    default:
        return UIListCtrl::KeyEvent(key, time);
    }
}

void UIFAKKServerList::UpdateUIElement( void )
{
    float width;

    RemoveAllColumns();

    width = getClientFrame().size.width;

    AddColumn("Server Name", 0, width * 0.31f, false, false);
    AddColumn("Map", 5, width * 0.135f, false, false);
    AddColumn("Players", 3, width * 0.085f, true, true);
    AddColumn("Gametype", 4, width * 0.18f, false, false);
    AddColumn("Ping", 2, width * 0.05f, true, false);
    AddColumn("IP", 1, width * 0.23f, false, false);

    uWinMan.ActivateControl(this);
}

void UIFAKKServerList::RefreshServerList( Event *ev )
{
    int i;
    FAKKServerListItem* pNewServerItem;

    for (i = 1; i <= getNumItems(); i++) {
        pNewServerItem = static_cast<FAKKServerListItem*>(GetItem(i));
        pNewServerItem->SetQueried(false);
        pNewServerItem->SetNumPlayers(0);
        pNewServerItem->SetQueryFailed(false);
    }

    if (m_serverList[0]) {
        ServerListClear(m_serverList[0]);
        ServerListFree(m_serverList[0]);
        m_serverList[0] = NULL;
    }

    if (m_serverList[1]) {
        ServerListClear(m_serverList[1]);
        ServerListFree(m_serverList[1]);
        m_serverList[1] = NULL;
    }
    
    if (!m_serverList[0]) {
        NewServerList();
    }

    g_bDoneUpdating[0] = false;
    g_bDoneUpdating[1] = false;

    Cvar_Set("dm_playercount", "0");
    {
        char filter[2048] = { 0 };
        static cvar_t* dm_max_players = Cvar_Get("dm_max_players", "0", CVAR_ARCHIVE);
        static cvar_t* dm_min_players = Cvar_Get("dm_min_players", "0", CVAR_ARCHIVE);
        static cvar_t* dm_show_demo_servers = Cvar_Get("dm_show_demo_servers", "1", CVAR_ARCHIVE);
        static cvar_t* dm_realism_mode = Cvar_Get("dm_realism_mode", "0", CVAR_ARCHIVE);
        static cvar_t* dm_filter_listen = Cvar_Get("dm_filter_listen", "1", CVAR_ARCHIVE);
        static cvar_t* dm_filter_empty = Cvar_Get("dm_filter_empty", "0", CVAR_ARCHIVE);
        static cvar_t* dm_filter_full = Cvar_Get("dm_filter_full", "0", CVAR_ARCHIVE);

        if (dm_min_players->integer) {
            AddFilter(filter, va("numplayers >= %d", dm_min_players->integer));
        }

        if (dm_max_players->integer) {
            AddFilter(filter, va("numplayers <= %d", dm_max_players->integer));
        }

        if (dm_show_demo_servers && !dm_show_demo_servers->integer) {
            AddFilter(filter, "gamever not like 'd%'");
        }

        if (dm_realism_mode && dm_realism_mode->integer == 1) {
            AddFilter(filter, "realism=1");
        }

        if (dm_filter_listen->integer == 1) {
            AddFilter(filter, "dedicated=1");
        }

        if (dm_filter_empty && dm_filter_empty->integer) {
            AddFilter(filter, "numplayers > 0 ");
        }

        if (dm_filter_full && dm_filter_full->integer == 1) {
            AddFilter(filter, "numplayers < maxplayers");
        }

        ServerListUpdate2(m_serverList[0], true, filter, GQueryType::qt_status);

        if (m_serverList[1]) {
            ServerListUpdate2(m_serverList[1], true, filter, GQueryType::qt_status);
        }
    }

    m_bUpdatingList = true;
}

void UIFAKKServerList::RefreshLANServerList( Event *ev )
{
    int i;
    FAKKServerListItem* pNewServerItem;

    for (i = 1; i <= getNumItems(); i++) {
        pNewServerItem = static_cast<FAKKServerListItem*>(GetItem(i));
        pNewServerItem->SetQueried(false);
        pNewServerItem->SetNumPlayers(0);
        pNewServerItem->SetQueryFailed(false);
    }

    if (m_serverList[1]) {
        ServerListClear(m_serverList[1]);
    }

    if (m_serverList[0]) {
        ServerListClear(m_serverList[0]);
    } else {
        NewServerList();
    }

    g_bDoneUpdating[0] = false;
    g_bDoneUpdating[1] = false;
    g_NeedAdditionalLANSearch = true;

    Cvar_Set("dm_playercount", "0");
    // Search all LAN servers from port 12300 to 12316
    ServerListLANUpdate(m_serverList[0], true, 12300, 12316, 1);
    
    if (m_serverList[1]) {
        // If another game is supported search for it
        ServerListLANUpdate(m_serverList[1], true, 12300, 12316, 1);
    }
}

static void AddFilter(char* filter, const char* value) {
    if (*filter) {
        strcat(filter, va(" and %s", value));
    } else {
        strcpy(filter, value);
    }
}

void UIFAKKServerList::CancelRefresh( Event *ev )
{
	ServerListHalt(m_serverList[0]);
    if (m_serverList[1]) {
        ServerListHalt(m_serverList[1]);
    }
}

void UIFAKKServerList::NewServerList( void )
{
    int iNumConcurrent;
    const char* secret_key;
    const char* game_name;
    cvar_t* pRateCvar = Cvar_Get("rate", "5000", CVAR_ARCHIVE | CVAR_USERINFO);

    if (pRateCvar->integer <= 3000) {
        iNumConcurrent = 4;
    } else if (pRateCvar->integer <= 5000) {
        iNumConcurrent = 6;
    } else if (pRateCvar->integer <= 25000) {
        iNumConcurrent = 10;
    } else {
        iNumConcurrent = 15;
    }

    if (com_target_game->integer < target_game_e::TG_MOHTT) {
        game_name = GS_GetCurrentGameName();
        secret_key = GS_GetCurrentGameKey();

        // standard mohaa server
        g_ServerListInst[0].iServerType = com_target_game->integer;
        g_ServerListInst[0].serverList = this;

        m_serverList[0] = ServerListNew(
            game_name,
            game_name,
            secret_key,
            iNumConcurrent,
            (void*)&UpdateServerListCallBack,
            1,
            (void*)&g_ServerListInst[0]
        );

        m_serverList[1] = NULL;
    } else {
        static cvar_t* dm_omit_spearhead = Cvar_Get("dm_omit_spearhead", "0", 1);

        game_name = GS_GetGameName(target_game_e::TG_MOHTT);
        secret_key = GS_GetGameKey(target_game_e::TG_MOHTT);

        g_ServerListInst[0].iServerType = target_game_e::TG_MOHTT;
        g_ServerListInst[0].serverList = this;

        m_serverList[0] = ServerListNew(
            game_name,
            game_name,
            secret_key,
            iNumConcurrent,
            (void*)&UpdateServerListCallBack,
            1,
            (void*)&g_ServerListInst[0]
        );
        
        if (!dm_omit_spearhead->integer) {
            // Since mohtt is compatible with mohta
            // Search for both type of servers
            game_name = GS_GetGameName(target_game_e::TG_MOHTA);
            secret_key = GS_GetGameKey(target_game_e::TG_MOHTA);

            g_ServerListInst[1].iServerType = target_game_e::TG_MOHTA;
            g_ServerListInst[1].serverList = this;

            m_serverList[1] = ServerListNew(
                game_name,
                game_name,
                secret_key,
                iNumConcurrent,
                (void*)&UpdateServerListCallBack,
                1,
                (void*)&g_ServerListInst[1]
            );
        }
    }
}

void UIFAKKServerList::MakeLANListing( Event *ev )
{
	m_bLANListing = true;
}

void UIFAKKServerList::UpdateServer( Event *ev )
{
	if (m_currentItem <= 0) {
		return;
	}

    FAKKServerListItem* item = (FAKKServerListItem*)GetItem(getCurrentItem());
    ServerListAuxUpdate(m_serverList[0], item->m_sIP.c_str(), item->m_iGameSpyPort, true, GQueryType::qt_status);

    if (com_target_game->integer >= target_game_e::TG_MOHTT) {
        const cvar_t* dm_omit_spearhead = Cvar_Get("dm_omit_spearhead", "0", CVAR_ARCHIVE);
        // check for Spearhead
        if (!dm_omit_spearhead->integer) {
            ServerListAuxUpdate(m_serverList[1], item->m_sIP.c_str(), item->m_iGameSpyPort, true, GQueryType::qt_status);
        }
    }
}

int UIFAKKServerList::ServerCompareFunction( const UIListCtrlItem *i1, const UIListCtrlItem *i2, int columnname )
{
    int iCompResult;
    int val1, val2;
    const FAKKServerListItem* fi1 = static_cast<const FAKKServerListItem*>(i1);
    const FAKKServerListItem* fi2 = static_cast<const FAKKServerListItem*>(i2);

    if (fi1->IsFavorite() != fi2->IsFavorite())
    {
        if (fi1->IsFavorite()) {
            iCompResult = -1;
        } else {
            iCompResult = 1;
        }

        if (g_bReverseSort) iCompResult = -iCompResult;
    } else if (fi1->IsQueried() != fi2->IsQueried()) {
        if (fi1->IsQueried()) {
            iCompResult = -1;
        } else {
            iCompResult = 1;
        }

        if (g_bReverseSort) iCompResult = -iCompResult;
    } else if (fi1->IsDifferentVersion() != fi2->IsDifferentVersion()) {

        if (fi1->IsDifferentVersion()) {
            iCompResult = 1;
        } else {
            iCompResult = -1;
        }

        if (g_bReverseSort) iCompResult = -iCompResult;
    } else if (fi1->IfQueryFailed() != fi2->IfQueryFailed()) {
        if (fi1->IfQueryFailed()) {
            iCompResult = 1;
        } else {
            iCompResult = -1;
        }

        if (g_bReverseSort) iCompResult = -iCompResult;
    } else if (g_bNumericSort) {
        val1 = fi1->getListItemValue(columnname);
        val2 = fi2->getListItemValue(columnname);

        if (val1 < val2) {
            iCompResult = -1;
        } else if (val1 > val2) {
            iCompResult = 1;
        } else {
            iCompResult = 0;
        }
    } else {
        iCompResult = str::icmp(fi1->getListItemString(columnname), fi2->getListItemString(columnname));
    }

    if (!iCompResult)
    {
        if (columnname != -2) {
            val1 = fi1->getListItemValue(2);
            val2 = fi2->getListItemValue(2);

            if (val1 < val2) {
                iCompResult = -1;
            } else if (val1 > val2) {
                iCompResult = 1;
            } else {
                iCompResult = 0;
            }
        }

        if (!iCompResult) {
            if (columnname != 4) {
                iCompResult = str::icmp(fi1->getListItemString(4), fi2->getListItemString(4));
            }
        }

        if (!iCompResult) {
            if (columnname != 5) {
                iCompResult = str::icmp(fi1->getListItemString(5), fi2->getListItemString(5));
            }
        }

        if (!iCompResult) {
            if (columnname != 3) {
                val1 = fi1->getListItemValue(3);
                val2 = fi2->getListItemValue(3);

                if (val1 < val2) {
                    iCompResult = 1;
                }
                else if (val1 > val2) {
                    iCompResult = -1;
                }
                else {
                    iCompResult = 0;
                }
            }
        }

        if (!iCompResult) {
            if (columnname != 0) {
                iCompResult = str::icmp(fi1->getListItemString(0), fi2->getListItemString(0));
            }
        }

        if (!iCompResult) {
            if (columnname != -1) {
                iCompResult = str::icmp(fi1->getListItemString(1), fi2->getListItemString(1));
            }
        }

        if (g_bReverseSort) iCompResult = -iCompResult;
    }

    return iCompResult;
}

void UIFAKKServerList::Draw( void )
{
    if (m_serverList[0])
    {
        GServerListState listState[2];

        ServerListThink(m_serverList[0]);
        if (m_serverList[1]) {
            ServerListThink(m_serverList[1]);
        }

        listState[0] = ServerListState(m_serverList[0]);
        listState[1] = m_serverList[1] ? ServerListState(m_serverList[1]) : GServerListState::sl_idle;
        if (listState[0] != GServerListState::sl_idle || listState[1] != GServerListState::sl_idle)
        {
            menuManager.PassEventToWidget("refresh", new Event(EV_Widget_Disable));
            menuManager.PassEventToWidget("cancelrefresh", new Event(EV_Widget_Enable));
        }
        else
        {
            menuManager.PassEventToWidget("refresh", new Event(EV_Widget_Enable));
            menuManager.PassEventToWidget("cancelrefresh", new Event(EV_Widget_Disable));
        }
    } else if (!m_bHasList) {

        if (m_bLANListing) {
            RefreshLANServerList(NULL);
        }
        else {
            RefreshServerList(NULL);
        }

        m_bHasList = true;

        menuManager.PassEventToWidget("refresh", new Event(EV_Widget_Enable));
        menuManager.PassEventToWidget("cancelrefresh", new Event(EV_Widget_Disable));

        if (g_NeedAdditionalLANSearch) {
            g_NeedAdditionalLANSearch = false;
            ServerListLANUpdate(m_serverList[0], true, 12201, 12233, 1);

            if (m_serverList[1]) {
                ServerListLANUpdate(m_serverList[1], true, 12201, 12233, 1);
            }
        }
    }

    UIListCtrl::Draw();
}

void UIFAKKServerList::SortByColumn( int column )
{
    int i;
    bool exists = 0;
    bool numeric = 0;
    bool reverse = 0;
    UIListCtrlItem* selected = NULL;
    bool selvisible = false;

    for (i = 1; i <= m_columnlist.NumObjects(); i++) {
        const columndef_t& def = m_columnlist.ObjectAt(i);
        if (def.name == column) {
            // found one
            numeric = def.numeric;
            reverse = def.reverse_sort;
            exists = true;
            break;
        }
    }

    if (!exists) {
        m_iLastSortColumn = 0;
        return;
    }

    m_iLastSortColumn = column;
    s_qsortcolumn = column;
    s_qsortobject = this;
    s_qsortreverse = reverse;
    g_bNumericSort = numeric;
    g_bReverseSort = reverse;

    if (getCurrentItem()) {
        selected = m_itemlist.ObjectAt(getCurrentItem());
        if (GetScrollBar()) {
            selvisible = getCurrentItem() >= GetScrollBar()->getTopItem() + 1
                      && getCurrentItem() <= GetScrollBar()->getPageHeight() + GetScrollBar()->getTopItem();
        } else {
            selvisible = getCurrentItem() > 0;
        }
    }

    setCompareFunction(&UIFAKKServerList::ServerCompareFunction);
    if (m_itemlist.NumObjects()) {
        m_itemlist.Sort(&UIListCtrl::QsortCompare);
    }

    if (selected) {
        if (selvisible) {
            TrySelectItem(FindItem(selected));
        } else {
            m_currentItem = FindItem(selected);
        }
    }
}

void UpdateServerListCallBack(GServerList serverlist, int msg, void* instance, void* param1, void* param2)
{
    int i, j;
    int iPort, iGameSpyPort;
    unsigned int iRealIP;
    str sAddress;
    GServer server;
    FAKKServerListItem* pNewServerItem;
    static int iServerQueryCount = 0;
    static int iServerTotalCount = 0;
    UIFAKKServerList* uiServerList;
    int iServerType;
    // filters
    static cvar_t* dm_filter_empty = Cvar_Get("dm_filter_empty", "0", CVAR_ARCHIVE);
    static cvar_t* dm_filter_full = Cvar_Get("dm_filter_full", "0", CVAR_ARCHIVE);
    static cvar_t* dm_filter_pure = Cvar_Get("dm_filter_pure", "0", CVAR_ARCHIVE);
    static cvar_t* dm_max_ping = Cvar_Get("dm_max_ping", "0", CVAR_ARCHIVE);
    static cvar_t* dm_free_for_all = Cvar_Get("dm_free_for_all", "1", CVAR_ARCHIVE);
    static cvar_t* dm_objective_match = Cvar_Get("dm_objective_match", "1", CVAR_ARCHIVE);
    static cvar_t* dm_round_based_match = Cvar_Get("dm_round_based_match", "1", CVAR_ARCHIVE);
    static cvar_t* dm_team_match = Cvar_Get("dm_team_match", "1", CVAR_ARCHIVE);
    static cvar_t* dm_tow_match = Cvar_Get("dm_tow_match", "1", CVAR_ARCHIVE);
    static cvar_t* dm_liberation_match = Cvar_Get("dm_liberation_match", "1", CVAR_ARCHIVE);
    static cvar_t* dm_run_fast = Cvar_Get("dm_run_fast", "1", CVAR_ARCHIVE);
    static cvar_t* dm_run_normal = Cvar_Get("dm_run_normal", "1", CVAR_ARCHIVE);
    static cvar_t* dm_omit_spearhead = Cvar_Get("dm_omit_spearhead", "0", CVAR_ARCHIVE);

    iServerType = ((ServerListInstance*)instance)->iServerType;
    uiServerList = ((ServerListInstance*)instance)->serverList;
    pNewServerItem = NULL;
    server = (GServer)param1;

    if (param2)
    {
        if (msg == LIST_PROGRESS && param2 == (void*)-1) {
            iRealIP = inet_addr(ServerGetAddress(server));
            ServerGetIntValue(server, "hostport", PORT_SERVER);
            iGameSpyPort = ServerGetQueryPort(server);

            for (i = 1; i <= uiServerList->getNumItems(); i++) {
                pNewServerItem = static_cast<FAKKServerListItem*>(uiServerList->GetItem(i));
                if (pNewServerItem->m_uiRealIP == iRealIP && pNewServerItem->m_iGameSpyPort == iGameSpyPort) {
                    break;
                }
            }

            if (i <= uiServerList->getNumItems() && pNewServerItem) {
                pNewServerItem->SetQueryFailed(true);
            }

            return;
        }

        Cvar_Set("dm_serverstatusbar", va("%i", (int)(uintptr_t)param2));
    }

    if (msg == LIST_PROGRESS)
    {
        const char* pszHostName;
        bool bDiffVersion;
        bool bIsDemo;
        str sServerName;
        str sPlayers;
        const char* pszGameVer;
        const char* pszGameVerNumber;

        pszHostName = ServerGetStringValue(server, "hostname", "(NONE)");
        bDiffVersion = false;
        bIsDemo = false;
        pszGameVer = ServerGetStringValue(server, "gamever", "1.00");
        pszGameVerNumber = pszGameVer;

        if (pszGameVerNumber[0] == 'd') {
            // demo server
            pszGameVerNumber++;
            bIsDemo = true;
        }

        if (com_target_game->integer >= target_game_e::TG_MOHTT) {
            if (iServerType == target_game_e::TG_MOHTT) {
                //if (fabs(atof(pszGameVerNumber) - com_target_version->value) > 0.1f) {
                //    bDiffVersion = true;
                //}
                if (fabs(atof(pszGameVerNumber)) < 2.3f) {
                    bDiffVersion = true;
                }
            } else {
                //if (fabs(atof(pszGameVerNumber) - com_target_version->value) > 0.3f) {
                //    bDiffVersion = true;
                //}
                if (fabs(atof(pszGameVerNumber)) < 2.1f) {
                    bDiffVersion = true;
                }
            }
        } else {
            if (fabs(atof(pszGameVerNumber) - com_target_version->value) > 0.1f) {
                bDiffVersion = true;
            }
        }
        // always show the version
        if (!bIsDemo) {
            sServerName = va(" (%s) %s", pszGameVerNumber, pszHostName);
        } else {
            sServerName = va(" (d%s) %s", pszGameVerNumber, pszHostName);
        }

        iRealIP = inet_addr(ServerGetAddress(server));
        iPort = ServerGetIntValue(server, "hostport", PORT_SERVER);
        iGameSpyPort = ServerGetQueryPort(server);
        sAddress = va("%s:%i", ServerGetAddress(server), iPort);
        sPlayers = va("%d/%d", ServerGetIntValue(server, "numplayers", 0), ServerGetIntValue(server, "maxplayers", 0));
        
        for (i = 1; i <= uiServerList->getNumItems(); i++) {
            pNewServerItem = static_cast<FAKKServerListItem*>(uiServerList->GetItem(i));
            if (pNewServerItem->m_uiRealIP == iRealIP && pNewServerItem->m_iGameSpyPort == iGameSpyPort) {
                break;
            }
        }

        if (i > uiServerList->getNumItems() || !pNewServerItem) {
            pNewServerItem = new FAKKServerListItem(uiServerList, "?", sAddress, "?", "?/?", "?", "?", "?");
            pNewServerItem->m_sIP = ServerGetAddress(server);
            pNewServerItem->m_uiRealIP = iRealIP;
            pNewServerItem->m_iGameSpyPort = iGameSpyPort;

            uiServerList->AddItem(pNewServerItem);
        }

        pNewServerItem->m_iPort = iPort;

        pNewServerItem->setListItemString(0, sServerName);
        pNewServerItem->setListItemString(1, sAddress);
        pNewServerItem->setListItemString(2, va("%d", ServerGetPing(server)));
        pNewServerItem->setListItemString(3, sPlayers.c_str());
        pNewServerItem->setListItemString(4, ServerGetStringValue(server, "gametype", "(NONE)"));
        pNewServerItem->setListItemString(5, ServerGetStringValue(server, "mapname", "(NONE)"));
        pNewServerItem->SetListItemVersion(pszGameVer);
        pNewServerItem->SetDifferentVersion(bDiffVersion);
        pNewServerItem->SetQueried(true);
        pNewServerItem->SetNumPlayers(ServerGetIntValue(server, "numplayers", 0));

        iServerQueryCount++;
        Cvar_Set("dm_servercount", va("%d/%d", iServerQueryCount, iServerTotalCount));

        uiServerList->SortByLastSortColumn();
    }
    else if (msg == LIST_STATECHANGED)
    {
        switch (ServerListState(serverlist))
        {
        case GServerListState::sl_idle:
            if (com_target_game->integer >= target_game_e::TG_MOHTT) {
                if (iServerType == target_game_e::TG_MOHTT) {
                    g_bDoneUpdating[0] = true;
                } else if (iServerType == target_game_e::TG_MOHTA || dm_omit_spearhead->integer) {
                    g_bDoneUpdating[1] = true;
                }
            } else {
                g_bDoneUpdating[0] = true;
                g_bDoneUpdating[1] = true;
            }

            if (g_bDoneUpdating[0] && g_bDoneUpdating[1]) {
                Cvar_Set("dm_serverstatus", "Done Updating.");
                Cvar_Set("dm_serverstatusbar", "0");
                uiServerList->m_bUpdatingList = false;
                Cvar_Set("dm_servercount", va("%d", uiServerList->getNumItems()));
                uiServerList->SortByLastSortColumn();
            }
            break;
        case GServerListState::sl_listxfer:
            Cvar_Set("dm_serverstatus", "Getting List.");
            if (com_target_game->integer >= target_game_e::TG_MOHTT) {
                if (iServerType == target_game_e::TG_MOHTT) uiServerList->m_bGettingList[0] = true;
                if (iServerType == target_game_e::TG_MOHTA) uiServerList->m_bGettingList[1] = true;
            } else {
                uiServerList->m_bGettingList[0] = true;
                uiServerList->m_bGettingList[1] = false;
            }
            uiServerList->m_bUpdatingList = true;
            iServerQueryCount = 0;
            return;
        case GServerListState::sl_lanlist:
            Cvar_Set("dm_serverstatus", "Searching LAN.");
            uiServerList->m_bUpdatingList = true;
            break;
        case GServerListState::sl_querying:
            Cvar_Set("dm_serverstatus", "Querying Servers.");
            uiServerList->m_bUpdatingList = true;
            iServerQueryCount = 0;
            iServerTotalCount = 0;
            break;
        default:
            break;
        }

        if (!uiServerList->m_bGettingList[0] && !uiServerList->m_bGettingList[1]) {
            return;
        }

        iServerTotalCount += ServerListCount(serverlist);

        // Removed in 2.0
        //  Only add entries for servers that are queried successfully
        //  it avoids unnecessary entries
#if 0
        for (j = 0; j < ServerListCount(serverlist); j++) {
            GServer arrayServer = ServerListGetServer(serverlist, j);
        
            iRealIP = inet_addr(ServerGetAddress(arrayServer));
            iGameSpyPort = ServerGetQueryPort(arrayServer);

            for (i = 1; i <= uiServerList->getNumItems(); i++) {
                pNewServerItem = static_cast<FAKKServerListItem*>(uiServerList->GetItem(i));
                if (pNewServerItem->m_uiRealIP == iRealIP && pNewServerItem->m_iGameSpyPort == iGameSpyPort) {
                    break;
                }
            }

            if (i <= uiServerList->getNumItems() && pNewServerItem) {
                continue;
            }

            pNewServerItem = new FAKKServerListItem(uiServerList, "?", sAddress, "?", "?/?", "?", "?", "?");
            pNewServerItem->m_sIP = ServerGetAddress(arrayServer);
            pNewServerItem->m_uiRealIP = iRealIP;
            pNewServerItem->m_iPort = PORT_SERVER;
            pNewServerItem->m_iGameSpyPort = iGameSpyPort;
            pNewServerItem->SetDifferentVersion(false);
            pNewServerItem->SetQueried(false);

            uiServerList->AddItem(pNewServerItem);
        }
#endif

        /*
        for (i = 1; i <= uiServerList->getNumItems(); i++)
        {
            pNewServerItem = static_cast<FAKKServerListItem*>(uiServerList->GetItem(i));

            if (!pNewServerItem->IsFavorite())
            {
                for (j = 0; j < ServerListCount(serverlist); j++) {
                    GServer arrayServer = ServerListGetServer(serverlist, j);

                    iRealIP = inet_addr(ServerGetAddress(arrayServer));
                    iGameSpyPort = ServerGetQueryPort(arrayServer);

                    if (pNewServerItem->m_uiRealIP == iRealIP && pNewServerItem->m_iGameSpyPort == iGameSpyPort) {
                        break;
                    }
                }

                if (j == ServerListCount(serverlist)) {
                    uiServerList->DeleteItem(j);
                    j--;
                }
            }
        }
        */
    }
}