/*
 * psql - the PostgreSQL interactive terminal
 *
 * Copyright (c) 2000-2006, PostgreSQL Global Development Group
 *
 * $PostgreSQL: pgsql/src/bin/psql/input.c,v 1.53 2006/03/21 13:38:12 momjian Exp $
 */
#include "postgres_fe.h"

#include "pqexpbuffer.h"
#include "input.h"
#include "settings.h"
#include "tab-complete.h"
#include "common.h"

#ifndef WIN32
#define PSQLHISTORY ".psql_history"
#else
#define PSQLHISTORY "psql_history"
#endif

/* Runtime options for turning off readline and history */
/* (of course there is no runtime command for doing that :) */
#ifdef USE_READLINE
static bool useReadline;
static bool useHistory;
char	   *psql_history;

/*
 *	Preserve newlines in saved queries by mapping '\n' to NL_IN_HISTORY
 *
 *	It is assumed NL_IN_HISTORY will never be entered by the user
 *	nor appear inside a multi-byte string.  0x00 is not properly
 *	handled by the readline routines so it can not be used
 *	for this purpose.
 */
#define NL_IN_HISTORY	0x01

enum histcontrol
{
	hctl_none = 0,
	hctl_ignorespace = 1,
	hctl_ignoredups = 2,
	hctl_ignoreboth = hctl_ignorespace | hctl_ignoredups
};
#endif

#ifdef HAVE_ATEXIT
static void finishInput(void);
#else
/* designed for use with on_exit() */
static void finishInput(int, void *);
#endif


#ifdef USE_READLINE
static enum histcontrol
GetHistControlConfig(void)
{
	enum histcontrol HC;
	const char *var;

	var = GetVariable(pset.vars, "HISTCONTROL");

	if (!var)
		HC = hctl_none;
	else if (strcmp(var, "ignorespace") == 0)
		HC = hctl_ignorespace;
	else if (strcmp(var, "ignoredups") == 0)
		HC = hctl_ignoredups;
	else if (strcmp(var, "ignoreboth") == 0)
		HC = hctl_ignoreboth;
	else
		HC = hctl_none;

	return HC;
}
#endif


static char *
gets_basic(const char prompt[])
{
	fputs(prompt, stdout);
	fflush(stdout);
	return gets_fromFile(stdin);
}


/*
 * gets_interactive()
 *
 * Gets a line of interactive input, using readline of desired.
 * The result is malloc'ed.
 */
char *
gets_interactive(const char *prompt)
{
#ifdef USE_READLINE
	char	   *s;

	if (useReadline)
		/* On some platforms, readline is declared as readline(char *) */
		s = readline((char *) prompt);
	else
		s = gets_basic(prompt);

	return s;
#else
	return gets_basic(prompt);
#endif
}


/* Put the line in the history buffer and also add the trailing \n */
void
pg_append_history(char *s, PQExpBuffer history_buf)
{
#ifdef USE_READLINE

	int slen;
	if (useReadline && useHistory && s && s[0])
	{
		slen = strlen(s);
		if (s[slen-1] == '\n')
			appendPQExpBufferStr(history_buf, s);
		else
		{
			appendPQExpBufferStr(history_buf, s);
			appendPQExpBufferChar(history_buf, '\n');
		}
	}	
#endif	
}


/*
 *	Feed the string to readline
 */
void
pg_write_history(char *s)
{
#ifdef USE_READLINE
	static char *prev_hist;
	int slen, i;
	
	if (useReadline && useHistory )
	{
		enum histcontrol HC;
		
		/* Flushing of empty buffer should do nothing */
		if  (*s == 0)
			return;
		
		prev_hist = NULL;
			
		HC = GetHistControlConfig();

		if (((HC & hctl_ignorespace) && s[0] == ' ') ||
		  ((HC & hctl_ignoredups) && prev_hist && strcmp(s, prev_hist) == 0))
		{
			/* Ignore this line as far as history is concerned */
		}
		else
		{
			free(prev_hist);
			slen = strlen(s);
			/* Trim the trailing \n's */
			for (i = slen-1; i >= 0 && s[i] == '\n'; i--)
				;
			s[i + 1] = '\0';
			prev_hist = pg_strdup(s);
			add_history(s);
		}
	}
#endif
}

