/*************************************************************************
*									 *
*	 YAP Prolog 							 *
*									 *
*	Yap Prolog was developed at NCCUP - Universidade do Porto	 *
*									 *
* Copyright L.Damas, V.S.Costa and Universidade do Porto 1985-1997	 *
*									 *
**************************************************************************
*									 *
* File:		readline.c							 *
* Last rev:	5/2/88							 *
* mods:									 *
* comments:	Input/Output C implemented predicates			 *
*									 *
*************************************************************************/
#ifdef SCCS
static char SccsId[] = "%W% %G%";
#endif

/*
 * This file includes the interface to the readline library, if installed in the system.
 *
 */

#include "Yap.h"
#include "Yatom.h"
#include "YapHeap.h"
#include "yapio.h"
#include <stdlib.h>
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#if HAVE_STDARG_H
#include <stdarg.h>
#endif
#ifdef _WIN32
#if HAVE_IO_H
/* Windows */
#include <io.h>
#endif
#if HAVE_SOCKET
#include <winsock2.h>
#endif
#include <windows.h>
#ifndef S_ISDIR
#define S_ISDIR(x) (((x)&_S_IFDIR)==_S_IFDIR)
#endif
#endif

#include "iopreds.h"

#if defined(HAVE_LIBREADLINE)

#include <readline/readline.h>
#include <readline/history.h>

static int ReadlineGetc( int);
static int ReadlinePutc( int,int);

#define READLINE_OUT_BUF_MAX 256

typedef struct scan_atoms {
  Int pos;
  Atom atom;
} scan_atoms_t;

static char *
atom_enumerate(const char *prefix, int state)
{
    CACHE_REGS
  struct scan_atoms *index;
  Atom            catom;
  Int            i;

  if ( !state )
    { index = (struct scan_atoms *)malloc(sizeof(struct scan_atoms));
      i = 0;
      catom = NIL;
  } else
  {
      CACHE_REGS
      index = LOCAL_search_atoms;
    catom = index->atom;
    i = index->pos;
  }

  while (catom != NIL || i < AtomHashTableSize) {
    //    if ( is_signalled() )		/* Notably allow windows version */
      //      PL_handle_signals();		/* to break out on ^C */
    AtomEntry *ap;

    if (catom == NIL) {
      /* move away from current hash table line */
      READ_LOCK(HashChain[i].AERWLock);
      catom = HashChain[i].Entry;
      READ_UNLOCK(HashChain[i].AERWLock);
      i++;
    } else {
      ap = RepAtom(catom);
      READ_LOCK(ap->ARWLock);
      if ( strstr( (char *)ap->StrOfAE, prefix) == ( char *)ap->StrOfAE) {
	index->pos = i;
	index->atom = ap->NextOfAE;
	LOCAL_search_atoms = index;
	READ_UNLOCK(ap->ARWLock);
	return ap->StrOfAE;
      }
      catom = ap->NextOfAE;
      READ_UNLOCK(ap->ARWLock);
    }
  }
  LOCAL_search_atoms = NULL;
  free(index);
  return NULL;
}


static char *
atom_generator(const char *prefix, int state)
{ char *s = atom_enumerate(prefix, state);

  if ( s )
  { char *copy = malloc(1 + strlen(s));

    if ( copy )				/* else pretend no completion */
      strcpy(copy, s);
    s = copy;
  }

  return s;
}


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

  if ( (start == 1 && rl_line_buffer[0] == '[') ||	/* [file */
       (start == 2 && strncmp(rl_line_buffer, "['", 2)) )
    matches = rl_completion_matches((char *)text,	/* for pre-4.2 */
				    rl_filename_completion_function);
  else
    matches = rl_completion_matches((char *)text,
				    atom_generator);

  return matches;
}


void
Yap_ReadlineFlush( int sno )
{
    if (GLOBAL_Stream[sno].status & Tty_Stream_f &&
      GLOBAL_Stream[sno].status & Output_Stream_f) {
      rl_redisplay();

  }
}

bool Yap_ReadlinePrompt( StreamDesc * s )
{
  if (s->status & Tty_Stream_f) {
    s->stream_getc = ReadlineGetc;
    if (GLOBAL_Stream[0].status & Tty_Stream_f &&
	s->name == GLOBAL_Stream[0].name)
      s->stream_putc = ReadlinePutc;
    return true;
  }
  return false;
}

bool
Yap_ReadlineOps( StreamDesc *s )
{
  if (s->status & Tty_Stream_f) {
    if (GLOBAL_Stream[0].status & Tty_Stream_f &&
	is_same_tty(s->file,GLOBAL_Stream[0].file))
      s->stream_putc = ReadlinePutc;
    s->stream_getc = ReadlineGetc;
    return true;
  }
  return false;
}


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

  return 0;
}

