//////////////////////////////////////////////////////////////////////////
//
// pgAdmin III - PostgreSQL Tools
//
// Copyright (C) 2002 - 2016, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// dlgAggregate.cpp - Aggregate properties dialog
//
//////////////////////////////////////////////////////////////////////////

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

// App headers
#include "pgAdmin3.h"
#include "utils/misc.h"
#include "utils/pgDefs.h"

#include "dlg/dlgAggregate.h"
#include "schema/pgSchema.h"
#include "schema/pgAggregate.h"
#include "schema/pgDatatype.h"
#include "ctl/ctlSeclabelPanel.h"
#include "frm/frmMain.h"
#include "schema/pgUser.h"
#include "schema/pgGroup.h"

#include "images/aggregate.pngc"

// pointer to controls
#define cbInputType         CTRL_COMBOBOX2("cbInputType")
#define lstInputTypes       CTRL_LISTCTRL("lstInputTypes")
#define cbStateType         CTRL_COMBOBOX2("cbStateType")
#define cbStateFunc         CTRL_COMBOBOX("cbStateFunc")
#define cbFinalFunc         CTRL_COMBOBOX("cbFinalFunc")
#define cbSortOp            CTRL_COMBOBOX("cbSortOp")
#define txtInitial          CTRL_TEXT("txtInitial")
#define btnAddType          CTRL_BUTTON("btnAddType")
#define btnRemoveType       CTRL_BUTTON("btnRemoveType")

dlgProperty *pgAggregateFactory::CreateDialog(frmMain *frame, pgObject *node, pgObject *parent)
{
	return new dlgAggregate(this, frame, (pgAggregate *)node, (pgSchema *)parent);
}

BEGIN_EVENT_TABLE(dlgAggregate, dlgTypeProperty)
	EVT_TEXT(XRCID("cbInputType"),                  dlgAggregate::OnChangeTypeBase)
	EVT_TEXT(XRCID("cbStateType"),                  dlgAggregate::OnChangeTypeState)
	EVT_COMBOBOX(XRCID("cbStateType"),              dlgAggregate::OnChangeType)
	EVT_COMBOBOX(XRCID("cbStateFunc"),              dlgProperty::OnChange)
	EVT_COMBOBOX(XRCID("cbSortOp"),                 dlgProperty::OnChange)
	EVT_TEXT(XRCID("cbStateFunc"),                  dlgProperty::OnChange)
	EVT_BUTTON(XRCID("btnAddType"),                 dlgAggregate::OnAddInputType)
	EVT_BUTTON(XRCID("btnRemoveType"),              dlgAggregate::OnRemoveInputType)
	EVT_LIST_ITEM_SELECTED(XRCID("lstInputTypes"),  dlgAggregate::OnSelectInputType)
	EVT_BUTTON(CTL_ADDPRIV,                         dlgAggregate::OnAddPriv)
	EVT_BUTTON(CTL_DELPRIV,                         dlgAggregate::OnDelPriv)
#ifdef __WXMAC__
	EVT_SIZE(                                       dlgAggregate::OnChangeSize)
#endif
END_EVENT_TABLE();

