/*
 * OpenClonk, http://www.openclonk.org
 *
 * Copyright (c) 2006-2009, RedWolf Design GmbH, http://www.clonk.de/
 * Copyright (c) 2009-2016, The OpenClonk Team and contributors
 *
 * Distributed under the terms of the ISC license; see accompanying file
 * "COPYING" for details.
 *
 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
 * See accompanying file "TRADEMARK" for details.
 *
 * To redistribute this file separately, substitute the full license texts
 * for the above references.
 */
// Startup screen for non-parameterized engine start: Network game selection dialog

#include "C4Include.h"
#include "gui/C4StartupNetDlg.h"

#include "game/C4Application.h"
#include "graphics/C4Draw.h"
#include "graphics/C4GraphicsResource.h"
#include "gui/C4ChatDlg.h"
#include "gui/C4StartupScenSelDlg.h"
#include "gui/C4UpdateDlg.h"
#include "network/C4Network2Reference.h"

// ----------- C4StartupNetListEntry -----------------------------------------------------------------------

C4StartupNetListEntry::C4StartupNetListEntry(C4GUI::ListBox *pForListBox, C4GUI::Element *pInsertBefore, C4StartupNetDlg *pNetDlg)
		: pNetDlg(pNetDlg), pList(pForListBox), pRefClient(nullptr), pRef(nullptr), fError(false), eQueryType(NRQT_Unknown), iTimeout(0), iInfoIconCount(0), iSortOrder(0), fIsSmall(false), fIsCollapsed(false), fIsEnabled(true), fIsImportant(false)
{
	// calc height
	int32_t iLineHgt = ::GraphicsResource.TextFont.GetLineHeight(), iHeight = iLineHgt * 2 + 4;
	// add icons - normal icons use small size, only animated netgetref uses full size
	rctIconLarge.Set(0, 0, iHeight, iHeight);
	int32_t iSmallIcon = iHeight * 2 / 3; rctIconSmall.Set((iHeight - iSmallIcon)/2, (iHeight - iSmallIcon)/2, iSmallIcon, iSmallIcon);
	pIcon = new C4GUI::Icon(rctIconSmall, C4GUI::Ico_Host);
	AddElement(pIcon);
	SetBounds(pIcon->GetBounds());
	// add to listbox (will get resized horizontally and moved)
	pForListBox->InsertElement(this, pInsertBefore);
	// add status icons and text labels now that width is known
	CStdFont *pUseFont = &(::GraphicsResource.TextFont);
	int32_t iIconSize = pUseFont->GetLineHeight();
	C4Rect rcIconRect = GetContainedClientRect();
	int32_t iThisWdt = rcIconRect.Wdt;
	rcIconRect.x = iThisWdt - iIconSize * (iInfoIconCount + 1);
	rcIconRect.Wdt = rcIconRect.Hgt = iIconSize;
	for (auto & pInfoIcon : pInfoIcons)
	{
		AddElement(pInfoIcon = new C4GUI::Icon(rcIconRect, C4GUI::Ico_None));
		rcIconRect.x -= rcIconRect.Wdt;
	}
	C4Rect rcLabelBounds;
	rcLabelBounds.x = iHeight+3;
	rcLabelBounds.Hgt = iLineHgt;
	for (int i=0; i<InfoLabelCount; ++i)
	{
		C4GUI::Label *pLbl;
		rcLabelBounds.y = 1+i*(iLineHgt+2);
		rcLabelBounds.Wdt = iThisWdt - rcLabelBounds.x - 1;
		if (!i) rcLabelBounds.Wdt -= iLineHgt; // leave space for topright extra icon
		AddElement(pLbl = pInfoLbl[i] = new C4GUI::Label("", rcLabelBounds, ALeft, C4GUI_CaptionFontClr));
		// label will have collapsed due to no text: Repair it
		pLbl->SetAutosize(false);
		pLbl->SetBounds(rcLabelBounds);
	}
	// update small state, which will resize this to a small entry
	UpdateSmallState();
	// Set*-function will fill icon and text and calculate actual size
}

C4StartupNetListEntry::~C4StartupNetListEntry()
{
	ClearRef();
}

void C4StartupNetListEntry::DrawElement(C4TargetFacet &cgo)
{
	typedef C4GUI::Window ParentClass;
	// background if important and not selected
	if (fIsImportant && !IsSelectedChild(this))
	{
		int32_t x1 = cgo.X+cgo.TargetX+rcBounds.x;
		int32_t y1 = cgo.Y+cgo.TargetY+rcBounds.y;
		pDraw->DrawBoxDw(cgo.Surface, x1,y1, x1+rcBounds.Wdt, y1+rcBounds.Hgt, C4GUI_ImportantBGColor);
	}
	// inherited
	ParentClass::DrawElement(cgo);
}

void C4StartupNetListEntry::ClearRef()
{
	// del old ref data
	if (pRefClient)
	{
		C4InteractiveThread &Thread = Application.InteractiveThread;
		Thread.RemoveProc(pRefClient);
		delete pRefClient; pRefClient = nullptr;
	}
	if (pRef) { delete pRef; pRef = nullptr; }
	eQueryType = NRQT_Unknown;
	iTimeout = iRequestTimeout = 0;
	fError = false;
	sError.Clear();
	int32_t i;
	for (i=0; i<InfoLabelCount; ++i) sInfoText[i].Clear();
	InvalidateStatusIcons();
	sRefClientAddress.Clear();
	fIsEnabled = true;
	fIsImportant = false;
}

const char *C4StartupNetListEntry::GetQueryTypeName(QueryType eQueryType)
{
	switch (eQueryType)
	{
	case NRQT_GameDiscovery: return LoadResStr("IDS_NET_QUERY_LOCALNET");
	case NRQT_Masterserver:  return LoadResStr("IDS_NET_QUERY_MASTERSRV");
	case NRQT_DirectJoin:    return LoadResStr("IDS_NET_QUERY_DIRECTJOIN");
	default: return "";
	};
}

void C4StartupNetListEntry::SetRefQuery(const char *szAddress, enum QueryType eQueryType)
{
	// safety: clear previous
	ClearRef();
	// setup layout
	const_cast<C4Facet &>(reinterpret_cast<const C4Facet &>(pIcon->GetFacet()))
	  = (const C4Facet &) C4Startup::Get()->Graphics.fctNetGetRef;
	pIcon->SetAnimated(true, 1);
	pIcon->SetBounds(rctIconLarge);
	// init a new ref client to query
	sRefClientAddress.Copy(szAddress);
	this->eQueryType = eQueryType;
	pRefClient = new C4Network2RefClient();
	if (!pRefClient->Init() || !pRefClient->SetServer(szAddress))
	{
		// should not happen
		sInfoText[0].Clear();
		SetError(pRefClient->GetError(), TT_RefReqWait);
		return;
	}
	// set info
	sInfoText[0].Format(LoadResStr("IDS_NET_CLIENTONNET"), GetQueryTypeName(eQueryType), pRefClient->getServerName());
	sInfoText[1].Copy(LoadResStr("IDS_NET_INFOQUERY"));
	UpdateSmallState(); UpdateText();
	pRefClient->SetNotify(&Application.InteractiveThread);
	// masterserver: always on top
	if (eQueryType == NRQT_Masterserver)
		iSortOrder = 100;
	// register proc
	C4InteractiveThread &Thread = Application.InteractiveThread;
	Thread.AddProc(pRefClient);
	// start querying!
	QueryReferences();
}

