/*
 *  libzvbi test
 *
 *  Copyright (C) 2000-2002, 2004 Michael H. Schimek
 *  Copyright (C) 2003 James Mastros
 *
 *  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., 51 Franklin Street, Fifth Floor, Boston,
 *  MA 02110-1301, USA.
 */

/* $Id: osc.c,v 1.35 2008-03-01 07:36:41 mschimek Exp $ */

#undef NDEBUG

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <unistd.h>
#ifdef HAVE_GETOPT_LONG
#include <getopt.h>
#endif

#include "src/inout.h"
#include "src/decoder.h"
#include "src/misc.h"
#include "src/hamm.h"
#include "src/io-sim.h"
#include "src/raw_decoder.h"	/* _vbi_service_table[] */
#include "src/proxy-msg.h"
#include "src/proxy-client.h"

#ifndef X_DISPLAY_MISSING

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

vbi_capture *		cap;
vbi_raw_decoder *	par;
vbi_proxy_client *      pxc = NULL;
int			src_w, src_h;
vbi_sliced *		sliced;
int			slines;
vbi_bool		quit;

int			do_sim;
int			ignore_error;
int			desync;

Display *		display;
int			screen;
Colormap		cmap;
Window			window;
int			dst_w, dst_h;
GC			gc;
XEvent			event;
XImage *		ximage;
void *			ximgdata;
unsigned char 		*raw1, *raw2;
int			palette[256];
int			depth;
int			draw_row, draw_offset;
int			draw_count = -1;
int                     cur_x, cur_y;

extern void
vbi_capture_set_log_fp		(vbi_capture *		capture,
				 FILE *			fp);
extern vbi_bool vbi_capture_force_read_mode;

#define PIL(day, mon, hour, min) \
	(((day) << 15) + ((mon) << 11) + ((hour) << 6) + ((min) << 0))

/* Return value must be free()d by caller! */
static char *
decode_ttx(uint8_t *buf, int line)
{
        char *text, *text_start;
	int packet_address;
	int magazine, packet;
	int j;

        text_start = text = malloc(255);
        memset(text, 0, 255);
	packet_address = vbi_unham16p (buf + 0);

	if (packet_address < 0)
		return text; /* hamming error */

	magazine = packet_address & 7;
	packet = packet_address >> 3;

        text += snprintf(text, 255,
			 "pg %x%02d ln %03d >", magazine, packet, line);

        for (j = 0; j < 42; j++) {
	   char c = _vbi_to_ascii (buf[j]);
	   
	   *text = c;
	   text++;
	}

        *text='<';
        *text=0;

        return text_start;
}

static char *
dump_pil(int pil)
{
	int day, mon, hour, min;
        static char text[255];
   
        memset(text, 0, 255);

	day = pil >> 15;
	mon = (pil >> 11) & 0xF;
	hour = (pil >> 6) & 0x1F;
	min = pil & 0x3F;

	if (pil == PIL(0, 15, 31, 63))
		snprintf(text, 255, " PDC: Timer-control (no PDC)\n");
	else if (pil == PIL(0, 15, 30, 63))
		snprintf(text, 255, " PDC: Recording inhibit/terminate\n");
	else if (pil == PIL(0, 15, 29, 63))
		snprintf(text, 255, " PDC: Interruption\n");
	else if (pil == PIL(0, 15, 28, 63))
		snprintf(text, 255, " PDC: Continue\n");
	else if (pil == PIL(31, 15, 31, 63))
		snprintf(text, 255, " PDC: No time\n");
	else
		snprintf(text, 255, " PDC: %05x, 200X-%02d-%02d %02d:%02d\n",
			pil, mon, day, hour, min);
        return text;
}

static char *
decode_vps(uint8_t *buf)
{
        char *text, *text_start;
	static char pr_label[20];
	static char label[20];
	static int l = 0;
	int cni, pcs, pty, pil;
	int c;

        text_start=text=malloc(255);
        memset(text, 0, 255);
        
	text += snprintf(text, 255, "VPS: ");

	c = vbi_rev8 (buf[1]);

	if ((int8_t) c < 0) {
		label[l] = 0;
		memcpy(pr_label, label, sizeof(pr_label));
		l = 0;
	}

	c &= 0x7F;

	label[l] = _vbi_to_ascii (c);

	l = (l + 1) % 16;

	text += snprintf(text, 250, " 3-10: %02x %02x %02x %02x %02x %02x %02x %02x (\"%s\")\n",
		buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], pr_label);

	pcs = buf[2] >> 6;

	cni = + ((buf[10] & 3) << 10)
	      + ((buf[11] & 0xC0) << 2)
	      + ((buf[8] & 0xC0) << 0)
	      + (buf[11] & 0x3F);

	pil = ((buf[8] & 0x3F) << 14) + (buf[9] << 6) + (buf[10] >> 2);

	pty = buf[12];

	/* FIXME use real buffer size. */
	text += snprintf(text, 100, " CNI: %04x PCS: %d PTY: %d ", cni, pcs, pty);

	text += snprintf(text, 50, " %s", dump_pil(pil));
   
        return(text_start);
}


