// input.cc                                             -*- C++ -*-
/*

Copyright (C) 1992, 1993, 1994, 1995 John W. Eaton

This file is part of Octave.

Octave is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.

Octave is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License
along with Octave; see the file COPYING.  If not, write to the Free
Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.

*/

/*

The 3 functions listed below were adapted from similar functions
from GNU Bash, the Bourne Again SHell, copyright (C) 1987, 1989, 1991
Free Software Foundation, Inc.

  read_octal    sub_append_string    decode_prompt_string

*/

// Use the GNU readline library for command line editing and hisory.

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <sys/types.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream.h>
#include <strstream.h>
#include <string.h>
#include <assert.h>
#include <signal.h>

// This must come before anything that includes iostream.h...
extern "C"
{
#include "readline/readline.h"
#include "readline/history.h"

extern void free_undo_list ();

extern char *xmalloc ();

// Yes, this sucks, but it avoids a conflict with another readline
// function declared in iostream.h.

#if 0
#define LINE_SIZE 8192
static int no_line_editing = 0;
#endif

char *
gnu_readline (char *s)
{
#if 0
  static int state = 0;
  static char *line_from_stdin = 0;
  if (no_line_editing)
    {
      if (! state)
	{
	  line_from_stdin = (char *) malloc (LINE_SIZE);
	  state = 1;
	}
      fputs ("octave> ", stdout);
      fgets (line_from_stdin, LINE_SIZE, stdin);
      return line_from_stdin;
    }
  else
#endif
    return readline (s);
}
}

#include "help.h"
#include "error.h"
#include "utils.h"
#include "input.h"
#include "pager.h"
#include "parse.h"
#include "dirfns.h"
#include "octave.h"
#include "sysdep.h"
#include "variables.h"
#include "tree-const.h"
#include "octave-hist.h"
#include "sighandlers.h"
#include "user-prefs.h"
#include "oct-obj.h"
#include "defun.h"

#ifndef MAXPATHLEN
#define MAXPATHLEN 1024
#endif

// The size that strings change by.
#ifndef DEFAULT_ARRAY_SIZE
#define DEFAULT_ARRAY_SIZE 512
#endif

// The growth rate for the prompt string.
#ifndef PROMPT_GROWTH
#define PROMPT_GROWTH 50
#endif

// Global pointer for eval().
const char *current_eval_string = 0;

// Nonzero means get input from current_eval_string.
int get_input_from_eval_string = 0;

// Nonzero means we're parsing a function file.
int reading_fcn_file = 0;

// Simple name of function file we are reading.
char *curr_fcn_file_name = 0;

// Nonzero means we're parsing a script file.
int reading_script_file = 0;

// If we are reading from an M-file, this is it.
FILE *ff_instream = 0;

// Nonzero means we are using readline.
int using_readline = 1;

// Nonzero means commands are echoed as they are executed.
// (--echo-commands; -x).
int echo_input = 0;

// Nonzero means this is an interactive shell.
int interactive = 0;

// Nonzero means the user forced this shell to be interactive (-i).
int forced_interactive = 0;

// Should we issue a prompt?
int promptflag = 1;

// The current line of input, from wherever.
char *current_input_line = 0;

// A line of input from readline.
static char *octave_gets_line = 0;

extern tree_constant eval_string (const char *string, int print,
				  int ans_assign, int& parse_status);

// Append SOURCE to TARGET at INDEX.  SIZE is the current amount of
// space allocated to TARGET.  SOURCE can be NULL, in which case
// nothing happens.  Gets rid of SOURCE by free ()ing it.  Returns
// TARGET in case the location has changed.

static char *
sub_append_string (char *source, char *target, int *index, int *size)
{
  if (source)
    {
      while ((int)strlen (source) >= (int)(*size - *index))
	{
	  char *tmp = new char [*size += DEFAULT_ARRAY_SIZE];
	  strcpy (tmp, target);
	  delete [] target;
	  target = tmp;
	}

      strcat (target, source);
      *index += strlen (source);

      delete [] source;
    }
  return target;
}

// Return the octal number parsed from STRING, or -1 to indicate that
// the string contained a bad number.

int
read_octal (const char *string)
{
  int result = 0;
  int digits = 0;

  while (*string && *string >= '0' && *string < '8')
    {
      digits++;
      result = (result * 8) + *string++ - '0';
    }

  if (! digits || result > 0777 || *string)
    result = -1;

  return result;
}

// Return a string which will be printed as a prompt.  The string may
// contain special characters which are decoded as follows: 
//   
//	\t	the time
//	\d	the date
//	\n	CRLF
//	\s	the name of the shell (program)
//	\w	the current working directory
//	\W	the last element of PWD
//	\u	your username
//	\h	the hostname
//	\#	the command number of this command
//	\!	the history number of this command
//	\$	a $ or a # if you are root
//	\<octal> character code in octal
//	\\	a backslash

