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

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

// App headers
#include "pgAdmin3.h"
#include "utils/misc.h"
#include "frm/frmMain.h"
#include "schema/pgForeignKey.h"
#include "schema/pgConstraints.h"

pgForeignKey::pgForeignKey(pgSchema *newSchema, const wxString &newName)
	: pgSchemaObject(newSchema, foreignKeyFactory, newName)
{
}

pgForeignKey::~pgForeignKey()
{
}


wxString pgForeignKey::GetTranslatedMessage(int kindOfMessage) const
{
	wxString message = wxEmptyString;

	switch (kindOfMessage)
	{
		case RETRIEVINGDETAILS:
			message = _("Retrieving details on foreign key");
			message += wxT(" ") + GetName();
			break;
		case REFRESHINGDETAILS:
			message = _("Refreshing foreign key");
			message += wxT(" ") + GetName();
			break;
		case GRANTWIZARDTITLE:
			message = _("Privileges for foreign key");
			message += wxT(" ") + GetName();
			break;
		case DROPINCLUDINGDEPS:
			message = wxString::Format(_("Are you sure you wish to drop foreign key \"%s\" including all objects that depend on it?"),
			                           GetFullIdentifier().c_str());
			break;
		case DROPEXCLUDINGDEPS:
			message = wxString::Format(_("Are you sure you wish to drop foreign key \"%s\"?"),
			                           GetFullIdentifier().c_str());
			break;
		case DROPCASCADETITLE:
			message = _("Drop foreign key cascaded?");
			break;
		case DROPTITLE:
			message = _("Drop foreign key?");
			break;
		case PROPERTIESREPORT:
			message = _("Foreign key properties report");
			message += wxT(" - ") + GetName();
			break;
		case PROPERTIES:
			message = _("Foreign key properties");
			break;
		case DDLREPORT:
			message = _("Foreign key DDL report");
			message += wxT(" - ") + GetName();
			break;
		case DDL:
			message = _("Foreign key DDL");
			break;
		case DEPENDENCIESREPORT:
			message = _("Foreign key dependencies report");
			message += wxT(" - ") + GetName();
			break;
		case DEPENDENCIES:
			message = _("Foreign key dependencies");
			break;
		case DEPENDENTSREPORT:
			message = _("Foreign key dependents report");
			message += wxT(" - ") + GetName();
			break;
		case DEPENDENTS:
			message = _("Foreign key dependents");
			break;
	}

	return message;
}


int pgForeignKey::GetIconId()
{
	if (!GetDatabase()->BackendMinimumVersion(9, 1) || GetValid())
		return foreignKeyFactory.GetIconId();
	else
		return foreignKeyFactory.GetClosedIconId();
}


bool pgForeignKey::DropObject(wxFrame *frame, ctlTree *browser, bool cascaded)
{
	wxString sql = wxT("ALTER TABLE ") + GetQuotedSchemaPrefix(fkSchema) + qtIdent(fkTable)
	               + wxT(" DROP CONSTRAINT ") + GetQuotedIdentifier();
	if (cascaded)
		sql += wxT(" CASCADE");
	return GetDatabase()->ExecuteVoid(sql);
}


wxString pgForeignKey::GetDefinition(int metatype)
{
	wxString sql;

	sql = wxT("(") + GetQuotedFkColumns()
	      +  wxT(")\n      REFERENCES ") + GetQuotedSchemaPrefix(GetRefSchema()) + qtIdent(GetReferences())
	      +  wxT(" (") + GetQuotedRefColumns()
	      +  wxT(")");

	if (GetDatabase()->BackendMinimumVersion(7, 4) || GetMatch() == wxT("FULL"))
		sql += wxT(" MATCH ") + GetMatch();

	sql += wxT("\n      ON UPDATE ") + GetOnUpdate()
	       +  wxT(" ON DELETE ") + GetOnDelete();
	if (!GetConfdelsetcols().IsEmpty())
		sql += wxT("(") + GetQuotedConfdelsetcols() + wxT(")");

	if (GetDeferrable())
	{
		sql += wxT(" DEFERRABLE INITIALLY ");
		if (GetDeferred())
			sql += wxT("DEFERRED");
		else
			sql += wxT("IMMEDIATE");
	}
	if (!GetEnforced()) sql += wxT(" NOT ENFORCED");
	if (GetDatabase()->BackendMinimumVersion(9, 1) && !GetValid() && metatype!=PGM_TABLE )
		sql += wxT("\n      NOT VALID");

	return sql;
}


wxString pgForeignKey::GetConstraint()
{
	wxString sql;
	sql = GetQuotedIdentifier()
	      +  wxT(" FOREIGN KEY ") + GetDefinition(PGM_FOREIGNKEY);

	return sql;
}


