//////////////////////////////////////////////////////////////////////////
//
// pgAdmin III - PostgreSQL Tools
//
// Copyright (C) 2002 - 2016, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// ctlSecurityPanel.cpp - Panel with security information
//
//////////////////////////////////////////////////////////////////////////


// wxWindows headers
#include <wx/wx.h>
#include <wx/settings.h>
#include <wx/imaglist.h>

// App headers
#include "pgAdmin3.h"
#include "utils/sysLogger.h"
#include "ctl/ctlSecurityPanel.h"
#include "db/pgConn.h"
#include "schema/pgObject.h"
#include "schema/pgGroup.h"
#include "schema/pgUser.h"



BEGIN_EVENT_TABLE(ctlSecurityPanel, wxPanel)
	EVT_LIST_ITEM_SELECTED(CTL_LBPRIV,  ctlSecurityPanel::OnPrivSelChange)
	EVT_BUTTON(CTL_ADDPRIV,             ctlSecurityPanel::OnAddPriv)
	EVT_BUTTON(CTL_DELPRIV,             ctlSecurityPanel::OnDelPriv)
	EVT_TEXT(CTL_CBGROUP,               ctlSecurityPanel::OnGroupChange)
	EVT_COMBOBOX(CTL_CBGROUP,           ctlSecurityPanel::OnGroupChange)
	EVT_CHECKBOX(CTL_ALLPRIV,           ctlSecurityPanel::OnPrivCheckAll)
	EVT_CHECKBOX(CTL_ALLPRIVGRANT,      ctlSecurityPanel::OnPrivCheckAllGrant)
	EVT_CHECKBOX(CTL_PRIVCB,            ctlSecurityPanel::OnPrivCheck)
	EVT_CHECKBOX(CTL_PRIVCB + 2,          ctlSecurityPanel::OnPrivCheck)
	EVT_CHECKBOX(CTL_PRIVCB + 4,          ctlSecurityPanel::OnPrivCheck)
	EVT_CHECKBOX(CTL_PRIVCB + 6,          ctlSecurityPanel::OnPrivCheck)
	EVT_CHECKBOX(CTL_PRIVCB + 8,          ctlSecurityPanel::OnPrivCheck)
	EVT_CHECKBOX(CTL_PRIVCB + 10,         ctlSecurityPanel::OnPrivCheck)
	EVT_CHECKBOX(CTL_PRIVCB + 12,         ctlSecurityPanel::OnPrivCheck)
	EVT_CHECKBOX(CTL_PRIVCB + 14,         ctlSecurityPanel::OnPrivCheck)
	EVT_CHECKBOX(CTL_PRIVCB + 16,         ctlSecurityPanel::OnPrivCheck)
	EVT_CHECKBOX(CTL_PRIVCB + 18,         ctlSecurityPanel::OnPrivCheck) //MAINTAIN
END_EVENT_TABLE();

DEFINE_LOCAL_EVENT_TYPE(EVT_SECURITYPANEL_CHANGE)

