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

#include <stdio.h>

#include "BanPanel.h"
#include "PlayerContextMenu.h"
#include "DialogAddBan.h"

#include <vgui/Cursor.h>
#include <vgui/ISystem.h>
#include <vgui/ISurface.h>
#include <vgui/ILocalize.h>
#include <vgui/IVGui.h>
#include <KeyValues.h>

#include <vgui_controls/Label.h>
#include <vgui_controls/Button.h>
#include <vgui_controls/QueryBox.h>
#include <vgui_controls/ListPanel.h>
#include <vgui_controls/FileOpenDialog.h>

#include "filesystem.h"

#include "DialogCvarChange.h"
#include "tokenline.h"

using namespace vgui;

//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CBanPanel::CBanPanel(vgui::Panel *parent, const char *name) : PropertyPage(parent, name)
{
	m_pBanListPanel = new ListPanel(this, "BanList");
	m_pBanListPanel->AddColumnHeader(0, "type", "#Ban_List_Type", 150 );
	m_pBanListPanel->AddColumnHeader(1, "id", "#Ban_List_ID", 200 );
	m_pBanListPanel->AddColumnHeader(2, "time", "#Ban_List_Time", 200 );
	m_pBanListPanel->SetSortColumn(2);
	m_pBanListPanel->SetEmptyListText("#Ban_List_Empty");

	m_pAddButton = new Button(this, "Add", "#Ban_List_Add");
	m_pRemoveButton = new Button(this, "Remove", "#Ban_List_Remove");
	m_pChangeButton = new Button(this, "Change", "#Ban_List_Edit");
	m_pImportButton = new Button(this, "Import", "#Ban_List_Import");

	m_pAddButton->SetCommand(new KeyValues("addban"));
	m_pRemoveButton->SetCommand(new KeyValues("removeban"));
	m_pChangeButton->SetCommand(new KeyValues("changeban"));
	m_pImportButton->SetCommand(new KeyValues("importban"));

	m_pBanContextMenu = new CBanContextMenu(this);
	m_pBanContextMenu->SetVisible(false);

	m_flUpdateTime = 0.0f;
	m_bPageViewed = false;

	LoadControlSettings("Admin/BanPanel.res", "PLATFORM");
}

//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
CBanPanel::~CBanPanel()
{
}

//-----------------------------------------------------------------------------
// Purpose: Activates the page
//-----------------------------------------------------------------------------
void CBanPanel::OnPageShow()
{
	BaseClass::OnPageShow();
	OnItemSelected();
	if (!m_bPageViewed)
	{
		m_bPageViewed = true;
		// force update on first page view
		m_flUpdateTime = 0.0f;
	}
}

//-----------------------------------------------------------------------------
// Purpose: Requests new data set from server
//-----------------------------------------------------------------------------
void CBanPanel::OnResetData()
{
	RemoteServer().RequestValue(this, "banlist");
	// update once every 5 minutes
	m_flUpdateTime = (float)system()->GetFrameTime() + (60 * 5.0f);
}

//-----------------------------------------------------------------------------
// Purpose: Checks to see if the page data should be refreshed
//-----------------------------------------------------------------------------
void CBanPanel::OnThink()
{
	if (m_flUpdateTime < system()->GetFrameTime())
	{
		OnResetData();
	}
}

//-----------------------------------------------------------------------------
// Purpose: Wrap g_pVGuiLocalize->Find() and not return NULL
//-----------------------------------------------------------------------------
static const wchar_t * LocalizeFind( const char *identifier, const wchar_t *defaultText )
{
	const wchar_t *str = g_pVGuiLocalize->Find(identifier);
	if ( !str )
		str = defaultText;
	return str;
}