static char *
decode_prompt_string (const char *string)
{
  int result_size = PROMPT_GROWTH;
  int result_index = 0;
  char *result = new char [PROMPT_GROWTH];
  int c;
  char *temp = 0;

  result[0] = 0;
  while (c = *string++)
    {
      if (c == '\\')
	{
	  c = *string;

	  switch (c)
	    {
	    case '0':
	    case '1':
	    case '2':
	    case '3':
	    case '4':
	    case '5':
	    case '6':
	    case '7':
	      {
		char octal_string[4];
		int n;

		strncpy (octal_string, string, 3);
		octal_string[3] = '\0';

		n = read_octal (octal_string);

		temp = strsave ("\\");
		if (n != -1)
		  {
		    string += 3;
		    temp[0] = n;
		  }

		c = 0;
		goto add_string;
	      }
	  
	    case 't':
	    case 'd':
	      /* Make the current time/date into a string. */
	      {
		time_t the_time = time (0);
		char *ttemp = ctime (&the_time);
		temp = strsave (ttemp);

		if (c == 't')
		  {
		    strcpy (temp, temp + 11);
		    temp[8] = '\0';
		  }
		else
		  temp[10] = '\0';

		goto add_string;
	      }

	    case 'n':
	      if (! no_line_editing)
		temp = strsave ("\r\n");
	      else
		temp = strsave ("\n");
	      goto add_string;

	    case 's':
	      {
		temp = base_pathname (prog_name);
		temp = strsave (temp);
		goto add_string;
	      }
	
	    case 'w':
	    case 'W':
	      {
		char t_string[MAXPATHLEN];
#define EFFICIENT
#ifdef EFFICIENT

// Use the value of PWD because it is much more effecient.

		temp = user_pref.pwd;

		if (! temp)
		  octave_getcwd (t_string, MAXPATHLEN);
		else
		  strcpy (t_string, temp);
#else
		octave_getcwd (t_string, MAXPATHLEN);
#endif	/* EFFICIENT */

		if (c == 'W')
		  {
		    char *dir = strrchr (t_string, '/');
		    if (dir && dir != t_string)
		      strcpy (t_string, dir + 1);
		    temp = strsave (t_string);
		  }
		else
		  temp = strsave (polite_directory_format (t_string));
		goto add_string;
	      }
      
	    case 'u':
	      {
		temp = strsave (user_name);

		goto add_string;
	      }

	    case 'h':
	      {
		char *t_string;

		temp = strsave (host_name);
		if (t_string = strchr (temp, '.'))
		  *t_string = '\0';
		
		goto add_string;
	      }

	    case '#':
	      {
		char number_buffer[128];
		sprintf (number_buffer, "%d", current_command_number);
		temp = strsave (number_buffer);
		goto add_string;
	      }

	    case '!':
	      {
		char number_buffer[128];
		int num = current_history_number ();
		if (num > 0)
                  sprintf (number_buffer, "%d", num);
		else
		  strcpy (number_buffer, "!");
		temp = strsave (number_buffer);
		goto add_string;
	      }

	    case '$':
	      temp = strsave (geteuid () == 0 ? "#" : "$");
	      goto add_string;

	    case '\\':
	      temp = strsave ("\\");
	      goto add_string;

	    default:
	      temp = strsave ("\\ ");
	      temp[1] = c;

	    add_string:
	      if (c)
		string++;
	      result =
		(char *)sub_append_string (temp, result,
					   &result_index, &result_size);
	      temp = 0; // Free ()'ed in sub_append_string ().
	      result[result_index] = '\0';
	      break;
	    }
	}
      else
	{
	  while (3 + result_index > result_size)
	    {
	      char *tmp = new char [result_size += PROMPT_GROWTH];
	      strcpy (tmp, result);
	      delete [] result;
	      result = tmp;
	    }
	  result[result_index++] = c;
	  result[result_index] = '\0';
	}
    }

#if 0
// I don't really think that this is a good idea.  Do you?
  if (! find_variable ("NO_PROMPT_VARS"))
    {
      WORD_LIST *expand_string (), *list;
      char *string_list ();

      list = expand_string (result, 1);
      free (result);
      result = string_list (list);
      dispose_words (list);
    }
#endif

  return result;
}