ctlSecurityPanel::ctlSecurityPanel(wxNotebook *nb, const wxString &privList, const char *privChars, wxImageList *imgList)
	: wxPanel(nb, -1, wxDefaultPosition, wxDefaultSize)
{
	nbNotebook = nb;
	nbNotebook->AddPage(this, _("Privileges"));

	connection = 0;
	privilegeChars = privChars;
	allPrivileges = 0;

	privCheckboxes = 0;

	wxStringTokenizer privileges(privList, wxT(","));
	privilegeCount = privileges.CountTokens();

	wxFlexGridSizer *item0 = new wxFlexGridSizer(3, 1, 5, 5);
	item0->AddGrowableCol(0);
	item0->AddGrowableRow(0);

	if (privilegeCount)
	{
		bool needAll = (privilegeCount > 1);
		privCheckboxes = new wxCheckBox*[privilegeCount * 2];
		int i = 0;

		wxFlexGridSizer *itemSizer1 = new wxFlexGridSizer(1, 1, 5, 5);
		itemSizer1->AddGrowableCol(0);
		itemSizer1->AddGrowableRow(0);
		lbPrivileges = new ctlListView(this, CTL_LBPRIV, wxDefaultPosition, wxDefaultSize, wxSUNKEN_BORDER | wxLC_REPORT);
		lbPrivileges->SetImageList(imgList, wxIMAGE_LIST_SMALL);
		lbPrivileges->AddColumn(_("User/Group"), 70, wxLIST_FORMAT_LEFT);
		lbPrivileges->AddColumn(_("Privileges"), 70, wxLIST_FORMAT_LEFT);
		itemSizer1->Add(lbPrivileges, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 4);
		item0->Add(itemSizer1, 0, wxEXPAND | wxALL, 5);

		wxBoxSizer *itemSizer2 = new wxBoxSizer(wxHORIZONTAL);
		btnAddPriv = new wxButton(this, CTL_ADDPRIV, _("Add/Change"));
		itemSizer2->Add(btnAddPriv, 0, wxEXPAND |  wxTOP | wxLEFT | wxRIGHT, 4);
		btnDelPriv = new wxButton(this, CTL_DELPRIV, _("Remove"));
		itemSizer2->Add(btnDelPriv, 0, wxEXPAND |  wxTOP | wxLEFT | wxRIGHT, 4);
		item0->Add(itemSizer2, 0, wxEXPAND | wxALL, 0);

		wxStaticBox *sb = new wxStaticBox(this, -1, _("Privileges"));
		wxBoxSizer *itemSizer3 = new wxStaticBoxSizer( sb, wxVERTICAL );
		item0->Add(itemSizer3, 0, wxEXPAND | wxALL, 5);

		wxBoxSizer *itemSizer4 = new wxBoxSizer(wxHORIZONTAL);
		stGroup = new wxStaticText(this, CTL_STATICGROUP, _("Group"));
#ifdef __WXMSW__
		stGroup->SetMinSize(wxSize(30, 15));
#endif // __WXMSW__
		itemSizer4->Add(stGroup, 0, wxEXPAND |  wxTOP | wxLEFT | wxRIGHT, 4);
		cbGroups = new ctlComboBox(this, CTL_CBGROUP, wxDefaultPosition, wxDefaultSize);
		cbGroups->Append(wxT("public"));
		cbGroups->SetSelection(0);
		itemSizer4->Add(cbGroups, wxEXPAND |  wxTOP | wxLEFT | wxRIGHT);
		itemSizer3->Add(itemSizer4, 0, wxEXPAND | wxALL, 0);

		/* border size depends on the plateform */
#ifdef __WXMSW__
		int bordersize = 4;
#endif
#ifdef __WXMAC__
		int bordersize = 3;
#endif
#ifdef __WXGTK__
		int bordersize = 0;
#endif

		if (needAll)
		{
			wxBoxSizer *itemSizer5 = new wxBoxSizer(wxHORIZONTAL);
			allPrivileges = new wxCheckBox(this, CTL_ALLPRIV, wxT("ALL"));
			itemSizer5->Add(allPrivileges, wxEXPAND | wxALIGN_CENTRE_VERTICAL | wxTOP | wxLEFT | wxRIGHT);
			allPrivilegesGrant = new wxCheckBox(this, CTL_ALLPRIVGRANT, wxT("WITH GRANT OPTION"));
			itemSizer5->Add(allPrivilegesGrant, wxEXPAND | wxALIGN_CENTRE_VERTICAL | wxTOP | wxLEFT | wxRIGHT);
			allPrivilegesGrant->Disable();
			itemSizer3->Add(itemSizer5, 0, wxALL, bordersize);
		}

		while (privileges.HasMoreTokens())
		{
			wxString priv = privileges.GetNextToken();
			wxCheckBox *cb;
			wxBoxSizer *itemSizer6 = new wxBoxSizer(wxHORIZONTAL);
			cb = new wxCheckBox(this, CTL_PRIVCB + i, priv);
			itemSizer6->Add(cb, wxEXPAND | wxALIGN_CENTRE_VERTICAL | wxTOP | wxLEFT | wxRIGHT);
			privCheckboxes[i++] = cb;
			cb = new wxCheckBox(this, CTL_PRIVCB + i, wxT("WITH GRANT OPTION"));
			itemSizer6->Add(cb, wxEXPAND | wxALIGN_CENTRE_VERTICAL | wxTOP | wxLEFT | wxRIGHT);
			cb->Disable();
			privCheckboxes[i++] = cb;
			itemSizer3->Add(itemSizer6, 0, wxALL, bordersize);
		}
	}

	this->SetSizer(item0);
	item0->Fit(this);
}


ctlSecurityPanel::~ctlSecurityPanel()
{
	if (privCheckboxes)
		delete[] privCheckboxes;
}


