//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================

#include "ServerInfoPanel.h"
#include "MapCycleEditDialog.h"

#include <vgui/ISystem.h>

#include <ctype.h>
#include <stdio.h>

using namespace vgui;

//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CServerInfoPanel::CServerInfoPanel(vgui::Panel *parent, const char *name) : CVarListPropertyPage(parent, name)
{
	LoadControlSettings("Admin/GamePanelInfo.res", "PLATFORM");
	LoadVarList("Admin/MainServerConfig.vdf");
	m_iLastUptimeDisplayed = 0;
	m_flUpdateTime = 0.0f;
	m_bMapListRetrieved = false;
	RemoteServer().AddServerMessageHandler(this, "UpdatePlayers");
	RemoteServer().AddServerMessageHandler(this, "UpdateMap");
}

//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
CServerInfoPanel::~CServerInfoPanel()
{
	RemoteServer().RemoveServerDataResponseTarget(this);
}

//-----------------------------------------------------------------------------
// Purpose: gets the current hostname
//-----------------------------------------------------------------------------
const char *CServerInfoPanel::GetHostname()
{
	return GetVarString("hostname");
}

//-----------------------------------------------------------------------------
// Purpose: Refreshes page if necessary
//-----------------------------------------------------------------------------
void CServerInfoPanel::OnThink()
{
	if (m_flUpdateTime < vgui::system()->GetFrameTime())
	{
		OnResetData();
	}

	// check uptime count
	int time = m_iLastUptimeReceived + (int)(vgui::system()->GetFrameTime() - m_flLastUptimeReceiveTime);
	if (time != m_iLastUptimeDisplayed)
	{
		m_iLastUptimeDisplayed = time;
		char timeText[64];
		_snprintf(timeText, sizeof(timeText), "%0.1i:%0.2i:%0.2i:%0.2i", (time / 3600) / 24, (time / 3600), (time / 60) % 60, time % 60);
		SetControlString("UpTimeText", timeText);
	}
}

//-----------------------------------------------------------------------------
// Purpose: Data is be reloaded from document into controls.
//-----------------------------------------------------------------------------
void CServerInfoPanel::OnResetData()
{
	// only ask for maps list once
	if (!m_bMapListRetrieved)
	{
		RemoteServer().RequestValue(this, "maplist");
		m_bMapListRetrieved = true;
	}

	// refresh our vars
	RefreshVarList();

	// ask for the constant data
	RemoteServer().RequestValue(this, "playercount");
	RemoteServer().RequestValue(this, "maxplayers");
	RemoteServer().RequestValue(this, "gamedescription");
	RemoteServer().RequestValue(this, "uptime");
	RemoteServer().RequestValue(this, "ipaddress");

	// update once every minute
	m_flUpdateTime = (float)system()->GetFrameTime() + (60 * 1.0f);
}

//-----------------------------------------------------------------------------
// Purpose: handles responses from the server
//-----------------------------------------------------------------------------
void CServerInfoPanel::OnServerDataResponse(const char *value, const char *response)
{
	if (!stricmp(value, "playercount"))
	{
		m_iPlayerCount = atoi(response);
	}
	else if (!stricmp(value, "maxplayers"))
	{
		m_iMaxPlayers = atoi(response);

		// update control
		char buf[128];
		buf[0] = 0;
		if (m_iMaxPlayers > 0)
		{
			sprintf(buf, "%d / %d", m_iPlayerCount, m_iMaxPlayers);
		}
		SetControlString("PlayersText", buf);
	}
	else if (!stricmp(value, "gamedescription"))
	{
		SetControlString("GameText", response);
	}
	else if (!stricmp(value, "hostname"))
	{
		PostActionSignal(new KeyValues("UpdateTitle"));
	}
	else if (!stricmp(value, "UpdateMap") || !stricmp(value, "UpdatePlayers"))
	{
		// server has indicated a change, force an update
		m_flUpdateTime = 0.0f;
	}
	else if (!stricmp(value, "maplist"))
	{
		SetCustomStringList("map", response);
		// save off maplist for use in mapcycle editing
		ParseIntoMapList(response, m_AvailableMaps);
		// don't chain through, not in varlist
		return;
	}
	else if (!stricmp(value, "uptime"))
	{
		// record uptime for extrapolation
		m_iLastUptimeReceived = atoi(response);
		m_flLastUptimeReceiveTime = (float)system()->GetFrameTime();
	}
	else if (!stricmp(value, "ipaddress"))
	{
		SetControlString("ServerIPText", response);
	}
	else if (!stricmp(value, "mapcycle"))
	{
		ParseIntoMapList(response, m_MapCycle);
		UpdateMapCycleValue();
		// don't chain through, we set the value ourself
		return;
	}

	// always chain through, in case this variable is in the list as well
	BaseClass::OnServerDataResponse(value, response);

	// post update
	if (!stricmp(value, "map"))
	{
		// map has changed, update map cycle view
		UpdateMapCycleValue();
	}
}

