/*
 *  libzvbi test
 *
 *  Copyright (C) 2000, 2001 Michael H. Schimek
 *
 *  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.
 */

/* $Id: caption.c,v 1.14 2006/05/22 08:57:05 mschimek Exp $ */

#undef NDEBUG

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>

#ifndef X_DISPLAY_MISSING

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

#include "src/vbi.h"
#include "src/exp-gfx.h"
#include "src/hamm.h"
#include "src/dvb_demux.h"
#include "sliced.h"

vbi_decoder *		vbi;
vbi_pgno		pgno = -1;
vbi_dvb_demux *		dx;

/*
 *  Rudimentary render code for CC test.
 *  Attention: RGB 5:6:5 little endian only.
 */

#define DISP_WIDTH	640
#define DISP_HEIGHT	480

#define CELL_WIDTH	16
#define CELL_HEIGHT	26

Display *		display;
int			screen;
Colormap		cmap;
Window			window;
GC			gc;
XEvent			event;
XImage *		ximage;
ushort *		ximgdata;

int			shift = 0, step = 3;
int			sh_first, sh_last;

vbi_rgba		row_buffer[64 * CELL_WIDTH * CELL_HEIGHT];

#define COLORKEY 0x80FF80 /* where video looks through */

#define RGB565(rgba)							\
	(((((rgba) >> 16) & 0xF8) << 8) | ((((rgba) >> 8) & 0xFC) << 3)	\
	 | (((rgba) & 0xF8) >> 3))

static void
draw_video			(int			x0,
				 int			y0,
				 int			w,
				 int			h)
{
	ushort *canvas = ximgdata + x0 + y0 * DISP_WIDTH;
	int x, y;

	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++)
			canvas[x] = RGB565(COLORKEY);
		canvas += DISP_WIDTH;
	}
}

static void
draw_blank			(int			column,
				 int			width)
{
	vbi_rgba *canvas = row_buffer + column * CELL_WIDTH;
	int x, y;

	for (y = 0; y < CELL_HEIGHT; y++) {
		for (x = 0; x < CELL_WIDTH * width; x++)
			canvas[x] = COLORKEY;
		canvas += sizeof(row_buffer) / sizeof(row_buffer[0])
			/ CELL_HEIGHT;
	}
}

/* Not exactly efficient, but this is only a test */
static void
draw_row			(ushort *		canvas,
				 vbi_page *		pg,
				 int			row)
{
	int i, j, num_tspaces = 0;
	vbi_rgba *s = row_buffer;

	for (i = 0; i < pg->columns; ++i) {
		if (pg->text[row * pg->columns + i].opacity
		    == VBI_TRANSPARENT_SPACE) {
			num_tspaces++;
			continue;
		}

		if (num_tspaces > 0) {
			draw_blank(i - num_tspaces, num_tspaces);
			num_tspaces = 0; 
		}

		vbi_draw_cc_page_region (pg, VBI_PIXFMT_RGBA32_LE,
					 row_buffer + i * CELL_WIDTH,
					 sizeof(row_buffer) / CELL_HEIGHT,
					 i, row, 1, 1);
	}

	if (num_tspaces > 0)
		draw_blank(i - num_tspaces, num_tspaces);

	for (i = 0; i < CELL_HEIGHT; i++) {
		for (j = 0; j < pg->columns * CELL_WIDTH; j++)
			canvas[j] = RGB565(s[j]);
		s += sizeof(row_buffer) / sizeof(row_buffer[0]) / CELL_HEIGHT;
		canvas += DISP_WIDTH;
	}
}

static void
bump				(int			n,
				 vbi_bool		draw)
{
	ushort *canvas = ximgdata + 45 * DISP_WIDTH;

	if (shift < n)
		n = shift;

	if (shift <= 0 || n <= 0)
		return;

	memmove (canvas + (sh_first * CELL_HEIGHT) * DISP_WIDTH,
		 canvas + (sh_first * CELL_HEIGHT + n) * DISP_WIDTH,
		 ((sh_last - sh_first + 1) * CELL_HEIGHT - n)
		 * DISP_WIDTH * 2);

	if (draw)
		XPutImage (display, window, gc, ximage,
			   0, 0, 0, 0, DISP_WIDTH, DISP_HEIGHT);

	shift -= n;
}