void ctlSecurityPanel::SetConnection(pgConn *conn)
{
	connection = conn;
	if (connection && stGroup && connection->BackendMinimumVersion(8, 1))
		stGroup->SetLabel(_("Role"));
	if (connection && !connection->BackendMinimumVersion(17, 0))
	{
		DisablePrivilege("MAINTAIN");
		;
	}
}

wxString ctlSecurityPanel::GetUserPrivileges()
{
	wxString strPrivilages = wxEmptyString;
	int cnt = lbPrivileges->GetItemCount();
	int pos;
	if(cnt > 0)
	{
		strPrivilages += wxT("{");
		for (pos = 0 ; pos < cnt ; pos++)
		{
			if (pos != 0)
				strPrivilages += wxT(",");

			strPrivilages +=
			    lbPrivileges->GetText(pos) + wxT("=") + lbPrivileges->GetText(pos, 1) +  wxT("/") + connection->GetUser();
		}
		strPrivilages += wxT("}");
	}
	return strPrivilages;
}

wxString ctlSecurityPanel::GetGrant(const wxString &allPattern, const wxString &grantObject, wxArrayString *currentAcl, wxString column)
{
	wxArrayString tmpAcl;
	if (currentAcl)
		tmpAcl = *currentAcl;

	wxString sql;
	int cnt = lbPrivileges->GetItemCount();
	int pos;
	unsigned int i;

	for (pos = 0 ; pos < cnt ; pos++)
	{
		wxString name = lbPrivileges->GetText(pos);
		wxString value = lbPrivileges->GetText(pos, 1);

		int nameLen = name.Length();

		bool privWasAssigned = false;
		bool privPartiallyAssigned = false;
		for (i = 0 ; i < tmpAcl.GetCount() ; i++)
		{
			if (tmpAcl.Item(i).Left(nameLen + 1) == name + wxT("="))
			{
				privPartiallyAssigned = true;
				if (tmpAcl.Item(i).Mid(nameLen + 1) == value)
					privWasAssigned = true;
				tmpAcl.RemoveAt(i);
				break;
			}
		}

		if (name.Left(6).IsSameAs(wxT("group "), false))
			name = wxT("GROUP ") + qtIdent(name.Mid(6));
		else
			name = qtIdent(name);

		if (!privWasAssigned)
		{
			if (privPartiallyAssigned)
				sql += pgObject::GetPrivileges(allPattern, wxT(""), grantObject, name, column);
			sql += pgObject::GetPrivileges(allPattern, value, grantObject, name, column);
		}
	}

	for (i = 0 ; i < tmpAcl.GetCount() ; i++)
	{
		wxString name = tmpAcl.Item(i).BeforeLast('=');

		if (name.Left(6).IsSameAs(wxT("group "), false))
			name = wxT("GROUP ") + qtIdent(name.Mid(6));
		else
			name = qtIdent(name);
		sql += pgObject::GetPrivileges(allPattern, wxT(""), grantObject, name, column);
	}
	return sql;
}


void ctlSecurityPanel::OnGroupChange(wxCommandEvent &ev)
{
	cbGroups->GuessSelection(ev);
	wxString name = cbGroups->GetGuessedStringSelection();

	btnAddPriv->Enable(!name.Strip(wxString::both).IsEmpty());
}


void ctlSecurityPanel::OnPrivCheckAll(wxCommandEvent &ev)
{
	bool all = allPrivileges->GetValue();
	int i;
	for (i = 0 ; i < privilegeCount ; i++)
	{
		if (all)
		{
			// We use the Name property of the checkboxes as a flag to note if that
			// box should remain disabled (eg. CONNECT for a DB on PG < 8.2
			if (privCheckboxes[i * 2]->GetName() != wxT("DISABLE"))
			{
				privCheckboxes[i * 2]->SetValue(true);
				privCheckboxes[i * 2]->Disable();
				privCheckboxes[i * 2 + 1]->Disable();
				allPrivilegesGrant->Enable(GrantAllowed());
			}
		}
		else
		{
			if (privCheckboxes[i * 2]->GetName() != wxT("DISABLE"))
			{
				allPrivilegesGrant->Disable();
				allPrivilegesGrant->SetValue(false);
				privCheckboxes[i * 2]->Enable();
				CheckGrantOpt(i);
			}
		}
	}
}



void ctlSecurityPanel::OnPrivCheckAllGrant(wxCommandEvent &ev)
{
	bool grant = allPrivilegesGrant->GetValue();
	int i;
	for (i = 0 ; i < privilegeCount ; i++)
		privCheckboxes[i * 2 + 1]->SetValue(grant);
}


