/*
 *      xdisplay.c -- actually displaying things
 *
 *      Copyright (C) 1996, 2009
 *          Thomas Sailer (t.sailer@alumni.ee.ethz.ch)
 *
 *      This program is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation; either version 2 of the License, or
 *      (at your option) any later version.
 *
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *
 *      You should have received a copy of the GNU General Public License
 *      along with this program; if not, write to the Free Software
 *      Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#ifndef NO_X11
/* ---------------------------------------------------------------------- */

#include "multimon.h"
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <signal.h>
#include <math.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

/* ---------------------------------------------------------------------- */

#define WIDTH 512
#define HEIGHT 256

union comdata {
	short s[WIDTH];
	unsigned char b[0];
};

#define SAMPLING_RATE 22050

/* ---------------------------------------------------------------------- */

/*
 * child data structures
 */

static Display *display = NULL;
static Window window;
static GC gc;
static Pixmap pixmap;
static unsigned long col_zeroline;
static unsigned long col_background;
static unsigned long col_trace;

static int cmdpipe[2];
static int datapipe[2];

/*
 * parent data
 */

struct xdisp {
	int used;
	pid_t pid;
	int cmdfd;
	int datafd;
};

#define NUMCLI 16

static struct xdisp cli[NUMCLI] = { { 0, 0, 0, 0}, };

/* ---------------------------------------------------------------------- */

static int x_error_handler(Display *disp, XErrorEvent *evt)
{
    char err_buf[256], mesg[256], number[256];
    char *mtype = "XlibMessage";

    XGetErrorText(disp, evt->error_code, err_buf, sizeof(err_buf));
    (void)fprintf(stderr, "X Error: %s\n", err_buf);
    XGetErrorDatabaseText(disp, mtype, "MajorCode",
                          "Request Major code %d", mesg, sizeof(mesg));
    (void)fprintf(stderr, mesg, evt->request_code);
    (void)sprintf(number, "%d", evt->request_code);
    XGetErrorDatabaseText(disp, "XRequest", number, "", err_buf,
                          sizeof(err_buf));
    (void)fprintf(stderr, " (%s)\n", err_buf);

    abort();
}

/* ---------------------------------------------------------------------- */

static char *x_getkey(void)
{
	XWindowAttributes winattrs;
	XEvent evt;
	static char kbuf[32];
	int i;

	if (!display)
		return NULL;
	while (XCheckWindowEvent(display, window, KeyPressMask |
				StructureNotifyMask | ExposureMask, &evt)) {
		switch (evt.type) {
		case KeyPress:
			i = XLookupString((XKeyEvent *)&evt, kbuf, 
					  sizeof(kbuf)-1, NULL, NULL);
			if (i) {
				kbuf[i] = 0;
				return kbuf;
			}
			continue;
		case DestroyNotify:
			XCloseDisplay(display);
			exit(0);
		case Expose:
			XGetWindowAttributes(display, window, &winattrs);
			XCopyArea(display, window, pixmap, gc, 0, 0, 
				  winattrs.width, winattrs.height, 0, 0);
			continue;
		default:
			continue;
		}
	}
	return NULL;
}

/* ---------------------------------------------------------------------- */

static void process_keystrokes(void)
{
	char *cp;

	while ((cp = x_getkey())) {
		printf("X: Keys pressed: %s\n", cp);
	}
}

/* ---------------------------------------------------------------------- */

static int do_x_select(int fd, int wr)
{
	int *xconn, xconnnum;
	int max = fd, i;
	fd_set rmask, wmask;
	
	if (!XInternalConnectionNumbers(display, &xconn, &xconnnum)) {
		perror("XInternalConnectionNumbers");
		exit(1);
	}
	FD_ZERO(&rmask);
	FD_ZERO(&wmask);
	if (wr)
		FD_SET(fd, &wmask);
	else
		FD_SET(fd, &rmask);
	for (i = 0; i < xconnnum; i++) {
		FD_SET(xconn[i], &rmask);
		if (xconn[i] > max)
			max = xconn[i];
	}
	i = select(max+1, &rmask, &wmask, NULL, NULL);
	if (i < 0) {
		perror("select");
		exit(1);
	}
	for (i = 0; i < xconnnum; i++) 
		if (FD_ISSET(xconn[i], &rmask))
			XProcessInternalConnection(display, xconn[i]);
	XFree(xconn);
	process_keystrokes();
	if (wr)
		return FD_ISSET(fd, &wmask);
	else
		return FD_ISSET(fd, &rmask);
}