bool C4StartupNetListEntry::QueryReferences()
{
	// begin querying
	if (!pRefClient->QueryReferences())
	{
		SetError(pRefClient->GetError(), TT_RefReqWait);
		return false;
	}
	// set up timeout
	iRequestTimeout = time(nullptr) + C4NetRefRequestTimeout;
	return true;
}

bool C4StartupNetListEntry::Execute()
{
	// update entries
	// if the return value is false, this entry will be deleted
	// timer running?
	if (iTimeout) if (time(nullptr) >= iTimeout)
		{
			// timeout!
			// for internet servers, this means refresh needed - search anew!
			if (pRefClient && eQueryType == NRQT_Masterserver)
			{
				fError = false;
				sError.Clear();
				pIcon->SetFacet(C4Startup::Get()->Graphics.fctNetGetRef);
				pIcon->SetAnimated(true, 1);
				pIcon->SetBounds(rctIconLarge);
				sInfoText[1].Copy(LoadResStr("IDS_NET_INFOQUERY"));
				iTimeout = 0;
				QueryReferences();
				// always keep item even if query failed
				return true;
			}
			// any other item is just removed - return value marks this
			return false;
		}
	// failed without a timer. Nothing to be done about it.
	if (fError) return true;
	// updates need to be done for references being retrieved only
	if (!pRefClient) return true;
	// check if it has arrived
	if (pRefClient->isBusy())
		// still requesting - but do not wait forever
		if (time(nullptr) >= iRequestTimeout)
		{
			SetError(LoadResStr("IDS_NET_ERR_REFREQTIMEOUT"), TT_RefReqWait);
			pRefClient->Cancel("Timeout");
		}
	return true;
}

bool C4StartupNetListEntry::OnReference()
{
	// wrong type / still busy?
	if (!pRefClient || pRefClient->isBusy())
		return true;
	// successful?
	if (!pRefClient->isSuccess())
	{
		// couldn't get references
		SetError(pRefClient->GetError(), TT_RefReqWait);
		return true;
	}
	// Ref getting done!
	pIcon->SetAnimated(false, 1);
	// Get reference information from client
	C4Network2Reference **ppNewRefs=nullptr; int32_t iNewRefCount;
	if (!pRefClient->GetReferences(ppNewRefs, iNewRefCount))
	{
		// References could be retrieved but not read
		SetError(LoadResStr("IDS_NET_ERR_REFINVALID"), TT_RefReqWait);
		delete [] ppNewRefs;
		return true;
	}
	if (!iNewRefCount)
	{
		// References retrieved but no game open: Inform user
		sInfoText[1].Copy(LoadResStr("IDS_NET_INFONOGAME"));
		UpdateText();
	}
	else
	{
		// Grab references, count players
		C4StartupNetListEntry *pNewRefEntry = this; int iPlayerCount = 0;
		for (int i = 0; i < iNewRefCount; i++)
		{
			pNewRefEntry = AddReference(ppNewRefs[i], pNewRefEntry->GetNextLower(ppNewRefs[i]->getSortOrder()));
			iPlayerCount += ppNewRefs[i]->Parameters.PlayerInfos.GetActivePlayerCount(false);
		}
		// Count player
		sInfoText[1].Format(LoadResStr("IDS_NET_INFOGAMES"), (int) iNewRefCount, iPlayerCount);
		UpdateText();
	}
	delete [] ppNewRefs;
	// special masterserver handling
	if (eQueryType == NRQT_Masterserver)
	{
		// masterserver: schedule next query
		sInfoText[1].Format(LoadResStr("IDS_NET_INFOGAMES"), (int) iNewRefCount);
		SetTimeout(TT_Masterserver);
		return true;
	}
	// non-masterserver
	if (iNewRefCount)
	{
		// this item has been "converted" into the references - remove without further feedback
		return false;
	}
	else
	{
		// no ref found on custom adress: Schedule re-check
		SetTimeout(TT_RefReqWait);
	}
	return true;
}

C4GUI::Element* C4StartupNetListEntry::GetNextLower(int32_t sortOrder)
{
	// search list for the next element of a lower sort order
	for (C4GUI::Element *pElem = pList->GetFirst(); pElem; pElem = pElem->GetNext())
	{
		C4StartupNetListEntry *pEntry = static_cast<C4StartupNetListEntry *>(pElem);
		if (pEntry->iSortOrder < sortOrder)
			return pElem;
	}
	// none found: insert at start
	return nullptr;
}


void C4StartupNetListEntry::UpdateCollapsed(bool fToCollapseValue)
{
	// if collapsed state changed, update the text
	if (fIsCollapsed == fToCollapseValue) return;
	fIsCollapsed = fToCollapseValue;
	UpdateSmallState();
}

void C4StartupNetListEntry::UpdateSmallState()
{
	// small view: Always collapsed if there is no extended text
	bool fNewIsSmall = !sInfoText[2].getLength() || fIsCollapsed;
	if (fNewIsSmall == fIsSmall) return;
	fIsSmall = fNewIsSmall;
	for (int i=2; i<InfoLabelCount; ++i) pInfoLbl[i]->SetVisibility(!fIsSmall);
	UpdateEntrySize();
}

void C4StartupNetListEntry::UpdateEntrySize()
{
	if(fVisible) {
		// restack all labels by their size
		int32_t iLblCnt = (fIsSmall ? 2 : InfoLabelCount), iY=1;
		for (int i=0; i<iLblCnt; ++i)
		{
			C4Rect rcBounds = pInfoLbl[i]->GetBounds();
			rcBounds.y = iY;
				iY += rcBounds.Hgt + 2;
		pInfoLbl[i]->SetBounds(rcBounds);
		}
		// resize this control
		GetBounds().Hgt = iY-1;
	} else GetBounds().Hgt = 0;
	UpdateSize();
}

void C4StartupNetListEntry::UpdateText()
{
	bool fRestackElements=false;
	CStdFont *pUseFont = &(::GraphicsResource.TextFont);
	// adjust icons
	int32_t sx=iInfoIconCount*pUseFont->GetLineHeight();
	int32_t i;
	for (i=iInfoIconCount; i<MaxInfoIconCount; ++i)
	{
		pInfoIcons[i]->SetIcon(C4GUI::Ico_None);
		pInfoIcons[i]->SetToolTip(nullptr);
	}
	// text to labels
	for (i=0; i<InfoLabelCount; ++i)
	{
		int iAvailableWdt = GetClientRect().Wdt - pInfoLbl[i]->GetBounds().x - 1;
		if (!i) iAvailableWdt -= sx;
		StdStrBuf BrokenText;
		pUseFont->BreakMessage(sInfoText[i].getData(), iAvailableWdt, &BrokenText, true);
		int32_t iHgt, iWdt;
		if (pUseFont->GetTextExtent(BrokenText.getData(), iWdt, iHgt, true))
		{
			if ((pInfoLbl[i]->GetBounds().Hgt != iHgt) || (pInfoLbl[i]->GetBounds().Wdt != iAvailableWdt))
			{
				C4Rect rcBounds = pInfoLbl[i]->GetBounds();
				rcBounds.Wdt = iAvailableWdt;
				rcBounds.Hgt = iHgt;
				pInfoLbl[i]->SetBounds(rcBounds);
				fRestackElements = true;
			}
		}
		pInfoLbl[i]->SetText(BrokenText.getData());
		pInfoLbl[i]->SetColor(fIsEnabled ? C4GUI_MessageFontClr : C4GUI_InactMessageFontClr);
	}
	if (fRestackElements) UpdateEntrySize();
}