void ctlSecurityPanel::OnPrivCheck(wxCommandEvent &ev)
{
	int id = (ev.GetId() - CTL_PRIVCB) / 2;
	CheckGrantOpt(id);
}


void ctlSecurityPanel::CheckGrantOpt(int id)
{
	bool canGrant = (GrantAllowed() && privCheckboxes[id * 2]->GetValue());
	if (canGrant)
		privCheckboxes[id * 2 + 1]->Enable();
	else
	{
		privCheckboxes[id * 2 + 1]->SetValue(false);
		privCheckboxes[id * 2 + 1]->Disable();
	}
}


void ctlSecurityPanel::OnDelPriv(wxCommandEvent &ev)
{
	if (lbPrivileges->GetFirstSelected() == -1)
		return;

	lbPrivileges->DeleteCurrentItem();

	wxCommandEvent event( EVT_SECURITYPANEL_CHANGE, GetId() );
	event.SetEventObject( this );
	GetEventHandler()->ProcessEvent( event );

	ev.Skip();
}


void ctlSecurityPanel::OnAddPriv(wxCommandEvent &ev)
{
	wxString name = cbGroups->GetGuessedStringSelection();

	long pos = lbPrivileges->FindItem(-1, name);
	if (pos < 0)
	{
		pos = lbPrivileges->GetItemCount();
		int icon = userFactory.GetIconId();

		if (name.Left(6).IsSameAs(wxT("group "), false))
			icon = groupFactory.GetIconId();
		else if (name.IsSameAs(wxT("public"), false))
			icon = PGICON_PUBLIC;

		lbPrivileges->InsertItem(pos, name, icon);
	}
	wxString value;
	int i;
	for (i = 0 ; i < privilegeCount ; i++)
	{
		if (privCheckboxes[i * 2]->GetValue())
		{
			value += privilegeChars[i];
			if (privCheckboxes[i * 2 + 1]->GetValue())
				value += '*';
		}
	}
	lbPrivileges->SetItem(pos, 1, value);

	wxCommandEvent event( EVT_SECURITYPANEL_CHANGE, GetId() );
	event.SetEventObject( this );
	GetEventHandler()->ProcessEvent( event );

	ev.Skip();
}


void ctlSecurityPanel::OnPrivSelChange(wxListEvent &ev)
{
	if (!cbGroups)
		return;
	if (allPrivileges)
	{
		allPrivileges->SetValue(false);
		allPrivilegesGrant->SetValue(false);
		allPrivilegesGrant->Disable();
	}
	long pos = lbPrivileges->GetSelection();
	if (pos >= 0)
	{
		wxString name = lbPrivileges->GetText(pos);
		wxString value = lbPrivileges->GetText(pos, 1);

		pos = cbGroups->FindString(name);
		if (pos < 0)
		{
			cbGroups->Append(name);
			pos = cbGroups->GetCount() - 1;
		}
		cbGroups->SetSelection(pos);

		int i;
		for (i = 0 ; i < privilegeCount ; i++)
		{
			if (privCheckboxes[i * 2]->GetName() != wxT("DISABLE"))
			{
				privCheckboxes[i * 2]->Enable();
				int index = value.Find(privilegeChars[i]);
				if (index >= 0)
				{
					privCheckboxes[i * 2]->SetValue(true);
					privCheckboxes[i * 2 + 1]->SetValue(value.Mid(index + 1, 1) == wxT("*"));
				}
				else
					privCheckboxes[i * 2]->SetValue(false);
			}
			CheckGrantOpt(i);
		}
	}
	btnAddPriv->Enable();
}



bool ctlSecurityPanel::GrantAllowed() const
{
	if (!connection->BackendMinimumVersion(7, 4))
		return false;

	wxString user = cbGroups->GetValue();
	if (user.IsSameAs(wxT("public"), false))
		return false;

	if (!connection->BackendMinimumVersion(8, 1) &&
	        user.Left(6).IsSameAs(wxT("group "), false))
		return false;

	return true;
}

bool ctlSecurityPanel::DisablePrivilege(const wxString &priv)
{
	for (int i = 0; i < privilegeCount; i++)
	{
		if (privCheckboxes[i * 2]->GetLabel() == priv)
		{
			// Use the Name property as a flag so we don't accidently reenable the control later
			privCheckboxes[i * 2]->SetName(wxT("DISABLE"));
			privCheckboxes[i * 2]->Disable();
			return true;
		}
	}
	return false;
}
