#ifdef WIN32
#include <windows.h>

#elif defined(__APPLE__) 
#include <unistd.h>
#include <util.h>
#include <CoreServices/CoreServices.h>

#else //X11
#include <unistd.h>
#ifdef sun
#include <stropts.h>
#endif

#endif
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <FL/x.H>
#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Return_Button.H>
#include <FL/Fl_Multiline_Output.H>
#include <FL/Fl_Scrollbar.H>
#include <FL/fl_ask.H>


enum {running, completed, aborted, closed};

struct listener_data {
	int status;
	Fl_Multiline_Output *o;
#ifdef WIN32
	HANDLE pid;
#else
	pid_t pid;
#endif
	Fl_Button *ok, *interrupt;
	Fl_Scrollbar *bar;
	int nl;
	int view_nl;
	int modified;
	char *from, *next, *eol;
	char buffer[10000000];
};

void ext_prog_display(void *ptr);

static void inter_callback(Fl_Widget *o, void *p)
{
	struct listener_data *data = (struct listener_data *)p;
	data->status = aborted;
#ifdef WIN32
	TerminateProcess(data->pid, 0);
	Sleep(500 /*msec*/); //some delay seems necessary between TerminateProcess and deletion of open files
#else
	kill(data->pid, SIGKILL);
#endif
}

static void ok_callback(Fl_Widget *o, void *p)
{
	struct listener_data *data = (struct listener_data *)p;
	data->status = closed;
}

static void bar_callback(Fl_Widget *wgt, void *data)
{
	Fl_Multiline_Output *o = (Fl_Multiline_Output *)data;
	int first_line = ((Fl_Scrollbar *)wgt)->value();
	const char *p = o->value();
	do {
		if(*p == '\n') first_line--;
		p++;
		}
	while(*p != 0 && first_line > 0);
	o->position(p - o->value());
	o->redraw();
}


static void copy_callback(Fl_Widget *wgt, void *p)
{
	Fl_Multiline_Output *o = (Fl_Multiline_Output *)p;
	o->position(o->size(), 0);
	o->copy(1);
}


void ext_prog_display(void *ptr)
//display data from memory to FLTK viewer
{
	struct listener_data *data = (struct listener_data *)ptr;
	if(data->modified) {
		Fl_Multiline_Output *o = data->o;
		o->static_value(data->from, data->next - data->from);
		o->redraw();
		data->modified = 0;
		}
	Fl::repeat_timeout(0.5, ext_prog_display, ptr);
	}

void ext_prog_listener(int fd, void *ptr)
{//transmits data from fd to memory with CR emulation
	char *p;
	struct listener_data *data = (struct listener_data *)ptr;
	static char fd_buff[10000];
	Fl_Multiline_Output *o = data->o;
	int l;
	if(data->next + sizeof(fd_buff) >= data->buffer + sizeof(data->buffer)) {
		data->next = data->from = data->eol = data->buffer;
		data->nl = 0;
		}
	l = read(fd, fd_buff, sizeof(fd_buff));
	if(l > 0) {
		data->modified = 1;
		for(p = fd_buff; p < fd_buff + l; p++) {
			if(*p == '\r') {
				if(p+1 >= fd_buff + l || *(p+1) != '\n') {
					while(data->eol > data->buffer && *(data->eol - 1) != '\n') 
						(data->eol)--;
					}
				}
			else {
				if(*p == '\n') {
					data->eol = data->next;
					data->nl++;
					if(data->nl >= data->view_nl) data->from = strchr(data->from, '\n') + 1;
					}
				*(data->eol++) = *p;
				if(data->eol > data->next) data->next = data->eol;
				}
			}
		}
	else {
		Fl::remove_timeout(ext_prog_display, ptr);
		o->static_value(data->buffer, data->next - data->buffer);
		o->position(data->next - data->buffer, data->next - data->buffer);
		data->bar->value(data->nl, data->view_nl, 0, data->nl);
		data->bar->show();
		if(!data->ok->active()) {
			data->ok->activate();
			data->interrupt->label("Cancel");
			data->status = completed;
			}
		}
}


