#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <readline/readline.h>
#include <readline/history.h>
#include <pty.h>

#include "cfstring.h"

//--------------------------------------------------------------------
static int exitus;
static fd_set fdsetin_orig;
static int total_bytes_red=0;
static const char rdlnNoPrompt[] = " ";
static int master_term;
	
static const char help_msg[] =	
			"\nrdln [OPTIONS] command\n\n"
			"launches the command and feed his stdin from the terminal\n"
			"providing the readline functionality.\n"
			"on startup copy the rdln.history file to the history buffer\n"
			"and also send the rdln.rc file to the child process\n\n"
			"OPTIONS:\n"
			"\t-h\tthis help screen\n"
			"\t-g n\tsets the log level to n (LOG_FATAL=0, LOG_ERR, LOG_MSG, LOG_INF, LOG_DEB=4)\n"
            "\t\tdefault is 1\n"
			"\n";
            /*
			"NOTES:\n"
			"the child program should flush the stdout and stderr buffers\n"
			"after every single line to work propperly with the rdln\n"
			"in C you can make it easily by issuing the line\n\n"
			"\tsetlinebuf(stdout); setlinebuf(stderr);\n"
			"\nin your code.\n\n";
            */
//--------------------------------------------------------------------
// -------------- log levels ------------------
#define LOG_FATAL   0
#define LOG_ERR     1
#define LOG_MSG     2
#define LOG_INF     3
#define LOG_DEB     4

static int log_cutoff_level = LOG_ERR;
static int log_is_cont = 0;

static void myvlog(int level, const char *format, va_list ap)
{
    //if(log_is_cont) level |= VCA_LOGL_CONT;
    if(level > log_cutoff_level) return;
    if(!log_is_cont) printf("<%i> ", level);
    vprintf(format, ap);
}