static void
do_input_echo (const char *input_string)
{
  if (echo_input)
    {
      ostrstream buf;

      if (! forced_interactive)
	{
	  char *prefix = decode_prompt_string (user_pref.ps4);
	  buf << prefix;
	  delete [] prefix;
	}

      if (input_string)
	{
	  buf << input_string;
	  int len = strlen (input_string);
	  if (input_string[len-1] != '\n')
	    buf << "\n";
	}

      maybe_page_output (buf);
    }
}


// Use GNU readline to get an input line and store it in the history
// list.

static char *
octave_gets (void)
{
  if (octave_gets_line)
    {
      free (octave_gets_line);
      octave_gets_line = 0;
    }

  if (interactive || forced_interactive)
    {
      char *ps = (promptflag > 0) ? user_pref.ps1 : user_pref.ps2;
      char *prompt = decode_prompt_string (ps);

      if (interactive)
	{
	  pipe_handler_error_count = 0;
	  flush_output_to_pager ();
	}

      maybe_write_to_diary_file (prompt);

      octave_gets_line = gnu_readline (prompt);

      delete [] prompt;
    }
  else
    octave_gets_line = gnu_readline ("");

  current_input_line = octave_gets_line;

  if (octave_gets_line && *octave_gets_line)
    {
      maybe_save_history (octave_gets_line);

      maybe_write_to_diary_file (octave_gets_line);

      do_input_echo (octave_gets_line);
    }

  maybe_write_to_diary_file ("\n");
  
  return octave_gets_line;
}

// Read a line from the input stream.

int
octave_read (char *buf, int max_size)
{
  int status = 0;

  static char *stashed_line = 0;

  if (get_input_from_eval_string)
    {
      int len = strlen (current_eval_string);
      if (len < max_size - 1)
	{
	  strcpy (buf, current_eval_string);
	  buf[len++] = '\n';
	  buf[len] = '\0';    // Paranoia.
	  status = len;
	}
      else
	status = -1;

      if (stashed_line)
	delete [] stashed_line;

      stashed_line = strsave (buf);
      current_input_line = stashed_line;
    }
  else if (using_readline)
    {
      char *cp = octave_gets ();
      if (cp)
	{
	  int len = strlen (cp);
	  if (len >= max_size)
	    status = -1;
	  else
	    {
	      strcpy (buf, cp);
	      buf[len++] = '\n';
	      buf[len] = '\0';    // Paranoia.
	      status = len;
	    }
	}
      current_input_line = cp;
    }
  else
    {
      FILE *curr_stream = rl_instream;
      if (reading_fcn_file || reading_script_file)
	curr_stream = ff_instream;

      assert (curr_stream);

// Why is this required?
      buf[0] = '\0';

      if (fgets (buf, max_size, curr_stream))
	{
	  int len = strlen (buf);
	  if (len > max_size - 2)
	    status = -1;
	  else
	    {
	      if (buf[len-1] != '\n')
		{
		  buf[len++] = '\n';
		  buf[len] = '\0';
		}
	      status = len;
	    }
	}
      else
	status = 0; // Tell yylex that we found EOF.

      if (stashed_line)
	delete [] stashed_line;

      stashed_line = strsave (buf);

      current_input_line = stashed_line;

      do_input_echo (current_input_line);
    }

  input_line_number++;

  return status;
}

// Fix things up so that input can come from file `name', printing a
// warning if the file doesn't exist.

FILE *
get_input_from_file (char *name, int warn)
{
  FILE *instream = 0;

  if (name && *name)
    instream = fopen (name, "r");

  if (! instream && warn)
    warning ("%s: no such file or directory", name);

  if (reading_fcn_file || reading_script_file)
    ff_instream = instream;
  else
    rl_instream = instream;

  return instream;
}

// Fix things up so that input can come from the standard input.  This
// may need to become much more complicated, which is why it's in a
// separate function.

FILE *
get_input_from_stdin (void)
{
  rl_instream = stdin;
  return rl_instream;
}

static char *
command_generator (char *text, int state)
{
  static int len = 0;
  static int list_index = 0;

  static char **name_list = 0;

  if (state == 0)
    {
      list_index = 0;
      len = strlen (text);

      if (name_list)
	{
	  char **ptr = name_list;
	  while (ptr && *ptr)
	    delete [] *ptr++;
	  delete [] name_list;
	}

      name_list = make_name_list ();
    }

  char *name;
  while ((name = name_list[list_index]) != 0)
    {
      list_index++;
      if (strncmp (name, text, len) == 0)
	{
	  char *buf = xmalloc (1 + strlen (name));
	  strcpy (buf, name);
	  return buf;
	}
    }

  return 0;
}

static char **
command_completer (char *text, int start, int end)
{
  char **matches = 0;
  matches = completion_matches (text, command_generator);
  return matches;
}