Fl_Window *create_and_run_pseudoterminal(const char *label, struct listener_data *data,
#ifdef WIN32
										 HANDLE pipe, HANDLE pid
#else
										 int masterfd, pid_t pid
#endif
								   )
#define FL_min(a,b)      ( (a) < (b) ? (a):(b) )
{
	static char message[100];
	Fl_Window *w = new Fl_Window( FL_min(700, Fl::w()), FL_min(600, Fl::h()-22 ) );
	w->xclass("Terminal");
	w->label(label);
	w->callback(inter_callback, data);
	Fl_Multiline_Output *o = new Fl_Multiline_Output(0, 3, w->w() - 15, w->h() - 40);
	o->textfont(FL_COURIER);
	o->textsize(12);
	o->maximum_size(5000000);
	data->bar = new Fl_Scrollbar(o->x() + o->w(), o->y(), 15, o->h());
	data->bar->callback(bar_callback, o);
	data->view_nl = o->h() / o->textsize();
#ifdef WIN32
	data->view_nl = (int)(data->view_nl * 0.80);
#endif
	data->bar->value(0, data->view_nl, 0, data->view_nl);
	data->bar->hide();
	sprintf(message, "Wait for %s completion", label);
	Fl_Box *w_message = new Fl_Box(0, o->y() + o->h() + 3, w->w(), 20, message);
	Fl_Button *w_copy = new Fl_Button(w->w()/2 - 30, w_message->y(), 60, 20, "Copy all");
	w_copy->hide();
	w_copy->callback(copy_callback, o);
	data->status = running;
	data->o = o;
	data->pid = pid;
	data->nl = 0;
	data->from = data->next = data->eol = data->buffer;
	data->modified = 0;
	Fl_Button *interrupt = new Fl_Button(5, w->h() - 35, 70, 30, "Interrupt");
	interrupt->callback(inter_callback, data);
	interrupt->labelfont(FL_HELVETICA_BOLD);
	Fl_Return_Button *ok = new Fl_Return_Button(w->w() - 60, interrupt->y(), 50, 30, "OK");
	ok->callback(ok_callback, data);
	ok->labelfont(FL_HELVETICA_BOLD);
	ok->deactivate();
	data->ok = ok;
	data->interrupt = interrupt;
	w->end();
    w->position( (Fl::w() - w->w())/2, (Fl::h() - w->h())/2 );
	Fl_Box *r = new Fl_Box(interrupt->x() + interrupt->w(), o->y(), ok->x() - interrupt->x() - interrupt->w(), o->h());
	w->resizable(r);
	w->show();
#ifndef MICRO
	w->hotspot(w);
#endif
#ifdef WIN32
	//add_fd does not work well under WIN32: Fl::wait() blocks in read(fd when nothing comes in the pipe
	//so an equivalent is done outside from Fl::wait()
	int masterfd = _open_osfhandle(
#ifdef _WIN64
				       (intptr_t)
#else
				       (long)
#endif
				       pipe, _O_RDONLY);
#else
	Fl::add_fd(masterfd, FL_READ | FL_EXCEPT, ext_prog_listener, data);
#endif
	Fl::add_timeout(0.5, ext_prog_display, data);
	while(data->status == running) {
#ifdef WIN32
		while(TRUE) {
			DWORD avail = 0;
			PeekNamedPipe(pipe, NULL,0,NULL, &avail, NULL);//is there data in pipe?
			if(avail <= 0) break;
			ext_prog_listener(masterfd, data);//read data from pipe
			Fl::wait(0);//process any interface event, especially interrupt requests & timeout
			}
		if( WaitForSingleObject(pid, 0) == WAIT_OBJECT_0) {//is pid dead?
			//complete reading from pipe and detect closed pipe
			while(data->status == running) ext_prog_listener(masterfd, data);
			}
		Fl::wait(0.1);//process any interface event, especially interrupt requests & timeout
#else
		Fl::wait();
#endif
		}
	Fl::remove_timeout(ext_prog_display, data);
	w_message->hide();
	w_copy->show();
#ifdef WIN32
	_close(masterfd);
#else
	Fl::remove_fd(masterfd);
#endif
	return w;
}