/* End from capture.c */

static void
draw(unsigned char *raw)
{
	int rem = src_w - draw_offset;
	char buf[256];
	unsigned char *data = raw;
	int i, v, h0, field, end, line;
        XTextItem xti;
	int x;

	if (draw_count == 0)
		return;

	if (draw_count > 0)
		draw_count--;

	memcpy(raw2, raw, src_w * src_h);

	if (depth == 24) {
		unsigned int *p = ximgdata;
		
		for (i = src_w * src_h; i >= 0; i--)
			*p++ = palette[(int) *data++];
	} else {
		unsigned short *p = ximgdata; // 64 bit safe?

		for (i = src_w * src_h; i >= 0; i--)
			*p++ = palette[(int) *data++];
	}

	XPutImage(display, window, gc, ximage,
		draw_offset, 0, 0, 0, rem, src_h);
//XSync(display, False);

	XSetForeground(display, gc, 0);
//XSync(display, False);

	if (rem < dst_w) {
//		fprintf(stderr, "%u: %p %u %u  %u %u %u %u\n",__LINE__,
//			display, window, gc,
//			rem, 0, dst_w, src_h);
		XFillRectangle(display, window, gc,
			rem, 0, dst_w, src_h);
//XSync(display, False);
	}

	if ((v = dst_h - src_h) <= 0)
		return;

	XSetForeground(display, gc, 0);
//XSync(display, False);
//		fprintf(stderr, "%u: %p %u %u  %u %u %u %u\n",__LINE__,
//display, window, gc,0, src_h, dst_w, dst_h);
	XFillRectangle(display, window, gc,
		0, src_h, dst_w, dst_h);
//XSync(display, False);

	XSetForeground(display, gc, ~0);
//XSync(display, False);

	field = (draw_row >= par->count[0]);

	if (par->start[field] < 0) {
		xti.nchars = snprintf(buf, 255, "Row %d Line ?", draw_row);
		line = -1;
	} else if (field == 0) {
		line = draw_row + par->start[0];
		xti.nchars = snprintf(buf, 255, "Row %d Line %d", draw_row, line);
	} else {
		line = draw_row - par->count[0] + par->start[1];
		xti.nchars = snprintf(buf, 255, "Row %d Line %d", draw_row, line);
	}

	for (i = 0; i < slines; i++)
		if (sliced[i].line == (unsigned int) line)
			break;
	if (i < slines) {
	   int svc_idx=0;
	   while (_vbi_service_table[svc_idx].id !=0 && 
		  _vbi_service_table[svc_idx].id != sliced[i].id)
	     svc_idx++;
	   
	   if (_vbi_service_table[svc_idx].id == sliced[i].id) {
	      struct _vbi_service_par service;
	      service = _vbi_service_table[svc_idx];
	      
	      xti.nchars += snprintf(buf + xti.nchars, 255 - xti.nchars,
				     " %s (%x) +%dns",
				     service.label,
				     service.id,
				     service.offset
				    );
	      if (service.id & VBI_SLICED_TELETEXT_B) {
		 char *text = decode_ttx(sliced[i].data, sliced[i].line);
		 xti.nchars += snprintf(buf + xti.nchars, 255 - xti.nchars,
					": %s", text);
		 free(text);
	      } else if (service.id & VBI_SLICED_VPS) {
		 char *text = decode_vps(sliced[i].data);
		 xti.nchars += snprintf(buf + xti.nchars, 255 - xti.nchars,
					": %s", text);
		 free(text);
	      }
	   } else {
	      xti.nchars += snprintf(buf + xti.nchars, 255 - xti.nchars,
				     " %s (%d)", 
				     vbi_sliced_name(sliced[i].id) ?: "???", 
				     sliced[i].id);
	   }
	} else {
		int s = 0, sd = 0;

		data = raw + draw_row * src_w;

		for (i = 0; i < src_w; i++)
			s += data[i];
		s /= src_w;

		for (i = 0; i < src_w; i++)
			sd += abs(data[i] - s);

		sd /= src_w;

		xti.nchars += snprintf(buf + xti.nchars, 255 - xti.nchars,
				       (sd < 5) ? " Blank" : " Unknown signal");
	        xti.nchars += snprintf(buf + xti.nchars, 255 - xti.nchars,
				       " (%d)", sd);
	}

/*
        XSetForeground(display, gc, 0x00FFFF00);
        XFillRectangle(display, window, gc,
		       0xc0-draw_offset, 
		       src_h, 1, dst_h);
        XFillRectangle(display, window, gc,
		       0x19b-draw_offset,
		       src_h, 1, dst_h);
*/
        /* 50% grey */
        XSetForeground(display, gc, 0xAAAAAAAA);  
//XSync(display, False);
        x=draw_offset;
        while (x<src_w && (x-draw_offset)<dst_w) {
//		fprintf(stderr, "%u: %p %u %u  %u %u %u %u\n",__LINE__,
//display, window, gc,
//			  x-draw_offset, /* x,y, w,h */
//			  src_h, 1, dst_h);
	   XFillRectangle(display, window, gc,
			  x-draw_offset, /* x,y, w,h */
			  src_h, 1, dst_h);
//XSync(display, False);
	   x+=10;
	}
        XSetForeground(display, gc, ~0);
//XSync(display, False);

        xti.chars = buf;
	xti.delta = 0;
	xti.font = 0;

	XDrawText(display, window, gc, 4, src_h + 12, &xti, 1);
//XSync(display, False);
        xti.nchars = snprintf(buf, 255, "(%d, %3d)", cur_x+draw_offset,
	  (dst_h - cur_y) * 256 / v);
//	  (1000*(dst_h-cur_y))/(dst_h-src_h));
        XDrawText(display, window, gc, 4, src_h + 24, &xti, 1);
//XSync(display, False);

	data = raw + draw_offset + draw_row * src_w;
	h0 = dst_h - (data[0] * v) / 256;
	end = src_w - draw_offset;
	if (dst_w < end)
		end = dst_w;

	for (i = 1; i < end; i++) {
		int h = dst_h - (data[i] * v) / 256;

		XDrawLine(display, window, gc, i - 1, h0, i, h);
//XSync(display, False);
		h0 = h;
	}
}