void C4StartupNetListEntry::SetVisibility(bool fToValue) {
	bool fChange = fToValue != fVisible;
	C4GUI::Window::SetVisibility(fToValue);
	if(fChange) UpdateEntrySize();
}

void C4StartupNetListEntry::AddStatusIcon(C4GUI::Icons eIcon, const char *szToolTip)
{
	// safety
	if (iInfoIconCount==MaxInfoIconCount) return;
	// set icon to the left of the existing icons to the desired data
	pInfoIcons[iInfoIconCount]->SetIcon(eIcon);
	pInfoIcons[iInfoIconCount]->SetToolTip(szToolTip);
	++iInfoIconCount;
}

void C4StartupNetListEntry::SetReference(C4Network2Reference *pRef)
{
	// safety: clear previous
	ClearRef();
	// set info
	this->pRef = pRef;
	int32_t iIcon = pRef->getIcon();
	if (!Inside<int32_t>(iIcon, 0, C4StartupScenSel_IconCount-1)) iIcon = C4StartupScenSel_DefaultIcon_Scenario;
	pIcon->SetFacet(C4Startup::Get()->Graphics.fctScenSelIcons.GetPhase(iIcon));
	pIcon->SetAnimated(false, 0);
	pIcon->SetBounds(rctIconSmall);
	int32_t iPlrCnt = pRef->isEditor() ? pRef->Parameters.PlayerInfos.GetActivePlayerCount(false) : pRef->Parameters.Clients.getClientCnt();
	C4Client *pHost = pRef->Parameters.Clients.getHost();
	sInfoText[0].Format(LoadResStr("IDS_NET_REFONCLIENT"), pRef->getTitle(), pHost ? pHost->getName() : "unknown");
	if (pRef->isEditor())
	{
		sInfoText[1].Format(LoadResStr("IDS_NET_INFOEDITOR"),
			(int)iPlrCnt,
			StdStrBuf(pRef->getGameStatus().getDescription(), true).getData());
	}
	else
	{
		sInfoText[1].Format(LoadResStr("IDS_NET_INFOPLRSGOALDESC"),
			(int)iPlrCnt,
			(int)pRef->Parameters.MaxPlayers,
			pRef->getGameGoalString().getData(),
			StdStrBuf(pRef->getGameStatus().getDescription(), true).getData());
	}
	if (pRef->getTime() > 0)
	{
		StdStrBuf strDuration; strDuration.Format("%02d:%02d:%02d", pRef->getTime()/3600, (pRef->getTime() % 3600) / 60, pRef->getTime() % 60);
		sInfoText[1].Append(" - "); sInfoText[1].Append(strDuration);
	}
	sInfoText[2].Format(LoadResStr("IDS_DESC_VERSION"), pRef->getGameVersion().GetString().getData());
	sInfoText[3].Format("%s: %s", LoadResStr("IDS_CTL_COMMENT"), pRef->getComment());
	StdStrBuf sAddress;
	for (int i=0; i<pRef->getAddrCnt(); ++i)
	{
		if (i) sAddress.Append(", ");
		sAddress.Append(pRef->getAddr(i).toString());
	}
	// editor reference
	if (pRef->isEditor())
		AddStatusIcon(C4GUI::Ico_Editor, LoadResStr("IDS_CNS_CONSOLE"));
	// password
	if (pRef->isPasswordNeeded())
		AddStatusIcon(C4GUI::Ico_Ex_LockedFrontal, LoadResStr("IDS_NET_INFOPASSWORD"));
	// league
	if (pRef->Parameters.isLeague())
		AddStatusIcon(C4GUI::Ico_Ex_League, pRef->Parameters.getLeague());
	// lobby active
	if (pRef->getGameStatus().isLobbyActive())
		AddStatusIcon(C4GUI::Ico_Lobby, LoadResStr("IDS_DESC_EXPECTING"));
	// game running
	if (pRef->getGameStatus().isPastLobby())
		AddStatusIcon(C4GUI::Ico_GameRunning, LoadResStr("IDS_NET_INFOINPROGR"));
	// runtime join
	if (pRef->isJoinAllowed() && pRef->getGameStatus().isPastLobby()) // A little workaround to determine RuntimeJoin...
		AddStatusIcon(C4GUI::Ico_RuntimeJoin, LoadResStr("IDS_NET_RUNTIMEJOINFREE"));
	// official server
	if (pRef->isOfficialServer() && !Config.Network.UseAlternateServer) // Offical server icon is only displayed if references are obtained from official league server
	{
		fIsImportant = true;
		AddStatusIcon(C4GUI::Ico_OfficialServer, LoadResStr("IDS_NET_OFFICIALSERVER"));
	}
	// list participating player/client names
	if (pRef->isEditor())
	{
		sInfoText[4].Format("%s%s", LoadResStr("IDS_DESC_CLIENTS"), iPlrCnt ? pRef->Parameters.Clients.GetAllClientNames().getData() : LoadResStr("IDS_CTL_NONE"));
	}
	else
	{
		sInfoText[4].Format("%s: %s", LoadResStr("IDS_CTL_PLAYER"), iPlrCnt ? pRef->Parameters.PlayerInfos.GetActivePlayerNames(false).getData() : LoadResStr("IDS_CTL_NONE"));
	}
	// disabled if join is not possible for some reason
	C4GameVersion verThis;
	if (!pRef->isJoinAllowed() || !(pRef->getGameVersion() == verThis))
	{
		fIsEnabled = false;
	}
	// store sort order
	iSortOrder = pRef->getSortOrder();
	// all references expire after a while
	SetTimeout(TT_Reference);
	UpdateSmallState(); UpdateText();
}

void C4StartupNetListEntry::SetError(const char *szErrorText, TimeoutType eTimeout)
{
	// set error message
	fError = true;
	sInfoText[1].Copy(szErrorText);
	for (int i=2; i<InfoLabelCount; ++i) sInfoText[i].Clear();
	InvalidateStatusIcons();
	UpdateSmallState(); UpdateText();
	pIcon->SetIcon(C4GUI::Ico_Close);
	pIcon->SetAnimated(false, 0);
	pIcon->SetBounds(rctIconSmall);
	SetTimeout(eTimeout);
}

void C4StartupNetListEntry::SetTimeout(TimeoutType eTimeout)
{
	int iTime = 0;
	switch (eTimeout)
	{
	case TT_RefReqWait: iTime = (eQueryType == NRQT_Masterserver) ? C4NetMasterServerQueryInterval : C4NetErrorRefTimeout; break;
	case TT_Reference: iTime = C4NetReferenceTimeout; break;
	case TT_Masterserver: iTime = C4NetMasterServerQueryInterval; break;
	};
	if (!iTime) return;
	iTimeout = time(nullptr) + iTime;
}

C4StartupNetListEntry *C4StartupNetListEntry::AddReference(C4Network2Reference *pAddRef, C4GUI::Element *pInsertBefore)
{
	// check list whether the same reference has been added already
	for (C4GUI::Element *pElem = pList->GetFirst(); pElem; pElem = pElem->GetNext())
	{
		C4StartupNetListEntry *pEntry = static_cast<C4StartupNetListEntry *>(pElem);
		// match to existing reference entry:
		// * same host (checking for same name and nick)
		// * at least one match in address and port
		// * the incoming reference is newer than (or same as) the current one
		if ( pEntry->IsSameHost(pAddRef)
		     && pEntry->IsSameAddress(pAddRef)
		     && (pEntry->GetReference()->getStartTime() <= pAddRef->getStartTime()) )
		{
			// update existing entry
			pEntry->SetReference(pAddRef);
			return pEntry;
		}
	}
	// no update - just add
	C4StartupNetListEntry *pNewRefEntry = new C4StartupNetListEntry(pList, pInsertBefore, pNetDlg);
	pNewRefEntry->SetReference(pAddRef);
	pNetDlg->OnReferenceEntryAdd(pNewRefEntry);
	return pNewRefEntry;
}

