/*
 *	Top users/processes display for Unix
 *	Version 3
 *
 *	This program may be freely redistributed,
 *	but this entire comment MUST remain intact.
 *
 *	Copyright (c) 1984, 1989, William LeFebvre, Rice University
 *	Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University
 *	Copyright (c) 2007-2019, Mark Wong
 */

/*
 *	This file contains the routines that implement some of the interactive
 *	mode commands.	Note that some of the commands are implemented in-line
 *	in "main".	This is necessary because they change the global state of
 *	"top" (i.e.:  changing the number of processes to display).
 */

#include "os.h"
#include <ctype.h>
#include <signal.h>
#include <errno.h>
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif
#include <unistd.h>

#include "pg_top.h"
#include "boolean.h"
#include "utils.h"
#include "version.h"
#include "machine.h"
#include "help.h"
#include "display.h"
#include "pg.h"
#include "commands.h"
#include "screen.h"

extern int	errno;

extern char *copyright;

/* imported from screen.c */
extern int	overstrike;

extern int	max_topn;

#define BEGIN "BEGIN;"
#define ROLLBACK "ROLLBACK;"

struct cmd	cmd_map[] = {
	{'\014', cmd_redraw},
	{'#', cmd_number},
	{' ', cmd_update},
	{'?', cmd_help},
	{'A', cmd_explain_analyze},
	{'a', cmd_activity},
	{'c', cmd_cmdline},
#ifdef ENABLE_COLOR
	{'C', cmd_color},
#endif							/* ENABLE_COLOR */
	{'d', cmd_displays},
	{'E', cmd_explain},
	{'h', cmd_help},
	{'i', cmd_idletog},
	{'I', cmd_io},
	{'L', cmd_locks},
	{'n', cmd_number},
	{'o', cmd_order},
	{'q', cmd_quit},
	{'R', cmd_replication},
	{'Q', cmd_current_query},
	{'s', cmd_delay},
	{'u', cmd_user},
	{'\0', NULL},
};

int
cmd_activity(struct pg_top_context *pgtctx)
{
	pgtctx->mode = MODE_PROCESSES;
	pgtctx->header_text =
		pgtctx->header_options[pgtctx->mode_remote][pgtctx->mode];
	reset_display(pgtctx);
	return No;
}

#ifdef ENABLE_COLOR
int
cmd_color(struct pg_top_context *pgtctx)
{
	reset_display(pgtctx);
	if (pgtctx->color_on)
	{
		pgtctx->color_on = 0;
		display_resize();		/* To realloc screenbuf */
		new_message(MT_standout | MT_delayed, " Color off");
	}
	else
	{
		if (!smart_terminal)
		{
			new_message(MT_standout | MT_delayed,
						" Sorry, cannot do colors on this terminal type");
		}
		else
		{
			pgtctx->color_on = 1;
			new_message(MT_standout | MT_delayed, " Color on");
		}
	}
	return No;
}
#endif							/* ENABLE_COLOR */

int
cmd_cmdline(struct pg_top_context *pgtctx)
{
	if (pgtctx->statics.flags.fullcmds)
	{
		pgtctx->ps.fullcmd = (pgtctx->ps.fullcmd + 1) % 3;
		switch (pgtctx->ps.fullcmd)
		{
			case 2:
				new_message(MT_standout | MT_delayed, " Displaying current query.");
				break;
			case 1:
				new_message(MT_standout | MT_delayed,
							" Displaying full command lines.");
				break;
			case 0:
			default:
				new_message(MT_standout | MT_delayed,
							" Not displaying full command lines.");
		}
	}
	else
	{
		new_message(MT_standout, " Full command display not supported.");
		/* no_command = Yes; */
	}
	putchar('\r');
	return No;
}

int
cmd_current_query(struct pg_top_context *pgtctx)
{
	int			newval;
	char		tempbuf1[50];

	new_message(MT_standout, "Current query of process: ");
	newval = readline(tempbuf1, 8, Yes);
	reset_display(pgtctx);
	display_pagerstart();
	show_current_query(&pgtctx->conninfo, newval);
	display_pagerend();
	return No;
}