dlgAggregate::dlgAggregate(pgaFactory *f, frmMain *frame, pgAggregate *agg, pgSchema *sch)
	: dlgTypeProperty(f, frame, wxT("dlgAggregate"))
{
	schema = sch;
	aggregate = agg;
	seclabelPage = new ctlSeclabelPanel(nbNotebook);

	/* Aggregate Privileges */
	securityChanged = false;
	if (agg)
		connection = agg->GetConnection();
	securityPage = new ctlSecurityPanel(nbNotebook, wxT("EXECUTE"), "X", frame->GetImageList());
	if (connection && (!agg || agg->CanCreate()))
	{
		// Fetch Groups Information
		pgSet *setGrp = connection->ExecuteSet(wxT("SELECT groname FROM pg_group ORDER BY groname"));

		if (setGrp)
		{
			while (!setGrp->Eof())
			{
				groups.Add(setGrp->GetVal(0));
				setGrp->MoveNext();
			}
			delete setGrp;
		}

		if (agg)
		{
			wxString strAcl = agg->GetAcl();
			if (!strAcl.IsEmpty())
			{
				wxArrayString aclArray;
				strAcl = strAcl.Mid(1, strAcl.Length() - 2);
				getArrayFromCommaSeparatedList(strAcl, aclArray);
				wxString roleName;
				for (unsigned int index = 0; index < aclArray.Count(); index++)
				{
					wxString strCurrAcl = aclArray[index];

					/*
					* In rare case, we can have ',' (comma) in the user name.
					* But, we need to handle them also
					*/
					if (strCurrAcl.Find(wxChar('=')) == wxNOT_FOUND)
					{
						// Check it is start of the ACL
						if (strCurrAcl[0U] == (wxChar)'"')
							roleName = strCurrAcl + wxT(",");
						continue;
					}
					else
						strCurrAcl = roleName + strCurrAcl;

					if (strCurrAcl[0U] == (wxChar)'"')
						strCurrAcl = strCurrAcl.Mid(1, strCurrAcl.Length() - 1);
					roleName = strCurrAcl.BeforeLast('=');

					wxString value = strCurrAcl.Mid(roleName.Length() + 1).BeforeLast('/');

					int icon = userFactory.GetIconId();

					if (roleName.Left(6).IsSameAs(wxT("group "), false))
					{
						icon = groupFactory.GetIconId();
						roleName = wxT("group ") + qtStrip(roleName.Mid(6));
					}
					else if (roleName.IsEmpty())
					{
						icon = PGICON_PUBLIC;
						roleName = wxT("public");
					}
					else
					{
						roleName = qtStrip(roleName);
						for (unsigned int index = 0; index < groups.Count(); index++)
							if (roleName == groups[index])
							{
								roleName = wxT("group ") + roleName;
								icon = groupFactory.GetIconId();
								break;
							}
					}

					securityPage->lbPrivileges->AppendItem(icon, roleName, value);
					currentAcl.Add(roleName + wxT("=") + value);

					// Reset roleName
					roleName.Empty();
				}
			}
		}
	}
	else
		securityPage->Disable();
}

pgObject *dlgAggregate::GetObject()
{
	return aggregate;
}

int dlgAggregate::Go(bool modal)
{
	if (!connection->BackendMinimumVersion(8, 0))
		cbOwner->Disable();

	if (!connection->BackendMinimumVersion(8, 1))
		cbSortOp->Disable();

	if (connection->BackendMinimumVersion(9, 1))
	{
		seclabelPage->SetConnection(connection);
		seclabelPage->SetObject(aggregate);
		this->Connect(EVT_SECLABELPANEL_CHANGE, wxCommandEventHandler(dlgAggregate::OnChange));
	}
	else
		seclabelPage->Disable();

	lstInputTypes->InsertColumn(0, _("Input types"), wxLIST_FORMAT_LEFT, lstInputTypes->GetSize().x - 10);

	if (aggregate)
	{
		// edit mode
		txtName->Enable(connection->BackendMinimumVersion(7, 4));
		cbSchema->Enable(connection->BackendMinimumVersion(8, 1));

		for (unsigned int x = 0; x < aggregate->GetInputTypesArray().Count(); x++ )
		{
			lstInputTypes->InsertItem(x, aggregate->GetInputTypesArray().Item(x));
			AddType(wxT(" "), 0, aggregate->GetInputTypesArray().Item(x));
		}

		cbStateType->Append(aggregate->GetStateType());
		cbStateType->SetSelection(0);

		cbStateFunc->Append(aggregate->GetStateFunction());
		cbStateFunc->SetSelection(0);
		cbFinalFunc->Append(aggregate->GetFinalFunction());
		cbFinalFunc->SetSelection(0);

		cbSortOp->Append(aggregate->GetSortOp());
		cbSortOp->SetSelection(0);

		txtInitial->SetValue(aggregate->GetInitialCondition());

		cbInputType->Disable();
		btnAddType->Disable();
		btnRemoveType->Disable();
		cbStateFunc->Disable();
		cbStateType->Disable();
		cbFinalFunc->Disable();
		cbSortOp->Disable();
		txtInitial->Disable();
	}
	else
	{
		// create mode
		AddType(wxT(" "), PGOID_TYPE_ANY, wxT("\"any\""));
		cbInputType->Append(wxT("ANY"));

		FillDatatype(cbInputType, cbStateType, false);
		btnRemoveType->Disable();
	}

	securityPage->SetConnection(connection);

	if (securityPage->cbGroups)
	{
		// Fetch Groups Information
		for ( unsigned int index = 0; index < groups.Count();)
			securityPage->cbGroups->Append(wxT("group ") + groups[index++]);

		// Fetch Users Information
		if (settings->GetShowUsersForPrivileges())
		{
			securityPage->stGroup->SetLabel(_("Group/User"));
			dlgProperty::AddUsers(securityPage->cbGroups);
		}
	}
	securityPage->lbPrivileges->GetParent()->Layout();

	return dlgProperty::Go(modal);
}