/* ---------------------------------------------------------------------- */

static void child_win_init(void)
{
	XSetWindowAttributes attr;
	XGCValues gcv;
	XColor color, dummy;
	XSizeHints sizehints;
	
	/*
	 * start graphical output
	 */
	if (!(display = XOpenDisplay(NULL))) {
		fprintf(stderr, "X: Unable to open X display\n");
		exit(1);
	}
	XSetErrorHandler(x_error_handler);
        XAllocNamedColor(display, DefaultColormap(display, 0), "red",
                         &color, &dummy);
        col_zeroline = color.pixel;
        col_background = WhitePixel(display, 0);
        col_trace = BlackPixel(display, 0);
        attr.background_pixel = col_background;
	window = XCreateWindow(display, XRootWindow(display, 0),
			       200, 200, WIDTH, HEIGHT, 5, 
			       DefaultDepth(display, 0),
			       InputOutput, DefaultVisual(display, 0),
			       CWBackPixel, &attr);
        if (!(pixmap = XCreatePixmap(display, window, WIDTH, HEIGHT,
                                     DefaultDepth(display, 0)))) {
                fprintf(stderr, "X: unable to open offscreen pixmap\n");
                exit(1);
        }
        XSelectInput(display, window, KeyPressMask | StructureNotifyMask
                     | ExposureMask) ;
	gcv.line_width = 1;
	gcv.line_style = LineSolid;
	gc = XCreateGC(display, pixmap, GCForeground | GCLineWidth, &gcv);
	/*
	 * Do not allow the window to be resized
	 */
	memset(&sizehints, 0, sizeof(sizehints));
	sizehints.min_width = sizehints.max_width = WIDTH;
	sizehints.min_height = sizehints.max_height = HEIGHT;
	sizehints.flags = PMinSize | PMaxSize;
	XSetWMNormalHints(display, window, &sizehints);
	XMapWindow(display, window);
	XSynchronize(display, 1);
}	

/* ---------------------------------------------------------------------- */

#define YCOORD(x) (((x)>>8)+(HEIGHT/2))

static void child_process(void)
{
	union comdata d;
	unsigned char *bp;
	int i, j;

	/*
	 * main loop
	 */
	for (;;) {
		/*
		 * send synchronisation mark
		 */
		while (!do_x_select(cmdpipe[1], 1));
		i = write(cmdpipe[1], "r", 1);
		if (i < 1) {
			perror("write");
			exit(1);
		}
		/*
		 * read data
		 */
		for (j = sizeof(d), bp = d.b; j > 0; ) {
			while (!do_x_select(datapipe[0], 0));
			i = read(datapipe[0], bp, j);
			if (i < 1) {
				perror("read");
				exit(1);
			}
			j -= i;
			bp += i;
		}
		/*
		 * clear pixmap
		 */
		XSetState(display, gc, col_background, col_background,
			  GXcopy, AllPlanes);
                XFillRectangle(display, pixmap, gc, 0, 0, 
                               WIDTH, HEIGHT);
		/*
		 * draw zero line
		 */
		XSetForeground(display, gc, col_zeroline);
		XDrawLine(display, pixmap, gc, 0, YCOORD(0), WIDTH,
			  YCOORD(0));
		/*
		 * draw input
		 */
		XSetForeground(display, gc, col_trace);
		for (i = 1; i < WIDTH; i++)
                XDrawLine(display, pixmap, gc, i-1, YCOORD(d.s[i-1]),
                          i, YCOORD(d.s[i]));
		XCopyArea(display, pixmap, window, gc, 0, 0, 
			  WIDTH, HEIGHT, 0, 0);
		/* XSync(display, 0); */
	}
        XDestroyWindow(display, window);
        XCloseDisplay(display);
}

