/*
 *******************************************************************************
 * Authors:
 *     Maxime Villard, 2017
 *     Jankovic Marco, 01/07/2014
 *******************************************************************************
 */

#include "common.h"

/*
 * tab containing all the slave side of a dev/pts
 * this part is swappable with other device
 */
int slave_fd[NB_CHANNELS];

/*
 * tab containing all the masters that come from /dev/pts/ptmx
 * this part is swappable with other device
 */
int master_fd[NB_CHANNELS];

/*
 * file descriptor of fifo rx and tx
 * file descriptor device_fd
 * nb_channels to multiplex
 */
int fifo_rx_fd;
int fifo_tx_fd;

#if TEST_BENCH
int test_rx_fd; // file descriptor that simulates RX buffer
int test_tx_fd; // file descriptor that simulates TX buffer
#else
int device_fd;
#endif

pthread_t tid[3];

/*
 * Function that opens an xterm. You need a master side and a slave side.
 * master_fd -> contains master fd from /dev/ptmx
 * slavename -> slave name from /dev/pts
 */
static int exec_xterm(int *master_fd, char *slavename, int tty_id)
{
	char buf[64];
	char tty_name[64];

	*master_fd = posix_openpt(O_RDWR);
	grantpt(*master_fd);
	unlockpt(*master_fd);

	if (ptsname_r(*master_fd, slavename, 64) != 0)
		perror("ptsname_r");

	printf("-> master_fd: %d\n", *master_fd);
	printf("-> slavename: %s\n", slavename);

	snprintf(buf, sizeof(buf), "-S%s/%d", strrchr(slavename,'/')+1, *master_fd);
	snprintf(tty_name, sizeof(buf), "tty_%d", tty_id);
	if (!fork()) {
		execlp("xterm", "xterm",
				buf,
				"-T", tty_name,
				"-bg", "black",
				"-fg", "green",
				"-geometry", "80x25",
				NULL);
		_exit(1);
	}

	return 0;
}

/*
 * Function that closes all file descriptors, kills all the threads and resets
 * the rs 232 port.
 */
void termination_handler(int signum)
{
	int i;
	printf("termination handler %d \n",signum);

	for (i = 0; i < 3; i++) {
		if (pthread_kill(tid[i], SIGTERM) < 0)
			err(1, "pthread_kill");
	}

	if (close(fifo_rx_fd) < 0)
		err(1, "close");
	if (close(fifo_tx_fd) < 0)
		err(1, "close");

#if TEST_BENCH
	if (close(test_tx_fd) < 0)
		err(1, "close");
	if (close(test_rx_fd) < 0)
		err(1, "close");
#else
	if (close(device_fd) < 0)
		err(1, "close");
#endif

	for (i = 0; i < NB_CHANNELS; i++) {
		if (close(slave_fd[i]) < 0)
			err(1, "close");
		if (close(master_fd[i]) < 0)
			err(1, "close");
	}

	exit(0);
}

static void init_tio(struct termios *tio)
{
	// Input flags - Turn off input processing
	// convert break to null byte, no CR to NL translation,
	// no NL to CR translation, don't mark parity errors or breaks
	// no input parity check, don't strip high bit off,
	// no XON/XOFF software flow control
	tio->c_cflag &= ~(IGNBRK | BRKINT | ICRNL | INLCR | PARMRK | INPCK | ISTRIP | IXON | CRTSCTS);
	tio->c_cflag |= (CLOCAL | CREAD);

	// Output flags - Turn off output processing
	// // no CR to NL translation, no NL to CR-NL translation,
	// // no NL to CR translation, no column 0 CR suppression,
	// // no Ctrl-D suppression, no fill characters, no case mapping,
	// // no local output processing
	tio->c_oflag &=~(OCRNL | ONLCR | ONLRET | ONOCR | OFILL | OLCUC | OPOST);
	tio->c_oflag = 0;

	//// No line processing:
	//// echo off, echo newline off, canonical mode off,
	//// extended input processing off, signal chars off
	////
	tio->c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG);

	////
	//// Turn off character processing
	//// clear current char size mask, no parity checking,
	//// no output processing, force 8 bit input
	//// 8N1
	////
	tio->c_cflag &= ~(CSIZE |PARENB|CSTOPB);
	tio->c_cflag |= CS8;
	////

	tio->c_iflag &= ~(IXON | IXOFF | IXANY | INPCK | ISTRIP );

	////
	//// One input byte is enough to return from read()
	//// Inter-character timer off
	////
	tio->c_cc[VMIN]  = 1;
	tio->c_cc[VTIME] = 0;

	/* set baudrate */
	if (cfsetispeed(tio, BAUDRATE) < 0 || cfsetospeed(tio, BAUDRATE) < 0)
		err(1, "error setting baudrate");
}