pgObject *dlgAggregate::CreateObject(pgCollection *collection)
{
	pgObject *obj = aggregateFactory.CreateObjects(collection, 0,
	                wxT("\n   AND proname=") + qtDbString(GetName()) +
	                wxT("\n   AND pronamespace=") + schema->GetOidStr());

	return obj;
}

#ifdef __WXMAC__
void dlgAggregate::OnChangeSize(wxSizeEvent &ev)
{
	lstInputTypes->SetSize(wxDefaultCoord, wxDefaultCoord,
	                       ev.GetSize().GetWidth(), ev.GetSize().GetHeight() - 350);
	securityPage->lbPrivileges->SetSize(wxDefaultCoord, wxDefaultCoord,
	                                    ev.GetSize().GetWidth(), ev.GetSize().GetHeight() - 550);
	if (GetAutoLayout())
	{
		Layout();
	}
}
#endif

void dlgAggregate::CheckChange()
{
	bool enable = true;
	if (aggregate)
	{
		enable = GetName() != aggregate->GetName()
		         || cbSchema->GetValue() != aggregate->GetSchema()->GetName()
		         || txtComment->GetValue() != aggregate->GetComment()
		         || cbOwner->GetValue() != aggregate->GetOwner();
		if (seclabelPage && connection->BackendMinimumVersion(9, 1))
			enable = enable || !(seclabelPage->GetSqlForSecLabels().IsEmpty());
	}
	else
	{
		// For pre 8.2 servers, we can only have one input type.
		if (!connection->BackendMinimumVersion(8, 2))
		{
			if (lstInputTypes->GetItemCount() >= 1)
				btnAddType->Disable();
			else
				btnAddType->Enable();
		}

		// Multi parameter aggregates cannot have sort ops.
		if (connection->BackendMinimumVersion(8, 2))
		{
			if (lstInputTypes->GetItemCount() > 1)
			{
				cbSortOp->SetValue(wxEmptyString);
				cbSortOp->Disable();
			}
			else
				cbSortOp->Enable();
		}

		wxString name = GetName();
		CheckValid(enable, !name.IsEmpty(), _("Please specify name."));
		CheckValid(enable, cbStateType->GetGuessedSelection() >= 0, _("Please select state datatype."));
		CheckValid(enable, cbStateFunc->GetCurrentSelection() >= 0, _("Please specify state function."));
	}
	EnableOK(enable || securityChanged);
}

void dlgAggregate::OnChangeTypeBase(wxCommandEvent &ev)
{
	cbInputType->GuessSelection(ev);
	OnChangeType(ev);
}

void dlgAggregate::OnChangeTypeState(wxCommandEvent &ev)
{
	cbStateType->GuessSelection(ev);
	OnChangeType(ev);
}

