/* xvbidebug.c 
 *   by Nathan Laredo (laredo@gnu.org)
 */
#define DATA_FROM_FILE
#define PREFILTER_DATA 2

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <linux/types.h>
#include <linux/videodev.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <X11/Intrinsic.h>

Display *mydisplay;
Window mywindow;
GC mygc;
GC mygc2;
XGCValues gcvals;
XEvent myevent;
int myscreen;
XSizeHints myhint;
KeySym mykey;
XColor mycolor;
XFontStruct *font_struct;
unsigned long myforeground, mybackground;
XWindowAttributes attribs;	/* for vis, depth, width, height */
XSetWindowAttributes myattribs;	/* for window creation */
XPoint vbipoints[1600];		/* for drawing data */
long myevents;			/* keep track of our events */
char title[4096];		/* window title */
unsigned char vbibuf[65536];
char marquee[101];

char *ccode = " !\"#$%&'()a+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[e]iouabcdefghijklmnopqrstuvwxyzc/Nn.";	/* this is NOT exactly right */

char outbuf[2048];

int parityok(int n)
{				/* check parity for 2 bytes packed in n */
#if 1
    return 1;
#else
    int j, k;
    for (k = 0, j = 0; j < 7; j++)
	if (n & (1 << j))
	    k++;
    if ((k & 1) && (n && 0x80))
	return 0;
    for (k = 0, j = 8; j < 15; j++)
	if (n & (1 << j))
	    k++;
    if ((k & 1) && (n && 0x8000))
	return 0;
    return 1;
#endif
}

#ifdef PREFILTER_DATA
#define NOISE 8
#else
#define NOISE 45
#endif

int decodebit(unsigned char *data, int threshold)
{
    int i, sum = 0;
    for (i = 0; i < 32; i++)
	sum += data[i];
    return (sum > (threshold << 5));
}
int maxpos[7] =
{200, 260, 320, 380, 440, 500, 600};

int ccdecode(unsigned char *vbiline)
{
    int max[7], min[7], val[7], i, clk, tmp, sample, packedbits = 0;

    for (clk = 0; clk < 7; clk++)
	max[clk] = min[clk] = val[clk] = -1;
    clk = tmp = 0;
    i = 10;
    while (clk < 7 && i < maxpos[clk]) {	/* find and lock all 7 clocks */
	sample = vbiline[i];
	if (max[clk] < 0) {	/* find maximum value before drop */
	    if (sample > 50 && sample > val[clk])
		(val[clk] = sample, tmp = i);	/* mark new maximum found */
	    else if (val[clk]-sample>NOISE) {	/* far enough */
		XDrawLine(mydisplay, mywindow, mygc, tmp>>1, 30, tmp>>1, 128);
		max[clk] = tmp;
		i = tmp + 16;
		if (clk) {
		    tmp -= max[clk - 1];
		    sprintf(title, "%d", tmp);
		    XDrawString(mydisplay, mywindow, mygc,
			min[clk - 1]>>1, 50, title, strlen(title));
		    if (tmp < 42 || tmp > 84)
			return 0;	/* wrong frequency clock */
		}
	    }
	} else {		/* find minimum value after drop */
	    if (!clk && sample > vbiline[max[0]]) {
		max[0] = -1;	/* mistake on first data */
		continue;
	    }
	    if (sample < val[clk])
		(val[clk] = sample, tmp = i);	/* mark new minimum found */
	    else if (sample-val[clk]>NOISE) {	/* searched far enough */
		if (clk < 6)
		    XDrawLine(mydisplay, mywindow, mygc, tmp>>1, 226,
			tmp>>1, 256);
		min[clk++] = tmp;
		i = tmp + 16;
	    }
	}
	i++;
    }
    if (clk != 7)		/* failure to locate clock lead-in */
	return 0;

    /* calculate threshold */
    sample = (vbiline[max[6]] + vbiline[min[6]]) >> 1;

    /* found clock lead-in, double-check start */
    i = max[6] + 144;
    if (!decodebit(&vbiline[i], sample))
	return 0;
    XDrawLine(mydisplay, mywindow, mygc, i>>1, 30, i>>1, 256);
    tmp = i + 57;		/* tmp = data bit zero */
    for (i = 0; i < 16; i++) {
	clk = tmp + i * 57;
	if (decodebit(&vbiline[clk], sample)) {
	    XDrawLine(mydisplay, mywindow, mygc, clk>>1, 30,
		clk>>1, 256 - sample);
	    packedbits |= 1 << i;
	} else
	    XDrawLine(mydisplay, mywindow, mygc, clk>>1, 256 - sample,
		clk>>1, 256);
    }
    if (parityok(packedbits))
	return packedbits;
    return 0;
}				/* ccdecode */