int
cmd_delay(struct pg_top_context *pgtctx)
{
	int			i;
	char		tempbuf[50];

	new_message(MT_standout, "Seconds to delay: ");
	if ((i = readline(tempbuf, 8, Yes)) > -1)
	{
		if ((pgtctx->delay = i) == 0 && getuid() != 0)
		{
			pgtctx->delay = 1;
		}
	}
	clear_message();
	return No;
}

int
cmd_displays(struct pg_top_context *pgtctx)
{
	int			i;
	char		tempbuf[50];

	new_message(MT_standout, "Displays to show (currently %s): ",
				pgtctx->displays == -1 ? "infinite" : itoa(pgtctx->displays));
	if ((i = readline(tempbuf, 10, Yes)) > 0)
	{
		pgtctx->displays = i;
	}
	else if (i == 0)
	{
		quit(0);
	}
	clear_message();
	return No;
}

int
cmd_explain(struct pg_top_context *pgtctx)
{
	int			newval;
	char		tempbuf1[50];

	new_message(MT_standout, "Re-determine execution plan: ");
	newval = readline(tempbuf1, 8, Yes);
	reset_display(pgtctx);
	display_pagerstart();
	show_explain(&pgtctx->conninfo, newval, EXPLAIN);
	display_pagerend();
	return No;
}

int
cmd_explain_analyze(struct pg_top_context *pgtctx)
{
	int			newval;
	char		tempbuf1[50];

	new_message(MT_standout, "Re-run SQL for analysis: ");
	newval = readline(tempbuf1, 8, Yes);
	reset_display(pgtctx);
	display_pagerstart();
	show_explain(&pgtctx->conninfo, newval, EXPLAIN_ANALYZE);
	display_pagerend();
	return No;
}

int
cmd_help(struct pg_top_context *pgtctx)
{
	reset_display(pgtctx);
	display_pagerstart();
	show_help(&pgtctx->statics);
	display_pagerend();
	return No;
}

int
cmd_idletog(struct pg_top_context *pgtctx)
{
	pgtctx->ps.idle = !pgtctx->ps.idle;
	new_message(MT_standout | MT_delayed, " %sisplaying idle processes.",
				pgtctx->ps.idle ? "D" : "Not d");
	putchar('\r');
	return No;
}

int
cmd_io(struct pg_top_context *pgtctx)
{
	pgtctx->mode = MODE_IO_STATS;
	pgtctx->header_text =
		pgtctx->header_options[pgtctx->mode_remote][pgtctx->mode];
	reset_display(pgtctx);
	return No;
}

int
cmd_locks(struct pg_top_context *pgtctx)
{
	int			newval;
	char		tempbuf1[50];

	new_message(MT_standout, "Show locks held by process: ");
	newval = readline(tempbuf1, 8, Yes);
	reset_display(pgtctx);
	display_pagerstart();
	show_locks(&pgtctx->conninfo, newval);
	display_pagerend();
	return No;
}

int
cmd_number(struct pg_top_context *pgtctx)
{
	int			newval;
	char		tempbuf[50];

	new_message(MT_standout, "Number of processes to show: ");
	newval = readline(tempbuf, 8, Yes);
	if (newval > -1)
	{
		if (newval > max_topn)
		{
			new_message(MT_standout | MT_delayed,
						" This terminal can only display %d processes.",
						max_topn);
			putchar('\r');
		}

		if (newval == 0)
		{
			/* inhibit the header */
			display_header(No);
		}
		else if (newval > pgtctx->topn && pgtctx->topn == 0)
		{
			/* redraw the header */
			display_header(Yes);
			pgtctx->d_header = i_header;
		}
		pgtctx->topn = newval;
	}
	return No;
}

int
cmd_quit(struct pg_top_context *pgtctx)
{
	quit(0);
	/* NOT REACHED */
	return No;
}

