/*-------------------------------------------------------------------------
 *
 * deparse_foreign_server_stmts.c
 *	  All routines to deparse foreign server statements.
 *
 * Copyright (c) Citus Data, Inc.
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "commands/defrem.h"
#include "lib/stringinfo.h"
#include "nodes/nodes.h"
#include "utils/builtins.h"

#include "distributed/citus_ruleutils.h"
#include "distributed/deparser.h"
#include "distributed/listutils.h"
#include "distributed/relay_utility.h"

static void AppendCreateForeignServerStmt(StringInfo buf, CreateForeignServerStmt *stmt);
static void AppendAlterForeignServerStmt(StringInfo buf, AlterForeignServerStmt *stmt);
static void AppendAlterForeignServerOptions(StringInfo buf, AlterForeignServerStmt *stmt);
static void AppendAlterForeignServerRenameStmt(StringInfo buf, RenameStmt *stmt);
static void AppendAlterForeignServerOwnerStmt(StringInfo buf, AlterOwnerStmt *stmt);
static void AppendDropForeignServerStmt(StringInfo buf, DropStmt *stmt);
static void AppendServerNames(StringInfo buf, DropStmt *stmt);
static void AppendBehavior(StringInfo buf, DropStmt *stmt);
static char * GetDefElemActionString(DefElemAction action);
static void AppendGrantOnForeignServerStmt(StringInfo buf, GrantStmt *stmt);
static void AppendGrantOnForeignServerServers(StringInfo buf, GrantStmt *stmt);

char *
DeparseCreateForeignServerStmt(Node *node)
{
	CreateForeignServerStmt *stmt = castNode(CreateForeignServerStmt, node);

	StringInfoData str;
	initStringInfo(&str);

	AppendCreateForeignServerStmt(&str, stmt);

	return str.data;
}


char *
DeparseAlterForeignServerStmt(Node *node)
{
	AlterForeignServerStmt *stmt = castNode(AlterForeignServerStmt, node);

	StringInfoData str;
	initStringInfo(&str);

	AppendAlterForeignServerStmt(&str, stmt);

	return str.data;
}


char *
DeparseAlterForeignServerRenameStmt(Node *node)
{
	RenameStmt *stmt = castNode(RenameStmt, node);

	Assert(stmt->renameType == OBJECT_FOREIGN_SERVER);

	StringInfoData str;
	initStringInfo(&str);

	AppendAlterForeignServerRenameStmt(&str, stmt);

	return str.data;
}


char *
DeparseAlterForeignServerOwnerStmt(Node *node)
{
	AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);

	Assert(stmt->objectType == OBJECT_FOREIGN_SERVER);

	StringInfoData str;
	initStringInfo(&str);

	AppendAlterForeignServerOwnerStmt(&str, stmt);

	return str.data;
}


char *
DeparseDropForeignServerStmt(Node *node)
{
	DropStmt *stmt = castNode(DropStmt, node);

	Assert(stmt->removeType == OBJECT_FOREIGN_SERVER);

	StringInfoData str;
	initStringInfo(&str);

	AppendDropForeignServerStmt(&str, stmt);

	return str.data;
}


char *
DeparseGrantOnForeignServerStmt(Node *node)
{
	GrantStmt *stmt = castNode(GrantStmt, node);
	Assert(stmt->objtype == OBJECT_FOREIGN_SERVER);

	StringInfoData str = { 0 };
	initStringInfo(&str);

	AppendGrantOnForeignServerStmt(&str, stmt);

	return str.data;
}


static void
AppendCreateForeignServerStmt(StringInfo buf, CreateForeignServerStmt *stmt)
{
	appendStringInfoString(buf, "CREATE SERVER ");

	if (stmt->if_not_exists)
	{
		appendStringInfoString(buf, "IF NOT EXISTS ");
	}

	appendStringInfo(buf, "%s ", quote_identifier(stmt->servername));

	if (stmt->servertype)
	{
		appendStringInfo(buf, "TYPE %s ", quote_literal_cstr(stmt->servertype));
	}

	if (stmt->version)
	{
		appendStringInfo(buf, "VERSION %s ", quote_literal_cstr(stmt->version));
	}

	appendStringInfo(buf, "FOREIGN DATA WRAPPER %s ", quote_identifier(stmt->fdwname));

	AppendOptionListToString(buf, stmt->options);
}


static void
AppendAlterForeignServerStmt(StringInfo buf, AlterForeignServerStmt *stmt)
{
	appendStringInfo(buf, "ALTER SERVER %s ", quote_identifier(stmt->servername));

	if (stmt->has_version)
	{
		appendStringInfo(buf, "VERSION %s ", quote_literal_cstr(stmt->version));
	}

	AppendAlterForeignServerOptions(buf, stmt);
}


static void
AppendAlterForeignServerOptions(StringInfo buf, AlterForeignServerStmt *stmt)
{
	if (list_length(stmt->options) <= 0)
	{
		return;
	}

	appendStringInfoString(buf, "OPTIONS (");

	DefElemAction action = DEFELEM_UNSPEC;
	DefElem *def = NULL;
	foreach_declared_ptr(def, stmt->options)
	{
		if (def->defaction != DEFELEM_UNSPEC)
		{
			action = def->defaction;
			char *actionString = GetDefElemActionString(action);
			appendStringInfo(buf, "%s ", actionString);
		}

		appendStringInfo(buf, "%s", quote_identifier(def->defname));

		if (action != DEFELEM_DROP)
		{
			const char *value = quote_literal_cstr(defGetString(def));
			appendStringInfo(buf, " %s", value);
		}

		if (def != llast(stmt->options))
		{
			appendStringInfoString(buf, ", ");
		}
	}

	appendStringInfoString(buf, ")");
}


static void
AppendAlterForeignServerRenameStmt(StringInfo buf, RenameStmt *stmt)
{
	appendStringInfo(buf, "ALTER SERVER %s RENAME TO %s",
					 quote_identifier(strVal(stmt->object)),
					 quote_identifier(stmt->newname));
}


static void
AppendAlterForeignServerOwnerStmt(StringInfo buf, AlterOwnerStmt *stmt)
{
	const char *servername = quote_identifier(strVal(stmt->object));
	appendStringInfo(buf, "ALTER SERVER %s OWNER TO ", servername);

	appendStringInfo(buf, "%s", RoleSpecString(stmt->newowner, true));
}


static void
AppendDropForeignServerStmt(StringInfo buf, DropStmt *stmt)
{
	appendStringInfoString(buf, "DROP SERVER ");

	if (stmt->missing_ok)
	{
		appendStringInfoString(buf, "IF EXISTS ");
	}

	AppendServerNames(buf, stmt);

	AppendBehavior(buf, stmt);
}


static void
AppendServerNames(StringInfo buf, DropStmt *stmt)
{
	String *serverValue = NULL;
	foreach_declared_ptr(serverValue, stmt->objects)
	{
		const char *serverString = quote_identifier(strVal(serverValue));
		appendStringInfo(buf, "%s", serverString);

		if (serverValue != llast(stmt->objects))
		{
			appendStringInfoString(buf, ", ");
		}
	}
}


static void
AppendBehavior(StringInfo buf, DropStmt *stmt)
{
	if (stmt->behavior == DROP_CASCADE)
	{
		appendStringInfoString(buf, " CASCADE");
	}
	else if (stmt->behavior == DROP_RESTRICT)
	{
		appendStringInfoString(buf, " RESTRICT");
	}
}


static char *
GetDefElemActionString(DefElemAction action)
{
	switch (action)
	{
		case DEFELEM_ADD:
		{
			return "ADD";
		}

		case DEFELEM_SET:
		{
			return "SET";
		}

		case DEFELEM_DROP:
		{
			return "DROP";
		}

		default:
			return "";
	}
}


static void
AppendGrantOnForeignServerStmt(StringInfo buf, GrantStmt *stmt)
{
	Assert(stmt->objtype == OBJECT_FOREIGN_SERVER);
	AppendGrantSharedPrefix(buf, stmt);
	AppendGrantOnForeignServerServers(buf, stmt);
	AppendGrantSharedSuffix(buf, stmt);
}


static void
AppendGrantOnForeignServerServers(StringInfo buf, GrantStmt *stmt)
{
	ListCell *cell = NULL;
	appendStringInfo(buf, " ON FOREIGN SERVER ");

	foreach(cell, stmt->objects)
	{
		char *servername = strVal(lfirst(cell));
		appendStringInfoString(buf, quote_identifier(servername));
		if (cell != list_tail(stmt->objects))
		{
			appendStringInfo(buf, ", ");
		}
	}
}