void
pg_clear_history(PQExpBuffer history_buf)
{
#ifdef USE_READLINE	
	if (useReadline && useHistory)
		resetPQExpBuffer(history_buf);
#endif
}


/*
 * gets_fromFile
 *
 * Gets a line of noninteractive input from a file (which could be stdin).
 */
char *
gets_fromFile(FILE *source)
{
	PQExpBufferData buffer;
	char		line[1024];

	initPQExpBuffer(&buffer);

	while (fgets(line, sizeof(line), source) != NULL)
	{
		appendPQExpBufferStr(&buffer, line);
		if (buffer.data[buffer.len - 1] == '\n')
		{
			buffer.data[buffer.len - 1] = '\0';
			return buffer.data;
		}
	}

	if (buffer.len > 0)
		return buffer.data;		/* EOF after reading some bufferload(s) */

	/* EOF, so return null */
	termPQExpBuffer(&buffer);
	return NULL;
}


#ifdef USE_READLINE
static void
encode_history(void)
{
	HIST_ENTRY *cur_hist;
	char *cur_ptr;

	for (history_set_pos(0), cur_hist = current_history();
		 cur_hist; cur_hist = next_history())
		for (cur_ptr = cur_hist->line; *cur_ptr; cur_ptr++)
			if (*cur_ptr == '\n')
				*cur_ptr = NL_IN_HISTORY;
}

static void
decode_history(void)
{
	HIST_ENTRY *cur_hist;
	char *cur_ptr;

	for (history_set_pos(0), cur_hist = current_history();
		 cur_hist; cur_hist = next_history())
		for (cur_ptr = cur_hist->line; *cur_ptr; cur_ptr++)
			if (*cur_ptr == NL_IN_HISTORY)
				*cur_ptr = '\n';
}
#endif /* USE_READLINE */


/*
 * Put any startup stuff related to input in here. It's good to maintain
 * abstraction this way.
 *
 * The only "flag" right now is 1 for use readline & history.
 */
void
initializeInput(int flags)
{
#ifdef USE_READLINE
	if (flags & 1)
	{
		char		home[MAXPGPATH];

		useReadline = true;
		initialize_readline();

		useHistory = true;
		if (GetVariable(pset.vars, "HISTSIZE") == NULL)
			SetVariable(pset.vars, "HISTSIZE", "500");
		using_history();

		if (GetVariable(pset.vars, "HISTFILE") == NULL)
		{
			if (get_home_path(home))
			{
				psql_history = pg_malloc(strlen(home) + 1 +
										 strlen(PSQLHISTORY) + 1);
				snprintf(psql_history, MAXPGPATH, "%s/%s", home, PSQLHISTORY);
			}
		}
		else
		{
			psql_history = pg_strdup(GetVariable(pset.vars, "HISTFILE"));
			expand_tilde(&psql_history);
		}

		if (psql_history)
			read_history(psql_history);
			
		decode_history();
	}
#endif

#ifdef HAVE_ATEXIT
	atexit(finishInput);
#else
	on_exit(finishInput, NULL);
#endif
}


/* This function is designed for saving the readline history when user 
 * run \s command or when psql finishes. 
 * We have an argument named encodeFlag to handle those cases differently
 * In that case of call via \s we don't really need to encode \n as \x01,
 * but when we save history for Readline we must do that conversion
 */
bool
saveHistory(char *fname, bool encodeFlag)
{
#ifdef USE_READLINE
	if (useHistory && fname)
	{
		if (encodeFlag)
			encode_history();
		if (write_history(fname) == 0)
			return true;

		psql_error("could not save history to file \"%s\": %s\n", fname, strerror(errno));
	}
#else
	psql_error("history is not supported by this installation\n");
#endif

	return false;
}


static void
#ifdef HAVE_ATEXIT
finishInput(void)
#else
finishInput(int exitstatus, void *arg)
#endif
{
#ifdef USE_READLINE
	if (useHistory && psql_history)
	{
		int			hist_size;

		hist_size = GetVariableNum(pset.vars, "HISTSIZE", -1, -1, true);
		if (hist_size >= 0)
			stifle_history(hist_size);

		saveHistory(psql_history, true);
		free(psql_history);
		psql_history = NULL;
	}
#endif
}