static void
render				(vbi_page *		pg,
				 int			row)
{
	/* ushort *canvas = ximgdata + 48 + 45 * DISP_WIDTH; */

	if (shift > 0) {
		bump(shift, FALSE);
		draw_video (48, 45 + sh_last * CELL_HEIGHT,
			    DISP_WIDTH - 48, CELL_HEIGHT);
	}

	draw_row (ximgdata + 48 + (45 + row * CELL_HEIGHT) * DISP_WIDTH,
		  pg, row);

	XPutImage (display, window, gc, ximage,
		   0, 0, 0, 0, DISP_WIDTH, DISP_HEIGHT);
}

static void
clear				(vbi_page *		pg)
{
	pg = pg;

	draw_video (0, 0, DISP_WIDTH, DISP_HEIGHT);

	XPutImage (display, window, gc, ximage,
		   0, 0, 0, 0, DISP_WIDTH, DISP_HEIGHT);
}

static void
roll_up				(vbi_page *		pg,
				 int			first_row,
				 int			last_row)
{
	ushort scol, *canvas = ximgdata + 45 * DISP_WIDTH;
	vbi_rgba col;
	int i, j;

#if 1 /* soft */

	sh_first = first_row;
	sh_last = last_row;
	shift = 26;
	bump(step, FALSE);

	canvas += 48 + (((last_row * CELL_HEIGHT) + CELL_HEIGHT - step)
			* DISP_WIDTH);
	col = pg->color_map[pg->text[last_row * pg->columns].background];
	scol = RGB565 (col);

	for (j = 0; j < step; ++j) {
		if (pg->text[last_row * pg->columns].opacity
		    == VBI_TRANSPARENT_SPACE) {
			for (i = 0; i < CELL_WIDTH * pg->columns; ++i)
				canvas[i] = RGB565 (COLORKEY);
		} else {
			for (i = 0; i < CELL_WIDTH * pg->columns; ++i)
				canvas[i] = scol;
		}

		canvas += DISP_WIDTH;
	}

#else /* at once */

	memmove (canvas + first_row * CELL_HEIGHT * DISP_WIDTH,
		 canvas + (first_row + 1) * CELL_HEIGHT * DISP_WIDTH,
		 (last_row - first_row) * CELL_HEIGHT * DISP_WIDTH * 2);

	draw_video (48, 45 + last_row * CELL_HEIGHT,
		    DISP_WIDTH - 48, CELL_HEIGHT);

#endif

	XPutImage (display, window, gc, ximage,
		   0, 0, 0, 0, DISP_WIDTH, DISP_HEIGHT);
}

static void
xevent				(int			nap_usec);

static void
cc_handler			(vbi_event *		ev,
				 void *			user_data)
{
	vbi_page page;
	vbi_bool success;
	int row;

	user_data = user_data;

	if (pgno != -1 && ev->ev.caption.pgno != pgno)
		return;

	/* Fetching & rendering in the handler
           is a bad idea, but this is only a test */

	success = vbi_fetch_cc_page (vbi, &page, ev->ev.caption.pgno, TRUE);
	assert (success);

#if 1 /* optional */
	if (abs (page.dirty.roll) > page.rows) {
		clear (&page);
	} else if (page.dirty.roll == -1) {
		roll_up (&page, page.dirty.y0, page.dirty.y1);
	} else {
#endif
		for (row = page.dirty.y0; row <= page.dirty.y1; ++row)
			render (&page, row);
	}

	vbi_unref_page (&page);
}

static void
reset				(void)
{
	vbi_page page;
	vbi_bool success;
	int row;

	success = vbi_fetch_cc_page (vbi, &page, pgno, TRUE);
	assert (success);

	for (row = 0; row <= page.rows; ++row)
		render (&page, row);

	vbi_unref_page (&page);
}

/*
 *  X11 stuff
 */

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

		switch (event.type) {
		case KeyPress:
		{
			int c = XLookupKeysym (&event.xkey, 0);

			switch (c) {
			case 'q':
			case 'c':
				exit (EXIT_SUCCESS);

			case '1' ... '8':
				pgno = c - '1' + 1;
				reset ();
				return;

			case XK_F1 ... XK_F8:
				pgno = c - XK_F1 + 1;
				reset ();
				return;
			}

			break;
		}

		case Expose:
			XPutImage (display, window, gc, ximage,
				   0, 0, 0, 0, DISP_WIDTH, DISP_HEIGHT);
			break;

		case ClientMessage:
			exit (EXIT_SUCCESS);
		}
	}

	bump (step, TRUE);

	usleep (nap_usec / 4);
}