wxString pgForeignKey::GetSql(ctlTree *browser)
{
	if (sql.IsNull())
	{
		sql = wxT("-- Foreign Key: ") + GetQuotedFullIdentifier() + wxT("\n\n")
		      + wxT("-- ALTER TABLE ") + GetQuotedSchemaPrefix(fkSchema) + qtIdent(fkTable)
		      + wxT(" DROP CONSTRAINT ") + GetQuotedIdentifier() + wxT(";")
		      + wxT("\n\nALTER TABLE ") + GetQuotedSchemaPrefix(fkSchema) + qtIdent(fkTable)
		      + wxT("\n  ADD CONSTRAINT ") + GetConstraint()
		      + wxT(";\n");
		if (!GetComment().IsEmpty())
			sql += wxT("COMMENT ON CONSTRAINT ") + GetQuotedIdentifier() + wxT(" ON ") + GetQuotedSchemaPrefix(fkSchema) + qtIdent(fkTable)
			       +  wxT(" IS ") + qtDbString(GetComment()) + wxT(";\n");
	}

	return sql;
}


wxString pgForeignKey::GetFullName()
{
	return GetName() + wxT(" -> ") + GetReferences();
}

void pgForeignKey::ShowTreeDetail(ctlTree *browser, frmMain *form, ctlListView *properties, ctlSQLBox *sqlPane)
{
	if (!expandedKids)
	{
		expandedKids = true;

		wxTreeItemId id = browser->GetItemParent(GetId());
		pgTable *table = (pgTable *)browser->GetObject(id);

		wxStringTokenizer c1l(GetConkey(), wxT(","));
		wxStringTokenizer c2l(GetConfkey(), wxT(","));
		wxString c1, c2;
		// resolve column names for column confdelsetcols PG15
		wxStringTokenizer d1(GetConfdelsetcols(), wxT(","));
		while (d1.HasMoreTokens()) {
			c1 = d1.GetNextToken();
			pgSet* set = ExecuteSet(
				wxT("SELECT a1.attname as conattname\n")
				wxT("  FROM pg_attribute a1\n")
				wxT(" WHERE a1.attrelid=") + NumToStr(table->GetOid()) + wxT("::oid") + wxT(" AND a1.attnum=") + c1 + wxT("\n")
				);
			if (set)
			{
				if (!fkDelColumns.IsNull())
				{
					fkDelColumns += wxT(", ");
					quotedFkDelColumns += wxT(", ");
				}
				fkDelColumns += set->GetVal(0);
				quotedFkDelColumns += qtIdent(set->GetVal(0));
				delete set;
			}
		}
		// resolve column names
		while (c1l.HasMoreTokens())
		{
			c1 = c1l.GetNextToken();
			c2 = c2l.GetNextToken();
			pgSet *set = ExecuteSet(
			                 wxT("SELECT a1.attname as conattname, a2.attname as confattname\n")
			                 wxT("  FROM pg_attribute a1, pg_attribute a2\n")
			                 wxT(" WHERE a1.attrelid=") + NumToStr(table->GetOid()) + wxT("::oid") + wxT(" AND a1.attnum=") + c1 + wxT("\n")
			                 wxT("   AND a2.attrelid=") + GetRelTableOidStr() + wxT(" AND a2.attnum=") + c2);
			if (set)
			{
				if (!fkColumns.IsNull())
				{
					fkColumns += wxT(", ");
					refColumns += wxT(", ");
					quotedFkColumns += wxT(", ");
					quotedRefColumns += wxT(", ");
				}
				fkColumns += set->GetVal(0);
				refColumns += set->GetVal(1);
				quotedFkColumns += qtIdent(set->GetVal(0));
				quotedRefColumns += qtIdent(set->GetVal(1));
				delete set;
			}
		}
		wxTreeItemId item = browser->GetItemParent(GetId());
		while (item)
		{
			pgTable *table = (pgTable *)browser->GetObject(item);
			if (table && table->IsCreatedBy(tableFactory))
			{
				coveringIndex = table->GetCoveringIndex(browser, fkColumns);
				break;
			}
			item = browser->GetItemParent(item);
		}
	}

	if (properties)
	{
		CreateListColumns(properties);

		properties->AppendItem(_("Name"), GetName());
		properties->AppendItem(_("OID"), NumToStr(GetOid()));
		properties->AppendItem(_("Child columns"), GetFkColumns());
		properties->AppendItem(_("References"), GetReferences()
		                       + wxT("(") + GetRefColumns() + wxT(")"));

		properties->AppendItem(_("Covering index"), GetCoveringIndex());
		properties->AppendItem(_("Match type"), GetMatch());
		properties->AppendItem(_("On update"), GetOnUpdate());
		properties->AppendItem(_("On delete"), GetOnDelete());
		properties->AppendItem(_("Deferrable?"), BoolToYesNo(GetDeferrable()));
		if (GetDeferrable())
			properties->AppendItem(_("Initially?"),
			                       GetDeferred() ? wxT("DEFERRED") : wxT("IMMEDIATE"));
		if (GetDatabase()->BackendMinimumVersion(9, 1))
			properties->AppendItem(_("Valid?"), BoolToYesNo(GetValid()));
		properties->AppendItem(_("System foreign key?"), BoolToYesNo(GetSystemObject()));
		properties->AppendItem(_("Comment"), firstLineOnly(GetComment()));
	}
}