static void
xevent(void)
{
	while (XPending(display)) {
		XNextEvent(display, &event);

		switch (event.type) {
		case KeyPress:
		{
			switch (XLookupKeysym(&event.xkey, 0)) {
			case 'g':
				draw_count = 1;
				break;

			case 'l':
				draw_count = -1;
				break;

			case 'q':
			case 'c':
			case XK_Escape:
				quit = TRUE;
				break;

			case XK_Up:
			    if (draw_row > 0)
				    draw_row--;
			    goto redraw;

			case XK_Down:
			    if (draw_row < (src_h - 1))
				    draw_row++;
			    goto redraw;

			case XK_Left:
			    if (draw_offset > 0)
				    draw_offset -= 10;
			    goto redraw;

			case XK_Right:
			    if (draw_offset < (src_w - 10))
				    draw_offset += 10;
			    goto redraw;  
			}

			break;
		}

		case ConfigureNotify:
			dst_w = event.xconfigurerequest.width;
			dst_h = event.xconfigurerequest.height;
redraw:
			if (draw_count == 0) {
				draw_count = 1;
				draw(raw2);
			}

			break;

		case MotionNotify:
		       cur_x = event.xmotion.x;
		       cur_y = event.xmotion.y;
		       // printf("Got MotionNotify: (%d, %d)\n", event.xmotion.x, event.xmotion.y);
		       break;
		   
		case ClientMessage:
			exit(EXIT_SUCCESS);
		}
	}
}