//-----------------------------------------------------------------------------
// Purpose: Received response from server containing data
//-----------------------------------------------------------------------------
void CBanPanel::OnServerDataResponse(const char *value, const char *response)
{
	// build the list
	if (!stricmp(value, "banlist"))
	{
		// clear current list
		m_pBanListPanel->DeleteAllItems();

		// scan through response for all items
		int item = 0;
		float banTime = 0.0f;
		char id[64] = { 0 };
		while (3 == sscanf(response, "%i %s : %f min\n", &item, id, &banTime))
		{
			KeyValues *ban = new KeyValues("ban");

			// determine type
			if (IsIPAddress(id))
			{
				// ip address
				ban->SetWString("type", LocalizeFind("#Ban_IP", L"IP Address"));
			}
			else
			{
				// must be a userID
				ban->SetWString("type", LocalizeFind("#Ban_Auth_ID", L"AuthID"));
			}
			ban->SetString("id", id);

			if (banTime > 0.0f)
			{
				ban->SetFloat("time", banTime);
			}
			else
			{
				ban->SetWString("time", LocalizeFind("#Ban_Permanent", L"permanent"));
			}
			
			// add to list
			m_pBanListPanel->AddItem(ban, 0, false, false);

			// move to the next item
			response = (const char *)strchr(response, '\n');
			if (!response)
				break;
			response++;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Refreshes the list on the user hitting F5
//-----------------------------------------------------------------------------
void CBanPanel::OnKeyCodeTyped(vgui::KeyCode code)
{
	if (code == KEY_F5)
	{
		OnResetData();
	}
	else
	{
		BaseClass::OnKeyCodeTyped(code);
	}
}


//-----------------------------------------------------------------------------
// Purpose: opens context menu (user right clicked on a server)
//-----------------------------------------------------------------------------
void CBanPanel::OnOpenContextMenu(int row)
{
/* CONTEXT MENU CODE TEMPORARILY DISABLED UNTIL VERIFIED AS WORKING
	if (m_pBanListPanel->IsVisible() && m_pBanListPanel->IsCursorOver()
		&& m_pBanListPanel->GetNumSelectedRows())
	// show the ban changing menu IF its the visible panel and the cursor is
	// over it 
	{
	
		unsigned int banID =m_pBanListPanel->GetSelectedRow(0);
			
		// activate context menu
		m_pBanContextMenu->ShowMenu(this, banID);
	} 
	else
	{
		m_pBanContextMenu->ShowMenu(this, -1);
	}
*/
}

//-----------------------------------------------------------------------------
// Purpose: Manually adds a new ban
//-----------------------------------------------------------------------------
void CBanPanel::AddBan()
{
	CDialogAddBan *box = new CDialogAddBan(this);
	box->AddActionSignalTarget(this);
	box->Activate("addban", "", "");
}

//-----------------------------------------------------------------------------
// Purpose: prompts the user to remove an existing ban
//-----------------------------------------------------------------------------
void CBanPanel::RemoveBan()
{
	int itemID = m_pBanListPanel->GetSelectedItem(0);
	if ( itemID == -1 )
		return;

	// ask the user whether or not they want to remove the ban
	KeyValues *kv = m_pBanListPanel->GetItem(itemID);
	if (kv != NULL)
	{
		// build the message
		wchar_t id[256];
		g_pVGuiLocalize->ConvertANSIToUnicode(kv->GetString("id"), id, sizeof(id));
		wchar_t message[256];
		g_pVGuiLocalize->ConstructString(message, sizeof(message), g_pVGuiLocalize->Find("#Ban_Remove_Msg"), 1, id);

		// activate the confirmation dialog
		QueryBox *box = new QueryBox(g_pVGuiLocalize->Find("#Ban_Title_Remove"), message);
		box->SetOKCommand(new KeyValues("removebanbyid", "id", kv->GetString("id")));
		box->AddActionSignalTarget(this);
		box->DoModal();
	}
}

//-----------------------------------------------------------------------------
// Purpose: change the time length of a ban
//-----------------------------------------------------------------------------
void CBanPanel::ChangeBan()
{	
	int itemID = m_pBanListPanel->GetSelectedItem(0);
	if (itemID == -1)
		return;

	KeyValues *kv = m_pBanListPanel->GetItem(itemID);
	if (kv != NULL)
	{	
		char timeText[20];
		float time = kv->GetFloat("time");
		_snprintf(timeText, sizeof(timeText), "%0.2f", time);

		// open a dialog asking them what time to change the ban lenght to
		CDialogCvarChange *box = new CDialogCvarChange(this);
		box->AddActionSignalTarget(this);
		box->SetTitle("#Ban_Title_Change", true);
		box->Activate(kv->GetString("id"), timeText, "changeban", "#Ban_Change_Time");
	}
}

//-----------------------------------------------------------------------------
// Purpose: Removes the specified ban
//-----------------------------------------------------------------------------
void CBanPanel::RemoveBanByID(const char *id)
{
	Assert(id && *id);
	if (!id || !*id)
		return;

	// send down the command
	char cmd[512];
	_snprintf(cmd, sizeof(cmd) -1, "%s %s\n", IsIPAddress(id) ? "removeip" : "removeid", id);
	RemoteServer().SendCommand(cmd);

	// force the file to be written
	if (IsIPAddress(id))
	{
		RemoteServer().SendCommand("writeip");
	}
	else
	{
		RemoteServer().SendCommand("writeid");
	}

	// refresh
	OnResetData();
}

//-----------------------------------------------------------------------------
// Purpose: Changes a ban
//-----------------------------------------------------------------------------
void CBanPanel::ChangeBanTimeByID(const char *id, const char *newtime)
{
	Assert(id && *id);
	if (!id || !*id)
		return;

	// if the newtime string is not valid, then set it to 0 (permanent ban)
	if (!newtime || atof(newtime) < 0.001)
	{
		newtime = "0";
	}

	// send down the command
	char cmd[512];
	_snprintf(cmd, sizeof(cmd) -1, "%s %s %s\n", IsIPAddress(id) ? "addip" : "banid", newtime, id);
	RemoteServer().SendCommand(cmd);
	if (IsIPAddress(id))
	{
		RemoteServer().SendCommand("writeip");
	}
	else
	{
		RemoteServer().SendCommand("writeid");
	}

	// refresh
	OnResetData();
}

//-----------------------------------------------------------------------------
// Purpose: Changes a ban
//-----------------------------------------------------------------------------
void CBanPanel::OnCvarChangeValue( KeyValues *kv )
{
	const char *idText = kv->GetString( "player", "" );
	const char *durationText = kv->GetString( "value", "0" );

	ChangeBanTimeByID( idText, durationText );
}

//-----------------------------------------------------------------------------
// Purpose: called when a row on the list panel is selected.
//-----------------------------------------------------------------------------
void CBanPanel::OnItemSelected()
{
	int itemID = m_pBanListPanel->GetSelectedItem(0);
	if (itemID == -1)
	{
		m_pRemoveButton->SetEnabled(false);
		m_pChangeButton->SetEnabled(false);
	}
	else
	{
		m_pRemoveButton->SetEnabled(true);
		m_pChangeButton->SetEnabled(true);
	}
}

//-----------------------------------------------------------------------------
// Purpose: Asks the user for the ban file to import
//-----------------------------------------------------------------------------
void CBanPanel::ImportBanList()
{
	// Pop up the dialog
	FileOpenDialog *pFileDialog = new FileOpenDialog(this, "#Ban_Find_Ban_File", true);
	pFileDialog->AddFilter( "*.cfg", "#Config_files", true );
	pFileDialog->AddFilter( "*.*", "#All_files", false );
	pFileDialog->DoModal(true);
	pFileDialog->Activate();
}

//-----------------------------------------------------------------------------
// Purpose: When a file is selected print out its full path in the debugger
//-----------------------------------------------------------------------------
void CBanPanel::OnFileSelected(const char *fullpath)
{
	char line[255];
	TokenLine tok;

	// this can take a while, put up a waiting cursor
	surface()->SetCursor(dc_hourglass);

	// we don't use filesystem() here becuase we want to let the user pick
	// a file from anywhere on their filesystem... so we use stdio
	FILE *f = fopen(fullpath,"rb");
	while (!feof(f) && fgets(line, 255, f))
	{	
		// parse each line of the config file adding the ban
		tok.SetLine(line);
		if (tok.CountToken() == 3)
		{
			// add the ban
			const char *id = tok.GetToken(2);
			ChangeBanTimeByID(id, "0");
		}
	}

	// change the cursor back to normal and shutdown file
	surface()->SetCursor(dc_user);
	if (f) 
	{
		fclose(f);
	}
}

//-----------------------------------------------------------------------------
// Purpose: returns true if the id string is an IP address, false if it's a WON or STEAM ID
//-----------------------------------------------------------------------------
bool CBanPanel::IsIPAddress(const char *id)
{
	int s1, s2, s3, s4;
	return (4 == sscanf(id, "%d.%d.%d.%d", &s1, &s2, &s3, &s4));
}