/*
 * OpenClonk, http://www.openclonk.org
 *
 * Copyright (c) 2006-2007  Sven Eberhardt
 * Copyright (c) 2006, 2008-2010  Günther Brammer
 * Copyright (c) 2006-2007  Peter Wortmann
 * Copyright (c) 2006  Florian Groß
 * Copyright (c) 2007-2008  Matthes Bender
 * Copyright (c) 2010  Benjamin Herr
 * Copyright (c) 2010  Julius Michaelis
 * Copyright (c) 2010  Tobias Zwick
 * Copyright (c) 2010  Armin Burgmeier
 * Copyright (c) 2006-2009, RedWolf Design GmbH, http://www.clonk.de

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

"Clonk" is a registered trademark of Matthes Bender. */
// Startup screen for non-parameterized engine start: Network game selection dialog

#include <C4Include.h>
#include <C4StartupNetDlg.h>

#include <C4UpdateDlg.h>
#include <C4StartupScenSelDlg.h>
#include <C4StartupMainDlg.h>
#include <C4Game.h>
#include <C4Log.h>
#include "C4ChatDlg.h"
#include <C4GraphicsResource.h>

#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif

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

C4StartupNetListEntry::C4StartupNetListEntry(C4GUI::ListBox *pForListBox, C4GUI::Element *pInsertBefore, C4StartupNetDlg *pNetDlg)
		: pNetDlg(pNetDlg), pList(pForListBox), pRefClient(NULL), pRef(NULL), 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 (int32_t iIcon = 0; iIcon<MaxInfoIconCount; ++iIcon)
	{
		AddElement(pInfoIcons[iIcon] = 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 = NULL;
	}
	if (pRef) { delete pRef; pRef = NULL; }
	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
	((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(NULL) + 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(NULL) >= iTimeout)
		{
			// timeout!
			// for internet servers, this means refresh needed - search anew!
			if (pRefClient && eQueryType == NRQT_Masterserver)
			{
				fError = false;
				sError.Clear();
				((C4Facet &) pIcon->GetFacet()) = (const C4Facet &) 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(NULL) >= 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=NULL; 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 is official: So check version
		StdStrBuf updURL;
		StdStrBuf versInf;
		if (pRefClient->GetUpdateURL(&updURL) && pRefClient->GetVersion(&versInf))
			pNetDlg->CheckVersionUpdate(updURL.getData(),versInf.getData());
		// 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)
{
	// already have a next element? use this.
	//if (GetNext()) return GetNext();
	// 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 NULL;
}


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(NULL);
	}
	// 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->Parameters.PlayerInfos.GetActivePlayerCount(false);
	C4Client *pHost = pRef->Parameters.Clients.getHost();
	sInfoText[0].Format(LoadResStr("IDS_NET_REFONCLIENT"), pRef->getTitle(), pHost ? pHost->getName() : "unknown");
	sInfoText[1].Format( LoadResStr("IDS_NET_INFOPLRSGOALDESC"),
	                     (int)iPlrCnt,
	                     (int)pRef->Parameters.MaxPlayers,
	                     pRef->Parameters.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());
	}
	// 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 names
	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(NULL) + 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 NULL;
	// not masterservers (cannot join directly on clonk.de)
	if (eQueryType == NRQT_Masterserver) return NULL;
	// return join address
	return pRefClient->getServerName();
}

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




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

C4StartupNetDlg::C4StartupNetDlg() : C4StartupDlg(LoadResStr("IDS_DLG_NETSTART")), pChatTitleLabel(NULL), pMasterserverClient(NULL), fIsCollapsed(false), fUpdatingList(false), iGameDiscoverInterval(0), tLastRefresh(0)
{
	// ctor
	// key bindings
	C4CustomKey::CodeList keys;
	keys.push_back(C4KeyCodeEx(K_BACK)); keys.push_back(C4KeyCodeEx(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);
	//pKeyForward = new C4KeyBinding(C4KeyCodeEx(K_RIGHT), "StartupNetNext", KEYSCOPE_Gui, - blocks editbox
	//< new C4GUI::DlgKeyCB<C4StartupNetDlg>(*this, &C4StartupNetDlg::KeyForward), C4CustomKey::PRIO_CtrlOverride);

	// screen calculations
	UpdateSize();
	int32_t iIconSize = C4GUI_IconExWdt;
	int32_t iButtonWidth,iCaptionFontHgt, iSideSize = 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 = 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), Min<int32_t>(caMain.GetWidth()/20, (iSideSize-C4GUI_IconExWdt)/2), caMain.GetHeight()/40);
	C4GUI::ComponentAligner caConfigArea(caMain.GetFromRight(iSideSize), 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 = NULL;

	// 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(NULL);
	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);
	//const char *szGameSelListTip = LoadResStr("IDS_NET_GAMELIST_INFO"); disabled this tooltip, it's mainly disturbing when browsing the list
	//pGameListLbl->SetToolTip(szGameSelListTip);
	pSheetGameList->AddElement(pGameListLbl);
	pGameSelList = new C4GUI::ListBox(caGameList.GetFromTop(caGameList.GetHeight() - iCaptHgt));
	//pGameSelList->SetToolTip(szGameSelListTip);
	pGameSelList->SetDecoration(true, NULL, 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(NULL);
		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);
	DiscoverClient.Close();
	Application.Remove(this);
	if (pMasterserverClient) delete pMasterserverClient;
	// dtor
	//delete pKeyForward;
	delete pKeyBack;
	delete pKeyRefresh;
}