pgObject *pgForeignKey::Refresh(ctlTree *browser, const wxTreeItemId item)
{
	pgObject *foreignKey = 0;

	pgCollection *coll = browser->GetParentCollection(item);
	if (coll)
		foreignKey = foreignKeyFactory.CreateObjects(coll, 0, wxT("\n   AND ct.oid=") + GetOidStr());

	return foreignKey;
}


void pgForeignKey::Validate(frmMain *form)
{
	wxString sql = wxT("ALTER TABLE ") + GetQuotedSchemaPrefix(fkSchema) + qtIdent(fkTable)
	               + wxT("\n  VALIDATE CONSTRAINT ") + GetQuotedIdentifier();
	GetDatabase()->ExecuteVoid(sql);

	iSetValid(true);
	UpdateIcon(form->GetBrowser());
}


pgObject *pgForeignKeyFactory::CreateObjects(pgCollection *coll, ctlTree *browser, const wxString &restriction)
{
	wxString sql;
	pgTableObjCollection *collection = (pgTableObjCollection *)coll;
	pgForeignKey *foreignKey = 0;
	wxString enforced =collection->GetDatabase()->BackendMinimumVersion(18, 0) ? ",ct.conenforced enforced" :" ,true enforced";

	sql = wxT("SELECT ct.oid, conname, condeferrable, condeferred, confupdtype, confdeltype, confmatchtype, ")
	      wxT("conkey, confkey, confrelid, nl.nspname as fknsp, cl.relname as fktab, ")
	      wxT("nr.nspname as refnsp, cr.relname as reftab, description") + enforced;
	if (collection->GetDatabase()->BackendMinimumVersion(15, 0))
		sql += wxT(", confdelsetcols");
	if (collection->GetDatabase()->BackendMinimumVersion(9, 1))
		sql += wxT(", convalidated");
	sql += wxT("\n  FROM pg_constraint ct\n")
	       wxT("  JOIN pg_class cl ON cl.oid=conrelid\n")
	       wxT("  JOIN pg_namespace nl ON nl.oid=cl.relnamespace\n")
	       wxT("  JOIN pg_class cr ON cr.oid=confrelid\n")
	       wxT("  JOIN pg_namespace nr ON nr.oid=cr.relnamespace\n")
	       wxT("  LEFT OUTER JOIN pg_description des ON (des.objoid=ct.oid AND des.classoid='pg_constraint'::regclass)\n")
	       wxT(" WHERE contype='f' AND conrelid = ") + collection->GetOidStr()
	       + restriction + wxT("\n")
	       wxT(" ORDER BY conname");

	pgSet *foreignKeys = collection->GetDatabase()->ExecuteSet(sql);

	if (foreignKeys)
	{
		while (!foreignKeys->Eof())
		{
			foreignKey = new pgForeignKey(collection->GetSchema()->GetSchema(), foreignKeys->GetVal(wxT("conname")));

			foreignKey->iSetOid(foreignKeys->GetOid(wxT("oid")));
			foreignKey->iSetRelTableOid(foreignKeys->GetOid(wxT("confrelid")));
			foreignKey->iSetFkSchema(foreignKeys->GetVal(wxT("fknsp")));
			foreignKey->iSetComment(foreignKeys->GetVal(wxT("description")));
			foreignKey->iSetFkTable(foreignKeys->GetVal(wxT("fktab")));
			foreignKey->iSetRefSchema(foreignKeys->GetVal(wxT("refnsp")));
			foreignKey->iSetReferences(foreignKeys->GetVal(wxT("reftab")));
			if (collection->GetDatabase()->BackendMinimumVersion(9, 1))
				foreignKey->iSetValid(foreignKeys->GetBool(wxT("convalidated")));
			foreignKey->iSetEnforced(foreignKeys->GetBool(wxT("enforced")));
			wxString onUpd = foreignKeys->GetVal(wxT("confupdtype"));
			wxString onDel = foreignKeys->GetVal(wxT("confdeltype"));
			wxString match = foreignKeys->GetVal(wxT("confmatchtype"));
			foreignKey->iSetOnUpdate(
				onUpd.IsSameAs('a') ? wxT("NO ACTION") :
				onUpd.IsSameAs('r') ? wxT("RESTRICT") :
				onUpd.IsSameAs('c') ? wxT("CASCADE") :
				onUpd.IsSameAs('d') ? wxT("SET DEFAULT") :
				onUpd.IsSameAs('n') ? wxT("SET NULL") : wxT("Unknown"));
			foreignKey->iSetOnDelete(
				onDel.IsSameAs('a') ? wxT("NO ACTION") :
				onDel.IsSameAs('r') ? wxT("RESTRICT") :
				onDel.IsSameAs('c') ? wxT("CASCADE") :
				onDel.IsSameAs('d') ? wxT("SET DEFAULT") :
				onDel.IsSameAs('n') ? wxT("SET NULL") : wxT("Unknown"));
			foreignKey->iSetMatch(
				match.IsSameAs('f') ? wxT("FULL") :
				match.IsSameAs('s') ? wxT("SIMPLE") :
				match.IsSameAs('u') ? wxT("SIMPLE") : wxT("Unknown"));

			wxString cn = foreignKeys->GetVal(wxT("conkey"));
			cn = cn.Mid(1, cn.Length() - 2);
			foreignKey->iSetConkey(cn);
			cn = foreignKeys->GetVal(wxT("confkey"));
			cn = cn.Mid(1, cn.Length() - 2);
			foreignKey->iSetConfkey(cn);
			if (collection->GetDatabase()->BackendMinimumVersion(15, 0)) {
				cn = foreignKeys->GetVal(wxT("confdelsetcols"));
				cn = cn.Mid(1, cn.Length() - 2);
				foreignKey->iSetConfdelsetcols(cn);
			}
			foreignKey->iSetDeferrable(foreignKeys->GetBool(wxT("condeferrable")));
			foreignKey->iSetDeferred(foreignKeys->GetBool(wxT("condeferred")));

			if (browser)
			{
				browser->AppendObject(collection, foreignKey);
				foreignKeys->MoveNext();
			}
			else
				break;
		}

		delete foreignKeys;
	}
	return foreignKey;
}

