//////////////////////////////////////////////////////////////////////////
//
// pgAdmin III - PostgreSQL Tools
//
// Copyright (C) 2002 - 2016, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// dlgHbaConfig.cpp - Configure setting
//
//////////////////////////////////////////////////////////////////////////



// App headers
#include "pgAdmin3.h"

#include "dlg/dlgHbaConfig.h"
#include "db/pgConn.h"
#include "db/pgSet.h"
#include "utils/pgconfig.h"


// Icons
#include "images/property.pngc"





BEGIN_EVENT_TABLE(dlgHbaConfig, DialogWithHelp)
EVT_BUTTON(wxID_OK, dlgHbaConfig::OnOK)
EVT_BUTTON(wxID_CANCEL, dlgHbaConfig::OnCancel)
EVT_BUTTON(wxID_REFRESH, dlgHbaConfig::OnAddValue)
EVT_CHECKBOX(XRCID("chkEnabled"), dlgHbaConfig::OnChange)
EVT_COMBOBOX(XRCID("cbType"), dlgHbaConfig::OnChange)
EVT_TEXT(XRCID("cbDatabase"), dlgHbaConfig::OnChange)
EVT_TEXT(XRCID("cbUser"), dlgHbaConfig::OnChange)
EVT_COMBOBOX(XRCID("cbDatabase"), dlgHbaConfig::OnAddDatabase)
EVT_COMBOBOX(XRCID("cbUser"), dlgHbaConfig::OnAddUser)
EVT_COMBOBOX(XRCID("cbMethod"), dlgHbaConfig::OnChange)
EVT_TEXT(XRCID("txtIPaddress"), dlgHbaConfig::OnChange)
EVT_TEXT(XRCID("txtOption"), dlgHbaConfig::OnChange)
END_EVENT_TABLE()


#define chkEnabled          CTRL_CHECKBOX("chkEnabled")
#define cbType              CTRL_COMBOBOX("cbType")
#define cbDatabase          CTRL_COMBOBOX("cbDatabase")
#define cbUser              CTRL_COMBOBOX("cbUser")
#define cbMethod            CTRL_COMBOBOX("cbMethod")
#define txtIPaddress        CTRL_TEXT("txtIPaddress")
#define txtOption           CTRL_TEXT("txtOption")
#define stIPaddress         CTRL_STATIC("stIPaddress")
#define stOption            CTRL_STATIC("stOption")

