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

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

// App headers
#include "pgAdmin3.h"
#include "utils/misc.h"
#include "schema/pgForeignTable.h"
#include "schema/pgDatatype.h"


pgForeignTable::pgForeignTable(pgSchema *newSchema, const wxString &newName)
	: pgSchemaObject(newSchema, foreignTableFactory, newName)
{
}

pgForeignTable::~pgForeignTable()
{
}

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

	switch (kindOfMessage)
	{
		case RETRIEVINGDETAILS:
			message = _("Retrieving details on foreign table");
			message += wxT(" ") + GetName();
			break;
		case REFRESHINGDETAILS:
			message = _("Refreshing foreign table");
			message += wxT(" ") + GetName();
			break;
		case DROPINCLUDINGDEPS:
			message = wxString::Format(_("Are you sure you wish to drop foreign table \"%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 table \"%s\"?"),
			                           GetFullIdentifier().c_str());
			break;
		case DROPCASCADETITLE:
			message = _("Drop foreign table cascaded?");
			break;
		case DROPTITLE:
			message = _("Drop foreign table?");
			break;
		case PROPERTIESREPORT:
			message = _("Foreign table properties report");
			message += wxT(" - ") + GetName();
			break;
		case PROPERTIES:
			message = _("Foreign table properties");
			break;
		case DDLREPORT:
			message = _("Foreign table DDL report");
			message += wxT(" - ") + GetName();
			break;
		case DDL:
			message = _("Foreign table DDL");
			break;
		case DEPENDENCIESREPORT:
			message = _("Foreign table dependencies report");
			message += wxT(" - ") + GetName();
			break;
		case DEPENDENCIES:
			message = _("Foreign table dependencies");
			break;
		case DEPENDENTSREPORT:
			message = _("Foreign table dependents report");
			message += wxT(" - ") + GetName();
			break;
		case DEPENDENTS:
			message = _("Foreign table dependents");
			break;
	}

	return message;
}


bool pgForeignTable::DropObject(wxFrame *frame, ctlTree *browser, bool cascaded)
{
	wxString sql = wxT("DROP FOREIGN TABLE ") + this->GetSchema()->GetQuotedIdentifier() + wxT(".") + this->GetQuotedIdentifier();
	if (cascaded)
		sql += wxT(" CASCADE");
	return GetDatabase()->ExecuteVoid(sql);
}

wxString pgForeignTable::GetSql(ctlTree *browser)
{
	if (sql.IsNull())
	{
		sql = wxT("-- Foreign Table: ") + GetQuotedFullIdentifier() + wxT("\n\n")
		      + wxT("-- DROP FOREIGN TABLE ") + GetQuotedFullIdentifier() + wxT(";")
		      + wxT("\n\nCREATE FOREIGN TABLE ") + GetQuotedFullIdentifier()
		      + wxT("\n   (") + GetQuotedTypesList()
		      + wxT(")\n   SERVER ") + GetForeignServer();

		if (!GetOptionsList().IsEmpty())
			sql += wxT("\n   OPTIONS (") + GetOptionsList() + wxT(")");

		sql += wxT(";\n")
		       + GetOwnerSql(9, 1)
		       + GetCommentSql();

		if (GetConnection()->BackendMinimumVersion(9, 1))
			sql += GetSeqLabelsSql();
		if (GetConnection()->BackendMinimumVersion(8, 4))
		{
			wxString g = GetGrant(wxT("arwdDxt"));
			g.Replace("FOREIGN ", "");
			sql += g;
		}
		

	}

	return sql;
}



