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

#include <cstdlib>
#include <cassert>
#include <iostream>
#include <sstream>
#include <sys/types.h>
#include <csignal>
#include <cstdio>
#include <cstring>

#if !defined(_WIN32)
#include <sys/wait.h>
#include <unistd.h>
#else
#include <Windows.h>
#include <io.h>
#define isatty _isatty
#endif

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

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

#if !defined(_WIN32)
#define _fdopen fdopen
#endif

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(settings::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;
       throw EofException();
     }
   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