int run_external_prog_in_pseudoterm(char *cmd, const char *dialogfname, const char *label)
/* 
 cmd can be:
 prog arg1 arg2 ...
 or:
 prog arg1 arg2 ... > outfile
 and:
 prog can also use file named in dialogfname as stdin (NULL if no such file)
 label: short name of operation that is run
 returns 0 iff cmd completed correctly
 */
{
	static struct listener_data data;
	static bool inuse = 0;
	if(inuse) {
	  fl_alert("Operation %s is not possible while another alignment/tree-building is in progress",
		   label);
	  return 1;
	  }
	inuse = 1;
	Fl_Window *w;
#ifdef WIN32
	HANDLE ChildInput, ChildStdoutRd, ChildStdoutWr, stdouthandle = NULL;
	SECURITY_ATTRIBUTES saAttr; 
	PROCESS_INFORMATION pi; 
	STARTUPINFOW si;
	char *p;
	
	if(dialogfname != NULL) {
		FILE *in = fopen(dialogfname, "r");
		if(in == NULL) {
		  inuse = 0;
		  return 1;
		  }
		ChildInput = (HANDLE)_get_osfhandle(fileno(in));
	}
	p = cmd;//is there stdout redirection in cmd ?
	if(*p == '"') p = strchr(p+1, '"') + 1;
	p = strchr(p, '>');
	if(p != NULL) {
		*p = 0;
		p++;
		while(*p == ' ') p++;
		char *stdoutfname = strdup(p);
		FILE *out = fopen(stdoutfname, "w");
		free(stdoutfname);
		if(out != NULL) stdouthandle = (HANDLE)_get_osfhandle(fileno(out));
		}
	saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 
	saAttr.bInheritHandle = TRUE; 
	saAttr.lpSecurityDescriptor = NULL; 
	CreatePipe(&ChildStdoutRd, &ChildStdoutWr, &saAttr, 0/*suggested buffer size*/);
	DWORD written;
	WriteFile(ChildStdoutWr, "Running ", 8, &written, NULL);
	WriteFile(ChildStdoutWr, cmd, strlen(cmd), &written, NULL);
	WriteFile(ChildStdoutWr, "\n ", 1, &written, NULL);
	SetHandleInformation(ChildStdoutRd, HANDLE_FLAG_INHERIT, 0);
	ZeroMemory( &pi, sizeof(PROCESS_INFORMATION) );
	ZeroMemory( &si, sizeof(STARTUPINFOW) );
	si.cb = sizeof(STARTUPINFOW);
	si.hStdError = ChildStdoutWr;
	si.hStdOutput = stdouthandle ? stdouthandle : ChildStdoutWr;
	if(dialogfname != NULL) si.hStdInput = ChildInput;
	else si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
	si.dwFlags |= STARTF_USESTDHANDLES;
//conversion of utf-8 cmd into a WCHAR string
	int wlen = MultiByteToWideChar(CP_UTF8, 0, cmd, -1, NULL, 0);
	WCHAR *wcmd = new WCHAR[wlen];
	MultiByteToWideChar(CP_UTF8, 0, cmd, -1, wcmd, wlen);
	int retval = CreateProcessW(NULL, wcmd, 0,0,TRUE,CREATE_NO_WINDOW,0,0, &si, &pi);
	delete wcmd;
	CloseHandle(ChildStdoutWr);
	if(stdouthandle != NULL) CloseHandle(stdouthandle);
	if(dialogfname != NULL) CloseHandle(ChildInput);
	if(retval == 0) {
	  CloseHandle(ChildStdoutRd);
	  inuse = 0;
	  return 1;
	}
	CloseHandle(pi.hThread);
	w = create_and_run_pseudoterminal(label, &data, ChildStdoutRd, pi.hProcess);
	CloseHandle(pi.hProcess);
#else
  // creates a pseudoterminal
  pid_t pid;
  int masterfd;
  char *slavename;
#if defined(__APPLE__) 
  char slavename2[50];
  if (fl_mac_os_version < 100500) { //special for Mac OS X 10.4 + i386
    slavename = slavename2;
    pid = forkpty(&masterfd, slavename2, NULL, NULL);
    }
  else
#endif
    {
    masterfd = posix_openpt(O_RDWR|O_NOCTTY);
    grantpt(masterfd);
    unlockpt(masterfd);
    slavename = ptsname(masterfd);
    if(masterfd == -1 || slavename == NULL) {
      inuse = 0;
      return 1;
      }
      pid = fork();
    }
	if(pid == -1) {
	  inuse = 0;
	  return 1;
	  }
	else if(pid > 0) {// the parent
		w = create_and_run_pseudoterminal(label, &data, masterfd, pid);
	}
	else {//the child
		if(dialogfname != NULL) {//case of use of dialog file
			freopen(dialogfname, "r", stdin);
		}
		freopen(slavename, "a", stderr);
#ifdef sun
		ioctl(fileno(stderr), I_PUSH, "ptem");
		//		ioctl(fileno(stderr), I_PUSH, "ldterm");
		//		ioctl(fileno(stderr), I_PUSH, "ttcompat");
#endif
		fputs(cmd, stderr); fputc('\n', stderr); fflush(stderr);//show command
		//decode progname
		char *p, *q, *progpath, **argv;
		int argc, newout = -1;
		close(masterfd);
		p = cmd;
		while(*p == ' ') p++;
		if(*p == '"') q = strchr(++p, '"');
		else {
			q = strchr(p, ' ');
			if(q == NULL) q = p + strlen(p);
			}
		progpath = new char[q-p+1];
		memcpy(progpath, p, q-p); progpath[q-p] = 0;
		//goto end of progname
		p = q;
		if(*p == '"') p++;
	  // take care of stdout
	  // bug in Mac OS X 10.4 i386 requires us to close stdout and reopen it on fd = 1
		fclose(stdout);
		if((q = strchr(p, '>')) != NULL) {//open desired stdout file
			char stdoutfile[150];
			FILE *f;
			*q = 0;
			do q++; while(*q == ' ');
			strcpy(stdoutfile, q);
			q = stdoutfile + strlen(stdoutfile) - 1;
			while(q > stdoutfile && *q == ' ') q--;
			*(q + 1) = 0;
			f = fopen(stdoutfile, "w");
			if(f) newout = fileno(f);
		}
		else {
		  newout = open(slavename, O_WRONLY|O_APPEND);
		  }
		if(newout != 1 && newout != -1) {
		  dup2(newout, 1);
		  }
		//count args
		argc = 1;
		q = p - 1;
		while (*q != 0) {
			q++;
			if(*q == ' ') {
				argc++;
				do q++; while (*q == ' ');
				}
			}
		if(*(q-1)==' ')argc--;
		//put args in argv array
		argv = (char **)malloc((argc + 1)*sizeof(char *));
		argv[0] = progpath;
		argv[argc] = NULL;
		if(argc > 1) {
			argv[1] = strtok(p, " ");
			for(int i = 2; i < argc; i++) argv[i] = strtok(NULL, " ");
			}
		int err = 1; const char *errmess;
		if(access(progpath, X_OK) != 0) errmess = "executable";
		else {
			err = execv(progpath, argv);
			errmess = "a valid program";
			}
		fprintf(stderr, "\n\n\n%s is not %s\n", progpath, errmess);
		exit(err);
	}
	close(masterfd);
#endif
	while(data.status == completed) Fl::wait();
	delete w;
	if(dialogfname != NULL) remove(dialogfname);
	inuse = 0;
	return data.status == aborted;
} 