void dlgAggregate::OnChangeType(wxCommandEvent &ev)
{
	cbStateFunc->Clear();
	cbFinalFunc->Clear();
	cbSortOp->Clear();

	pgSet *set;
	wxString qry;

	// Get the possible state functions. They must return the state type, and take
	// input_types + 1 parameters, the first of which is of the state type.
	// If there are no input_types specified, assume "ANY" for a count(*) style aggregate.
	if (lstInputTypes->GetItemCount() > 0 && cbStateType->GetGuessedSelection() >= 0)
	{
		set = connection->ExecuteSet(
		          wxT("SELECT proname, nspname, prorettype\n")
		          wxT("  FROM pg_proc p\n")
		          wxT("  JOIN pg_type t ON t.oid=p.prorettype\n")
		          wxT("  JOIN pg_namespace n ON n.oid=pronamespace\n")
		          wxT(" WHERE prorettype = ") + GetTypeOid(cbStateType->GetGuessedSelection() + 1) +
		          wxT("\n   AND prokind <> 'a'")
		          wxT("\n   AND proargtypes = '") + GetTypeOid(cbStateType->GetGuessedSelection() + 1) + wxT(" ") + GetInputTypesOidList() + wxT("'"));

		if (set)
		{
			while (!set->Eof())
			{
				cbStateFunc->Append(database->GetSchemaPrefix(set->GetVal(wxT("nspname"))) + set->GetVal(wxT("proname")),
				                    database->GetQuotedSchemaPrefix(set->GetVal(wxT("nspname"))) + qtIdent(set->GetVal(wxT("proname"))));

				set->MoveNext();
			}
			delete set;
		}

		// Get the possible final_func options. This may be nothing, or a
		// function taking an argument of state_type
		cbFinalFunc->Append(wxT(" "));

		set = connection->ExecuteSet(
		          wxT("SELECT proname, nspname, prorettype\n")
		          wxT("  FROM pg_proc p\n")
		          wxT("  JOIN pg_type t ON t.oid=p.prorettype\n")
		          wxT("  JOIN pg_namespace n ON n.oid=pronamespace\n")
		          wxT(" WHERE proisagg = FALSE")
		          wxT("\n   AND proargtypes = '") + GetTypeOid(cbStateType->GetGuessedSelection() + 1) + wxT("'"));

		if (set)
		{
			while (!set->Eof())
			{
				cbFinalFunc->Append(database->GetSchemaPrefix(set->GetVal(wxT("nspname"))) + set->GetVal(wxT("proname")),
				                    database->GetQuotedSchemaPrefix(set->GetVal(wxT("nspname"))) + qtIdent(set->GetVal(wxT("proname"))));

				set->MoveNext();
			}
			delete set;
		}

		// Get the sort operators. This is only valid for a single arguement operator.
		cbSortOp->Append(wxT(" "), wxEmptyString);

		set = connection->ExecuteSet(
		          wxT("SELECT oprname, nspname\n")
		          wxT("  FROM pg_operator op\n")
		          wxT("  JOIN pg_namespace nsp on nsp.oid=oprnamespace\n")
		          wxT(" WHERE oprleft = ") + NumToStr(GetInputTypeOid(0)) + wxT("\n")
		          wxT("   AND oprright = ") + NumToStr(GetInputTypeOid(0)) + wxT("\n"));

		if (set)
		{
			while (!set->Eof())
			{
				wxString key = database->GetQuotedSchemaPrefix(set->GetVal(wxT("nspname"))) + qtIdent(set->GetVal(wxT("oprname")));
				wxString txt = database->GetSchemaPrefix(set->GetVal(wxT("nspname"))) + set->GetVal(wxT("oprname"));
				cbSortOp->Append(txt, key);
				set->MoveNext();
			}
			delete set;
		}
	}

	CheckChange();
}

wxString dlgAggregate::GetSql()
{
	wxString sql, name;

	if (aggregate)
	{
		// edit mode
		name = schema->GetQuotedPrefix() + qtIdent(GetName()) + wxT("(") + GetInputTypesList() + wxT(")");

		AppendNameChange(sql, wxT("AGGREGATE ") + aggregate->GetQuotedFullName());
		AppendOwnerChange(sql, wxT("AGGREGATE ") + name);
		AppendSchemaChange(sql,  wxT("AGGREGATE ") + name);
	}
	else
	{
		// create mode
		name = qtIdent(cbSchema->GetValue()) + wxT(".") + qtIdent(GetName());

		if (connection->BackendMinimumVersion(8, 2))
		{
			sql = wxT("CREATE AGGREGATE ") + name
			      + wxT("(") + GetInputTypesList() + wxT(") (\n");
		}
		else
		{
			sql = wxT("CREATE AGGREGATE ") + name
			      + wxT("(\n   BASETYPE=") + GetInputTypesList() + wxT(",\n");
		}

		sql += wxT("   SFUNC=") + cbStateFunc->GetStringKey()
		       + wxT(",\n   STYPE=") + GetQuotedTypename(cbStateType->GetGuessedSelection() + 1); // skip "any" type

		if (cbFinalFunc->GetCurrentSelection() > 0)
		{
			sql += wxT(",\n   FINALFUNC=")
			       + cbFinalFunc->GetStringKey();
		}
		wxString initial = txtInitial->GetValue().Strip(wxString::both);
		if (!initial.IsEmpty())
		{
			if (initial == wxT("''")) // Empty string
				sql += wxT(",\n   INITCOND=''");
			else if (initial == wxT("\\'\\'")) // Pair of quotes
				sql += wxT(",\n   INITCOND=''''''");
			else
				sql += wxT(",\n   INITCOND=") + qtDbString(initial);
		}

		if (!cbSortOp->GetValue().IsEmpty())
		{
			wxString opr = cbSortOp->GetStringKey();
			if (!opr.IsEmpty())
				sql += wxT(",\n   SORTOP=") + opr;
		}

		sql += wxT("\n);\n");

		AppendOwnerNew(sql, wxT("AGGREGATE ") + name + wxT("(") + GetInputTypesList() + wxT(")"));
	}

	AppendComment(sql, wxT("AGGREGATE ") + qtIdent(cbSchema->GetValue()) + wxT(".") + qtIdent(GetName())
	              + wxT("(") + GetInputTypesList() + wxT(")"), aggregate);

	sql += securityPage->GetGrant(wxT("X"),
	                              wxT("FUNCTION ") + qtIdent(cbSchema->GetValue()) + wxT(".") + qtIdent(GetName()) + wxT("(") + GetInputTypesList() + wxT(")"),
	                              &currentAcl);

	if (seclabelPage && connection->BackendMinimumVersion(9, 1))
		sql += seclabelPage->GetSqlForSecLabels(wxT("AGGREGATE"),
		                                        qtIdent(cbSchema->GetValue()) + wxT(".") + qtIdent(GetName())
		                                        + wxT("(") + GetInputTypesList() + wxT(")"));

	return sql;
}