static vbi_bool
init_window			(int			ac,
				 char **		av)
{
	Atom delete_window_atom;
	XWindowAttributes wa;
	int i;

	ac = ac;
	av = av;

	if (!(display = XOpenDisplay (NULL))) {
		return FALSE;
	}

	screen = DefaultScreen (display);
	cmap = DefaultColormap (display, screen);
 
	window = XCreateSimpleWindow (display,
				      RootWindow (display, screen),
				      /* x, y */ 0, 0,
				      DISP_WIDTH, DISP_HEIGHT,
				      /* borderwidth */ 2,
				      /* foreground */ 0xffffffff,
				      /* background */ 0x00000000);
	if (!window) {
		return FALSE;
	}

	XGetWindowAttributes (display, window, &wa);
			
	if (16 != wa.depth) {
		fprintf (stderr, "Can only run at "
			 "color depth 16 (5:6:5) LE\n");
		return FALSE;
	}

	if (!(ximgdata = malloc (DISP_WIDTH * DISP_HEIGHT * 2))) {
		return FALSE;
	}

	for (i = 0; i < DISP_WIDTH * DISP_HEIGHT; ++i)
		ximgdata[i] = RGB565 (COLORKEY);

	ximage = XCreateImage(display,
			      DefaultVisual (display, screen),
			      DefaultDepth (display, screen),
			      ZPixmap, 0, (char *) ximgdata,
			      DISP_WIDTH, DISP_HEIGHT,
			      8, 0);
	if (!ximage) {
		return FALSE;
	}

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

	XSelectInput (display, window,
		      KeyPressMask |
		      ExposureMask |
		      StructureNotifyMask);
	XSetWMProtocols (display, window, &delete_window_atom, 1);
	XStoreName (display, window, "Caption Test - [Q], [F1]..[F8]");

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

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

	XPutImage (display, window, gc, ximage,
		   0, 0, 0, 0, DISP_WIDTH, DISP_HEIGHT);

	return TRUE;
}

/*
 *  Feed caption from a sample stream
 */

static void
pes_mainloop			(void)
{
	uint8_t buffer[2048];

	while (1 == fread (buffer, sizeof (buffer), 1, stdin)) {
		const uint8_t *bp;
		unsigned int bytes_left;

		bp = buffer;
		bytes_left = sizeof (buffer);

		while (bytes_left > 0) {
			vbi_sliced sliced[64];
			unsigned int n_lines;
			int64_t pts;

			n_lines = vbi_dvb_demux_cor (dx, sliced, 64,
						     &pts, &bp, &bytes_left);
			if (n_lines > 0) {
				vbi_decode (vbi, sliced, n_lines,
					    pts / 90000.0);
			}

			/* xevent (1e6 / 30); */
		}
	}

	fprintf (stderr, "\rEnd of stream\n");
}

static void
old_mainloop			(void)
{
	for (;;) {
		vbi_sliced sliced[40];
		double timestamp;
		int n_lines;

		n_lines = read_sliced (sliced, &timestamp, /* max_lines */ 40);
		if (n_lines < 0)
			break;

		vbi_decode (vbi, sliced, n_lines, timestamp);

		/* xevent (1e6 / 30); */
	}

	fprintf (stderr, "\rEnd of stream\n");
}

/*
 *  Feed artificial caption
 */

static void
cmd				(unsigned int		n)
{
	vbi_sliced sliced;
	static double time = 0.0;

	sliced.id = VBI_SLICED_CAPTION_525;
	sliced.line = 21;
	sliced.data[0] = vbi_par8 (n >> 8);
	sliced.data[1] = vbi_par8 (n & 0x7F);

	vbi_decode (vbi, &sliced, 1, time);

	xevent (33333);

	time += 1 / 29.97;
}

static void
printc				(int			c)
{
	cmd (c * 256 + 0x80);

	xevent (33333);
}