int
cmd_replication(struct pg_top_context *pgtctx)
{
	pgtctx->mode = MODE_REPLICATION;
	pgtctx->header_text =
		pgtctx->header_options[pgtctx->mode_remote][pgtctx->mode];
	reset_display(pgtctx);
	return No;
}

int
cmd_order(struct pg_top_context *pgtctx)
{
	int			i;
	int			no_command = No;
	char		tempbuf[50];

	if (pgtctx->statics.order_names == NULL)
	{
		new_message(MT_standout, " Ordering not supported.");
		putchar('\r');
		no_command = Yes;
	}
	else
	{
		new_message(MT_standout, "Order to sort: ");
		if (readline(tempbuf, sizeof(tempbuf), No) > 0)
		{
			i = string_index(tempbuf, pgtctx->statics.order_names);
			if (i == -1)
			{
				new_message(MT_standout, " %s: unrecognized sorting order",
							tempbuf);
				no_command = Yes;
			}
			else
			{
				pgtctx->order_index = i;
			}
			putchar('\r');
		}
		else
		{
			clear_message();
		}
	}
	return no_command;
}

int
cmd_order_cpu(struct pg_top_context *pgtctx)
{
	int			i;

	if ((i = string_index("cpu", pgtctx->statics.order_names)) == -1)
	{
		new_message(MT_standout, " Unrecognized sorting order");
		putchar('\r');
		/* no_command = Yes; */
	}
	else
	{
		pgtctx->order_index = i;
	}
	return No;
}

int
cmd_order_mem(struct pg_top_context *pgtctx)
{
	int			i;

	if ((i = string_index("size", pgtctx->statics.order_names)) == -1)
	{
		new_message(MT_standout, " Unrecognized sorting order");
		putchar('\r');
		return Yes;
	}
	else
	{
		pgtctx->order_index = i;
	}
	return No;
}

int
cmd_redraw(struct pg_top_context *pgtctx)
{
	reset_display(pgtctx);
	return No;
}

int
cmd_update(struct pg_top_context *pgtctx)
{
	/* go home for visual feedback */
	go_home();
	fflush(stdout);
	return No;
}

int
cmd_user(struct pg_top_context *pgtctx)
{
	new_message(MT_standout, "Username to show: ");
	if (readline(pgtctx->ps.usename, sizeof(pgtctx->ps.usename), No) > 0)
	{
		putchar('\r');
	}
	else
	{
		clear_message();
	}
	return No;
}

int
execute_command(struct pg_top_context *pgtctx, char ch)
{
	struct cmd *cmap;

	cmap = cmd_map;

	while (cmap->func != NULL)
	{
		if (cmap->ch == ch)
		{
			return (cmap->func) (pgtctx);
		}
		++cmap;
	}
	return No;
}

/*
 *	str_adderr(str, len, err) - add an explanation of error "err" to
 *	the string "str".
 */

int
str_adderr(char *str, int len, int err)
{
	register char *msg;
	register int msglen;

	msg = err == 0 ? "Not a number" : errmsg(err);
	msglen = strlen(msg) + 2;
	if (len <= msglen)
	{
		return (0);
	}
	(void) strcat(str, ": ");
	(void) strcat(str, msg);
	return (len - msglen);
}

/*
 *	str_addarg(str, len, arg, first) - add the string argument "arg" to
 *	the string "str".  This is the first in the group when "first"
 *	is set (indicating that a comma should NOT be added to the front).
 */

int
str_addarg(char *str, int len, char *arg, int first)
{
	register int arglen;

	arglen = strlen(arg);
	if (!first)
	{
		arglen += 2;
	}
	if (len <= arglen)
	{
		return (0);
	}
	if (!first)
	{
		(void) strcat(str, ", ");
	}
	(void) strcat(str, arg);
	return (len - arglen);
}

/*
 *	show_help() - display the help screen; invoked in response to
 *		either 'h' or '?'.
 */