//-----------------------------------------------------------------------------
// Purpose: special editing of map cycle list
//-----------------------------------------------------------------------------
void CServerInfoPanel::OnEditVariable(KeyValues *rule)
{
	if (!stricmp(rule->GetName(), "mapcycle"))
	{
		CMapCycleEditDialog *dlg = new CMapCycleEditDialog(this, "MapCycleEditDialog");
		dlg->Activate(this, m_AvailableMaps, m_MapCycle);
	}
	else
	{
		BaseClass::OnEditVariable(rule);
	}
}

//-----------------------------------------------------------------------------
// Purpose: Updates the value shown in the mapcycle field
//-----------------------------------------------------------------------------
void CServerInfoPanel::UpdateMapCycleValue()
{
	// look at current map
	CUtlSymbol currentMap = GetVarString("map");
	if (!currentMap.IsValid())
		return;

	// find it in the map cycle list
	int listPoint = -1;
	for (int i = 0; i < m_MapCycle.Count(); i++)
	{
		if (!stricmp(m_MapCycle[i].String(), currentMap.String()))
		{
			listPoint = i;
		}
	}

	// take out the next 2 maps and make them the value
	char nextMaps[512];
	nextMaps[0] = 0;
	bool needComma = false;
	for (int i = 0; i < 2; i++)
	{
		int point = listPoint + i + 1;
		if (point >= m_MapCycle.Count())
		{
			point -= m_MapCycle.Count();
		}

		if (m_MapCycle.IsValidIndex(point))
		{
			if (needComma)
			{
				strcat(nextMaps, ", ");
			}
			strcat(nextMaps, m_MapCycle[point].String());
			needComma = true;
		}
	}

	// add some elipses to show there is more maps
	if (needComma)
	{
		strcat(nextMaps, ", ");
	}
	strcat(nextMaps, "...");

	// show in varlist
	SetVarString("mapcycle", nextMaps);
}

//-----------------------------------------------------------------------------
// Purpose: Seperates out a newline-seperated map list into an array
//-----------------------------------------------------------------------------
void CServerInfoPanel::ParseIntoMapList(const char *maplist, CUtlVector<CUtlSymbol> &mapArray)
{
	mapArray.RemoveAll();

	const char *parse = maplist;
	while (*parse)
	{
		// newline-seperated map list
		//!! this should be done with a more standard tokenizer
		if (isspace(*parse))
		{
			parse++;
			continue;
		}

		// pull out the map name
		const char *end = strstr(parse, "\n");
		const char *end2 = strstr(parse, "\r");
		if (end && end2 && end2 < end)
		{
			end = end2;
		}
		if (!end)
			break;

		char customString[64];
		int nameSize = end - parse;
		if (nameSize >= sizeof(customString))
		{
			nameSize = sizeof(customString) - 1;
		}

		// copy in the name
		strncpy(customString, parse, nameSize);
		customString[nameSize] = 0;
		parse = end;

		// add to the list string that aren't comments
		if (nameSize > 0 && !(customString[0] == '/' && customString[1] == '/'))
		{
			int i = mapArray.AddToTail();
			mapArray[i] = customString;
		}
	}
}