extern const wxChar* pgHbaMethodStrings[];
dlgHbaConfig::dlgHbaConfig(pgFrame *parent, pgHbaConfigLine *_line, pgConn *_conn) :
	DialogWithHelp((frmMain *)parent)
{
	SetFont(settings->GetSystemFont());
	LoadResource((wxWindow *)parent, wxT("dlgHbaConfig"));

	conn = _conn;

	userAdding = databaseAdding = false;

	// Icon
	SetIcon(*property_png_ico);
	RestorePosition();

	line = _line;

	cbType->Append(wxT("local"));
	cbType->Append(wxT("host"));
	cbType->Append(wxT("hostssl"));
	cbType->Append(wxT("hostnossl"));

	cbDatabase->Append(wxT("all"));
	cbDatabase->Append(wxT("sameuser"));
	cbDatabase->Append(wxT("@<filename>"));
	if (conn)
	{
		// role is supported from 8.1
		if (conn->BackendMinimumVersion(8, 1))
			cbDatabase->Append(wxT("samerole"));
		else
			cbDatabase->Append(wxT("samegroup"));

		// replication is supported from 9.0
		if (conn->BackendMinimumVersion(9, 0))
			cbDatabase->Append(wxT("replication"));
	}
	else
	{
		cbDatabase->Append(wxT("samegroup"));
		cbDatabase->Append(wxT("samerole"));
		cbDatabase->Append(wxT("replication"));
	}

	cbUser->Append(wxT("all"));

	cbMethod->Append(wxT("trust"));
	cbMethod->Append(wxT("reject"));
	cbMethod->Append(wxT("md5"));
	cbMethod->Append(wxT("password"));
	cbMethod->Append(wxT("krb4"));
	cbMethod->Append(wxT("krb5"));
	cbMethod->Append(wxT("ident"));
	cbMethod->Append(wxT("pam"));

	if (conn)
	{

		// LDAP is supported from 8.2
		if (conn->BackendMinimumVersion(8, 2))
			cbMethod->Append(wxT("ldap"));

		// GSS/SSPI are supported from 8.3
		if (conn->BackendMinimumVersion(8, 3))
		{
			cbMethod->Append(wxT("gss"));
			cbMethod->Append(wxT("sspi"));
		}

		// CERT is supported from 8.4
		// but crypt is no longer supported in 8.4
		if (conn->BackendMinimumVersion(8, 4))
		{
			cbMethod->Append(wxT("cert"));
		}
		else
		{
			cbMethod->Append(wxT("crypt"));
		}

		// Radius is supported from 9.0
		if (conn->BackendMinimumVersion(9, 0))
		{
			cbMethod->Append(wxT("radius"));
		}

		// Peer is supported from 9.1
		if (conn->BackendMinimumVersion(9, 1))
		{
			cbMethod->Append(wxT("peer"));
		}
		if (conn->BackendMinimumVersion(10, 0))
		{
			cbMethod->Append(wxT("scram-sha-256"));
		}
	}
	else
	{
		// Add all version-dependent methods if we don't know what version we have.
		cbMethod->Append(wxT("ldap"));
		cbMethod->Append(wxT("gss"));
		cbMethod->Append(wxT("sspi"));
		cbMethod->Append(wxT("cert"));
		cbMethod->Append(wxT("crypt"));
		cbMethod->Append(wxT("radius"));
		cbMethod->Append(wxT("peer"));
	}
	cbMethod->Clear();
	int index;
	for (index = 0; pgHbaMethodStrings[index]; index++)
	{
		cbMethod->Append(pgHbaMethodStrings[index]);
	}

	if (conn)
	{
		pgSet *set = conn->ExecuteSet(wxT("SELECT datname FROM pg_database"));
		if (set)
		{
			while (!set->Eof())
			{
				cbDatabase->Append(set->GetVal(0));
				set->MoveNext();
			}
			delete set;
		}

		wxString sql = wxT("SELECT usename FROM pg_user\n")
		               wxT("UNION\n")
		               wxT("SELECT 'group ' || groname FROM pg_group");
		set = conn->ExecuteSet(sql);
		if (set)
		{
			while (!set->Eof())
			{
				cbUser->Append(set->GetVal(0));
				set->MoveNext();
			}
			delete set;
		}
	}

	// Setup the default values

	chkEnabled->SetValue(!line->isComment);
	if(line->connectType != pgHbaConfigLine::PGC_INVALIDCONF)
	{
		database = line->database;
		user = line->user;
		userAdding = true;
		cbType->SetSelection(line->connectType);
		cbMethod->SetSelection(line->method);
		cbDatabase->SetValue(database);
		cbUser->SetValue(user);
		txtIPaddress->SetValue(line->ipaddress);
		txtOption->SetValue(line->option);
		userAdding = false;
	}
	wxCommandEvent noEvent;
	OnChange(noEvent);
}


dlgHbaConfig::~dlgHbaConfig()
{
	SavePosition();
}


wxString dlgHbaConfig::GetHelpPage() const
{
	return wxT("pg/client-authentication");
	// auth-methods#auth-trust #auth-password #kerberos-auth #auth-ident #auth-pam
}


void dlgHbaConfig::OnAddDatabase(wxCommandEvent &ev)
{
	int sel = cbDatabase->GetCurrentSelection();
	if (sel < 3)
		return;

	wxString newDatabase;
	if (database == wxT("all") || database == wxT("sameuser") || database == wxT("samegroup")
	        || database == wxT("samerole") || database == wxT("replication") || database.Left(1) == wxT("@"))
		database = wxEmptyString;

	if (sel == 3)    // file
		newDatabase = wxT("@");
	else
	{
		wxString str = cbDatabase->GetString(sel);
		if (str.Find(' ') >= 0)
			str = wxT("\"") + str + wxT("\"");

		int pos = database.Find(str);
		if (pos >= 0)
		{
			if (pos > 0  && database.Mid(pos - 1, 1) != wxT(","))
				pos = -1;
			if (database.Length() > str.Length() + pos && database.Mid(pos + str.Length(), 1) != wxT(","))
				pos = -1;
		}
		if (pos < 0)
		{
			if (!database.IsEmpty())
				database += wxT(",");

			newDatabase = database + str;
		}
		else
			newDatabase = database;
	}

	wxTheApp->Yield(true);

	database = newDatabase;
	databaseAdding = true;
	wxCommandEvent buttonEvent(wxEVT_COMMAND_BUTTON_CLICKED, wxID_REFRESH);
	AddPendingEvent(buttonEvent);
}