int main(void)
{
	char slavename[NB_CHANNELS][64];
	int i;
	char buf[64];

	pthread_attr_t thread_attr;

	/******************** signal handler ****************/
	sigset_t sig_set;
	sigset_t sig_set_act;

	sigemptyset(&sig_set);
	sigfillset(&sig_set);
	sigdelset(&sig_set, SIGINT);
	sigprocmask(SIG_BLOCK, &sig_set, NULL);

	sigemptyset(&sig_set_act);

	struct sigaction action;
	action.sa_handler = termination_handler;
	action.sa_mask = sig_set_act;
	action.sa_flags = 0;
	sigaction(SIGINT, &action, NULL);

	/****************************************************/
	printf("[+] device path: %s\n", DEVICE_PATH);
	printf("[+] nb_channels: %d\n", NB_CHANNELS);

	/*
	 * Opening 2 FIFOs: one fifo for input data and the other one for
	 * output data.
	 */
	if (mkfifo(FIFO_RX, S_IRWXU) < 0)
		err(1, "mkfifo");
	if (mkfifo(FIFO_TX, S_IRWXU) < 0)
		err(1, "mkfifo");

	if ((fifo_rx_fd = open(FIFO_RX, O_RDWR|O_SYNC|O_NONBLOCK, 0777)) < 0)
		err(1, "open");
	if ((fifo_tx_fd = open(FIFO_TX, O_RDWR|O_SYNC|O_NONBLOCK, 0777)) < 0)
		err(1, "open");
	/*******************************************************************/

	printf("[+] Opening the xterms\n");

	for (i = 0; i < NB_CHANNELS; i++) {
		exec_xterm(&master_fd[i], slavename[i], i);
		slave_fd[i] = open(slavename[i], O_RDWR|O_SYNC, 0777);
		do {
			read(slave_fd[i], buf, 1);
		} while (*buf != '\n' && *buf != '\r');
	}

#if TEST_BENCH
	printf("opening test files\n");
	if (mkfifo("test_rx.txt", S_IRWXU) < 0)
		err(1, "mkfifo");
	if (mkfifo("test_tx.txt", S_IRWXU) < 0)
		err(1, "mkfifo");
	if ((test_rx_fd = open("test_rx.txt", O_RDWR|O_SYNC|O_NONBLOCK, 0777)) < 0)
		err(1, "open");
	if ((test_tx_fd = open("test_tx.txt", O_RDWR|O_SYNC|O_NONBLOCK, 0777)) < 0)
		err(1, "open");
#else
	printf("[+] Opening %s\n", DEVICE_PATH);
	if ((device_fd = open(DEVICE_PATH, O_RDWR|O_NOCTTY|O_NDELAY)) < 0)
		err(1, "open");
	fcntl(device_fd, F_SETFL, 0);

	/*
	 * Set the rs232 port
	 */
	struct termios newtio;

	printf("[+] Setting %s rs232 port\n", DEVICE_PATH);

	memset(&newtio, 0, sizeof(newtio));
	init_tio(&newtio);

	if (tcsetattr(device_fd, TCSAFLUSH, &newtio) < 0)
		err(1, "tcsetattr");
#endif

	printf("[+] Launching the threads\n");

	/*
	 * Initialize the thread attributes.
	 */
	if (pthread_attr_init(&thread_attr) != 0)
		err(1, "pthread_attr_init");
	if (pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED) != 0)
		err(1, "pthread_attr_setdetachstate");

	/*
	 * Launch the threads.
	 */
	if (pthread_create(tid, &thread_attr, thread_mux_demux, NULL) != 0)
		err(1, "pthread_create");
	if (pthread_create(tid + 1, &thread_attr, thread_read_rx, NULL) != 0)
		err(1, "pthread_create");
	if (pthread_create(tid + 2, &thread_attr, thread_write_tx, NULL) != 0)
		err(1, "pthread_create");

#if TEST_BENCH
	char buftest[2];
	unsigned char pkt[PACKET_SIZE];

	sleep(1);

	while (1) {
		printf("ttyid> ");

		buftest[0] = getchar();
		buftest[1] = '\n';
		getchar();

		pkt[PKT_TTY] = 0x80 | atoi(buftest);

		printf("cmd> ");
		do {
			pkt[PKT_DAT] = getchar();
			write(test_rx_fd, pkt, PACKET_SIZE);
		} while (pkt[PKT_DAT] != '\n');
	}
#else
	while(1)
		sleep(1);
#endif

	return 0;
}