bool C4StartupNetListEntry::IsSameHost(const C4Network2Reference *pRef2)
{
	// not if ref has not been retrieved yet
	if (!pRef) return false;
	C4Client *pHost1 = pRef->Parameters.Clients.getHost();
	C4Client *pHost2 = pRef2->Parameters.Clients.getHost();
	if (!pHost1 || !pHost2) return false;
	// check
	return SEqual(pHost1->getCUID(), pHost2->getCUID()) && SEqual(pHost1->getName(), pHost2->getName());
}

bool C4StartupNetListEntry::IsSameAddress(const C4Network2Reference *pRef2)
{
	// not if ref has not been retrieved yet
	if (!pRef) return false;
	// check all of our addresses
	for (int i = 0; i < pRef->getAddrCnt(); i++)
		// against all of the other ref's addresses
		for (int j = 0; j < pRef2->getAddrCnt(); j++)
			// at least one match!
			if (pRef->getAddr(i) == pRef2->getAddr(j))
				return true;
	// no match
	return false;
}

bool C4StartupNetListEntry::IsSameRefQueryAddress(const char *szJoinaddress)
{
	// only unretrieved references
	if (!pRefClient) return false;
	// if request failed, create a duplicate anyway in case the game is opened now
	// except masterservers, which would re-search some time later anyway
	if (fError && eQueryType != NRQT_Masterserver) return false;
	// check equality of address
	// do it the simple way for now
	return SEqualNoCase(sRefClientAddress.getData(), szJoinaddress);
}

bool C4StartupNetListEntry::KeywordMatch(const char *szMatch)
{
	// only finished references
	if (!pRef) return false;
	if(SSearchNoCase(pRef->getTitle(),szMatch)) return true;
	C4Client *pHost = pRef->Parameters.Clients.getHost();
	if(pHost && SSearchNoCase(pHost->getName(),szMatch)) return true;
	if(SSearchNoCase(pRef->getComment(),szMatch)) return true;
	return false;
}

const char *C4StartupNetListEntry::GetJoinAddress()
{
	// only unresolved references
	if (!pRefClient) return nullptr;
	// not masterservers (cannot join directly on clonk.de)
	if (eQueryType == NRQT_Masterserver) return nullptr;
	// return join address
	return pRefClient->getServerName();
}

C4Network2Reference *C4StartupNetListEntry::GrabReference()
{
	C4Network2Reference *pOldRef = pRef;
	pRef = nullptr;
	return pOldRef;
}




// ----------- C4StartupNetDlg ---------------------------------------------------------------------------------