// Return the list of input types
wxString dlgAggregate::GetInputTypesList()
{
	wxString types;

	for (int i = 0; i < lstInputTypes->GetItemCount(); i++)
	{
		if (i > 0)
			types += wxT(", ");

		if (lstInputTypes->GetItemText(i) == wxT("ANY") || lstInputTypes->GetItemText(i) == wxT("*"))
			return wxT("*");
		else
			types += qtTypeIdent(lstInputTypes->GetItemText(i));
	}
	return types;
}

void dlgAggregate::OnAddInputType(wxCommandEvent &ev)
{
	if (cbInputType->GetValue() != wxEmptyString)
	{
		if (cbInputType->GetValue() == wxT("ANY") && lstInputTypes->GetItemCount() > 0)
		{
			wxLogError(_("The ANY pseudo-datatype cannot be used in multi parameter aggregates."));
			return;
		}

		wxListItem itm;
		itm.SetMask(wxLIST_MASK_DATA | wxLIST_MASK_TEXT);

		itm.SetData(StrToLong(GetTypeOid(cbInputType->GetGuessedSelection())));
		itm.SetText(cbInputType->GetValue());

		lstInputTypes->InsertItem(itm);

		if (cbInputType->GetValue() == wxT("ANY"))
			btnAddType->Disable();

		OnChangeType(ev);
	}
}

void dlgAggregate::OnRemoveInputType(wxCommandEvent &ev)
{
	long pos = lstInputTypes->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);;
	if (pos >= 0)
	{
		lstInputTypes->DeleteItem(pos);

		OnChangeType(ev);
		btnRemoveType->Disable();

		// Re-enable the Add button, in case it was disabled because
		// ANY was specified.
		btnAddType->Enable();
	}
}

void dlgAggregate::OnSelectInputType(wxListEvent &ev)
{
	if (!aggregate)
		btnRemoveType->Enable();
}

// Returnt the datatype OID for the given parameter number
long dlgAggregate::GetInputTypeOid(int param)
{
	wxListItem itm;
	itm.SetMask(wxLIST_MASK_DATA | wxLIST_MASK_TEXT);
	itm.SetId(param);

	lstInputTypes->GetItem(itm);

	return itm.GetData();
}

// Return the list of datatype OIDs
wxString dlgAggregate::GetInputTypesOidList()
{
	wxString types;

	for (int i = 0; i < lstInputTypes->GetItemCount(); i++)
	{
		if (i > 0)
			types += wxT(" ");

		types += NumToStr(GetInputTypeOid(i));
	}
	return types;
}

void dlgAggregate::OnChange(wxCommandEvent &event)
{
	CheckChange();
}

void dlgAggregate::OnAddPriv(wxCommandEvent &ev)
{
	securityChanged = true;
	CheckChange();
}

void dlgAggregate::OnDelPriv(wxCommandEvent &ev)
{
	securityChanged = true;
	CheckChange();
}