void
show_help(struct statics *stp)
{
	static char *fullhelp;
	char	   *p = NULL;

	if (fullhelp == NULL)
	{
		/* set it up first time thru */
		if (stp->order_names != NULL)
		{
			p = string_list(stp->order_names);
		}
		if (p == NULL)
		{
			p = "not supported";
		}
		fullhelp = (char *) malloc(strlen(help_text) + strlen(p) + 2);
		sprintf(fullhelp, help_text, p);
	}

	display_pager("pg_top version ");
	display_pager(version_string());
	display_pager(", ");
	display_pager(copyright);
	display_pager("\n");
	display_pager(fullhelp);
}

/*
 *	Utility routines that help with some of the commands.
 */

char *
next_field(char *str)
{
	if ((str = strchr(str, ' ')) == NULL)
	{
		return (NULL);
	}
	*str = '\0';
	while (*++str == ' ') /* loop */ ;

	/* if there is nothing left of the string, return NULL */
	/* This fix is dedicated to Greg Earle */
	return (*str == '\0' ? NULL : str);
}

int
scanint(char *str, int *intp)
{
	register int val = 0;
	register char ch;

	/* if there is nothing left of the string, flag it as an error */
	/* This fix is dedicated to Greg Earle */
	if (*str == '\0')
	{
		return (-1);
	}

	while ((ch = *str++) != '\0')
	{
		if (isdigit(ch))
		{
			val = val * 10 + (ch - '0');
		}
		else if (isspace(ch))
		{
			break;
		}
		else
		{
			return (-1);
		}
	}
	*intp = val;
	return (0);
}

void
show_current_query(struct pg_conninfo_ctx *conninfo, int procpid)
{
	int			i;
	int			rows;
	char		info[64];
	PGresult   *pgresult = NULL;

	sprintf(info, "Current query for procpid %d:\n\n", procpid);
	display_pager(info);

	/* Get the currently running query. */
	connect_to_db(conninfo);
	if (conninfo->connection != NULL)
	{
		pgresult = pg_query(conninfo->connection, procpid);
		rows = PQntuples(pgresult);
	}
	else
	{
		rows = 0;
	}
	for (i = 0; i < rows; i++)
	{
		display_pager(PQgetvalue(pgresult, i, 0));
	}
	display_pager("\n\n");

	if (pgresult != NULL)
		PQclear(pgresult);
	disconnect_from_db(conninfo);
}

void
show_explain(struct pg_conninfo_ctx *conninfo, int procpid, int analyze)
{
	int			i,
				j;
	int			rows,
				r;
	char		sql[4096];
	char		info[1024];
	PGresult   *pgresult_query = NULL;
	PGresult   *pgresult_explain = NULL;

	sprintf(info,
			"Current query plan for procpid %d:\n\n Statement:\n\n",
			procpid);
	display_pager(info);

	/* Get the currently running query. */
	connect_to_db(conninfo);
	if (conninfo->connection != NULL)
	{
		pgresult_query = pg_query(conninfo->connection, procpid);
		rows = PQntuples(pgresult_query);
	}
	else
	{
		rows = 0;
	}
	for (i = 0; i < rows; i++)
	{
		/* Display the query before the query plan. */
		display_pager(PQgetvalue(pgresult_query, i, 0));

		/* Execute the EXPLAIN. */
		if (analyze == EXPLAIN_ANALYZE)
		{
			sprintf(sql, "EXPLAIN (ANALYZE, VERBOSE, BUFFERS)\n%s",
					PQgetvalue(pgresult_query, i, 0));
		}
		else
		{
			sprintf(sql, "EXPLAIN\n%s", PQgetvalue(pgresult_query, i, 0));
		}
		PQexec(conninfo->connection, BEGIN);
		pgresult_explain = PQexec(conninfo->connection, sql);
		PQexec(conninfo->connection, ROLLBACK);
		r = PQntuples(pgresult_explain);
		/* This will display an error if the EXPLAIN fails. */
		display_pager("\n\nQuery Plan:\n\n");
		display_pager(PQresultErrorMessage(pgresult_explain));
		for (j = 0; j < r; j++)
		{
			display_pager(PQgetvalue(pgresult_explain, j, 0));
			display_pager("\n");
		}
		if (pgresult_explain != NULL)
			PQclear(pgresult_explain);
	}
	display_pager("\n\n");

	if (pgresult_query != NULL)
		PQclear(pgresult_query);
	disconnect_from_db(conninfo);
}