void ProcessLine(unsigned char *s)
{
    int w1, b1, b2;
    static lastchar = 0, mode = 0;
    static nocc = 0;
    int m, n;

    m = strlen(outbuf);
    w1 = ccdecode(s);
    if (!w1)
	nocc++;
    sprintf(title, "%d", nocc);
    XDrawString(mydisplay, mywindow, mygc, 20, 245, title, strlen(title));
    
    b1 = w1 & 0x7f;
    b2 = (w1 >> 8) & 0x7f;
    if ((b1 & 96)) {
	if (b1 > 31) {
	    strncat(outbuf, &ccode[b1 - 32], 1);
	    if (lastchar == '.' || lastchar == '['
		|| lastchar == '>' || lastchar == ']'
		|| lastchar == '!' || lastchar == '?')
		outbuf[strlen(outbuf) - 1] =
		    toupper(ccode[b1 - 32]);
	    if (b1 > 32)
		lastchar = ccode[b1 - 32];
	}
	if (b2 > 31) {
	    strncat(outbuf, &ccode[b2 - 32], 1);
	    if (lastchar == '.' || lastchar == '['
		|| lastchar == '>' || lastchar == ']'
		|| lastchar == '!' || lastchar == '?')
		outbuf[strlen(outbuf) - 1] =
		    toupper(ccode[b2 - 32]);
	    if (b2 > 32)
		lastchar = ccode[b2 - 32];
	}
    }
    if (!(b1 & 96) && b1 && *outbuf)
	if (outbuf[strlen(outbuf) - 1] != ' ')
	    strncat(outbuf, ccode, 1);
    n = strlen(outbuf);
    if (n - m > 0) {
	memcpy(marquee, &marquee[n - m], 100 - (n - m));
	strncpy(&marquee[100 - (n - m)], &outbuf[m], n-m);
    }
    if (!(b1 & 96) && b1 && *outbuf) {
	if (++mode > 4) {
	    fprintf(stderr, "%s\n", outbuf);
	    *outbuf = 0;
	    mode = 0;
	}
    }
}				/* ProcessLine */

void init_display(int argc, char **argv)
{

    if (!getenv("DISPLAY")) {
	fprintf(stderr, "DISPLAY not set.\n");
	exit(1);
    }
    if (!(mydisplay = XOpenDisplay(getenv("DISPLAY")))) {
	fprintf(stderr, "Unable to open display.\n");
	exit(1);
    }
    mybackground = BlackPixel(mydisplay, myscreen);
    myforeground = WhitePixel(mydisplay, myscreen);
    myhint.width = 800;
    myhint.height = 256;
    myhint.max_width = 800;
    myhint.max_height = 256;
    myhint.min_width = 800;
    myhint.min_width = 256;
    myhint.x = 0;
    myhint.y = 580;
    /* to add aspect ratio restriction, add to mask below */
    myhint.flags = PSize | PMinSize | PMaxSize | PPosition;
    myevents = myattribs.event_mask = KeyPressMask | ButtonPressMask;
    myattribs.background_pixel = mybackground;
    mywindow = XCreateWindow(mydisplay, DefaultRootWindow(mydisplay),
	0, 0, myhint.width, myhint.height, 0, attribs.depth, InputOutput,
		  attribs.visual, CWEventMask | CWBackPixel, &myattribs);
    sprintf(title, "xvbidebug 11");
    XSetStandardProperties(mydisplay, mywindow, title, title, None, argv,
			   argc, &myhint);
    gcvals.foreground = myforeground;
    gcvals.background = mybackground;
    mygc = XCreateGC(mydisplay, mywindow, GCForeground | GCBackground, &gcvals);
    mycolor.flags = DoRed | DoGreen | DoBlue;
    mycolor.red = 32767;
    mycolor.green = 49152;
    mycolor.blue = 65535;
    mycolor.pad = 0;
    XAllocColor(mydisplay, DefaultColormap(mydisplay, myscreen), &mycolor);
    gcvals.foreground = mycolor.pixel;
    mygc2 = XCreateGC(mydisplay, mywindow,
		      GCForeground | GCBackground, &gcvals);
    XMapRaised(mydisplay, mywindow);
}