void C4StartupNetDlg::DrawElement(C4TargetFacet &cgo)
{
	// draw background
	DrawBackground(cgo, C4Startup::Get()->Graphics.fctNetBG);
}

void C4StartupNetDlg::OnShown()
{
	// callback when shown: Start searching for games
	C4StartupDlg::OnShown();
	UpdateList();
	UpdateMasterserver();
	OnSec1Timer();
	tLastRefresh = time(0);
	// also update chat
	if (pChatCtrl) pChatCtrl->OnShown();
}

void C4StartupNetDlg::OnClosed(bool fOK)
{
	// dlg abort: return to main screen
	if (pMasterserverClient) { delete pMasterserverClient; pMasterserverClient=NULL; }
	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 NULL;
}

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 = Config.General.DefRec = Game.Record = !Game.Record;
	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 = NULL;
	}
	else
	{
		pMasterserverClient = new C4StartupNetListEntry(pGameSelList, NULL, this);
		StdStrBuf strVersion; strVersion.Format("%d.%d.%d", C4XVER1, C4XVER2, C4XVER3);
		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;
		Address.Format("%s:%d", inet_ntoa(Discover.sin_addr), htons(Discover.sin_port));
		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();
	// 2do
	// no selection: join button disabled
	//pJoinBtn->SetEnabled(false);
}

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)
{
	UpdateList(true);
}

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;
		}
		// 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;
			}
		}
	}
	// OK; joining! 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 = 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(0);
	if (tLastRefresh && tNow < tLastRefresh + C4NetMinRefreshInterval)
	{
		// avoid hammering on refresh key
		C4GUI::GUISound("Error");
		return;
	}
	tLastRefresh = tNow;
	// empty list of all old entries
	fUpdatingList = true;
	while (pGameSelList->GetFirst()) delete pGameSelList->GetFirst();
	pMasterserverClient=NULL;
	// (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, NULL, 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(const char *szUpdateURL, const char *szVersion)
{
#ifdef WITH_AUTOMATIC_UPDATE
	// Is a valid update
	if (C4UpdateDlg::IsValidUpdate(szVersion))
	{
		UpdateURL = szUpdateURL;
		btnUpdate->SetVisibility(true);
	}
	// Otherwise: no update available
	else
	{
		btnUpdate->SetVisibility(false);
	}
#else
	if(szUpdateURL && *szUpdateURL)
	{
		// TODO: We could show an item notifying the user that an
		// update is available externally.
	}
#endif
}

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