/* ---------------------------------------------------------------------- */

static void sigchld_handler(int sig)
{
	pid_t pid;
	int st;
	unsigned int cnum;

	(void) sig;  // Suppress the warning.

	while ((pid = wait4(0, &st, WNOHANG, NULL)) != (pid_t)-1) {
		for (cnum = 0; cnum < NUMCLI; cnum++)
			if (cli[cnum].used && cli[cnum].pid == pid) {
				cli[cnum].used = 0;
				close(cli[cnum].cmdfd);
				close(cli[cnum].datafd);
				cli[cnum].pid = (pid_t)-1;
				fprintf(stderr, "child process %i died, "
					"status %i\n", (int)pid, st);
			}
	}
}

/* ---------------------------------------------------------------------- */

void xdisp_terminate(int cnum)
{
	if (cnum  < 0 || cnum >= NUMCLI)
		return;
	if (!cli[cnum].used)
		return;
	kill(cli[cnum].pid, SIGTERM);
}

/* ---------------------------------------------------------------------- */

int xdisp_start(void)
{
	unsigned int cnum;

	/*
	 * find free client struct
	 */
	for (cnum = 0; (cnum < NUMCLI) && cli[cnum].used; cnum++);
	if (cnum >= NUMCLI)
		return -1;
	signal(SIGCHLD, sigchld_handler);
	/*
	 * start "IPC" mechanism (using the pipes)
	 */
	if (pipe(cmdpipe))
		return -1;
	if (pipe(datapipe)) {
		close(cmdpipe[0]);
		close(cmdpipe[1]);
	}
	if ((cli[cnum].pid = fork())) {
		if (cli[cnum].pid == (pid_t)-1) {
			/* error */
			close(cmdpipe[0]);
			close(cmdpipe[1]);
			close(datapipe[0]);
			close(datapipe[1]);
			return -1;
		}
		/* parent */
		cli[cnum].cmdfd = cmdpipe[0];
		close(cmdpipe[1]);
		close(datapipe[0]);
		cli[cnum].datafd = datapipe[1];
		cli[cnum].used = 1;
		fcntl(cmdpipe[0], F_SETFL, 
		      (fcntl(cmdpipe[0], F_GETFL, 0) | O_NDELAY));
		return cnum;
	}
	/*
	 * child; the X process
	 */
	close(cmdpipe[0]);
	close(datapipe[1]);
	close(0);  /* close stdin */
	child_win_init();
	child_process();
	exit(0);
}

/* ---------------------------------------------------------------------- */

int xdisp_update(int cnum, float *f)
{
	unsigned char *bp;
	short *sp;
	int i, j;
	char c;
	union comdata d;

	if (cnum < 0 || cnum >= NUMCLI)
		return 0;
	if (!cli[cnum].used)
		return 0;
	i = read(cli[cnum].cmdfd, &c, 1);
	if (i < 0 && errno != EAGAIN) {
		perror("read");
		xdisp_terminate(cnum);
		return 0;
	}
	if (i < 1)
		return 0;
	if (c != 'r')
		return 0;
	for (sp = d.s, i = 0; i < WIDTH; i++, sp++, f++) {
		if (*f >= 1)
			*sp = 32767;
		else if (*f <= -1)
			*sp = -32767;
		else 
			*sp = 32767.0 * (*f);
	}
	bp = d.b;
	j = sizeof(d);
	while (j > 0) {
		i = write(cli[cnum].datafd, bp, j);
		if (i < 0 && errno != EAGAIN) {
			perror("write");
			xdisp_terminate(cnum);
			return 0;
		}
		if (i > 0) {
			bp += i;
			j -= i;
		}
	}
	return 1;
}

/* ---------------------------------------------------------------------- */
#endif //NO_X11
