/*  $Id: pl-rl.c,v 1.13 1998/02/04 16:22:59 jan Exp $

    Copyright (c) 1990 Jan Wielemaker. All rights reserved.
    See ../LICENCE to find out about your rights.
    jan@swi.psy.uva.nl

    Purpose: Binding to the GNU readline library
*/

#ifndef __WIN32__
#include "pl-incl.h"
#endif
#include <string.h>
#include <stdlib.h>
#include "pl-stream.h"
#include "pl-itf.h"

#ifdef __WIN32__
#include "config/win32.h"
#else
#include <config.h>
#endif

#ifdef HAVE_LIBREADLINE

#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_UXNT_H
#include <uxnt.h>
#endif
#ifdef HAVE_CLOCK
#include <time.h>
#endif
#ifdef __WIN32__
#include <io.h>
#endif
#ifdef O_RLC
#include <console.h>
#endif

#ifdef HAVE_RL_INSERT_CLOSE
#define PAREN_MATCHING 1
extern int rl_delete_text(int from, int to);
#endif

#undef ESC				/* will be redefined ... */
#include <stdio.h>			/* readline needs it */
#define savestring(x)			/* avoid definition there */
#include <readline/readline.h>
extern void add_history(char *);	/* should be in readline.h */
extern int rl_begin_undo_group(void);	/* delete when conflict arrises! */
extern int rl_end_undo_group(void);
extern Function *rl_event_hook;
extern char *filename_completion_function(char *, int);


static foreign_t
pl_rl_read_init_file(term_t file)
{ char *f;

  if ( (f = PL_get_filename(file, NULL, 0)) )
  {
#ifdef O_XOS
    char buf[MAXPATHLEN];
    rl_read_init_file(_xos_os_filename(f, buf));
#else
    rl_read_init_file(f);
#endif

    PL_succeed;
  }

  PL_fail;
}


static foreign_t
pl_rl_add_history(term_t text)
{ char *s;

  if ( PL_get_chars(text, &s, CVT_ALL) )
  { add_history(s);

    PL_succeed;
  }

  return PL_warning("rl_add_history/1: instantation fault");
}


static int
event_hook()
{ return PL_dispatch(0, PL_DISPATCH_NOWAIT);
}


static int
Sread_readline(void *handle, char *buf, int size)
{ long h = (long)handle;
  int fd = (int) h;
  int ttymode = PL_ttymode(fd);
  int rval;
#ifdef HAVE_CLOCK
  long oldclock = clock();
#endif

  PL_write_prompt(fd, ttymode == PL_NOTTY);
  
  switch( ttymode )
  { case PL_RAWTTY:			/* get_single_char/1 */
#ifdef O_RLC
    { int chr = getkey();
      
      if ( chr == 04 || chr == 26 )
	return 0;			/* EOF */

      buf[0] = chr & 0xff;
      return 1;
    }
#endif
    case PL_NOTTY:			/* -tty */
    { PL_dispatch(fd, PL_DISPATCH_WAIT);
      rval = read(fd, buf, size);
      if ( rval > 0 && buf[rval-1] == '\n' )
	PL_prompt_next(fd);

      break;
    }
    case PL_COOKEDTTY:
    default:
    { char *line;
      char *prompt;

      if ( PL_dispatch(0, PL_DISPATCH_INSTALLED) )
	rl_event_hook = event_hook;
      else
	rl_event_hook = NULL;

      prompt = PL_prompt_string(fd);
      if ( prompt )
	PL_add_to_protocol(prompt, strlen(prompt));

      if ( (line = readline(prompt)) )
      { char *s;
	int l = strlen(line);
	  
	if ( l > size )
	{ PL_warning("Input line too long");	/* must be tested! */
	  l = size-1;
	}
	memcpy(buf, line, l);
	buf[l++] = '\n';
	rval = l;

	for(s = line; *s; s++)
	{ if ( (*s & 0xff) > ' ' )
	    break;
	}

        if ( !*s )			/* blanks only! */
	  free(line);
      } else
	rval = 0;
    }
  }

  if ( ttymode != PL_RAWTTY )
  { PL_add_to_protocol(buf, rval);
  }

#ifdef HAVE_CLOCK
  PL_clock_wait_ticks(clock() - oldclock);
#endif

  return rval;
}


static void
prolog_complete(int ignore, int key)
{ if ( rl_point > 0 && rl_line_buffer[rl_point-1] != ' ' )
  { rl_begin_undo_group();
    rl_complete(ignore, key);
    if ( rl_point > 0 && rl_line_buffer[rl_point-1] == ' ' )
    {
#ifdef HAVE_RL_INSERT_CLOSE		/* actually version >= 1.2 */
      rl_delete_text(rl_point-1, rl_point);
      rl_point -= 1;
#else
      rl_delete(-1);
#endif
    }
    rl_end_undo_group();
  } else
    rl_complete(ignore, key);
}


static char *
atom_generator(char *prefix, int state)
{ char *s = PL_atom_generator(prefix, state);
  
  if ( s )
    return strcpy(xmalloc(1 + strlen(s)), s);

  return s;
}


static char **
prolog_completion(char *text, int start, int end)
{ char **matches = NULL;

  if ( (start == 1 && rl_line_buffer[0] == '[') )	/* [file */
    matches = completion_matches(text,
				 (Function *) filename_completion_function);
  else if (start == 2 && strncmp(rl_line_buffer, "['", 2))
    matches = completion_matches(text,
				 (Function *) filename_completion_function);
  else
    matches = completion_matches(text, atom_generator);

  return matches;
}

#undef read				/* UXNT redefinition */

install_t
PL_install_readline()
{ 
#ifndef __WIN32__
  if ( GD->cmdline.notty || !isatty(0) )
    return;
#endif

  rl_readline_name = "Prolog";
  rl_attempted_completion_function = prolog_completion;
#ifdef __WIN32__
  rl_basic_word_break_characters = "\t\n\"\\'`@$><= [](){}+*!,|%&?";
#else
  rl_basic_word_break_characters = ":\t\n\"\\'`@$><= [](){}+*!,|%&?";
#endif
  rl_add_defun("prolog-complete", (Function *) prolog_complete, '\t');
#if HAVE_RL_INSERT_CLOSE
  rl_add_defun("insert-close", rl_insert_close, ')');
#endif

  GD->os.rl_functions = *Sinput->functions;	/* structure copy */
  GD->os.rl_functions.read = Sread_readline;	/* read through readline */

  Sinput->functions  = &GD->os.rl_functions;
  Soutput->functions = &GD->os.rl_functions;
  Serror->functions  = &GD->os.rl_functions;

  PL_register_foreign("rl_read_init_file", 1, pl_rl_read_init_file, 0);
  PL_register_foreign("rl_add_history",    1, pl_rl_add_history,
		      PL_FA_NOTRACE);
  PL_set_feature("readline", PL_ATOM, "true");
  PL_set_feature("tty_control", PL_ATOM, "true");
}

#else /*HAVE_LIBREADLINE*/

install_t
PL_install_readline()
{
}

#endif /*HAVE_LIBREADLINE*/