static void
InitReadline(void) {
  // don't call readline within emacs
  //if (getenv("ËMACS"))
  //  return;
  GLOBAL_Stream[StdInStream].u.irl.buf = NULL;
  GLOBAL_Stream[StdInStream].u.irl.ptr = NULL;
#if _MSC_VER || defined(__MINGW32__)
  rl_instream = stdin;
#endif
  rl_outstream = stderr;
  using_history();
  char *s = Yap_AbsoluteFile("~/.YAP.history",NULL);
  if (!read_history (s))
    { FILE *f = fopen(s, "w");
      if (f) {
	fclose(f);
	read_history (s);
      }
    }
  rl_readline_name = "Prolog";
  rl_attempted_completion_function = prolog_completion;
#ifdef HAVE_RL_COMPLETION_FUNC_T
  rl_add_defun("prolog-complete", prolog_complete, '\t');
#else
  rl_add_defun("prolog-complete", (void *)prolog_complete, '\t');
#endif

}

static bool
getLine( int inp, int out )
{
    CACHE_REGS
  rl_instream = GLOBAL_Stream[inp].file;
  rl_outstream = GLOBAL_Stream[out].file;
  const char *myrl_line;
  StreamDesc *s = GLOBAL_Stream+inp;

  if (!(s->status & Tty_Stream_f))
    return false;

  /* window of vulnerability opened */
  LOCAL_PrologMode |= ConsoleGetcMode;

  if (GLOBAL_Stream[out].linepos == 0) { // no output so far
    fflush(NULL);
    myrl_line = readline (LOCAL_Prompt);
  } else {
    LOCAL_PrologMode |= ConsoleGetcMode;
    myrl_line = readline (NULL);
  }
  /* Do it the gnu way */
  if (LOCAL_PrologMode & InterruptMode) {
    Yap_external_signal( 0, YAP_INT_SIGNAL );
    LOCAL_PrologMode &= ~ConsoleGetcMode;
    if (LOCAL_PrologMode & AbortMode) {
      Yap_Error(ABORT_EVENT, TermNil, "");
      LOCAL_ErrorMessage = "Abort";
      return console_post_process_eof(s);
    }
  } else {
    LOCAL_PrologMode &= ~ConsoleGetcMode;
  }
  strncpy (LOCAL_Prompt, RepAtom (LOCAL_AtPrompt)->StrOfAE, MAX_PROMPT);
  /* window of vulnerability closed */
  if (myrl_line == NULL)
    return false;
  if (myrl_line[0] != '\0' && myrl_line[1] != '\0') {
    add_history (myrl_line);
      write_history ( Yap_AbsoluteFile("~/.YAP.history", NULL));
    }
    s->u.irl.ptr = s->u.irl.buf = myrl_line;
    return true;
  }

static int
ReadlinePutc (int sno, int ch)
{
  StreamDesc *s = &GLOBAL_Stream[sno];
#if MAC || _MSC_VER || defined(__MINGW32__)
  if (ch == 10)
    {
      putc ('\n', s->file);
    }
  else
#endif
    putc (ch, s->file);
  console_count_output_char(ch,s);
 if (ch == 10) {
    Yap_ReadlineFlush(  sno );
  }
  return ((int) ch);
}



/*
  reading from the console is complicated because we need to
  know whether to prompt and so on...
*/
static int
ReadlineGetc(int sno)
{
  StreamDesc *s = &GLOBAL_Stream[sno];
  int ch;
  bool fetch = (s->u.irl.buf == NULL);

  if (!fetch || getLine( sno, StdErrStream ) ) {
    const char *ttyptr = s->u.irl.ptr++, *myrl_line =  s->u.irl.buf;
    ch = *ttyptr;
    if (ch == '\0') {
      ch = '\n';
      free ((void *)myrl_line);
      s->u.irl.ptr =  s->u.irl.buf = NULL;
    }
  } else {
    return EOF;
  }
  return console_post_process_read_char(ch, s);
}

int
Yap_ReadlineForSIGINT(void)
{
    CACHE_REGS
  int ch;
  StreamDesc *s = &GLOBAL_Stream[StdInStream];
  const char *myrl_line =  s->u.irl.buf;

  if ((LOCAL_PrologMode & ConsoleGetcMode) && myrl_line != (char *) NULL) {
    ch = myrl_line[0];
    free((void *)myrl_line);
    myrl_line = NULL;
    return ch;
  } else {
    myrl_line = readline ("Action (h for help): ");
    if (!myrl_line) {
      ch = EOF;
      return ch;
    } else {
      ch = myrl_line[0];
      free((void *)myrl_line);
      myrl_line = NULL;
      return ch;
    }
  }
}

static Int has_readline(USES_REGS1)
{
#if HAVE_LIBREADLINE && HAVE_READLINE_READLINE_H
  return true;
#else
  return false;
#endif
}


void Yap_InitReadline(void) {
  Yap_InitCPred ("$has_readline", 0, has_readline, SafePredFlag|HiddenPredFlag);
  InitReadline();
}

#endif