static void
prints				(const char *		s)
{
	for (; s[0] && s[1]; s += 2)
		cmd (s[0] * 256 + s[1]);

	if (s[0])
		cmd (s[0] * 256 + 0x80);

	xevent (33333);
}

enum {
	white, green, red, yellow, blue, cyan, magenta, black
};

static int
mapping_row [] = {
	2, 3, 4, 5,  10, 11, 12, 13, 14, 15,  0, 6, 7, 8, 9, -1
};

#define italic 7
#define underline 1
#define opaque 0
#define semi_transp 1

#define BACKG(bg, t)							\
	(cmd (0x2000), cmd (0x1020 + ((ch & 1) << 11) + (bg << 1) + t))
#define PREAMBLE(r, fg, u)						\
	cmd (0x1040 + ((ch & 1) << 11) + ((mapping_row[r] & 14) << 7)	\
	     + ((mapping_row[r] & 1) << 5) + (fg << 1) + u)
#define INDENT(r, fg, u)						\
	cmd (0x1050 + ((ch & 1) << 11) + ((mapping_row[r] & 14) << 7)	\
	     + ((mapping_row[r] & 1) << 5) + ((fg / 4) << 1) + u)
#define MIDROW(fg, u)	cmd (0x1120 + ((ch & 1) << 11) + (fg << 1) + u)
#define SPECIAL_CHAR(n)	cmd (0x1130 + ((ch & 1) << 11) + n)
#define CCODE(code, ch)	(code + ((ch & 1) << 11) + ((ch & 2) << 7))
#define RESUME_CAPTION	cmd (CCODE (0x1420, ch))
#define BACKSPACE	cmd (CCODE (0x1421, ch))
#define DELETE_EOR	cmd (CCODE (0x1424, ch))
#define ROLL_UP(rows)	cmd (CCODE (0x1425, ch) + rows - 2)
#define FLASH_ON	cmd (CCODE (0x1428, ch))
#define RESUME_DIRECT	cmd (CCODE (0x1429, ch))
#define TEXT_RESTART	cmd (CCODE (0x142A, ch))
#define RESUME_TEXT	cmd (CCODE (0x142B, ch))
#define END_OF_CAPTION	cmd (CCODE (0x142F, ch))
#define ERASE_DISPLAY	cmd (CCODE (0x142C, ch))
#define CR		cmd (CCODE (0x142D, ch))
#define ERASE_HIDDEN	cmd (CCODE (0x142E, ch))
#define TAB(t)		cmd (CCODE (0x1720, ch) + t)
#define TRANSP		(cmd (0x2000), cmd (0x172D + ((ch & 1) << 11)))
#define BLACK(u)	(cmd (0x2000), cmd (0x172E + ((ch & 1) << 11) + u))

static void
PAUSE				(unsigned int		n_frames)
{
	while (n_frames-- > 0)
		xevent (33333);
}