void dlgHbaConfig::OnAddUser(wxCommandEvent &ev)
{
	int sel = cbUser->GetCurrentSelection();
	if (sel < 1)
		return;

	wxString newUser;

	wxString str = cbUser->GetString(sel);
	if (str.Left(6) == wxT("group "))
	{
		if (str.Find(' ', true) > 5)
			str = wxT("+\"") + str.Mid(6) + wxT("\"");
		else
			str = wxT("+") + str.Mid(6);
	}
	else
	{
		if (str.Find(' ') >= 0)
			str = wxT("\"") + str + wxT("\"");
	}

	if (user == wxT("all"))
		newUser = str;
	else
	{
		int pos = user.Find(str);
		if (pos >= 0)
		{
			if (pos > 0  && user.Mid(pos - 1, 1) != wxT(","))
				pos = -1;
			if (user.Length() > str.Length() + pos && user.Mid(pos + str.Length(), 1) != wxT(","))
				pos = -1;
		}

		if (pos < 0)
		{
			if (!user.IsEmpty())
				user += wxT(",");
			newUser = user + str;
		}
		else
			newUser = user;
	}

	wxTheApp->Yield(true);

	user = newUser;
	userAdding = true;
	wxCommandEvent buttonEvent(wxEVT_COMMAND_BUTTON_CLICKED, wxID_REFRESH);
	AddPendingEvent(buttonEvent);
}



void dlgHbaConfig::OnAddValue(wxCommandEvent &ev)
{
	if (databaseAdding)
	{
		cbDatabase->SetSelection(-1);
		cbDatabase->SetValue(database);
		cbDatabase->SetInsertionPointEnd();
		databaseAdding = false;
	}

	if (userAdding)
	{
		cbUser->SetSelection(-1);
		cbUser->SetValue(user);
		cbUser->SetInsertionPointEnd();
		userAdding = false;
	}
	OnChange(ev);
}


void dlgHbaConfig::OnChange(wxCommandEvent &ev)
{
	if (databaseAdding || userAdding)
		return;

	database = cbDatabase->GetValue();
	user = cbUser->GetValue();

	bool needIp = (cbType->GetCurrentSelection() != 0);

	stIPaddress->Enable(needIp);
	txtIPaddress->Enable(needIp);

	bool needOption = false;
	// IDENT and LDAP always take an option
	if (cbMethod->GetCurrentSelection() == pgHbaConfigLine::PGC_IDENT ||
	        cbMethod->GetCurrentSelection() == pgHbaConfigLine::PGC_LDAP)
	{
		needOption = true;
	}
	else if (cbMethod->GetCurrentSelection() == pgHbaConfigLine::PGC_GSS ||
	         cbMethod->GetCurrentSelection() == pgHbaConfigLine::PGC_SSPI ||
	         cbMethod->GetCurrentSelection() == pgHbaConfigLine::PGC_KRB5 ||
	         cbMethod->GetCurrentSelection() == pgHbaConfigLine::PGC_PAM ||
	         cbMethod->GetCurrentSelection() == pgHbaConfigLine::PGC_CERT)
	{
		// GSS/SSPI/KRB5/PAM/CERT take options from 8.4 onwards. If we don't know the version
		// then allow the option to be specified.
		if (conn)
		{
			if (conn->BackendMinimumVersion(8, 4))
				needOption = true;
		}
		else
			needOption = true;
	}

	// On 8.4 and above, any hostssl lines can take an option
	if (cbType->GetCurrentSelection() == pgHbaConfigLine::PGC_HOSTSSL)
	{
		if (conn)
		{
			if (conn->BackendMinimumVersion(8, 4))
				needOption = true;
		}
		else
			needOption = true;
	}

	stOption->Enable(needOption);
	txtOption->Enable(needOption);

	bool ipValid = !chkEnabled->GetValue() || !needIp;
	if (!ipValid)
	{
		// we should check for validity of txtIPaddress->GetValue() here
		ipValid = true;
	}
	btnOK->Enable(cbType->GetCurrentSelection() >= 0 && !database.IsEmpty() && !user.IsEmpty() &&
	              cbMethod->GetCurrentSelection() >= 0 && ipValid);
}


void dlgHbaConfig::OnOK(wxCommandEvent &ev)
{
#ifdef __WXGTK__
	if (!btnOK->IsEnabled())
		return;
#endif
	line->isComment = !chkEnabled->GetValue();
	line->connectType = (pgHbaConfigLine::pgHbaConnectType)cbType->GetCurrentSelection();
	line->database = database;
	line->user = user;
	line->ipaddress = txtIPaddress->GetValue();
	line->method = (pgHbaConfigLine::pgHbaMethod)cbMethod->GetCurrentSelection();
	if (txtOption->IsEnabled())
		line->option = txtOption->GetValue();
	else
		line->option = wxEmptyString;
	line->changed = true;

	EndModal(wxID_OK);
}


void dlgHbaConfig::OnCancel(wxCommandEvent &ev)
{
	EndModal(wxID_CANCEL);
}


int dlgHbaConfig::Go()
{
	// Set focus on the Password textbox and show modal
	return ShowModal();
}