void pgForeignTable::ShowTreeDetail(ctlTree *browser, frmMain *form, ctlListView *properties, ctlSQLBox *sqlPane)
{
	wxString constraint;

	if (!expandedKids)
	{
		expandedKids = true;
		pgSet *set = ExecuteSet(
		                 wxT("SELECT attname, format_type(t.oid,NULL) AS typname, attndims, atttypmod, nspname, attnotnull,attfdwoptions,\n")
		                 wxT("       (SELECT COUNT(1) from pg_type t2 WHERE t2.typname=t.typname) > 1 AS isdup\n")
		                 wxT("  FROM pg_attribute att\n")
		                 wxT("  JOIN pg_type t ON t.oid=atttypid\n")
		                 wxT("  JOIN pg_namespace nsp ON t.typnamespace=nsp.oid\n")
		                 wxT("  LEFT OUTER JOIN pg_type b ON t.typelem=b.oid\n")
		                 wxT(" WHERE att.attrelid=") + GetOidStr() + wxT("\n")
		                 wxT(" AND attnum>0\n")
		                 wxT(" ORDER by attnum"));
		if (set)
		{
			int anzvar = 0;
			while (!set->Eof())
			{
				pgDatatype dt(set->GetVal(wxT("nspname")), set->GetVal(wxT("typname")),
				              set->GetBool(wxT("isdup")), set->GetLong(wxT("attndims")) > 0, set->GetLong(wxT("atttypmod")));
				constraint = set->GetBool(wxT("attnotnull")) ? wxT("NOT NULL") : wxT("");

				if (anzvar++)
				{
					typesList += wxT(", ");
					quotedTypesList += wxT(",\n    ");
				}
				wxString str = set->GetVal("attfdwoptions");
				if (!str.IsEmpty()) str="OPTIONS("+ ParseColumnOptions(str) + ") ";
					//FillArray(column->GetVariables(), str.Mid(1, str.Length() - 2));

				typesList += set->GetVal(wxT("attname")) + wxT(" ")
				             + dt.GetSchemaPrefix(GetDatabase()) + dt.FullName() + wxT(" ")
				             + constraint;

				quotedTypesList += qtIdent(set->GetVal(wxT("attname"))) + wxT(" ")
				                   + dt.GetQuotedSchemaPrefix(GetDatabase()) + dt.QuotedFullName() + wxT(" ")
				                   + str+ constraint;

				typesArray.Add(set->GetVal(wxT("attname")));
				typesArray.Add(dt.GetSchemaPrefix(GetDatabase()) + dt.FullName());
				typesArray.Add(constraint);

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

	if (properties)
	{
		CreateListColumns(properties);

		properties->AppendItem(_("Name"), GetName());
		properties->AppendItem(_("OID"), GetOid());
		properties->AppendItem(_("Owner"), GetOwner());
		properties->AppendItem(_("ACL"), GetAcl());
		properties->AppendItem(_("Server"), GetForeignServer());
		properties->AppendItem(_("Columns"), GetQuotedTypesList());
		properties->AppendItem(_("Options"), GetOptionsList());
		properties->AppendItem(_("Comment"), firstLineOnly(GetComment()));

		if (!GetLabels().IsEmpty())
		{
			wxArrayString seclabels = GetProviderLabelArray();
			if (seclabels.GetCount() > 0)
			{
				for (unsigned int index = 0 ; index < seclabels.GetCount() - 1 ; index += 2)
				{
					properties->AppendItem(seclabels.Item(index), seclabels.Item(index + 1));
				}
			}
		}
	}
}


wxString pgForeignTable::GetSelectSql(ctlTree *browser)
{

	wxString columns = wxEmptyString;
	wxArrayString elements = GetTypesArray();
	size_t i;
	for (i = 0 ; i < elements.GetCount() ; i += 3)
	{
		if (!columns.IsEmpty())
			columns += wxT(", ");
		columns += qtIdent(elements.Item(i));
	}

	wxString sql =
	    wxT("SELECT ") + columns + wxT("\n")
	    wxT("  FROM ") + GetQuotedFullIdentifier() + wxT(";\n");
	return sql;
}


pgObject *pgForeignTable::Refresh(ctlTree *browser, const wxTreeItemId item)
{
	pgObject *ft = 0;
	pgCollection *coll = browser->GetParentCollection(item);
	if (coll)
		ft = foreignTableFactory.CreateObjects(coll, 0, wxT("\n   AND c.oid=") + GetOidStr());

	return ft;
}


void pgForeignTable::iSetOptions(const wxString &tmpoptions)
{
	wxString tmp;
	wxString option;
	wxString value;
	wxString currentChar;
	bool wrappedInQuotes = false, antislash = false;

	options = wxEmptyString;

	if (tmpoptions == wxEmptyString)
		return;

	// parse the options string
	// we start at 1 and stop at length-1 to get rid of the { and } of the array
	for (unsigned int index = 1 ; index < tmpoptions.Length() - 1 ; index++)
	{
		// get current char
		currentChar = tmpoptions.Mid(index, 1);

		// if there is a double quote at the beginning of an option,
		// the whole option=value will be wrapped in quotes
		if (currentChar == wxT("\"") && tmp.IsEmpty())
			wrappedInQuotes = true;
		else if (currentChar == wxT("\\") && wrappedInQuotes)
			antislash = true;
		else
		{
			if ((currentChar == wxT(",") && !wrappedInQuotes && !tmp.IsEmpty())
			        || (currentChar == wxT("\"") && wrappedInQuotes && !antislash && !tmp.IsEmpty()))
			{
				// new options

				if (currentChar == wxT("\"") && wrappedInQuotes && !antislash && !tmp.IsEmpty())
				{
					// In this specific case, the next character is the comma,
					// but we don't want to start the next option with the comma
					// so we skip it right now
					index++;
				}

				// we need to grab option and value from tmp string
				option = tmp.BeforeFirst('=');
				value = tmp.AfterFirst('=');

				// put them in the array
				optionsArray.Add(option);
				optionsArray.Add(value);

				// build the options string
				if (options.Length() > 0)
					options += wxT(", ");
				options += option + wxT(" '") + value + wxT("'");

				// reinit tmp
				tmp = wxEmptyString;
				wrappedInQuotes = false;
			}
			else
				tmp += currentChar;
			antislash = false;
		}
	}

	// last options

	if (!tmp.IsEmpty())
	{
		// we need to grab option and value from tmp string
		option = tmp.BeforeFirst('=');
		value = tmp.AfterFirst('=');

		// put them in the array
		optionsArray.Add(option);
		optionsArray.Add(value);

		// build the options string
		if (options.Length() > 0)
			options += wxT(", ");
		options += option + wxT(" '") + value + wxT("'");
	}
}
wxString pgForeignTable::ParseColumnOptions(const wxString& tmpoptions)
{
	wxString tmp;
	wxString option;
	wxString value;
	wxString currentChar;
	bool wrappedInQuotes = false, antislash = false;

	wxString options = wxEmptyString;

	if (tmpoptions == wxEmptyString)
		return "";

	// parse the options string
	// we start at 1 and stop at length-1 to get rid of the { and } of the array
	for (unsigned int index = 1; index < tmpoptions.Length() - 1; index++)
	{
		// get current char
		currentChar = tmpoptions.Mid(index, 1);

		// if there is a double quote at the beginning of an option,
		// the whole option=value will be wrapped in quotes
		if (currentChar == wxT("\"") && tmp.IsEmpty())
			wrappedInQuotes = true;
		else if (currentChar == wxT("\\") && wrappedInQuotes)
			antislash = true;
		else
		{
			if ((currentChar == wxT(",") && !wrappedInQuotes && !tmp.IsEmpty())
				|| (currentChar == wxT("\"") && wrappedInQuotes && !antislash && !tmp.IsEmpty()))
			{
				// new options

				if (currentChar == wxT("\"") && wrappedInQuotes && !antislash && !tmp.IsEmpty())
				{
					// In this specific case, the next character is the comma,
					// but we don't want to start the next option with the comma
					// so we skip it right now
					index++;
				}

				// we need to grab option and value from tmp string
				option = tmp.BeforeFirst('=');
				value = tmp.AfterFirst('=');

				// put them in the array

				// build the options string
				if (options.Length() > 0)
					options += wxT(", ");
				options += option + wxT(" '") + value + wxT("'");

				// reinit tmp
				tmp = wxEmptyString;
				wrappedInQuotes = false;
			}
			else
				tmp += currentChar;
			antislash = false;
		}
	}

	// last options

	if (!tmp.IsEmpty())
	{
		// we need to grab option and value from tmp string
		option = tmp.BeforeFirst('=');
		value = tmp.AfterFirst('=');

		// build the options string
		if (options.Length() > 0)
			options += wxT(", ");
		options += option + wxT(" '") + value + wxT("'");
	}
	return options;
}


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


pgForeignTableCollection::pgForeignTableCollection(pgaFactory *factory, pgSchema *sch)
	: pgSchemaObjCollection(factory, sch)
{
}


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

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

	return message;
}


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


pgObject *pgForeignTableFactory::CreateObjects(pgCollection *collection, ctlTree *browser, const wxString &restriction)
{
	pgForeignTable *foreigntable = 0;

	wxString sql =	wxT("SELECT c.oid AS ftoid, c.relname AS ftrelname, pg_get_userbyid(relowner) AS ftowner,\n")
	                wxT("  ftoptions, srvname AS ftsrvname, description,c.relacl,\n")
	                wxT("  (SELECT array_agg(label) FROM pg_seclabels sl1 WHERE sl1.objoid=c.oid) AS labels,\n")
	                wxT("  (SELECT array_agg(provider) FROM pg_seclabels sl2 WHERE sl2.objoid=c.oid) AS providers\n")
	                wxT("  FROM pg_class c\n")
	                wxT("  JOIN pg_foreign_table ft ON c.oid=ft.ftrelid\n")
	                wxT("  LEFT OUTER JOIN pg_foreign_server fs ON ft.ftserver=fs.oid\n")
	                wxT("  LEFT OUTER JOIN pg_description des ON (des.objoid=c.oid AND des.classoid='pg_class'::regclass AND des.objsubid=0)\n")
	                wxT(" WHERE c.relnamespace = ") + collection->GetSchema()->GetOidStr() + wxT("\n")
	                + restriction +
	                wxT(" ORDER BY c.relname");

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

	if (foreigntables)
	{
		while (!foreigntables->Eof())
		{
			foreigntable = new pgForeignTable(collection->GetSchema(), foreigntables->GetVal(wxT("ftrelname")));

			foreigntable->iSetOid(foreigntables->GetOid(wxT("ftoid")));
			foreigntable->iSetOwner(foreigntables->GetVal(wxT("ftowner")));
			foreigntable->iSetForeignServer(foreigntables->GetVal(wxT("ftsrvname")));
			foreigntable->iSetOptions(foreigntables->GetVal(wxT("ftoptions")));
			foreigntable->iSetComment(foreigntables->GetVal(wxT("description")));
			foreigntable->iSetProviders(foreigntables->GetVal(wxT("providers")));
			foreigntable->iSetLabels(foreigntables->GetVal(wxT("labels")));
			foreigntable->iSetAcl(foreigntables->GetVal(wxT("relacl")));

			if (browser)
			{
				browser->AppendObject(collection, foreigntable);
				foreigntables->MoveNext();
			}
			else
				break;
		}

		delete foreigntables;
	}
	return foreigntable;
}


#include "images/foreigntable.pngc"
#include "images/foreigntables.pngc"

pgForeignTableFactory::pgForeignTableFactory()
	: pgSchemaObjFactory(__("Foreign Table"), __("New Foreign Table..."), __("Create a new Foreign Table."), foreigntable_png_img)
{
	metaType = PGM_FOREIGNTABLE;
}


pgCollection *pgForeignTableFactory::CreateCollection(pgObject *obj)
{
	return new pgForeignTableCollection(GetCollectionFactory(), (pgSchema *)obj);
}

pgForeignTableFactory foreignTableFactory;
static pgaCollectionFactory cf(&foreignTableFactory, __("Foreign Tables"), foreigntables_png_img);