// The next two functions implement the equivalent of the K*rn shell
// C-o operate-and-get-next-history-line editing command.  Stolen from
// the GNU Bourne Again SHell.

// ??
static int saved_history_line_to_use = 0;

// ??
static Function *old_rl_startup_hook = 0;

static void
set_saved_history (void)
{
  HIST_ENTRY *h;

  if (saved_history_line_to_use)
    {
      if (history_set_pos (saved_history_line_to_use))
	{
	  h = current_history ();
	  if (h)
	    {
	      rl_insert_text (h->line);

// Get rid of any undo list created by the previous insert, so the
// line won't totally be erased when the edits are undone (they will
// be normally, because this is a history  line -- cf. readline.c:
// line 380 or so).
	      if (rl_undo_list)
		{
		  free_undo_list ();
		  rl_undo_list = 0;
		}
	    }
	}
    }
  saved_history_line_to_use = 0;
  rl_startup_hook = old_rl_startup_hook;
}

static void
operate_and_get_next (int count, int c)
{
  int where;
  extern int history_stifled, history_length, max_input_history;

// Accept the current line.
  rl_newline ();

// Find the current line, and find the next line to use.
  where = where_history ();

  if (history_stifled && (history_length >= max_input_history))
    saved_history_line_to_use = where;
  else
    saved_history_line_to_use = where + 1;

  old_rl_startup_hook = rl_startup_hook;
  rl_startup_hook = (Function *) set_saved_history;
}

void
initialize_readline (void)
{
// Allow conditional parsing of the ~/.inputrc file
  rl_readline_name = "Octave";

// Tell the completer that we want to try first.
  rl_attempted_completion_function = (CPPFunction *) command_completer;

// Bind operate-and-get-next.
  rl_add_defun ("operate-and-get-next",
		(Function *) operate_and_get_next, CTRL ('O'));
}

static int
match_sans_spaces (const char *standard, const char *test)
{
  char *tmp = strsave (test);

  char *tp = tmp;
  while (*tp == ' ' || *tp == '\t')
    tp++;

  char *ep = tmp + strlen (tmp) - 1;
  while (*ep == ' ' || *ep == '\t')
    ep--;

  *(ep+1) = '\0';

  int retval = strcmp (standard, tp) == 0;

  delete [] tmp;

  return retval;

}

// If the user simply hits return, this will produce an empty matrix.

static Octave_object
get_user_input (const Octave_object& args, int nargout, int debug = 0)
{
  tree_constant retval;

  int nargin = args.length ();

  int read_as_string = 0;

  if (nargin == 2)
    read_as_string++;

  char *prompt = "debug> ";
  if (nargin > 0)
   {
     prompt = args(0).string_value ();

     if (error_state)
       {
	 error ("input: unrecognized argument");
	 return retval;
       }
    }

 again:

  flush_output_to_pager ();

  char *input_buf = gnu_readline (prompt);

  if (input_buf)
    {
      maybe_save_history (input_buf);

      int len = strlen (input_buf);

      if (len < 1)
	{
	  if (debug)
	    goto again;
	  else
	    {
	      if (read_as_string)
		return "";
	      else
		return Matrix ();
	    }
	}

      if (match_sans_spaces ("exit", input_buf)
	  || match_sans_spaces ("quit", input_buf)
	  || match_sans_spaces ("return", input_buf))
	{
	  return retval;
	}
      else if (read_as_string)
	{
	  retval = input_buf;
	}
      else
	{
	  int parse_status = 0;
	  retval = eval_string (input_buf, 0, 0, parse_status);
	  if (retval.is_defined ())
	    {
	      if (debug)
		retval.eval (1);
	    }
	  else
	    retval = Matrix ();
	}
    }
  else
    error ("input: reading user-input failed!");

  if (debug)
    goto again;

  return retval;
}

DEFUN ("input", Finput, Sinput, 2, 1,
  "input (PROMPT [, S])\n\
\n\
Prompt user for input.  If the second argument is present, return
value as a string.")
{
  Octave_object retval;

  int nargin = args.length ();

  if (nargin == 1 || nargin == 2)
    retval = get_user_input (args, nargout);
  else
    print_usage ("input");

  return retval;
}

DEFUN ("keyboard", Fkeyboard, Skeyboard, 1, 1,
  "keyboard (PROMPT)\n\
\n\
maybe help in debugging function files")
{
  Octave_object retval;

  int nargin = args.length ();

  if (nargin == 0 || nargin == 1)
    retval = get_user_input (args, nargout, 1);
  else
    print_usage ("keyboard");

  return retval;
}

/*
;;; Local Variables: ***
;;; mode: C++ ***
;;; page-delimiter: "^/\\*" ***
;;; End: ***
*/