int main(int argc, char **argv)
{
    fd_set rdfs;
    int vbifd, xfd, done = 0, i, j = 22528, k, m;
    unsigned int *vbicount, oldcount = 0, dropcount = 0;

#ifdef DATA_FROM_FILE
    if ((vbifd = open("/home/ftp/vbidata", O_RDONLY)) < 0) {
	perror("open /dev/vbi");
	exit(1);
    }
#else
    if ((vbifd = open("/dev/vbi", O_RDONLY)) < 0) {
	perror("open /dev/vbi");
	exit(1);
    }
#endif
    init_display(argc, argv);
    for (i=0; i<100; i++)
	marquee[i] = ' ';
    marquee[i] = 0;

    xfd = ConnectionNumber(mydisplay);
    for (i = 0; i < 1600; i++)
	vbipoints[i].x = i / 2;

    /* Main event loop */
    while (!done) {
	FD_ZERO(&rdfs);
	FD_SET(xfd, &rdfs);
	FD_SET(vbifd, &rdfs);
	select(FD_SETSIZE, &rdfs, NULL, NULL, NULL);
	if (FD_ISSET(xfd, &rdfs))
	    while (XPending(mydisplay)) {
		XNextEvent(mydisplay, &myevent);
		j += 2048;
		if (j >= 65536)
		    j = 0;
		sprintf(title, "xvbidebug %d", j >> 11);
		XStoreName(mydisplay, mywindow, title);
	    }
	if (FD_ISSET(vbifd, &rdfs)) {
	    read(vbifd, vbibuf, 65536);
#ifdef DATA_FROM_FILE
	    sleep(1);	/* pass through data slowly for close observation */
#endif
	    vbicount = (unsigned int *) &vbibuf[65532];
	    if (oldcount && oldcount + 1 != *vbicount) {
		dropcount += (*vbicount - oldcount);
	    }
	    oldcount = *vbicount;
	    XClearWindow(mydisplay, mywindow);
	    sprintf(title, "%d %d %s", dropcount, *vbicount, marquee);
#ifdef PREFILTER_DATA
	    for (i = 0; i < 1600; i++)
		vbipoints[i].y = 256 - vbibuf[i + j];
	    for (m = 0; m < PREFILTER_DATA; m++)
		if (m & 1)
		for (i = 3; i < 1600; i++) {
		    k = vbibuf[i + j - 3] + vbibuf[i + j - 2] +
			vbibuf[i + j - 1] + vbibuf[i + j] +
			vbibuf[i + j + 1] + vbibuf[i + j + 2] +
			vbibuf[i + j + 3] + vbibuf[i + j + 4];
		    k >>= 3;
		    vbibuf[i + j - 3] = vbibuf[i + j - 2] =
			vbibuf[i + j - 1] = vbibuf[i + j] =
			vbibuf[i + j + 1] = vbibuf[i + j + 2] =
			vbibuf[i + j + 3] = vbibuf[i + j + 4] = k;
		} else
		for (i = 1597; i > 3; i--) {
		    k = vbibuf[i + j - 3] + vbibuf[i + j - 2] +
			vbibuf[i + j - 1] + vbibuf[i + j] +
			vbibuf[i + j + 1] + vbibuf[i + j + 2] +
			vbibuf[i + j + 3] + vbibuf[i + j + 4];
		    k >>= 3;
		    vbibuf[i + j - 3] = vbibuf[i + j - 2] =
			vbibuf[i + j - 1] = vbibuf[i + j] =
			vbibuf[i + j + 1] = vbibuf[i + j + 2] =
			vbibuf[i + j + 3] = vbibuf[i + j + 4] = k;
		}
	    XDrawPoints(mydisplay, mywindow, mygc2, vbipoints,
			1600, CoordModeOrigin);
#endif
	    for (i = 0; i < 1600; i++)
		vbipoints[i].y = 256 - vbibuf[i + j];
	    XDrawString(mydisplay, mywindow, mygc, 20, 20,
			title, strlen(title));
	    XDrawLines(mydisplay, mywindow, mygc, vbipoints,
		       1600, CoordModeOrigin);
	    ProcessLine(&vbibuf[j]);
	    XFlush(mydisplay);
	}
    }				/* while */
    close(vbifd);
    XFreeGC(mydisplay, mygc);
    XDestroyWindow(mydisplay, mywindow);
    XCloseDisplay(mydisplay);
    exit(0);
}				/* main */