C4StartupNetDlg::C4StartupNetDlg() : C4StartupDlg(LoadResStr("IDS_DLG_NETSTART"))
{
	// ctor
	// key bindings
	C4CustomKey::CodeList keys;
	keys.emplace_back(K_BACK); keys.emplace_back(K_LEFT);
	pKeyBack = new C4KeyBinding(keys, "StartupNetBack", KEYSCOPE_Gui,
	                            new C4GUI::DlgKeyCB<C4StartupNetDlg>(*this, &C4StartupNetDlg::KeyBack), C4CustomKey::PRIO_Dlg);
	pKeyRefresh = new C4KeyBinding(C4KeyCodeEx(K_F5), "StartupNetReload", KEYSCOPE_Gui,
	                               new C4GUI::DlgKeyCB<C4StartupNetDlg>(*this, &C4StartupNetDlg::KeyRefresh), C4CustomKey::PRIO_CtrlOverride);

	// screen calculations
	UpdateSize();
	int32_t iIconSize = C4GUI_IconExWdt;
	int32_t iButtonWidth,iCaptionFontHgt, iSideSize = std::max<int32_t>(GetBounds().Wdt/6, iIconSize);
	int32_t iButtonHeight = C4GUI_ButtonHgt, iButtonIndent = GetBounds().Wdt/40;
	::GraphicsResource.CaptionFont.GetTextExtent("<< BACK", iButtonWidth, iCaptionFontHgt, true);
	iButtonWidth *= 3;
	C4GUI::ComponentAligner caMain(GetClientRect(), 0,0, true);
	C4GUI::ComponentAligner caButtonArea(caMain.GetFromBottom(caMain.GetHeight()/7),0,0);
	int32_t iButtonAreaWdt = caButtonArea.GetWidth()*7/8;
	iButtonWidth = std::min<int32_t>(iButtonWidth, (iButtonAreaWdt - 8 * iButtonIndent)/4);
	iButtonIndent = (iButtonAreaWdt - 4 * iButtonWidth) / 8;
	C4GUI::ComponentAligner caButtons(caButtonArea.GetCentered(iButtonAreaWdt, iButtonHeight),iButtonIndent,0);
	C4GUI::ComponentAligner caLeftBtnArea(caMain.GetFromLeft(iSideSize), std::min<int32_t>(caMain.GetWidth()/20, (iSideSize-C4GUI_IconExWdt)/2), caMain.GetHeight()/40);
	C4GUI::ComponentAligner caConfigArea(caMain.GetFromRight(iSideSize), std::min<int32_t>(caMain.GetWidth()/20, (iSideSize-C4GUI_IconExWdt)/2), caMain.GetHeight()/40);

	// left button area: Switch between chat and game list
	if (C4ChatDlg::IsChatEnabled())
	{
		btnGameList = new C4GUI::CallbackButton<C4StartupNetDlg, C4GUI::IconButton>(C4GUI::Ico_Ex_GameList, caLeftBtnArea.GetFromTop(iIconSize, iIconSize), '\0', &C4StartupNetDlg::OnBtnGameList);
		btnGameList->SetToolTip(LoadResStr("IDS_DESC_SHOWSAVAILABLENETWORKGAME"));
		btnGameList->SetText(LoadResStr("IDS_BTN_GAMES"));
		AddElement(btnGameList);
		btnChat = new C4GUI::CallbackButton<C4StartupNetDlg, C4GUI::IconButton>(C4GUI::Ico_Ex_Chat, caLeftBtnArea.GetFromTop(iIconSize, iIconSize), '\0', &C4StartupNetDlg::OnBtnChat);
		btnChat->SetToolTip(LoadResStr("IDS_DESC_CONNECTSTOANIRCCHATSERVER"));
		btnChat->SetText(LoadResStr("IDS_BTN_CHAT"));
		AddElement(btnChat);
	}
	else btnChat = nullptr;

	// main area: Tabular to switch between game list and chat
	pMainTabular = new C4GUI::Tabular(caMain.GetAll(), C4GUI::Tabular::tbNone);
	pMainTabular->SetDrawDecoration(false);
	pMainTabular->SetSheetMargin(0);
	AddElement(pMainTabular);

	// main area: game selection sheet
	C4GUI::Tabular::Sheet *pSheetGameList = pMainTabular->AddSheet(nullptr);
	C4GUI::ComponentAligner caGameList(pSheetGameList->GetContainedClientRect(), 0,0, false);
	C4GUI::WoodenLabel *pGameListLbl; int32_t iCaptHgt = C4GUI::WoodenLabel::GetDefaultHeight(&::GraphicsResource.TextFont);
	pGameListLbl = new C4GUI::WoodenLabel(LoadResStr("IDS_NET_GAMELIST"), caGameList.GetFromTop(iCaptHgt), C4GUI_Caption2FontClr, &::GraphicsResource.TextFont, ALeft);
	// search field
	C4GUI::WoodenLabel *pSearchLbl;
	const char *szSearchLblText = LoadResStr("IDS_NET_MSSEARCH");
	int32_t iSearchWdt=100, iSearchHgt;
	::GraphicsResource.TextFont.GetTextExtent(szSearchLblText, iSearchWdt, iSearchHgt, true);
	C4GUI::ComponentAligner caSearch(caGameList.GetFromTop(iSearchHgt), 0,0);
	pSearchLbl = new C4GUI::WoodenLabel(szSearchLblText, caSearch.GetFromLeft(iSearchWdt+10), C4GUI_Caption2FontClr, &::GraphicsResource.TextFont);
	const char *szSearchTip = LoadResStr("IDS_NET_MSSEARCH_DESC");
	pSearchLbl->SetToolTip(szSearchTip);
	pSheetGameList->AddElement(pSearchLbl);
	pSearchFieldEdt = new C4GUI::CallbackEdit<C4StartupNetDlg>(caSearch.GetAll(), this, &C4StartupNetDlg::OnSearchFieldEnter);
	pSearchFieldEdt->SetToolTip(szSearchTip);
	pSheetGameList->AddElement(pSearchFieldEdt);
	pSheetGameList->AddElement(pGameListLbl);
	pGameSelList = new C4GUI::ListBox(caGameList.GetFromTop(caGameList.GetHeight() - iCaptHgt));
	pGameSelList->SetDecoration(true, nullptr, true, true);
	pGameSelList->UpdateElementPositions();
	pGameSelList->SetSelectionDblClickFn(new C4GUI::CallbackHandler<C4StartupNetDlg>(this, &C4StartupNetDlg::OnSelDblClick));
	pGameSelList->SetSelectionChangeCallbackFn(new C4GUI::CallbackHandler<C4StartupNetDlg>(this, &C4StartupNetDlg::OnSelChange));
	pSheetGameList->AddElement(pGameSelList);
	C4GUI::ComponentAligner caIP(caGameList.GetAll(), 0,0);
	C4GUI::WoodenLabel *pIPLbl;
	const char *szIPLblText = LoadResStr("IDS_NET_IP");
	int32_t iIPWdt=100, Q;
	::GraphicsResource.TextFont.GetTextExtent(szIPLblText, iIPWdt, Q, true);
	pIPLbl = new C4GUI::WoodenLabel(szIPLblText, caIP.GetFromLeft(iIPWdt+10), C4GUI_Caption2FontClr, &::GraphicsResource.TextFont);
	const char *szIPTip = LoadResStr("IDS_NET_IP_DESC");
	pIPLbl->SetToolTip(szIPTip);
	pSheetGameList->AddElement(pIPLbl);
	pJoinAddressEdt = new C4GUI::CallbackEdit<C4StartupNetDlg>(caIP.GetAll(), this, &C4StartupNetDlg::OnJoinAddressEnter);
	pJoinAddressEdt->SetToolTip(szIPTip);
	pSheetGameList->AddElement(pJoinAddressEdt);

	// main area: chat sheet
	if (C4ChatDlg::IsChatEnabled())
	{
		C4GUI::Tabular::Sheet *pSheetChat = pMainTabular->AddSheet(nullptr);
		C4GUI::ComponentAligner caChat(pSheetChat->GetContainedClientRect(), 0,0, false);
		pSheetChat->AddElement(pChatTitleLabel = new C4GUI::WoodenLabel("", caChat.GetFromTop(iCaptHgt), C4GUI_Caption2FontClr, &::GraphicsResource.TextFont, ALeft, false));
		C4GUI::GroupBox *pChatGroup = new C4GUI::GroupBox(caChat.GetAll());
		pChatGroup->SetColors(0u, C4GUI_CaptionFontClr, C4GUI_StandardBGColor);
		pChatGroup->SetMargin(2);
		pSheetChat->AddElement(pChatGroup);
		pChatCtrl = new C4ChatControl(&Application.IRCClient);
		pChatCtrl->SetBounds(pChatGroup->GetContainedClientRect());
		pChatCtrl->SetTitleChangeCB(new C4GUI::InputCallback<C4StartupNetDlg>(this, &C4StartupNetDlg::OnChatTitleChange));
		StdStrBuf sCurrTitle; sCurrTitle.Ref(pChatCtrl->GetTitle()); OnChatTitleChange(sCurrTitle);
		pChatGroup->AddElement(pChatCtrl);
	}

	// config area
	btnInternet = new C4GUI::CallbackButton<C4StartupNetDlg, C4GUI::IconButton>(Config.Network.MasterServerSignUp ? C4GUI::Ico_Ex_InternetOn : C4GUI::Ico_Ex_InternetOff, caConfigArea.GetFromTop(iIconSize, iIconSize), '\0', &C4StartupNetDlg::OnBtnInternet);
	btnInternet->SetToolTip(LoadResStr("IDS_DLGTIP_SEARCHINTERNETGAME"));
	btnInternet->SetText(LoadResStr("IDS_CTL_INETSERVER"));
	AddElement(btnInternet);
	btnRecord = new C4GUI::CallbackButton<C4StartupNetDlg, C4GUI::IconButton>(Game.Record ? C4GUI::Ico_Ex_RecordOn : C4GUI::Ico_Ex_RecordOff, caConfigArea.GetFromTop(iIconSize, iIconSize), '\0', &C4StartupNetDlg::OnBtnRecord);
	btnRecord->SetToolTip(LoadResStr("IDS_DLGTIP_RECORD"));
	btnRecord->SetText(LoadResStr("IDS_CTL_RECORD"));
	AddElement(btnRecord);
#ifdef WITH_AUTOMATIC_UPDATE
	btnUpdate = new C4GUI::CallbackButton<C4StartupNetDlg, C4GUI::IconButton>(C4GUI::Ico_Ex_Update, caConfigArea.GetFromTop(iIconSize, iIconSize), '\0', &C4StartupNetDlg::OnBtnUpdate);
	btnUpdate->SetVisibility(false); // update only available if masterserver notifies it
	btnUpdate->SetToolTip(LoadResStr("IDS_DLGTIP_UPDATE"));
	btnUpdate->SetText(LoadResStr("IDS_CTL_UPDATE"));
	AddElement(btnUpdate);
#endif

	// button area
	C4GUI::CallbackButton<C4StartupNetDlg> *btn;
	AddElement(btn = new C4GUI::CallbackButton<C4StartupNetDlg>(LoadResStr("IDS_BTN_BACK"), caButtons.GetFromLeft(iButtonWidth), &C4StartupNetDlg::OnBackBtn));
	btn->SetToolTip(LoadResStr("IDS_DLGTIP_BACKMAIN"));
	AddElement(btnRefresh = new C4GUI::CallbackButton<C4StartupNetDlg>(LoadResStr("IDS_BTN_RELOAD"), caButtons.GetFromLeft(iButtonWidth), &C4StartupNetDlg::OnRefreshBtn));
	btnRefresh->SetToolTip(LoadResStr("IDS_NET_RELOAD_DESC"));
	AddElement(btnJoin = new C4GUI::CallbackButton<C4StartupNetDlg>(LoadResStr("IDS_NET_JOINGAME_BTN"), caButtons.GetFromLeft(iButtonWidth), &C4StartupNetDlg::OnJoinGameBtn));
	btnJoin->SetToolTip(LoadResStr("IDS_NET_JOINGAME_DESC"));
	AddElement(btn = new C4GUI::CallbackButton<C4StartupNetDlg>(LoadResStr("IDS_NET_NEWGAME"), caButtons.GetFromLeft(iButtonWidth), &C4StartupNetDlg::OnCreateGameBtn));
	btn->SetToolTip(LoadResStr("IDS_NET_NEWGAME_DESC"));

	// initial dlg mode
	UpdateDlgMode();

	// initial focus
	SetFocus(GetDlgModeFocusControl(), false);

	// initialize discovery
	DiscoverClient.Init(Config.Network.PortDiscovery);
	DiscoverClient.StartDiscovery();
	iGameDiscoverInterval = C4NetGameDiscoveryInterval;

	// register timer
	Application.Add(this);

	// register as receiver of reference notifies
	Application.InteractiveThread.SetCallback(Ev_HTTP_Response, this);

}

