/*****
 * interact.cc
 *
 * The glue between the lexical analyzer and the readline library.
 *****/

#include <cstdlib>
#include <cassert>
#include <iostream>
#include <sstream>
#if !defined(MIKTEX_WINDOWS)
#include <sys/wait.h>
#endif
#include <sys/types.h>
#include <unistd.h>
#include <csignal>
#include <cstdio>
#include <cstring>

#include "interact.h"
#include "runhistory.h"

#ifdef HAVE_LIBCURSES
#ifdef HAVE_LIBREADLINE
#include <readline/readline.h>
#include <readline/history.h>
#else
#ifdef HAVE_LIBEDIT
// Work around incorrect declaration in NetBSD readline.h v1.33
#define rl_completion_entry_function rl_completion_entry_function_declaration
#ifdef HAVE_EDITLINE_READLINE_H
#include <editline/readline.h>
#else
#include <readline/readline.h>
#endif
#undef rl_completion_entry_function
extern "C" rl_compentry_func_t *rl_completion_entry_function;
#endif
#endif
#endif

#include "util.h"
#include "errormsg.h"

using namespace settings;

namespace run {
void init_readline(bool);
}

namespace interact {

bool interactive=false;
bool uptodate=true;
int lines=0;
bool query=false;

bool tty=isatty(STDIN_FILENO);
completer *currentCompleter=0;

void setCompleter(completer *c) {
  currentCompleter=c;
}

char *call_completer(const char *text, int state) {
  return currentCompleter ? (*currentCompleter)(text, state) : 0;
}

#if defined(HAVE_READLINE) && defined(HAVE_LIBCURSES)
void init_completion() {
  rl_completion_entry_function=call_completer;

  rl_completion_append_character='\0'; // Don't add a space after a match.

  /*
  // Build a string containing all characters that separate words to be
  // completed.  All characters that can't form part of an identifier are
  // treated as break characters.
  static char break_characters[128];
  Int j=0;
  for (unsigned char c=9; c < 128; ++c)
  if (!isalnum(c) && c != '_') {
  break_characters[j]=c;
  ++j;
  }
  break_characters[j]='\0';
  rl_completer_word_break_characters=break_characters;
  */
}
#endif

char *(*Readline)(const char *prompt);

char *readverbatimline(const char *prompt)
{
  if(!cin.good()) {cin.clear(); return NULL;}
  cout << prompt;
  string s;
  getline(cin,s);
  return StrdupMalloc(s);
}

FILE *fin=NULL;

char *readpipeline(const char *prompt)
{
#if _POSIX_VERSION >= 200809L
  char *line=NULL;
  size_t n=0;
  return getline(&line,&n,fin) >= 0 ? line : NULL;
#else
  const int max_size=256;
  static char buf[max_size];
  ostringstream s;
  do {
    if(fgets(buf,max_size-1,fin) == NULL) break;
    s << buf;
  } while(buf[std::strlen(buf)-1] != '\n');
  return StrdupMalloc(s.str());
#endif
}

void pre_readline()
{
  int fd=intcast(settings::getSetting<Int>("inpipe"));
  if(fd >= 0) {
    if(!fin) fin=fdopen(fd,"r");
    if(!fin) {
      cerr << "Cannot open inpipe " << fd << endl;
      exit(-1);
    }
    Readline=readpipeline;
  } else {
#if defined(HAVE_READLINE) && defined(HAVE_LIBCURSES)
    if(tty) {
      Readline=readline;
    } else
#endif
      Readline=readverbatimline;
  }
}

void init_readline(bool tabcompletion)
{
#if defined(HAVE_READLINE) && defined(HAVE_LIBCURSES)
  rl_bind_key('\t',tabcompletion ? rl_complete : rl_insert);
#endif
}

void init_interactive()
{
  if(getSetting<bool>("xasy")) tty=false;
#if defined(HAVE_LIBREADLINE) && defined(HAVE_LIBCURSES)
  if(tty) {
    init_completion();
    interact::init_readline(getSetting<bool>("tabcompletion"));
    read_history(historyname.c_str());
  }
#endif
}

string simpleline(string prompt) {
  // Rebind tab key, as the setting tabcompletion may be changed at runtime.
  pre_readline();

  Signal(SIGINT,SIG_IGN);
  // Get a line from the user.
  char *line=Readline(prompt.c_str());
  Signal(SIGINT,interruptHandler);

  // Reset scroll count.
  interact::lines=0;
  interact::query=tty;

  // Ignore keyboard interrupts while taking input.
  errorstream::interrupt=false;

  if(line) {
    string s=line;
    free(line);
    return s;
  } else {
#if defined(HAVE_READLINE) && defined(HAVE_LIBCURSES)
    if(!tty || getSetting<bool>("exitonEOF"))
#endif
      {
        cout << endl;
#if defined(MIKTEX)
        throw eof_exception();
#else
        throw eof();
#endif
      }
    return "";
  }
}

void addToHistory(string line) {
#if defined(HAVE_LIBREADLINE) && defined(HAVE_LIBCURSES)
  // Only add it if it has something other than newlines.
  if(tty && line.find_first_not_of('\n') != string::npos) {
    add_history(line.c_str());
  }
#endif
}

string getLastHistoryLine() {
#if defined(HAVE_LIBREADLINE) && defined(HAVE_LIBCURSES)
  if(tty && history_length > 0) {
    HIST_ENTRY *entry=history_list()[history_length-1];
    if(!entry) {
      em.compiler();
      em << "cannot access last history line";
      return "";
    } else
      return entry->line;
  } else
#endif
    return "";
}

void setLastHistoryLine(string line) {
#if defined(HAVE_LIBREADLINE) && defined(HAVE_LIBCURSES)
  if(tty) {
    if (history_length > 0) {
      HIST_ENTRY *entry=remove_history(history_length-1);

      if(!entry) {
        em.compiler();
        em << "cannot modify last history line";
      } else {
        free((char *) entry->line);
        free(entry);
      }
    }
    addToHistory(line);
  }
#endif
}

void deleteLastLine() {
#if defined(HAVE_LIBREADLINE) && defined(HAVE_LIBCURSES)
  if(tty) {
    HIST_ENTRY *entry=remove_history(history_length-1);
    if(!entry) {
      em.compiler();
      em << "cannot delete last history line";
    } else {
      free((char *) entry->line);
      free(entry);
    }
  }
#endif
}

void cleanup_interactive() {
#if defined(HAVE_LIBREADLINE) && defined(HAVE_LIBCURSES)
  // Write the history file.
  if(tty) {
    stifle_history(intcast(getSetting<Int>("historylines")));
    write_history(historyname.c_str());
  }
#endif
}

} // namespace interact
