// SPDX-License-Identifier: GPL-2.0-or-later
/* SPDX-FileCopyrightText: 2004-2015  Simon Wunderlich <sw@simonwunderlich.de>
 * SPDX-FileCopyrightText: 2002  Alexander Graf <helly@gmx.net>
 */

#define _GNU_SOURCE
#include "s3dvt.h"
#include <stdio.h>    /*  FILE,NULL */
#include <unistd.h>   /*  read(),write(), sleep(),close() ... */
#include <errno.h>   /*  errno() */
#include <fcntl.h>    /*  open() */
#include <signal.h>   /*  signal() */
#include <stdlib.h>   /*  exit(),getenv(),setenv(), getpt */
#include <sys/ioctl.h>   /*  ioctl() */
#include <pthread.h>  /*  pthread_create() */
#include <s3d.h>   /*  s3d_* */
#include <s3d_keysym.h>  /*  key symbols */
#include <time.h> /* nanosleep() */
static struct timespec t = {
	0, 10*1000*1000
}; /* 10 mili seconds */


static int pid;
static int term_mode = 0;

static int cursor;  /* the object id of the cursor */

static pthread_t term_thread;

#ifdef M_PIPE
static int mpipe_in[2];
static int mpipe_out[2];
#endif
#ifdef M_PTY
static int curtty, curpty;
#endif

#ifdef M_CHAR
static unsigned int charbuf[256];
static int screenbuf[MAX_LINES*MAX_CHARS];
static char   last_c[MAX_LINES*MAX_CHARS];
#endif

#ifdef M_LINE
static int    lines[MAX_LINES];
#endif

static void* thread_terminal(void *S3DVTUNUSED(a))
{
	int iscon = 1, ret;
	char buffer[1024];
	while (iscon) {
		switch (term_mode) {
		case M_PIPE:
			ret = read(mpipe_out[0], &buffer, 1000);
			break;
		case M_PTY:
			ret = read(curpty, &buffer, 1000);
			break;
		default:
			ret = -1;
		}
		if (ret > 0) {
			buffer[ret] = '\0';
			printf("<<<new data(%d bytes)\n", ret);
			AddChar(buffer);
		} else {
			if (ret == -1) {
				perror("read()");
				printf("Connection to pty lost (ret=%d)\n", ret);
				close(mpipe_in[0]);
				close(mpipe_out[1]);
				/*   close(curpty);  */
				/*   close(curtty);  */
				printf("********CONNECTION TO PTY LOST!*******\n");
				sleep(1);
				s3d_quit();
				exit(0);
			}
		}
	}
	return NULL; /* huh?! */
}

static int open_pty_pair(int *amaster, int *aslave)
{
	int master, slave;
	char *name;

	master = getpt();
	if (master < 0)
		return 0;

	if (grantpt (master) < 0 || unlockpt (master) < 0)
		goto close_master;
	name = ptsname(master);
	if (name == NULL)
		goto close_master;

	slave = open(name, O_RDWR);
	if (slave == -1)
		goto close_master;

	*amaster = master;
	*aslave = slave;
	return 1;

close_master:
	close (master);
	return 0;
}

static int pty_init_terminal(void)
{
	char tmpstr[1024];
	int curtty;
	int uid = 0, gid = 0;
	char exe[] = "/bin/bash";

	uid = getuid();
	gid = getgid();
	term_mode = M_PTY;
	if (!open_pty_pair(&curpty, &curtty)) {
		printf("Error creating pty pair\n");
		return 0;
	}

	if (curpty < 0) {
		printf("Error opening pty\n");
		if (curtty >= 0)
			close(curtty);
		return 0;
	}
	/*  fnctl(F_SETFL,O_NONBLOCK); */
	signal(SIGCHLD, SIG_IGN);
	pid = fork();
	if (!pid) {
		if (curtty < 0) {
			printf("Error opening tty\n");
			return 0;
		}
		if (setgid(gid) < 0) {
			printf("Couldn't set gid %d\n", gid);
			exit(255);
		}
		if (setuid(uid) < 0) {
			printf("Couldn't set uid %d\n", uid);
			exit(255);
		}
		if (setsid() < 0)
			printf("ERROR (setsid)\n");
		/*     tcflush(curpty, TCIOFLUSH); */
#ifdef TIOCSCTTY
		if (ioctl(curtty, TIOCSCTTY, NULL))
			printf("ERROR! (ttyflush)\n");;
#endif /* TIOCSCTTY */
		dup2(curtty, 0);
		dup2(curtty, 1);
		dup2(curtty, 2);
		sprintf(tmpstr, "%d", MAX_LINES - 1);
		setenv("LINES", tmpstr, 1);
		sprintf(tmpstr, "%d", MAX_CHARS - 1);
		setenv("COLUMNS", tmpstr, 1);
		setenv("TERM", "rxvt", 1);
		execl(exe, exe, NULL);
		sleep(1);
		printf("that's it, exiting");
		close(curtty);
		exit(0);
	} else if (pid < 0) {
		close(curtty);
		close(curpty);
		printf("Cant fork()\n");
		exit(0);
	} else {
		close(curtty);
		pthread_create(&term_thread, NULL, thread_terminal, NULL);
	}
	return 1;
}
/*  terminal.c */
void term_addchar(char toprint)
{
	/*    printf("sending: %.3d\n", toprint);*/
	switch (term_mode) {
	case M_PIPE:
		write(mpipe_in[1], &toprint, 1);
		write(mpipe_out[1], &toprint, 1);
		break;
	case M_PTY:
		write(curpty, &toprint, 1);
		break;
	}
}