C4StartupNetDlg::~C4StartupNetDlg()
{
	// disable notifies
	Application.InteractiveThread.ClearCallback(Ev_HTTP_Response, this);
	Application.InteractiveThread.RemoveProc(&pUpdateClient);

	DiscoverClient.Close();
	Application.Remove(this);
	if (pMasterserverClient) delete pMasterserverClient;
	// dtor
	delete pKeyBack;
	delete pKeyRefresh;
}

void C4StartupNetDlg::DrawElement(C4TargetFacet &cgo)
{
	// draw background
	typedef C4GUI::FullscreenDialog Base;
	Base::DrawElement(cgo);
}

void C4StartupNetDlg::OnShown()
{
	// callback when shown: Start searching for games
	C4StartupDlg::OnShown();
	CheckVersionUpdate();
	UpdateList();
	UpdateUpdateButton(); // in case update check was finished before callback registration
	UpdateMasterserver();
	OnSec1Timer();
	tLastRefresh = time(nullptr);
	// also update chat
	if (pChatCtrl) pChatCtrl->OnShown();
}

void C4StartupNetDlg::OnClosed(bool fOK)
{
	// dlg abort: return to main screen
	if (pMasterserverClient) { delete pMasterserverClient; pMasterserverClient=nullptr; }
	if (!fOK) DoBack();
}

C4GUI::Control *C4StartupNetDlg::GetDefaultControl()
{
	// default control depends on whether dlg is in chat or game list mode
	if (GetDlgMode() == SNDM_Chat && pChatCtrl)
		// chat mode: Chat input edit
		return pChatCtrl->GetDefaultControl();
	else
		// game list mode: No default control, because it would move focus away from IP input edit
		return nullptr;
}

C4GUI::Control *C4StartupNetDlg::GetDlgModeFocusControl()
{
	// default control depends on whether dlg is in chat or game list mode
	if (GetDlgMode() == SNDM_Chat && pChatCtrl)
		// chat mode: Chat input edit
		return pChatCtrl->GetDefaultControl();
	else
		// game list mode: Game list box
		return pGameSelList;
}

void C4StartupNetDlg::OnBtnGameList(C4GUI::Control *btn)
{
	// switch to game list dialog
	pMainTabular->SelectSheet(SNDM_GameList, true);
	UpdateDlgMode();
}

void C4StartupNetDlg::OnBtnChat(C4GUI::Control *btn)
{
	// toggle chat / game list
	if (pChatCtrl)
	{
		if (pMainTabular->GetActiveSheetIndex() == SNDM_GameList)
		{
			pMainTabular->SelectSheet(SNDM_Chat, true);
			pChatCtrl->OnShown();
			UpdateDlgMode();
		}
		else
		{
			pMainTabular->SelectSheet(SNDM_GameList, true);
			UpdateDlgMode();
		}
	}
}

void C4StartupNetDlg::OnBtnInternet(C4GUI::Control *btn)
{
	// toggle masterserver game search
	Config.Network.MasterServerSignUp = !Config.Network.MasterServerSignUp;
	UpdateMasterserver();
}

void C4StartupNetDlg::OnBtnRecord(C4GUI::Control *btn)
{
	// toggle league signup flag
	bool fCheck = !Game.Record;
	Game.Record = fCheck;
	Config.General.DefRec = fCheck;
	btnRecord->SetIcon(fCheck ? C4GUI::Ico_Ex_RecordOn : C4GUI::Ico_Ex_RecordOff);
}

#ifdef WITH_AUTOMATIC_UPDATE
void C4StartupNetDlg::OnBtnUpdate(C4GUI::Control *btn)
{
	// do update
	if (!C4UpdateDlg::DoUpdate(UpdateURL.getData(), GetScreen()))
	{
		GetScreen()->ShowMessage(LoadResStr("IDS_MSG_UPDATEFAILED"), LoadResStr("IDS_TYPE_UPDATE"), C4GUI::Ico_Ex_Update);
	}
}
#endif

void C4StartupNetDlg::UpdateMasterserver()
{
	// update button icon to current state
	btnInternet->SetIcon(Config.Network.MasterServerSignUp ? C4GUI::Ico_Ex_InternetOn : C4GUI::Ico_Ex_InternetOff);
	// creates masterserver object if masterserver is enabled; destroy otherwise
	if (!Config.Network.MasterServerSignUp == !pMasterserverClient) return;
	if (!Config.Network.MasterServerSignUp)
	{
		delete pMasterserverClient;
		pMasterserverClient = nullptr;
	}
	else
	{
		pMasterserverClient = new C4StartupNetListEntry(pGameSelList, nullptr, this);
		StdStrBuf strVersion; strVersion.Format("%d.%d", C4XVER1, C4XVER2);
		StdStrBuf strQuery; strQuery.Format("%s?version=%s&platform=%s", Config.Network.GetLeagueServerAddress(), strVersion.getData(), C4_OS);
		pMasterserverClient->SetRefQuery(strQuery.getData(), C4StartupNetListEntry::NRQT_Masterserver);
	}
}