static void
init_window(int ac, char **av, const char *dev_name)
{
	char buf[256];
	Atom delete_window_atom;
	XWindowAttributes wa;
	int i;

	ac = ac;
	av = av;

	if (!(display = XOpenDisplay(NULL))) {
		fprintf(stderr, "No display\n");
		exit(EXIT_FAILURE);
	}

	screen = DefaultScreen(display);
	cmap = DefaultColormap(display, screen);
 
	window = XCreateSimpleWindow(display,
		RootWindow(display, screen),
		0, 0,		// x, y
		dst_w = 768, dst_h = src_h + 110,
				// w, h
		2,		// borderwidth
		0xffffffff,	// fgd
		0x00000000);	// bgd 

	if (!window) {
		fprintf(stderr, "No window\n");
		exit(EXIT_FAILURE);
	}
			
	XGetWindowAttributes(display, window, &wa);
	depth = wa.depth;
			
	if (depth != 15 && depth != 16 && depth != 24) {
		fprintf(stderr, "Sorry, cannot run at colour depth %d\n", depth);
		exit(EXIT_FAILURE);
	}

	for (i = 0; i < 256; i++) {
		switch (depth) {
		case 15:
			palette[i] = ((i & 0xF8) << 7)
				   + ((i & 0xF8) << 2)
				   + ((i & 0xF8) >> 3);
				break;

		case 16:
			palette[i] = ((i & 0xF8) << 8)
				   + ((i & 0xFC) << 3)
				   + ((i & 0xF8) >> 3);
				break;

		case 24:
			palette[i] = (i << 16) + (i << 8) + i;
				break;
		}
	}

	if (depth == 24) {
		if (!(ximgdata = malloc(src_w * src_h * 4))) {
			fprintf(stderr, "Virtual memory exhausted\n");
			exit(EXIT_FAILURE);
		}
	} else {
		if (!(ximgdata = malloc(src_w * src_h * 2))) {
			fprintf(stderr, "Virtual memory exhausted\n");
			exit(EXIT_FAILURE);
		}
	}

	if (!(raw1 = malloc(src_w * src_h))) {
		fprintf(stderr, "Virtual memory exhausted\n");
		exit(EXIT_FAILURE);
	}

	if (!(raw2 = malloc(src_w * src_h))) {
		fprintf(stderr, "Virtual memory exhausted\n");
		exit(EXIT_FAILURE);
	}

	ximage = XCreateImage(display,
		DefaultVisual(display, screen),
		DefaultDepth(display, screen),
		ZPixmap, 0, (char *) ximgdata,
		src_w, src_h,
		8, 0);

	if (!ximage) {
		fprintf(stderr, "No ximage\n");
		exit(EXIT_FAILURE);
	}

	delete_window_atom = XInternAtom(display, "WM_DELETE_WINDOW", False);

	XSelectInput(display, window, PointerMotionMask | KeyPressMask | ExposureMask | StructureNotifyMask);
	XSetWMProtocols(display, window, &delete_window_atom, 1);
	snprintf(buf, sizeof(buf) - 1, "%s - [cursor] [g]rab [l]ive", dev_name);
	XStoreName(display, window, buf);

	gc = XCreateGC(display, window, 0, NULL);

	XMapWindow(display, window);
	       
	XSync(display, False);
}

static void
mainloop(void)
{
	double timestamp;
	struct timeval tv;

	tv.tv_sec = 2;
	tv.tv_usec = 0;

	assert((sliced = malloc(sizeof(vbi_sliced) * src_h)));

	for (quit = FALSE; !quit;) {
		int r;

		r = vbi_capture_read(cap, raw1, sliced,
				     &slines, &timestamp, &tv);

		switch (r) {
		case -1:
			fprintf(stderr, "VBI read error: %d, %s%s\n",
				errno, strerror(errno),
				ignore_error ? " (ignored)" : "");
			if (ignore_error)
				continue;
			else
				exit(EXIT_FAILURE);
		case 0: 
			fprintf(stderr, "VBI read timeout%s\n",
				ignore_error ? " (ignored)" : "");
			if (ignore_error || (pxc != NULL))
				continue;
			else
				exit(EXIT_FAILURE);
		case 1:
			break;
		default:
			assert(!"reached");
		}

		draw(raw1);

/*		printf("raw: %f; sliced: %d\n", timestamp, slines); */

		xevent();
	}
}

static const char short_options[] = "1234cd:enpsv";

#ifdef HAVE_GETOPT_LONG
static const struct option
long_options[] = {
	{ "desync",	no_argument,		NULL,		'c' },
	{ "device",	required_argument,	NULL,		'd' },
	{ "ignore-error", no_argument,		NULL,		'e' },
	{ "ntsc",	no_argument,		NULL,		'n' },
	{ "pal",	no_argument,		NULL,		'p' },
	{ "sim",	no_argument,		NULL,		's' },
	{ "v4l",	no_argument,		NULL,		'1' },
	{ "v4l2-read",	no_argument,		NULL,		'2' },
	{ "v4l2-mmap",	no_argument,		NULL,		'3' },
	{ "proxy",	no_argument,		NULL,		'4' },
	{ "verbose",	no_argument,		NULL,		'v' },
	{ 0, 0, 0, 0 }
};
#else
#define getopt_long(ac, av, s, l, i) getopt(ac, av, s)
#endif