static int pipe_init_terminal(void)
{
	int uid = 0, gid = 0;
	const char *exe = "/bin/bash";
	const char *args = "-i";

	term_mode = M_PIPE;
	if ((pipe(mpipe_in) == -1) || (pipe(mpipe_out) == -1)) {
		printf("pipe failed\n");
		return -1;
	}
	uid = getuid();
	gid = getgid();
	pid = fork();
	if (pid == 0) { /*  the child */
		char tmpstr[1024];
		if (setgid(gid) < 0) {
			printf("Couldn't set gid %d\n", gid);
			exit(255);
		}
		if (setuid(uid) < 0) {
			printf("Couldn't set uid %d\n", uid);
			exit(255);
		}
		if (setsid() < 0)
			printf("ERROR (setsid)\n");
		/*     tcflush(curpty, TCIOFLUSH); */
		/*     if(ioctl(curtty, TIOCSCTTY, NULL)) printf("ERROR! (ttyflush)\n");; */
		setvbuf(stdout, (char*)NULL, _IONBF, 0);
		dup2(mpipe_in[0], fileno(stdin));
		dup2(mpipe_out[1], fileno(stdout));
		dup2(mpipe_out[1], fileno(stderr));
		/*  close unneded things: */
		close(mpipe_out[0]);
		close(mpipe_in[1]);
		sprintf(tmpstr, "%d", MAX_LINES - 1);
		setenv("LINES", tmpstr, 1);
		sprintf(tmpstr, "%d", MAX_CHARS - 1);
		setenv("COLUMNS", tmpstr, 1);
		setenv("TERM", "rxvt", 1);
		execl(exe, exe, args, NULL);
		printf("that's it, exiting");
		close(curtty);
		exit(0);
	} else if (pid < 0) {
		printf("Cant fork()\n");
		exit(0);
	} else {
		/*  close unneded things... */
		close(mpipe_in[0]);
		pthread_create(&term_thread, NULL, thread_terminal, NULL);
	}
	return 1;
}
static int init_terminal(void)
{
	int i;
	for (i = 0; i < 5; i++)
		if (pty_init_terminal())  /*  find an open pty. */
			return 0;
	return pipe_init_terminal();  /*  if not, fallback to pipe mode */
}
static void term_unload(void)
{
	printf("unloading tty!!\n");
	switch (term_mode) {
	case M_PTY:
		write(curpty, "\0", 1);  /*  send an EOF, just in case */
		close(curpty);
		close(curtty);
		/* kill(pid); */
		break;
	case M_PIPE:
		/*  bash should fade with "broken pipe" */
		close(mpipe_in[1]);
		close(mpipe_out[0]);
		break;
	}
}
#ifdef M_CHAR
void paintit(void)
{
	int cline;
	int c;
	unsigned char ch;
	unsigned int ci;
	int i, line_end;

	s3d_translate(cursor, cx*X_RATIO*CS - CS*X_RATIO*MAX_CHARS / 2, -cy*CS + CS*MAX_LINES / 2, 0);
	s3d_scale(cursor, CS);
	for (cline = 0; cline < MAX_LINES; cline++) {
		line_end = 0;
		for (c = 0; c < MAX_CHARS; c++) {
			i = cline * MAX_CHARS + c;    /*  calculate position */
			if (((ch = line[cline].chars[c].character) != last_c[i])) {
				if (screenbuf[i] == -1) {
					screenbuf[i] = s3d_new_object();
					s3d_translate(screenbuf[i], c*X_RATIO*CS - CS*X_RATIO*MAX_CHARS / 2, -cline*CS + CS*MAX_LINES / 2, 0);
					s3d_scale(screenbuf[i], CS);
					s3d_flags_on(screenbuf[i], S3D_OF_VISIBLE);
				}
				if ((ch == 0) || (line_end)) { /*  the new character is zero! delete! */
					line[cline].chars[c].character = 0;
					ci = charbuf[' '];
					s3d_clone_target(screenbuf[i], ci);
					line_end = 1;
					/*      printf("-%03d",ch); */
				} else {
					ci = charbuf[ch];
					/*      printf("!%03d",ch); */
					s3d_clone_target(screenbuf[i], ci);
				}
			} else {
				/*     printf("=%03d",ch); */
			}
			last_c[i] = ch;
		}
		/*   printf("\n"); */
	}
	gotnewdata = 0;
}
#endif
#ifdef M_LINE
void paintit(void)
{
	int cline;
	int oid, c;
	int len;
	int changed;
	char cl[MAX_CHARS];
	for (cline = 0; cline < MAX_LINES; cline++) {
		len = MAX_CHARS;
		changed = 0;
		for (c = MAX_CHARS; c >= 0; c--) {
			cl[c] = line[cline].chars[c].character;
			if (line[cline].chars[c].character != line[cline].chars[c].last_c) {
				changed = c + 1;
				/*    printf("changed [%d/%d] from %d to %d\n",cline,c,
				        line[cline].chars[c].last_c,
				        line[cline].chars[c].character
				        );*/
				line[cline].chars[c].last_c = line[cline].chars[c].character;
			}
			if (cl[c] == 0) len = c;
		}
		if (changed) {
			if (len > 0) {
				printf("[p: line %d [len: %d/changed: %d]\n", cline, len, changed);
				oid = s3d_draw_string(cl, NULL);
				s3d_translate(oid, 0, -cline, 0);
				if (lines[cline] != -1) {
					s3d_del_object(lines[cline]);
					lines[cline] = -1;
				}
				s3d_flags_on(oid, S3D_OF_VISIBLE);
				lines[cline] = oid;
			} else {
				if (lines[cline] != -1) {
					s3d_del_object(lines[cline]);
					lines[cline] = -1;
				}
			}
		}

	}
	gotnewdata = 0;
}
#endif
/*
void paint_chars()
{
 char c;
 int oid;
 int cline,cchar;
 for(cline=0;cline<MAX_LINES;cline++)
 {
     for(cchar=0;cchar<MAX_CHARS;cchar++)
  {
   c=line[cline].chars[cchar].character;
/ *    printf("%02x|",c); * /
         if(!islastchar(cline,cchar))
          if(line[cline].chars[cchar].character && line[cline].chars[cchar].character != ' ')
    {
/ *     if (screenbuf[MAX_LINES*MAX_CHARS]!=-1) s3d_del_object(screenbuf[MAX_LINES*MAX_CHARS]); * /
    oid=s3d_clone(cobjs[c]);
    s3d_translate(oid,10*cchar,10*cline,0);
    s3d_flags_on(oid,S3D_OF_VISIBLE);
    screenbuf[MAX_LINES*MAX_CHARS]=oid;
    }
     }
/ *   printf("\n"); * /
 }
}*/
static int keypress(struct s3d_evt *event)
{
	struct s3d_key_event *keys = (struct s3d_key_event *)event->buf;
	int key;
	/* printf("received key: %d\n",key);*/
	switch (key = keys->keysym) {
	case S3DK_F1:
	case S3DK_F2:
	case S3DK_F3:
	case S3DK_F4:
	case S3DK_F5:
		term_addchar(0x1b);
		term_addchar('[');
		term_addchar('1');
		term_addchar(key - S3DK_F1 + '1');
		break;
	case S3DK_F6:
	case S3DK_F7:
	case S3DK_F8:
		term_addchar(0x1b);
		term_addchar('[');
		term_addchar('1');
		term_addchar((key - S3DK_F6) + '7');
		break;
	case S3DK_F9:
	case S3DK_F10:
		term_addchar(0x1b);
		term_addchar('[');
		term_addchar('2');
		term_addchar((key - S3DK_F9) + '0');
		break;
	case S3DK_F11:
	case S3DK_F12:
		term_addchar(0x1b);
		term_addchar('[');
		term_addchar('2');
		term_addchar((key - S3DK_F11) + '3');
		break;
	case S3DK_UP:
		term_addchar(0x1b);
		term_addchar('[');
		term_addchar('A');
		break;
	case S3DK_DOWN:
		term_addchar(0x1b);
		term_addchar('[');
		term_addchar('B');
		break;
	case S3DK_RIGHT:
		term_addchar(0x1b);
		term_addchar('[');
		term_addchar('C');
		break;
	case S3DK_LEFT:
		term_addchar(0x1b);
		term_addchar('[');
		term_addchar('D');
		break;
	case S3DK_PAGEUP:
		term_addchar(0x1b);
		term_addchar('[');
		term_addchar('5');
		term_addchar('~');
		break;
	case S3DK_PAGEDOWN:
		term_addchar(0x1b);
		term_addchar('[');
		term_addchar('6');
		term_addchar('~');
		break;
	case S3DK_HOME:
		term_addchar(0x1b);
		term_addchar('[');
		term_addchar('7');
		term_addchar('~');
		break;
	case S3DK_END:
		term_addchar(0x1b);
		term_addchar('[');
		term_addchar('8');
		term_addchar('~');
		break;
	case 13:
		term_addchar(10);
		break;
	default:
		if ((char)keys->unicode)  /*  \0 is no good idea .. */
			term_addchar((char)keys->unicode);
	}
	return 0;

}
static int i = 0;
static void mainloop(void)
{
	usleep(10000);
	nanosleep(&t, NULL);

	if ((i += 2) > 100) {
		if (i % 2) {
			s3d_flags_on(cursor, S3D_OF_VISIBLE);
			i = 0;
		} else {
			s3d_flags_off(cursor, S3D_OF_VISIBLE);
			i = 1;
		}
	}
	if (gotnewdata) {
		/*   printf("got new data,displaying\n"); */
		paintit();
	}
}
static int stop(struct s3d_evt *S3DVTUNUSED(event))
{
	s3d_quit();
	return 0;
}
static unsigned int draw_background(void)
{
	unsigned int b;
	b = s3d_new_object();
	s3d_push_vertex(b, -MAX_CHARS / 2*X_RATIO*CS , CS + CS*MAX_LINES / 2  , -0.01);
	s3d_push_vertex(b, MAX_CHARS / 2*X_RATIO*CS , CS + CS*MAX_LINES / 2  , -0.01);
	s3d_push_vertex(b, MAX_CHARS / 2*X_RATIO*CS , -CS*MAX_LINES / 2  , -0.01);
	s3d_push_vertex(b, -MAX_CHARS / 2*X_RATIO*CS , -CS*MAX_LINES / 2  , -0.01);
	s3d_push_material_a(b, 0.5, 0.5, 0.5, 0.7,
	                    1, 1, 1, 0.7,
	                    0, 0, 0, 0.7);
	s3d_push_polygon(b, 1, 2, 0, 0);
	s3d_push_polygon(b, 2, 3, 0, 0);
	s3d_flags_on(b, S3D_OF_VISIBLE);
	return b;
}
static void chars_s3d_init(void)
{
#ifdef M_CHAR
	char c[2];
	c[1] = '\0';
	for (i = 0; i < 128; i++) {
		c[0] = i;
		charbuf[i] = s3d_draw_string(c, NULL);
	}
	for (i = 128; i < 256; i++) {
		charbuf[i] = s3d_new_object();
	}
	cursor = s3d_new_object();
	s3d_clone_target(cursor, charbuf['_']);
#endif
}
static void chars_init(void)
{
#ifdef M_CHAR
	int x, y;
	for (y = 0; y < (MAX_LINES); y++)
		for (x = 0; x < (MAX_CHARS); x++) {
			line[y].chars[x].character = 0;
			i = y * MAX_CHARS + x;
			screenbuf[i] = -1;
			last_c[i] = 0;
		}
#endif
#ifdef M_LINE
	int i;
	for (i = 0; i < MAX_LINES; i++)
		lines[i] = -1;
#endif
}
int main(int argc, char **argv)
{
	chars_init();
	init_terminal();
	s3d_set_callback(S3D_EVENT_QUIT, stop);
	s3d_set_callback(S3D_EVENT_OBJ_CLICK, stop);
	s3d_set_callback(S3D_EVENT_KEY, keypress);

	if (!s3d_init(&argc, &argv, "s3dvt")) {
		if (s3d_select_font("vera")) {
			printf("font not found\n");
			exit(-1);
		}
		draw_background();
		chars_s3d_init();
		s3d_mainloop(mainloop);
		s3d_quit();
	}
	term_unload();
	return 0;
}