void C4StartupNetDlg::UpdateList(bool fGotReference)
{
	// recursion check
	if (fUpdatingList) return;
	fUpdatingList = true;
	pGameSelList->FreezeScrolling();
	// Games display mask
	const char *szGameMask = pSearchFieldEdt->GetText();
	if (!szGameMask) szGameMask = "";
	// Update all child entries
	bool fAnyRemoval = false;
	C4GUI::Element *pElem, *pNextElem = pGameSelList->GetFirst();
	while ((pElem=pNextElem))
	{
		pNextElem = pElem->GetNext(); // determine next exec element now - execution
		C4StartupNetListEntry *pEntry = static_cast<C4StartupNetListEntry *>(pElem);
		// do item updates
		if(pEntry->GetReference()) pEntry->SetVisibility(pEntry->KeywordMatch(szGameMask));
		bool fKeepEntry = true;
		if (fGotReference)
			fKeepEntry = pEntry->OnReference();
		if (fKeepEntry)
			fKeepEntry = pEntry->Execute();
		// remove?
		if (!fKeepEntry)
		{
			// entry wishes to be removed
			// if the selected entry is being removed, the next entry should be selected (which might be the ref for a finished refquery)
			if (pGameSelList->GetSelectedItem() == pEntry)
				if (pEntry->GetNext())
				{
					pGameSelList->SelectEntry(pEntry->GetNext(), false);
				}
			delete pEntry;
			fAnyRemoval = true; // setting any removal will also update collapsed state of all entries; so no need to do updates because of selection change here
		}
	}

	// Add LAN games
	C4NetIO::addr_t Discover;
	while (DiscoverClient.PopDiscover(Discover))
	{
		StdStrBuf Address(Discover.ToString());
		AddReferenceQuery(Address.getData(), C4StartupNetListEntry::NRQT_GameDiscovery);
	}

	// check whether view needs to be collapsed or uncollapsed
	if (fIsCollapsed && fAnyRemoval)
	{
		// try uncollapsing
		fIsCollapsed = false;
		UpdateCollapsed();
		// if scrolling is still necessary, the view will be collapsed again immediately
	}
	if (!fIsCollapsed && pGameSelList->IsScrollingNecessary())
	{
		fIsCollapsed = true;
		UpdateCollapsed();
	}

	fUpdatingList = false;
	// done; selection might have changed
	pGameSelList->UnFreezeScrolling();
	UpdateSelection(false);
}

void C4StartupNetDlg::UpdateCollapsed()
{
	// update collapsed state for all child entries
	for (C4GUI::Element *pElem = pGameSelList->GetFirst(); pElem; pElem = pElem->GetNext())
	{
		C4StartupNetListEntry *pEntry = static_cast<C4StartupNetListEntry *>(pElem);
		pEntry->UpdateCollapsed(fIsCollapsed && pElem != pGameSelList->GetSelectedItem());
	}
}

void C4StartupNetDlg::UpdateSelection(bool fUpdateCollapsed)
{
	// not during list updates - list update call will do this
	if (fUpdatingList) return;
	// in collapsed view, updating the selection may uncollapse something
	if (fIsCollapsed && fUpdateCollapsed) UpdateCollapsed();
}

void C4StartupNetDlg::UpdateDlgMode()
{
	DlgMode eMode = GetDlgMode();
	// buttons for game joining only visible in game list mode
	btnInternet->SetVisibility(eMode == SNDM_GameList);
	btnRecord->SetVisibility(eMode == SNDM_GameList);
	btnJoin->SetVisibility(eMode == SNDM_GameList);
	btnRefresh->SetVisibility(eMode == SNDM_GameList);
	// focus update
	if (!GetFocus()) SetFocus(GetDlgModeFocusControl(), false);
}

C4StartupNetDlg::DlgMode C4StartupNetDlg::GetDlgMode()
{
	// dlg mode determined by active tabular sheet
	if (pMainTabular->GetActiveSheetIndex() == SNDM_Chat) return SNDM_Chat; else return SNDM_GameList;
}

void C4StartupNetDlg::OnThreadEvent(C4InteractiveEventType eEvent, void *pEventData)
{
	UpdateUpdateButton();
	UpdateList(true);
}

void C4StartupNetDlg::UpdateUpdateButton()
{
	if (!fUpdateCheckPending) return;
	if(!pUpdateClient.isSuccess() || pUpdateClient.isBusy()) return;

	pUpdateClient.SetNotify(nullptr);

	StdCopyStrBuf versionInfo;

	pUpdateClient.GetVersion(&versionInfo);
	pUpdateClient.GetUpdateURL(&UpdateURL);

#ifdef WITH_AUTOMATIC_UPDATE
	btnUpdate->SetVisibility(C4UpdateDlg::IsValidUpdate(versionInfo.getData()));
#endif
	fUpdateCheckPending = false;
}

bool C4StartupNetDlg::DoOK()
{
	// OK in chat mode? Forward to chat control
	if (GetDlgMode() == SNDM_Chat) return pChatCtrl->DlgEnter();
	// OK on editbox with text enetered: Add the specified IP for reference retrieval
	if (GetFocus() == pJoinAddressEdt)
	{
		const char *szDirectJoinAddress = pJoinAddressEdt->GetText();
		if (szDirectJoinAddress && *szDirectJoinAddress)
		{
			// First do some acrobatics to avoid trying to resolve addresses with leading
			// or trailing whitespace, which is easily pasted in with an IP address.
			// We can trivially skip whitespace at the beginning, but we need a copy to
			// omit whitespace at the end.
			while (std::isspace(*szDirectJoinAddress))
				// skip whitespace at the beginning
				++szDirectJoinAddress;
			if (!*szDirectJoinAddress)
				// entry empty, apart from whitespace
				return true;
			const char *szDirectJoinAddressEnd = szDirectJoinAddress + std::strlen(szDirectJoinAddress) - 1;
			while (std::isspace(*szDirectJoinAddressEnd))
				// skip whitespace at the end
				--szDirectJoinAddressEnd;
			if (*++szDirectJoinAddressEnd)
			{
				// Make a temporary copy of the part that is not trailing whitespace, if any
				std::string strDirectJoinAddressStripped(szDirectJoinAddress, szDirectJoinAddressEnd - szDirectJoinAddress);
				AddReferenceQuery(strDirectJoinAddressStripped.c_str(), C4StartupNetListEntry::NRQT_DirectJoin);
			}
			else
				AddReferenceQuery(szDirectJoinAddress, C4StartupNetListEntry::NRQT_DirectJoin);
			// Switch focus to list so another OK joins the specified address
			SetFocus(pGameSelList, true);
			return true;
		}
	}
	if (GetFocus() == pSearchFieldEdt)
	{
		UpdateList();
		return true;
	}
	// get currently selected item
	C4GUI::Element *pSelection = pGameSelList->GetSelectedItem();
	StdCopyStrBuf strNoJoin(LoadResStr("IDS_NET_NOJOIN"));
	if (!pSelection)
	{
		// no ref selected: Oh noes!
		::pGUI->ShowMessageModal(
		  LoadResStr("IDS_NET_NOJOIN_NOREF"),
		  strNoJoin.getData(),
		  C4GUI::MessageDialog::btnOK,
		  C4GUI::Ico_Error);
		return true;
	}
	C4StartupNetListEntry *pRefEntry = static_cast<C4StartupNetListEntry *>(pSelection);
	const char *szError;
	if ((szError = pRefEntry->GetError()))
	{
		// erroneous ref selected: Oh noes!
		::pGUI->ShowMessageModal(
		  FormatString(LoadResStr("IDS_NET_NOJOIN_BADREF"), szError).getData(),
		  strNoJoin.getData(),
		  C4GUI::MessageDialog::btnOK,
		  C4GUI::Ico_Error);
		return true;
	}
	C4Network2Reference *pRef = pRefEntry->GetReference();
	const char *szDirectJoinAddress = pRefEntry->GetJoinAddress();
	if (!pRef && !(szDirectJoinAddress && *szDirectJoinAddress))
	{
		// something strange has been selected (e.g., a masterserver entry). Error.
		::pGUI->ShowMessageModal(
		  LoadResStr("IDS_NET_NOJOIN_NOREF"),
		  strNoJoin.getData(),
		  C4GUI::MessageDialog::btnOK,
		  C4GUI::Ico_Error);
		return true;
	}
	// check if join to this reference is possible at all
	if (pRef)
	{
		// version mismatch
		C4GameVersion verThis;
		if (!(pRef->getGameVersion() == verThis))
		{
			::pGUI->ShowMessageModal(
			  FormatString(LoadResStr("IDS_NET_NOJOIN_BADVER"),
			               pRef->getGameVersion().GetString().getData(),
			               verThis.GetString().getData()).getData(),
			  strNoJoin.getData(),
			  C4GUI::MessageDialog::btnOK,
			  C4GUI::Ico_Error);
			return true;
		}
		if (pRef->getGameStatus().isPastLobby())
		{
			// no runtime join
			if (!pRef->isJoinAllowed())
			{
				if (!::pGUI->ShowMessageModal(
					  LoadResStr("IDS_NET_NOJOIN_NORUNTIME"),
					  strNoJoin.getData(),
					  C4GUI::MessageDialog::btnYes | C4GUI::MessageDialog::btnNo,
					  C4GUI::Ico_Error))
				{
					return true;
				}
			}
			else
			{
				if (!::pGUI->ShowMessageModal(
					  LoadResStr("IDS_NET_NOJOIN_RUNTIMEBROKEN"),
					  strNoJoin.getData(),
					  C4GUI::MessageDialog::btnYes | C4GUI::MessageDialog::btnNo,
					  C4GUI::Ico_Error))
				{
					return true;
				}
			}
		}
	}
	// OK; joining!
	if (pRef->isEditor())
	{
		bool success = false;
		// Editor mode join: Serialize reference to temp file and join on that
		// (could pass through environment, but that's hard to do platform-independent
		// (QProcessEnvironment? But then there's a Qt dependency in the network init code))
		StdStrBuf tmpfn(Config.AtTempPath("ocjoin"), true);
		MakeTempFilename(&tmpfn);
		StdBuf join_data = DecompileToBuf<StdCompilerBinWrite>(*pRef);
		if (join_data.getSize())
		{
			if (join_data.SaveToFile(tmpfn.getData()))
			{
				if (RestartApplication({"--editor", FormatString("--join=%s%s", C4Game::DirectJoinFilePrefix, tmpfn.getData()).getData()})) // hope for no " in temp path
				{
					// Application.Quit() has been called. Will quit after returning from this callback.
					// The temp file will be deleted by the new instance
					success = true;
				}
				else
				{
					EraseFile(tmpfn.getData());
				}
			}
		}
		if (!success)
		{
			C4GUI::TheScreen.ShowErrorMessage(LoadResStr("IDS_ERR_STARTEDITOR"));
		}
		return true;
	}
	else
	{
		// Player mode join
		// Take over reference
		pRefEntry->GrabReference();
		// Set join parameters
		*Game.ScenarioFilename = '\0';
		if (szDirectJoinAddress) SCopy(szDirectJoinAddress, Game.DirectJoinAddress, _MAX_PATH); else *Game.DirectJoinAddress = '\0';
		SCopy("Objects.ocd", Game.DefinitionFilenames);
		Game.NetworkActive = true;
		Game.fObserve = false;
		Game.pJoinReference.reset(pRef);
		// start with this set!
		Application.OpenGame();
		return true;
	}
}