static void
hello_world			(void)
{
	int ch = 0;
	int i;

	pgno = -1;

	prints (" HELLO WORLD! ");
	PAUSE (30);

	ch = 4;
	TEXT_RESTART;
	prints ("Character set - Text 1");
	CR; CR;
	for (i = 32; i <= 127; i++) {
		printc (i);
		if ((i & 15) == 15)
			CR;
	}
	MIDROW (italic, 0);
	for (i = 32; i <= 127; i++) {
		printc (i);
		if ((i & 15) == 15)
			CR;
	}
	MIDROW (white, underline);
	for (i = 32; i <= 127; i++) {
		printc (i);
		if ((i & 15) == 15)
			CR;
	}
	MIDROW (white, 0);
	prints ("Special: ");
	for (i = 0; i <= 15; i++) {
		SPECIAL_CHAR (i);
	}
	CR;
	prints ("DONE - Text 1 ");
	PAUSE (50);

	ch = 5;
	TEXT_RESTART;
	prints ("Styles - Text 2");
	CR; CR;
	MIDROW (white, 0); prints ("WHITE"); CR;
	MIDROW (red, 0); prints ("RED"); CR;
	MIDROW (green, 0); prints ("GREEN"); CR;
	MIDROW (blue, 0); prints ("BLUE"); CR;
	MIDROW (yellow, 0); prints ("YELLOW"); CR;
	MIDROW (cyan, 0); prints ("CYAN"); CR;
	MIDROW (magenta, 0); prints ("MAGENTA"); BLACK (0); CR;
	BACKG (white, opaque); prints ("WHITE"); BACKG (black, opaque); CR;
	BACKG (red, opaque); prints ("RED"); BACKG (black, opaque); CR;
	BACKG (green, opaque); prints ("GREEN"); BACKG (black, opaque); CR;
	BACKG (blue, opaque); prints ("BLUE"); BACKG (black, opaque); CR;
	BACKG (yellow, opaque); prints ("YELLOW"); BACKG (black, opaque); CR;
	BACKG (cyan, opaque); prints ("CYAN"); BACKG (black, opaque); CR;
	BACKG (magenta, opaque); prints ("MAGENTA"); BACKG (black, opaque); CR;
	TRANSP;
	prints (" TRANSPARENT BACKGROUND ");
	BACKG (black, opaque); CR;
	MIDROW (white, 0); FLASH_ON;
	prints (" Flashing Text  (if implemented) "); CR;
	MIDROW (white, 0); prints ("DONE - Text 2 ");
	PAUSE (50);

	ch = 0;
	ROLL_UP (2);
	ERASE_DISPLAY;
	prints (" ROLL-UP TEST "); CR; PAUSE (20);
	prints (">> A young Jedi named Darth"); CR; PAUSE (20);
	prints ("Vader, who was a pupil of"); CR; PAUSE (20);
	prints ("mine until he turned to evil,"); CR; PAUSE (20);
	prints ("helped the Empire hunt down"); CR; PAUSE (20);
	prints ("and destroy the Jedi Knights."); CR; PAUSE (20);
	prints ("He betrayed and murdered your"); CR; PAUSE (20);
	prints ("father. Now the Jedi are all"); CR; PAUSE (20);
	prints ("but extinct. Vader was seduced"); CR; PAUSE (20);
	prints ("by the dark side of the Force."); CR; PAUSE (20);                        
	prints (">> The Force?"); CR; PAUSE (20);
	prints (">> Well, the Force is what gives"); CR; PAUSE (20);
	prints ("a Jedi his power. It's an energy"); CR; PAUSE (20);
	prints ("field created by all living"); CR; PAUSE (20);
	prints ("things."); CR; PAUSE (20);
	prints ("It surrounds us and penetrates"); CR; PAUSE (20);
	prints ("us."); CR; PAUSE (20);
	prints ("It binds the galaxy together."); CR; PAUSE (20);
	CR; PAUSE (30);
	prints (" DONE - Caption 1 ");
	PAUSE (30);

	ch = 1;
	RESUME_DIRECT;
	ERASE_DISPLAY;
	MIDROW (yellow, 0);
	INDENT (2, 10, 0); prints (" FOO "); CR;
	INDENT (3, 10, 0); prints (" MIKE WAS HERE "); CR; PAUSE (20);
	MIDROW (red, 0);
	INDENT (6, 13, 0); prints (" AND NOW... "); CR;
	INDENT (8, 13, 0); prints (" HE'S HERE "); CR; PAUSE (20);
	PREAMBLE (12, cyan, 0);
	prints ("01234567890123456789012345678901234567890123456789"); CR;
	MIDROW (white, 0);
	prints (" DONE - Caption 2 "); CR;
	PAUSE (30);
}

int
main				 (int			argc,
				 char **		argv)
{
	vbi_bool success;

	if  (!init_window (argc, argv))
		exit (EXIT_FAILURE);

	vbi = vbi_decoder_new ();
	assert (NULL != vbi);

	success = vbi_event_handler_add (vbi, VBI_EVENT_CAPTION,
					 cc_handler, /* used_data */ NULL);
	assert (success);

	if (isatty (STDIN_FILENO)) {
		hello_world ();
	} else {
		int c;

		pgno = 1;

		c = getchar ();
		ungetc (c, stdin);

		if (0 == c) {
			dx = vbi_dvb_pes_demux_new (/* callback */ NULL,
						    /* user_data */ NULL);
			assert (NULL != dx);

			pes_mainloop ();

			vbi_dvb_demux_delete (dx);
		} else {
			open_sliced_read (stdin);

			old_mainloop ();
		}
	}

	printf ("Done.\n");

	for (;;)
		xevent (33333);

	vbi_decoder_delete (vbi);

	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


syntax highlighted by Code2HTML, v. 0.9.1