static void mylog(int level, const char *format, ...)
{
    va_list ap; 
    va_start(ap, format); 
    myvlog(level, format, ap);
    va_end(ap);
}
//--------------------------------------------------------------------
void signal_trap(int sig)
{
	printf("rdln: signal catched, aborting ...\n");
	exitus = EXIT_SUCCESS;
}
//-----------------------------------------------------------------------------
int UserInput(char *line)
{
	mylog(LOG_DEB, "sending line to the child: %s\n", line);
	write(master_term, line, strlen(line));
	write(master_term, "\n", strlen("\n"));
	if(*line) add_history(line);
	return 0;
}
//--------------------------------------------------------------------
void myReadlineHandler(char *line)
// callback funkce, kterou readline zavola, po enteru
{
	if(line == 0) {
		mylog(LOG_ERR, "stdin EOF\n");
		exitus = EXIT_SUCCESS;
	}
	else if(*line != '\0') {
		UserInput(line);
		free(line);
	}
}
//-----------------------------------------------------------------------------
int PrintPipe(int *pfd)
{
	//const char *fdNames[] = {"stdin", "stdout", "stderr"};
	//const char *fdPrompts[] = {"", "", "stderr>> "};
	int fd = *pfd;
	int nread;
	
	ioctl(fd, FIONREAD, &nread);

	if(nread == 0) {
		mylog(LOG_DEB, "child exited\n");
		FD_CLR(fd, &fdsetin_orig);
		*pfd = -1;
		exitus = EXIT_SUCCESS;
	}
	else if(nread < 0) {
		mylog(LOG_ERR, "ERROR reading child's pipe\n");
		perror("");
		exitus = EXIT_FAILURE;
		return 1;
	}

	#define RDLN_BUFF_LEN 100
	char rdln_buff[RDLN_BUFF_LEN];
	total_bytes_red += nread;
	mylog(LOG_DEB, "%i bytes ready in child's pipe\n", nread);
	//printf(fdPrompts[fdID]);
    int i;
	for(i=0; i<nread; ) {
		int n = read(fd, rdln_buff, RDLN_BUFF_LEN-1);
		rdln_buff[n] = '\0';
        //strtrimf(rdln_buff);
		mylog(LOG_DEB, "%i/%i bytes red from the child's pipe: %s", n, nread, rdln_buff);
		char *pc = rdln_buff;
		do {
			for( ; *pc && *pc!='\n'; pc++) putchar(*pc);
			if(*pc) {
				putchar(*pc++);
				if(*pc) printf("%s", rdlnNoPrompt);
			}
		} while(*pc);
		i += n;
	}
	if(total_bytes_red == 1 && rdln_buff[0] == 10)
		mylog(LOG_ERR, "ERROR: It seems I can't find file with executed program code\n");
	return 0;
}
//====================================================================
int main(int argc, char *argv[]) 
{
	struct sigaction act;
	
	fd_set fdsetin;
	int selres;

	int o_help = 0;
	
	#define RDLN_PROMPT_LEN 20
	char rdln_prompt[RDLN_PROMPT_LEN];
	int cmdix;
	pid_t fork_result;
	
	act.sa_handler = signal_trap;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	
	for(cmdix=1; cmdix<argc; cmdix++) {
		if(argv[cmdix][0] == '-') {
            char *pc;
			for(pc=argv[cmdix]+1; *pc; pc++) switch(*pc) {
				case 'h':
					o_help = 1;
					break;
				case 'g':
                    sscanf(argv[++cmdix], "%i", &log_cutoff_level);
					mylog(LOG_DEB, "log level: %i\n", log_cutoff_level);
					break;
				default:
					mylog(LOG_ERR, "ERROR: invalid option -%c\n", *pc);
					o_help = 1;
					break;
			}
		}
		else break;
	}
	if(o_help) {
		mylog(LOG_ERR, help_msg);
		exit(EXIT_SUCCESS);
	}
	
	if(cmdix == argc) {
		mylog(LOG_ERR, "ERROR: No command to run!\n");
		exit(EXIT_FAILURE);
	}
	strncpyf(rdln_prompt, argv[cmdix], RDLN_PROMPT_LEN-1);
	strcat(rdln_prompt, ">");
	
	sigaction(SIGINT, &act, 0);

	fork_result = forkpty(&master_term, NULL, NULL, NULL);
	if(fork_result == (pid_t)-1) {
		//fork failure
		mylog(LOG_ERR, "ERROR: fork failure\n");
		exit(EXIT_FAILURE);
	}
	if(fork_result == (pid_t)0) {
		// child process
		mylog(LOG_DEB, "child is executing: ");
        log_is_cont = 1;
        int i;
		for(i=cmdix; i < argc; i++) mylog(LOG_DEB, "%s ", argv[i]);
		mylog(LOG_DEB, "\n");
        log_is_cont = 0;
		execvp(argv[cmdix], argv + cmdix);
	}
	else {
		//parrent process
		mylog(LOG_DEB, "child id: %i\n", fork_result);
		// copy initial script rdln.history to history buffer
		char *rcfilename = "rdln.history";
		FILE *rcfile = fopen(rcfilename, "rb");
		if(rcfile) {
			mylog(LOG_DEB, "Copying %s file to the history buffer\n", rcfilename);
			char *line = 0;
			size_t n;
			while(getline(&line, &n, rcfile) > 0) {
				strtrimf(line);
				if(*line) add_history(line);
			}
			if(line) free(line);
			fclose(rcfile);
		}

		// copy initial script rdln.rc to child
		rcfilename = "rdln.rc";
		rcfile = fopen(rcfilename, "rb");
		if(rcfile) {
			mylog(LOG_DEB, "Sending %s file to the child\n", rcfilename);
			char *line = 0;
			size_t n;
			while(getline(&line, &n, rcfile) > 0) {
				strtrimf(line);
				if(*line) {
					printf("%s\n", line);
					UserInput(line);
				}
			}
			if(line) free(line);
			fclose(rcfile);
		}
		
		// install call back for readline
		rl_callback_handler_install(rdln_prompt, myReadlineHandler);

		//int out_fd = from_pipe[0], err_fd = err_pipe[0];
		FD_ZERO(&fdsetin_orig);
		FD_SET(0, &fdsetin_orig);
		FD_SET(master_term, &fdsetin_orig);
		//if(out_fd > 0) FD_SET(out_fd, &fdsetin_orig);
		//if(err_fd > 0) FD_SET(err_fd, &fdsetin_orig);
		for(exitus = -1; exitus == -1;) {
			fdsetin = fdsetin_orig;
			selres = select(FD_SETSIZE, &fdsetin, NULL, NULL, NULL);
			switch(selres) {
				case 0:		//timeout, currently not used
					break;
				case -1:
					perror("ERROR: select:");
					break;
				default:
					if(FD_ISSET(0, &fdsetin)) {
						rl_callback_read_char();
					} 
					else {
						// readline clear prompt
						// aby novy prompt fungoval nesmi byt ""
						// tak je " "
						rl_set_prompt(rdlnNoPrompt);
						rl_redisplay();

						if(FD_ISSET(master_term, &fdsetin)) {
							if(PrintPipe(&master_term)) break;
						}
                        /*
						if(FD_ISSET(err_fd, &fdsetin)) {
							if(PrintPipe(&err_fd, 2)) break;
						}
                        */
						// readline update prompt
						rl_set_prompt(rdln_prompt);
						rl_forced_update_display();
					}
					break;
			}
		}
	}

	// remove call back for readline
	rl_callback_handler_remove();
	printf("\n");
	
	exit(exitus);	// close all file descriptors
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