bool C4StartupNetDlg::DoBack()
{
	// abort dialog: Back to main
	C4Startup::Get()->SwitchDialog(C4Startup::SDID_Back);
	return true;
}

void C4StartupNetDlg::DoRefresh()
{
	// check min refresh timer
	time_t tNow = time(nullptr);
	if (tLastRefresh && tNow < tLastRefresh + C4NetMinRefreshInterval)
	{
		// avoid hammering on refresh key
		C4GUI::GUISound("UI::Error");
		return;
	}
	tLastRefresh = tNow;
	// empty list of all old entries
	fUpdatingList = true;
	while (pGameSelList->GetFirst()) delete pGameSelList->GetFirst();
	pMasterserverClient=nullptr;
	// (Re-)Start discovery
	if (!DiscoverClient.StartDiscovery())
	{
		StdCopyStrBuf strNoDiscovery(LoadResStr("IDS_NET_NODISCOVERY"));
		::pGUI->ShowMessageModal(
		  FormatString(LoadResStr("IDS_NET_NODISCOVERY_DESC"), DiscoverClient.GetError()).getData(),
		  strNoDiscovery.getData(),
		  C4GUI::MessageDialog::btnAbort,
		  C4GUI::Ico_Error);
	}
	iGameDiscoverInterval = C4NetGameDiscoveryInterval;
	// restart masterserver query
	UpdateMasterserver();
	// done; update stuff
	fUpdatingList = false;
	UpdateList();
}

void C4StartupNetDlg::CreateGame()
{
	C4Startup::Get()->SwitchDialog(C4Startup::SDID_ScenSelNetwork);
}

void C4StartupNetDlg::OnSec1Timer()
{
	// no updates if dialog is inactive (e.g., because a join password dlg is shown!)
	if (!IsActive(true))
		return;

	// Execute discovery
	if (!iGameDiscoverInterval--)
	{
		DiscoverClient.StartDiscovery();
		iGameDiscoverInterval = C4NetGameDiscoveryInterval;
	}
	DiscoverClient.Execute(0);

	UpdateList(false);
}

void C4StartupNetDlg::AddReferenceQuery(const char *szAddress, C4StartupNetListEntry::QueryType eQueryType)
{
	// Check for an active reference query to the same address
	for (C4GUI::Element *pElem = pGameSelList->GetFirst(); pElem; pElem = pElem->GetNext())
	{
		C4StartupNetListEntry *pEntry = static_cast<C4StartupNetListEntry *>(pElem);
		// same address
		if (pEntry->IsSameRefQueryAddress(szAddress))
		{
			// nothing to do, xcept maybe select it
			if (eQueryType == C4StartupNetListEntry::NRQT_DirectJoin) pGameSelList->SelectEntry(pEntry, true);
			return;
		}
	}
	// No reference from same host found - create a new entry
	C4StartupNetListEntry *pEntry = new C4StartupNetListEntry(pGameSelList, nullptr, this);
	pEntry->SetRefQuery(szAddress, eQueryType);
	if (eQueryType == C4StartupNetListEntry::NRQT_DirectJoin)
		pGameSelList->SelectEntry(pEntry, true);
	else if (fIsCollapsed)
		pEntry->UpdateCollapsed(true);
}

void C4StartupNetDlg::OnReferenceEntryAdd(C4StartupNetListEntry *pEntry)
{
	// collapse the new entry if desired
	if (fIsCollapsed && pEntry != pGameSelList->GetSelectedItem())
		pEntry->UpdateCollapsed(true);
}

void C4StartupNetDlg::CheckVersionUpdate()
{
#ifdef WITH_AUTOMATIC_UPDATE
	StdStrBuf strVersion; strVersion.Format("%d.%d", C4XVER1, C4XVER2);
	StdStrBuf strQuery; strQuery.Format("%s?version=%s&platform=%s&action=version", Config.Network.UpdateServerAddress, strVersion.getData(), C4_OS);

	if (pUpdateClient.Init() && pUpdateClient.SetServer(strQuery.getData()) && pUpdateClient.QueryUpdateURL())
	{
		pUpdateClient.SetNotify(&Application.InteractiveThread);
		Application.InteractiveThread.AddProc(&pUpdateClient);
	}
	fUpdateCheckPending = true;
#endif
}

void C4StartupNetDlg::OnChatTitleChange(const StdStrBuf &sNewTitle)
{
	// update label
	if (pChatTitleLabel) pChatTitleLabel->SetText(FormatString("%s - %s", LoadResStr("IDS_DLG_CHAT"), sNewTitle.getData()).getData());
}