int
main(int argc, char **argv)
{
	const char *dev_name = "/dev/vbi";
	char *errstr;
	unsigned int services;
	int scanning = 625;
	int strict;
	int verbose = 0;
	int interface = 0;
	int c, index;

	while ((c = getopt_long(argc, argv, short_options,
				long_options, &index)) != -1)
		switch (c) {
		case 0: /* set flag */
			break;
		case '2':
			/* Preliminary hack for tests. */
			vbi_capture_force_read_mode = TRUE;
			/* fall through */
		case '1':
		case '3':
		case '4':
			interface = c - '0';
			break;
		case 'c':
			desync ^= TRUE;
			break;
		case 'd':
			dev_name = strdup (optarg);
			break;
		case 'e':
			ignore_error ^= TRUE;
			break;
		case 'n':
			scanning = 525;
			break;
		case 'p':
			scanning = 625;
			break;
		case 's':
			do_sim ^= TRUE;
			break;
		case 'v':
			++verbose;
			break;
		default:
			fprintf(stderr, "Unknown option\n");
			exit(EXIT_FAILURE);
		}

	services = VBI_SLICED_VBI_525 | VBI_SLICED_VBI_625
		| VBI_SLICED_TELETEXT_B | VBI_SLICED_CAPTION_525
		| VBI_SLICED_CAPTION_625 | VBI_SLICED_VPS
		| VBI_SLICED_WSS_625 | VBI_SLICED_WSS_CPR1204;

	strict = 0;

	if (do_sim) {
		cap = vbi_capture_sim_new (scanning, &services,
					   /* interlaced */ FALSE, !desync);
		assert ((par = vbi_capture_parameters(cap)));
	} else {
		do {
			if ((2 == interface) || (3 == interface)) {
				cap = vbi_capture_v4l2k_new
					(dev_name, /* fd */ -1,
					 /* buffers */ 5, &services,
					 strict, &errstr,
					 /* trace */ !!verbose);

				if (cap)
					break;

				fprintf (stderr, "Cannot capture vbi data "
					 "with v4l2k interface:\n%s\n",
					 errstr);

				free (errstr);

				cap = vbi_capture_v4l2_new (dev_name,
							    /* buffers */ 5,
							    &services,
							    strict,
							    &errstr,
							    /* trace */
							    !!verbose);
				if (cap)
					break;

				fprintf (stderr, "Cannot capture vbi data "
					 "with v4l2 interface:\n%s\n", errstr);

				free (errstr);
			}

			if (interface < 2) {
				cap = vbi_capture_v4l_new (dev_name,
							   scanning,
							   &services,
							   strict,
							   &errstr,
							   /* trace */
							   !!verbose);
				if (cap)
					break;

				fprintf (stderr, "Cannot capture vbi data "
					 "with v4l interface:\n%s\n", errstr);

				free (errstr);
			}

			if (interface == 4) {
                                pxc = vbi_proxy_client_create(dev_name, "capture", 0,
                                                                &errstr, !!verbose);
                                if (pxc != NULL) {
                                        /* strip non-raw services, else request for raw is masked out */
                                        unsigned int sv = services & (VBI_SLICED_VBI_525 |
                                                                      VBI_SLICED_VBI_625);
                                        cap = vbi_capture_proxy_new(pxc, 5, 0, &sv,
                                                                    strict, &errstr );
                                        if (cap)
                                                break;

                                        fprintf (stderr, "Cannot capture vbi data "
                                                 "through proxy:\n%s\n", errstr);
                                }
				fprintf (stderr, "Cannot initialize proxy\n%s\n", errstr);
                        }

			/* BSD interface */
			if (1) {
				cap = vbi_capture_bktr_new (dev_name,
							    scanning,
							    &services,
							    strict,
							    &errstr,
							    /* trace */
							    !!verbose);
				if (cap)
					break;

				fprintf (stderr, "Cannot capture vbi data "
					 "with bktr interface:\n%s\n", errstr);

				free (errstr);
			}

			exit(EXIT_FAILURE);
		} while (0);

		assert ((par = vbi_capture_parameters(cap)));
	}

	if (verbose > 1) {
		vbi_capture_set_log_fp (cap, stderr);
	}

	assert (par->sampling_format == VBI_PIXFMT_YUV420);

	src_w = par->bytes_per_line / 1;
	src_h = par->count[0] + par->count[1];

	init_window(argc, argv, dev_name);

	mainloop();

	if (!do_sim)
		vbi_capture_delete(cap);

	exit(EXIT_SUCCESS);	
}


#else /* X_DISPLAY_MISSING */

int
main(int argc, char **argv)
{
	printf("Could not find X11 or has been disabled at configuration time\n");
	exit(EXIT_FAILURE);
}

#endif