void
show_locks(struct pg_conninfo_ctx *conninfo, int procpid)
{
	int			i,
				j,
				k;
	int			rows;
	char		info[64];
	int			width[7] = {1, 8, 6, 5, 5, 4, 7};
	PGresult   *pgresult = NULL;
	char		header_format[1024];
	char		line_format[1024];
	char		prefix[21];		/* Should hold any 64 bit integer. */
	char		line[1024];

	sprintf(info, "Locks held by procpid %d:\n\n", procpid);
	display_pager(info);

	/* Get the locks helf by the process. */
	connect_to_db(conninfo);
	if (conninfo->connection == NULL)
	{
		disconnect_from_db(conninfo);
		return;
	}

	pgresult = pg_locks(conninfo->connection, procpid);
	rows = PQntuples(pgresult);

	/* Determine column sizes. */
	sprintf(prefix, "%d", rows);
	width[0] = strlen(prefix);
	for (i = 0; i < rows; i++)
	{
		if (strlen(PQgetvalue(pgresult, i, 0)) > width[1])
			width[1] = strlen(PQgetvalue(pgresult, i, 0));
		if (strlen(PQgetvalue(pgresult, i, 1)) > width[2])
			width[2] = strlen(PQgetvalue(pgresult, i, 1));
		if (strlen(PQgetvalue(pgresult, i, 2)) > width[3])
			width[3] = strlen(PQgetvalue(pgresult, i, 2));
		if (strlen(PQgetvalue(pgresult, i, 3)) > width[4])
			width[4] = strlen(PQgetvalue(pgresult, i, 3));
		if (strlen(PQgetvalue(pgresult, i, 4)) > width[5])
			width[5] = strlen(PQgetvalue(pgresult, i, 4));
		if (strlen(PQgetvalue(pgresult, i, 5)) > width[6])
			width[6] = strlen(PQgetvalue(pgresult, i, 5));
	}
	sprintf(header_format,
			"%%-%ds | %%-%ds | %%-%ds | %%-%ds | %%-%ds | %%-%ds | %%-%ds\n",
			width[0], width[1], width[2], width[3], width[4], width[5],
			width[6]);
	sprintf(line_format,
			"%%%dd | %%-%ds | %%-%ds | %%-%ds | %%-%ds | %%-%ds | %%-%ds\n",
			width[0], width[1], width[2], width[3], width[4], width[5],
			width[6]);

	/* Display the header. */
	sprintf(line, header_format, "", "database", "schema", "table", "index",
			"type", "granted");
	display_pager(line);
	for (i = 0, k = 0; i < 7; i++)
	{
		for (j = 0; j < width[i]; j++, k++)
		{
			line[k] = '-';
		}
		line[k++] = '-';
		line[k++] = '+';
		line[k++] = '-';
	}
	line[k - 3] = '\n';
	line[k - 2] = '\0';
	display_pager(line);

	/* Display data. */
	for (i = 0; i < rows; i++)
	{
		sprintf(line, line_format, i + 1, PQgetvalue(pgresult, i, 0),
				PQgetvalue(pgresult, i, 1), PQgetvalue(pgresult, i, 2),
				PQgetvalue(pgresult, i, 3), PQgetvalue(pgresult, i, 4),
				PQgetvalue(pgresult, i, 5));
		display_pager(line);
	}
	display_pager("\n");

	PQclear(pgresult);
	disconnect_from_db(conninfo);
}