/////////////////////////////

wxString pgForeignKeyCollection::GetTranslatedMessage(int kindOfMessage) const
{
	wxString message = wxEmptyString;

	switch (kindOfMessage)
	{
		case RETRIEVINGDETAILS:
			message = _("Retrieving details on foreign keys");
			break;
		case REFRESHINGDETAILS:
			message = _("Refreshing foreign keys");
			break;
		case OBJECTSLISTREPORT:
			message = _("Foreign keys list report");
			break;
	}

	return message;
}

/////////////////////////////

#include "images/foreignkey.pngc"
#include "images/foreignkeybad.pngc"


pgForeignKeyFactory::pgForeignKeyFactory()
	: pgSchemaObjFactory(__("Foreign Key"), __("New Foreign Key..."), __("Create a new Foreign Key constraint."), foreignkey_png_img)
{
	metaType = PGM_FOREIGNKEY;
	collectionFactory = &constraintCollectionFactory;
	closedId = addIcon(foreignkeybad_png_img);
}


pgForeignKeyFactory foreignKeyFactory;

validateForeignKeyFactory::validateForeignKeyFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar) : contextActionFactory(list)
{
	mnu->Append(id, _("Validate foreign key"), _("Validate the selected foreign key."));
}


wxWindow *validateForeignKeyFactory::StartDialog(frmMain *form, pgObject *obj)
{
	((pgForeignKey *)obj)->Validate(form);
	((pgForeignKey *)obj)->SetDirty();

	wxTreeItemId item = form->GetBrowser()->GetSelection();
	if (obj == form->GetBrowser()->GetObject(item))
	{
		obj->ShowTreeDetail(form->GetBrowser(), 0, form->GetProperties());
		form->GetSqlPane()->SetReadOnly(false);
		form->GetSqlPane()->SetText(((pgForeignKey *)obj)->GetSql(form->GetBrowser()));
		form->GetSqlPane()->SetReadOnly(true);
	}
	form->GetMenuFactories()->CheckMenu(obj, form->GetMenuBar(), (ctlMenuToolbar *)form->GetToolBar());

	return 0;
}


bool validateForeignKeyFactory::CheckEnable(pgObject *obj)
{
	return obj && obj->IsCreatedBy(foreignKeyFactory) && obj->CanEdit()
	       && ((pgForeignKey *)obj)->GetConnection()->BackendMinimumVersion(9, 1)
	       && !((pgForeignKey *)obj)->GetValid();
}
