#define _GNU_SOURCE
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <iconv.h>
#include <langinfo.h>
#include <stdarg.h>
#include <sys/ioctl.h>
#include <sys/poll.h>
#include "os.h"
#include "vt.h"
#include "misc.h"
#include "vbi.h"
#include "fdset.h"
#include "hamm.h"
#include "lang.h"

#ifdef USE_LIBZVBI
# include <libzvbi.h>
static vbi_capture      * pZvbiCapt;
static vbi_raw_decoder  * pZvbiRawDec;
static vbi_sliced       * pZvbiData;
static vbi_proxy_client * pProxy;
# define ZVBI_BUFFER_COUNT  10
# define ZVBI_TRACE          0
#endif

static int vbi_dvb_open(struct vbi *vbi, const char *vbi_name, const char *progname, int txtpid, int info);
static void dvb_handler(struct vbi *vbi, int fd);

#define FAC	(1<<16)		// factor for fix-point arithmetic

static u8 *rawbuf;		// one common buffer for raw vbi data.
static int rawbuf_size;		// its current size
static char *vbi_names[] = { "/dev/vbi", "/dev/vbi0", "/dev/video0", "/dev/dvb/adapter0/demux0", NULL }; // default device names if none was given at the command line

#ifndef USE_LIBZVBI

/***** bttv api *****/

#define BASE_VIDIOCPRIVATE	192
#define BTTV_VBISIZE		_IOR('v' , BASE_VIDIOCPRIVATE+8, int)

/***** v4l2 vbi-api *****/

#define V4L2_PIX_FMT_GREY		0x59455247
#define VIDIOC_G_FMT			_IOWR('V',  4, struct v4l2_format)

/* #include "/usr/src/linux/include/linux/videodev2.h" */

enum v4l2_buf_type {
	V4L2_BUF_TYPE_VIDEO_CAPTURE  = 1,
	V4L2_BUF_TYPE_VIDEO_OUTPUT   = 2,
	V4L2_BUF_TYPE_VIDEO_OVERLAY  = 3,
	V4L2_BUF_TYPE_VBI_CAPTURE    = 4,
	V4L2_BUF_TYPE_VBI_OUTPUT     = 5,
	V4L2_BUF_TYPE_PRIVATE        = 0x80,
};

struct v4l2_vbi_format
{
    u32 sampling_rate;		/* in 1 Hz */
    u32 offset;			/* sampling starts # samples after rising hs */
    u32 samples_per_line;
    u32 sample_format;		/* V4L2_VBI_SF_* */
    s32 start[2];
    u32 count[2];
    u32 flags;			/* V4L2_VBI_* */
    u32 reserved2;		/* must be zero */
};

struct v4l2_format
{
    enum v4l2_buf_type type;            /* V4L2_BUF_TYPE_* */
    union
    {
	void *alignment_dummy;
	struct v4l2_vbi_format vbi;	/*  VBI data  */
	u8 raw_data[200];		/* user-defined */
    } fmt;
};

/***** end of api definitions *****/
#endif  // USE_LIBZVBI


static void
out_of_sync(struct vbi *vbi)
{
    int i;

    // discard all in progress pages
    for (i = 0; i < 8; ++i)
	vbi->rpage[i].page->flags &= ~PG_ACTIVE;
}


// send an event to all clients

static void
vbi_send(struct vbi *vbi, int type, int i1, int i2, int i3, void *p1)
{
    struct vt_event ev[1];
    struct vbi_client *cl, *cln;

    ev->resource = vbi;
    ev->type = type;
    ev->i1 = i1;
    ev->i2 = i2;
    ev->i3 = i3;
    ev->p1 = p1;

    for (cl = PTR vbi->clients->first; cln = PTR cl->node->next; cl = cln)
	cl->handler(cl->data, ev);
}

static void
vbi_send_page(struct vbi *vbi, struct raw_page *rvtp, int page)
{
    struct vt_page *cvtp = 0;

    if (rvtp->page->flags & PG_ACTIVE)
    {
	if (rvtp->page->pgno % 256 != page)
	{
	    rvtp->page->flags &= ~PG_ACTIVE;
	    enhance(rvtp->enh, rvtp->page);
	    if (vbi->cache)
		cvtp = vbi->cache->op->put(vbi->cache, rvtp->page);
	    vbi_send(vbi, EV_PAGE, 0, 0, 0, cvtp ?: rvtp->page);
	}
    }
}

#ifndef USE_LIBZVBI
// fine tune pll
// this routines tries to adjust the sampling point of the decoder.
// it collects parity and hamming errors and moves the sampling point
// a 10th of a bitlength left or right.

#define PLL_SAMPLES	4	// number of err vals to collect
#define PLL_ERROR	4	// if this err val is crossed, readjust
//#define PLL_ADJUST	4	// max/min adjust (10th of bitlength)

static void
pll_add(struct vbi *vbi, int n, int err)
{
    if (vbi->pll_fixed)
	return;

    if (err > PLL_ERROR*2/3)	// limit burst errors
	err = PLL_ERROR*2/3;

    vbi->pll_err += err;
    vbi->pll_cnt += n;
    if (vbi->pll_cnt < PLL_SAMPLES)
	return;

    if (vbi->pll_err > PLL_ERROR)
    {
	if (vbi->pll_err > vbi->pll_lerr)
	    vbi->pll_dir = -vbi->pll_dir;
	vbi->pll_lerr = vbi->pll_err;

	vbi->pll_adj += vbi->pll_dir;
	if (vbi->pll_adj < -PLL_ADJUST || vbi->pll_adj > PLL_ADJUST)
	{
	    vbi->pll_adj = 0;
	    vbi->pll_dir = -1;
	    vbi->pll_lerr = 0;
	}

	if (debug)
	    printf("pll_adj = %2d\n", vbi->pll_adj);
    }
    vbi->pll_cnt = 0;
    vbi->pll_err = 0;
}

void
vbi_pll_reset(struct vbi *vbi, int fine_tune)
{
    vbi->pll_fixed = fine_tune >= -PLL_ADJUST && fine_tune <= PLL_ADJUST;

    vbi->pll_err = 0;
    vbi->pll_lerr = 0;
    vbi->pll_cnt = 0;
    vbi->pll_dir = -1;
    vbi->pll_adj = 0;
    if (vbi->pll_fixed)
	vbi->pll_adj = fine_tune;
    if (debug)
	if (vbi->pll_fixed)
	    printf("pll_reset (fixed@%d)\n", vbi->pll_adj);
	else
	    printf("pll_reset (auto)\n");
}

#else  // USE_LIBZVBI
void
vbi_pll_reset(struct vbi *vbi, int fine_tune)
{
}
static void
pll_add(struct vbi *vbi, int n, int err)
{
}
#endif  // USE_LIBZVBI

// process one videotext packet

static int
vt_line(struct vbi *vbi, u8 *p)
{
    struct vt_page *cvtp;
    struct raw_page *rvtp;
    int hdr, mag, mag8, pkt, i;
    int err = 0;

    hdr = hamm16(p, &err);
    if (err & 0xf000)
	return -4;

    mag = hdr & 7;
    mag8 = mag?: 8;
    pkt = (hdr >> 3) & 0x1f;
    p += 2;

    rvtp = vbi->rpage + mag;
    cvtp = rvtp->page;

    switch (pkt)
    {
	case 0:
	{
	    int b1, b2, b3, b4;

	    b1 = hamm16(p, &err);	// page number
	    b2 = hamm16(p+2, &err);	// subpage number + flags
	    b3 = hamm16(p+4, &err);	// subpage number + flags
	    b4 = hamm16(p+6, &err);	// language code + more flags
	    if (vbi->ppage->page->flags & PG_MAGSERIAL)
		vbi_send_page(vbi, vbi->ppage, b1);
	    vbi_send_page(vbi, rvtp, b1);

	    if (err & 0xf000)
		return 4;

	    cvtp->errors = (err >> 8) + chk_parity(p + 8, 32);;
	    cvtp->pgno = mag8 * 256 + b1;
	    cvtp->subno = (b2 + b3 * 256) & 0x3f7f;
	    cvtp->lang = "\0\4\2\6\1\5\3\7"[b4 >> 5] + (latin1==LATIN1 ? 0 : 8);
	    cvtp->flags = b4 & 0x1f;
	    cvtp->flags |= b3 & 0xc0;
	    cvtp->flags |= (b2 & 0x80) >> 2;
	    cvtp->lines = 1;
	    cvtp->flof = 0;
	    vbi->ppage = rvtp;

	    pll_add(vbi, 1, cvtp->errors);

	    conv2latin(p + 8, 32, cvtp->lang);
	    vbi_send(vbi, EV_HEADER, cvtp->pgno, cvtp->subno, cvtp->flags, p);

	    if (b1 == 0xff)
		return 0;

	    cvtp->flags |= PG_ACTIVE;
	    init_enhance(rvtp->enh);
	    memcpy(cvtp->data[0]+0, p, 40);
	    memset(cvtp->data[0]+40, ' ', sizeof(cvtp->data)-40);
	    return 0;
	}

	case 1 ... 24:
	{
	    pll_add(vbi, 1, err = chk_parity(p, 40));

	    if (~cvtp->flags & PG_ACTIVE)
		return 0;

	    cvtp->errors += err;
	    cvtp->lines |= 1 << pkt;
	    
	    conv2latin(p, 40, cvtp->lang);
	    memcpy(cvtp->data[pkt], p, 40);
	    return 0;
	}
	case 26:
	{
	    int d, t[13];

	    if (~cvtp->flags & PG_ACTIVE)
		return 0;

	    d = hamm8(p, &err);
	    if (err & 0xf000)
		return 4;

	    for (i = 0; i < 13; ++i)
		t[i] = hamm24(p + 1 + 3*i, &err);
	    if (err & 0xf000)
		return 4;

	    //printf("enhance on %x/%x\n", cvtp->pgno, cvtp->subno);
	    add_enhance(rvtp->enh, d, t);
	    return 0;
	}
	case 27:
	{
	    // FLOF data (FastText)
	    int b1,b2,b3,x;
	    
	    if (~cvtp->flags & PG_ACTIVE)
		return 0; // -1 flushes all pages.  we may never resync again :(

	    b1 = hamm8(p, &err);
	    b2 = hamm8(p + 37, &err);
	    if (err & 0xf000)
		return 4;
	    if (b1 != 0 || not(b2 & 8))
		return 0;

	    for (i = 0; i < 6; ++i)
	    {
		err = 0;
		b1 = hamm16(p+1+6*i, &err);
		b2 = hamm16(p+3+6*i, &err);
		b3 = hamm16(p+5+6*i, &err);
		if (err & 0xf000)
		    return 1;
		x = (b2 >> 7) | ((b3 >> 5) & 0x06);
		cvtp->link[i].pgno = ((mag ^ x) ?: 8) * 256 + b1;
		cvtp->link[i].subno = (b2 + b3 * 256) & 0x3f7f;
	    }
	    cvtp->flof = 1;
	    return 0;
	}
	case 30:
	{
	    if (mag8 != 8)
		return 0;

	    p[0] = hamm8(p, &err);		// designation code
	    p[1] = hamm16(p+1, &err);		// initial page
	    p[3] = hamm16(p+3, &err);		// initial subpage + mag
	    p[5] = hamm16(p+5, &err);		// initial subpage + mag
	    if (err & 0xf000)
		return 4;

	    err += chk_parity(p+20, 20);
	    conv2latin(p+20, 20, 0);

	    vbi_send(vbi, EV_XPACKET, mag8, pkt, err, p);
	    return 0;
	}
	default:
	    // unused at the moment...
	    //vbi_send(vbi, EV_XPACKET, mag8, pkt, err, p);
	    //printf ("pkt=%d ",pkt);
	    return 0;
    }
    return 0;
}



#ifndef USE_LIBZVBI
// process one raw vbi line

static int
vbi_line(struct vbi *vbi, u8 *p)
{
    u8 data[43], min, max;
    int dt[256], hi[6], lo[6];
    int i, n, sync, thr;
    int bpb = vbi->bpb;

    /* remove DC. edge-detector */
    for (i = vbi->soc; i < vbi->eoc; ++i)
	dt[i] = p[i+bpb/FAC] - p[i];	// amplifies the edges best.

    /* set barrier */
    for (i = vbi->eoc; i < vbi->eoc+16; i += 2)
	dt[i] = 100, dt[i+1] = -100;

    /* find 6 rising and falling edges */
    for (i = vbi->soc, n = 0; n < 6; ++n)
    {
	while (dt[i] < 32)
	    i++;
	hi[n] = i;
	while (dt[i] > -32)
	    i++;
	lo[n] = i;
    }
    if (i >= vbi->eoc)
	return -1;	// not enough periods found

    i = hi[5] - hi[1];	// length of 4 periods (8 bits)
    if (i < vbi->bp8bl || i > vbi->bp8bh)
	return -1;	// bad frequency

    /* AGC and sync-reference */
    min = 255, max = 0, sync = 0;
    for (i = hi[4]; i < hi[5]; ++i)
	if (p[i] > max)
	    max = p[i], sync = i;
    for (i = lo[4]; i < lo[5]; ++i)
	if (p[i] < min)
	    min = p[i];
    thr = (min + max) / 2;

    p += sync;

    /* search start-byte 11100100 */
    for (i = 4*bpb + vbi->pll_adj*bpb/10; i < 16*bpb; i += bpb)
	if (p[i/FAC] > thr && p[(i+bpb)/FAC] > thr) // two ones is enough...
	{
	    /* got it... */
	    memset(data, 0, sizeof(data));

	    for (n = 0; n < 43*8; ++n, i += bpb)
		if (p[i/FAC] > thr)
		    data[n/8] |= 1 << (n%8);

	    if (data[0] != 0x27)	// really 11100100? (rev order!)
		return -1;

	    if (i = vt_line(vbi, data+1))
		if (i < 0)
		    pll_add(vbi, 2, -i);
		else
		    pll_add(vbi, 1, i);
	    return 0;
	}
    return -1;
}
#endif  // USE_LIBZVBI


// called when new vbi data is waiting

static void
vbi_handler(struct vbi *vbi, int fd)
{
#ifndef USE_LIBZVBI
    int n, i;
    u32 seq;

    n = read(vbi->fd, rawbuf, vbi->bufsize);

    if (dl_empty(vbi->clients))
	return;

    if (n != vbi->bufsize)
	return;

    seq = *(u32 *)&rawbuf[n - 4];
    if (vbi->seq+1 != seq)
    {
	out_of_sync(vbi);
	if (seq < 3 && vbi->seq >= 3)
	    vbi_reset(vbi);
    }
    vbi->seq = seq;

    if (seq > 1)	// the first may contain data from prev channel
    {
	for (i = 0; i+vbi->bpl <= n; i += vbi->bpl)
	    vbi_line(vbi, rawbuf + i);
    }

#else  // USE_LIBZVBI
    double timestamp;
    struct timeval timeout;
    int  lineCount;
    int  line;
    int  res;

    timeout.tv_sec  = 0;
    timeout.tv_usec = 25000;
    res = vbi_capture_read_sliced(pZvbiCapt, pZvbiData, &lineCount, &timestamp, &timeout);
    if (res > 0)
    {
	for (line=0; line < lineCount; line++)
	{  // dump all TTX packets, even non-EPG ones
            if ((pZvbiData[line].id & VBI_SLICED_TELETEXT_B) != 0)
	    {
		vt_line(vbi, pZvbiData[line].data);
	    }
	}
    }
    else if (res < 0)
    {  // I/O error
    }
#endif  // USE_LIBZVBI
}



int
vbi_add_handler(struct vbi *vbi, void *handler, void *data)
{
    struct vbi_client *cl;

    if (not(cl = malloc(sizeof(*cl))))
	return -1;
    cl->handler = handler;
    cl->data = data;
    dl_insert_last(vbi->clients, cl->node);
    return 0;
}



void
vbi_del_handler(struct vbi *vbi, void *handler, void *data)
{
    struct vbi_client *cl;

    for (cl = PTR vbi->clients->first; cl->node->next; cl = PTR cl->node->next)
	if (cl->handler == handler && cl->data == data)
	{
	    dl_remove(cl->node);
	    break;
	}
    return;
}


#ifndef USE_LIBZVBI

static int
set_decode_parms(struct vbi *vbi, struct v4l2_vbi_format *p)
{
    double fs;		// sampling rate
    double bpb;		// bytes per bit
    int soc, eoc;	// start/end of clock run-in
    int bpl;		// bytes per line

    if (p->sample_format != V4L2_PIX_FMT_GREY)
    {
	error("v4l2: unsupported vbi data format 0x%08lx", p->sample_format);
	return -1;
    }

    // some constants from the standard:
    //   horizontal frequency			fh = 15625Hz
    //   teletext bitrate			ft = 444*fh = 6937500Hz
    //   teletext identification sequence	10101010 10101010 11100100
    //   13th bit of seq rel to falling hsync	12us -1us +0.4us
    // I search for the clock run-in (10101010 10101010) from 12us-1us-12.5/ft
    // (earliest first bit) to 12us+0.4us+3.5/ft (latest last bit)
    //   earlist first bit			tf = 12us-1us-12.5/ft = 9.2us
    //   latest last bit			tl = 12us+0.4us+3.5/ft = 12.9us
    //   total number of used bits		n = (2+1+2+40)*8 = 360

    bpl = p->samples_per_line;
    fs = p->sampling_rate;
    bpb = fs/6937500.0;
    soc = (int)(9.2e-6*fs) - (int)p->offset;
    eoc = (int)(12.9e-6*fs) - (int)p->offset;
    if (soc < 0)
	soc = 0;
    if (eoc > bpl - (int)(43*8*bpb))
	eoc = bpl - (int)(43*8*bpb);
    if (eoc - soc < (int)(16*bpb))
    {
	// line too short or offset too large or wrong sample_rate
	error("v4l2: broken vbi format specification");
	return -1;
    }
    if (eoc > 240)
    {
	// the vbi_line routine can hold max 240 values in its work buffer
	error("v4l2: unable to handle these sampling parameters");
	return -1;
    }

    vbi->bpb = bpb * FAC + 0.5;
    vbi->soc = soc;
    vbi->eoc = eoc;
    vbi->bp8bl = 0.97 * 8*bpb; // -3% tolerance
    vbi->bp8bh = 1.03 * 8*bpb; // +3% tolerance

    vbi->bpl = bpl;
    vbi->bufsize = bpl * (p->count[0] + p->count[1]);

    return 0;
}


static int
setup_dev(struct vbi *vbi)
{
    struct v4l2_format v4l2_format[1];
    struct v4l2_vbi_format *vbifmt = &v4l2_format->fmt.vbi;

    memset(&v4l2_format, 0, sizeof(v4l2_format));
    v4l2_format->type = V4L2_BUF_TYPE_VBI_CAPTURE;
    if (ioctl(vbi->fd, VIDIOC_G_FMT, v4l2_format) == -1
	|| v4l2_format->type != V4L2_BUF_TYPE_VBI_CAPTURE)
    {
	// not a v4l2 device.  assume bttv and create a standard fmt-struct.
	int size;

	vbifmt->sample_format = V4L2_PIX_FMT_GREY;
	vbifmt->sampling_rate = 35468950;
	vbifmt->samples_per_line = 2048;
	vbifmt->offset = 244;
	if ((size = ioctl(vbi->fd, BTTV_VBISIZE, 0)) == -1)
	{
	    // BSD or older bttv driver.
	    vbifmt->count[0] = 16;
	    vbifmt->count[1] = 16;
	}
	else if (size % 2048)
	{
	    error("broken bttv driver (bad buffer size)");
	    return -1;
	}
	else
	{
	    size /= 2048;
	    vbifmt->count[0] = size/2;
	    vbifmt->count[1] = size - size/2;
	}
    }

    if (set_decode_parms(vbi, vbifmt) == -1)
	return -1;

    if (vbi->bpl < 1 || vbi->bufsize < vbi->bpl || vbi->bufsize % vbi->bpl != 0)
    {
	error("strange size of vbi buffer (%d/%d)", vbi->bufsize, vbi->bpl);
	return -1;
    }

    // grow buffer if necessary
    if (rawbuf_size < vbi->bufsize)
    {
	if (rawbuf)
	    free(rawbuf);
	if (not(rawbuf = malloc(rawbuf_size = vbi->bufsize)))
	    out_of_mem(rawbuf_size); // old buffer already freed.  abort.
    }

    return 0;
}

#endif  // USE_LIBZVBI


struct vbi *
vbi_open(char *vbi_name, struct cache *ca, int fine_tune, int big_buf, const char *progname, int txtpid, int info)
{
    static int inited = 0;
    struct vbi *vbi;
#ifdef USE_LIBZVBI
    char * pErrStr;
    int services;
#endif
    
    if (vbi_name == NULL)
    {
        int i;
        char *tried_devices = NULL;
        char *old_tried_devices = NULL;
        for (i = 0; vbi_names[i] != NULL; i++)
        {
            vbi_name = vbi_names[i];

            // collect device names for the error message below
            if (old_tried_devices)
            {
                if (asprintf(&tried_devices, "%s, %s", old_tried_devices, vbi_name) < 0)
                    tried_devices = NULL;
                free(old_tried_devices);
            }
            else if (asprintf(&tried_devices, "%s", vbi_name) < 0)
                tried_devices = NULL;
            if (tried_devices == NULL)
                out_of_mem(-1);
            old_tried_devices = tried_devices;
            
            if (access(vbi_name, R_OK) != 0)
                continue;
            vbi = vbi_open(vbi_name, ca, fine_tune, big_buf, progname, txtpid, info);
            if (vbi != NULL)
            {
                if (tried_devices != NULL)
                    free(tried_devices);
                return vbi;
            }
        }
	
	error("could not open any of the standard devices (%s)", tried_devices);
        free(tried_devices);
	return NULL;
    }

    if (not inited)
	lang_init();
    inited = 1;

    if (not(vbi = malloc(sizeof(*vbi))))
    {
	error("out of memory");
	goto fail1;
    }

#ifndef USE_LIBZVBI
    if (!vbi_dvb_open(vbi, vbi_name, progname, txtpid, info)) {
	    vbi->cache = ca;

	    dl_init(vbi->clients);
	    vbi->seq = 0;
	    out_of_sync(vbi);
	    vbi->ppage = vbi->rpage;

	    //vbi_pll_reset(vbi, fine_tune);
	    fdset_add_fd(fds, vbi->fd, dvb_handler, vbi);
	    return vbi;
    }
    if ((vbi->fd = open(vbi_name, O_RDONLY)) == -1)
	    {
		    ioerror(vbi_name);
		    goto fail2;
	    }
	    
    if (big_buf != -1)
	    error("-oldbttv/-newbttv is obsolete.  option ignored.");
	    
    if (setup_dev(vbi) == -1)
	    goto fail3;

    vbi_pll_reset(vbi, fine_tune);
    vbi->seq = 0;

#else  // USE_LIBZVBI
    services = VBI_SLICED_TELETEXT_B;
    pErrStr = NULL;
    vbi->fd = -1;

    pProxy = vbi_proxy_client_create(vbi_name, "alevt", VBI_PROXY_CLIENT_NO_STATUS_IND, &pErrStr, ZVBI_TRACE);
    if (pProxy != NULL)
    {
       pZvbiCapt = vbi_capture_proxy_new(pProxy, ZVBI_BUFFER_COUNT, 0, &services, 0, &pErrStr);
       if (pZvbiCapt == NULL)
       {
          vbi_proxy_client_destroy(pProxy);
          pProxy = NULL;
       }
    }
    if (pZvbiCapt == NULL)
        pZvbiCapt = vbi_capture_v4l2_new(vbi_name, ZVBI_BUFFER_COUNT, &services, 0, &pErrStr, ZVBI_TRACE);
    if (pZvbiCapt == NULL)
        pZvbiCapt = vbi_capture_v4l_new(vbi_name, 0, &services, 0, &pErrStr, ZVBI_TRACE);

    if (pZvbiCapt != NULL)
    {
        pZvbiRawDec = vbi_capture_parameters(pZvbiCapt);
        if ((pZvbiRawDec != NULL) && ((services & VBI_SLICED_TELETEXT_B) != 0))
        {
            pZvbiData = malloc((pZvbiRawDec->count[0] + pZvbiRawDec->count[1]) * sizeof(*pZvbiData));

            vbi->fd = vbi_capture_fd(pZvbiCapt);;
        }
        else
            vbi_capture_delete(pZvbiCapt);
    }

    if (pErrStr != NULL)
    {
        fprintf(stderr, "libzvbi: %s\n", pErrStr);
        free(pErrStr);
    }

    if (vbi->fd == -1)
        goto fail2;
#endif

    vbi->cache = ca;

    dl_init(vbi->clients);
    out_of_sync(vbi);
    vbi->ppage = vbi->rpage;

    fdset_add_fd(fds, vbi->fd, vbi_handler, vbi);
    return vbi;

fail3:
    close(vbi->fd);
fail2:
    free(vbi);
fail1:
    return 0;
}



void
vbi_close(struct vbi *vbi)
{
    fdset_del_fd(fds, vbi->fd);
    if (vbi->cache)
	vbi->cache->op->close(vbi->cache);

#ifndef USE_LIBZVBI
    close(vbi->fd);

#else  // USE_LIBZVBI
    if (pZvbiData != NULL)
	free(pZvbiData);
    pZvbiData = NULL;

    if (pZvbiCapt != NULL)
    {
	vbi_capture_delete(pZvbiCapt);
       pZvbiCapt = NULL;
    }
    if (pProxy != NULL)
    {
       vbi_proxy_client_destroy(pProxy);
       pProxy = NULL;
    }
#endif

    free(vbi);
}


struct vt_page *
vbi_query_page(struct vbi *vbi, int pgno, int subno)
{
    struct vt_page *vtp = 0;

    if (vbi->cache)
	vtp = vbi->cache->op->get(vbi->cache, pgno, subno);
    if (vtp == 0)
    {
	// EV_PAGE will come later...
	return 0;
    }

    vbi_send(vbi, EV_PAGE, 1, 0, 0, vtp);
    return vtp;
}

void
vbi_reset(struct vbi *vbi)
{
    if (vbi->cache)
	vbi->cache->op->reset(vbi->cache);
    vbi_send(vbi, EV_RESET, 0, 0, 0, 0);

#ifdef USE_LIBZVBI
    if (pZvbiRawDec != NULL)
        vbi_raw_decoder_reset(pZvbiRawDec);
#endif
}



/*
 * Starting from here: DVB
 */

/* DVB API */
#include "dvb/dmx.h"
#include "dvb/frontend.h"
/*#include "dvb/sec.h"*/
#include "dvb/video.h"

static int dvb_get_table(int fd, u_int16_t pid, u_int8_t tblid, u_int8_t *buf, size_t bufsz)
{
        struct dmx_sct_filter_params sctFilterParams;
	struct pollfd pfd;
	int r;

	memset(&sctFilterParams, 0, sizeof(sctFilterParams));
        sctFilterParams.pid = pid;
        sctFilterParams.timeout = 10000;
        sctFilterParams.flags = DMX_ONESHOT | DMX_IMMEDIATE_START | DMX_CHECK_CRC;
        sctFilterParams.filter.filter[0] = tblid;
        sctFilterParams.filter.mask[0] = 0xff;
	if (ioctl(fd, DMX_SET_FILTER, &sctFilterParams)) {
		perror("DMX_SET_FILTER");
		return -1;
	}
	pfd.fd = fd;
	pfd.events = POLLIN;
	r = poll(&pfd, 1, 10000);
	if (r < 0) {
		perror("poll");
		goto out;
	}
	if (r > 0) {
		r = read(fd, buf, bufsz);
		if (r < 0) {
			perror("read");
			goto out;
		}
	}
 out:
	ioctl(fd, DMX_STOP, 0);
	return r;
}

static const char *dvb_charsets[22] = {
	"ISO-8859-1",	/* Latin/Western European alphabet */
	"ISO-8859-2",	/* Latin/Central and Eastern European alphabet */
	"ISO-8859-3",	/* Latin/Southern European alphabet */
	"ISO-8859-4",	/* Latin/Northern European alphabet */
	"ISO-8859-5",	/* Latin/Cyrillic alphabet */
	"ISO-8859-6",	/* Latin/Arabic alphabet */
	"ISO-8859-7",	/* Latin/Greek alphabet*/
	"ISO-8859-8",	/* Latin/Hebrew alphabet */
	"ISO-8859-9",	/* Latin/Turkish alphabet */
	"ISO-8859-10",	/* Latin/Nordic alphabet */
	"ISO-8859-11",	/* Latin/Thai alphabet */
	"ISO-8859-12",	/* - never published - */
	"ISO-8859-13",	/* Latin/Baltic alphabet */
	"ISO-8859-14",	/* Latin/Celtic alphabet */
	"ISO-8859-15",	/* Latin/Western European alphabet (incl. EUR) */
	NULL,
	"UTF-16",	/* Unicode */
	"EUC-KR",	/* Korean character set */
	"GB2312",	/* Simplified Chinese charater set */
	"GB18030",	/* Chinese coded character set for Unicode */
	"UTF-8",	/* UTF-8 encoding for Unicode */
	NULL
};

static size_t dvb_get_string(char *inbuf, size_t inbuf_size, char *outbuf, size_t outbuf_size)
{
	static char cs_out[64] = { 0 };
	const char *cs_in = dvb_charsets[0];	/* default according to DVB-SI Annex A */
	int cs_sel;
	iconv_t cd;
	size_t l, r;

	if ((outbuf == NULL) || (outbuf_size == 0)) {
		return 0;
	}

	/* determine input encoding */
	if ((inbuf_size > 0) && (inbuf[0] < 0x20)) {
		cs_sel = inbuf[0];
		switch (cs_sel) {
		case 0x1f:	/* registered encoding_type_id */
			if (inbuf_size < 2) {
				error("iconv_open: corrupt encoding id");
				return 0;
			}
			cs_sel = (cs_sel << 8) | inbuf[1];
			inbuf += 2;
			inbuf_size -= 2;
			break;
		case 0x10:	/* DVB-SI Annex A, table A.4 */
			if (inbuf_size < 3) {
				error("iconv_open: corrupt encoding id");
				return 0;
			}
			cs_sel = (inbuf[1] << 8) | inbuf[2];
			inbuf += 3;
			inbuf_size -= 3;
			break;
		default:	/* DVB-SI Annex A, table A.3 */
			if (cs_sel < 0x0d)
				cs_sel += 4;
			++inbuf;
			--inbuf_size;
		}
		if ((cs_sel > 0) && (cs_sel < 22)) {
			cs_in = dvb_charsets[cs_sel-1];
		} else {
			error("iconv_open: unknown encoding id (0x%x)", cs_sel);
			return 0;
		}
	}

	/* determine output encoding once from current locale */
	if (cs_out[0] == 0) {
		snprintf(cs_out, sizeof(cs_out), "%s//TRANSLIT", nl_langinfo(CODESET));
	}

	/* create conversion descriptor */
	if ((cd = iconv_open(cs_out, cs_in)) == (iconv_t)-1) {
		switch (errno) {
		case EINVAL:
			error("iconv_open: missing character set (%s)", cs_in);
			break;
		case ENOMEM:
			error("iconv_open: out of memory");
			break;
		default:
			perror("iconv_open");
		}
		return 0;
	}

	/* do the actual conversion */
	for (l = outbuf_size; inbuf_size > 0; r = outbuf_size - l) {
		if (iconv(cd, &inbuf, &inbuf_size, &outbuf, &l) == (size_t)-1) {
			switch (errno) {
			case EINVAL:
				error("iconv: incomplete character sequence (first byte: 0x%02x)", inbuf[0]);
				break;
			case EILSEQ:
				error("iconv: invalid character sequence (first byte: 0x%02x)", inbuf[0]);
				break;
			case E2BIG:
				error("iconv: output buffer too small for converted input");
				break;
			default:
				perror("iconv");
			}
			r = 0;
			break;
		}
	}
	if (l > 0) {
		memset(outbuf, 0, l);
	}

	/* free conversion descriptor */
	if (iconv_close(cd)) {
		perror("iconv_close");
		return 0;
	}

	/* return number of bytes written to outbuf */
	return r;
}

static const u_int8_t byterev8[256] = {
        0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 
        0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, 
        0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 
        0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, 
        0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 
        0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, 
        0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 
        0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, 
        0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 
        0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, 
        0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 
        0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, 
        0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 
        0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, 
        0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 
        0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, 
        0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 
        0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, 
        0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 
        0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, 
        0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 
        0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, 
        0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 
        0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, 
        0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 
        0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, 
        0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 
        0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, 
        0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 
        0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, 
        0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 
        0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff
};

static void dvb_handle_pes_payload(struct vbi *vbi, const u_int8_t *buf, unsigned int len)
{
	unsigned int p, i;
	u_int8_t data[42];

	if (buf[0] < 0x10 || buf[0] > 0x1f)
		return;  /* no EBU teletext data */
	for (p = 1; p < len; p += /*6 + 40*/ 2 + buf[p + 1]) {
#if 0
		printf("Txt Line:\n"
		       "  data_unit_id             0x%02x\n"
		       "  data_unit_length         0x%02x\n"
		       "  reserved_for_future_use  0x%01x\n"
		       "  field_parity             0x%01x\n"
		       "  line_offset              0x%02x\n"
		       "  framing_code             0x%02x\n"
		       "  magazine_and_packet_addr 0x%04x\n"
		       "  data_block               0x%02x 0x%02x 0x%02x 0x%02x\n",
		       buf[p], buf[p+1],
		       buf[p+2] >> 6,
		       (buf[p+2] >> 5) & 1,
		       buf[p+2] & 0x1f,
		       buf[p+3],
		       (buf[p+4] << 8) | buf[p+5],
		       buf[p+6], buf[p+7], buf[p+8], buf[p+9]);
#endif
		for (i = 0; i < sizeof(data); i++)
			data[i] = byterev8[buf[p+4+i]];
		/*
		 * note: we should probably check for missing lines and then
		 * call out_of_sync(vbi); and/or vbi_reset(vbi);
		 */
		vt_line(vbi, data);
	}
}

static unsigned int rawptr;

static void dvb_handler(struct vbi *vbi, int fd)
{
	/* PES packet start code prefix and stream_id == private_stream_1 */
	static const u_int8_t peshdr[4] = { 0x00, 0x00, 0x01, 0xbd };
	u_int8_t *bp;
	int n;
	unsigned int p, i, len;
        u_int16_t rpid;
        u_int32_t crc, crccomp;

	if (rawptr >= (unsigned int)rawbuf_size)
		rawptr = 0;
	n = read(vbi->fd, rawbuf + rawptr, rawbuf_size - rawptr);
	if (n <= 0)
		return;
	rawptr += n;
	if (rawptr < 6)
		return;
	if (memcmp(rawbuf, peshdr, sizeof(peshdr))) {
		bp = memmem(rawbuf, rawptr, peshdr, sizeof(peshdr));
		if (!bp)
			return;
		rawptr -= (bp - rawbuf);
		memmove(rawbuf, bp, rawptr);
		if (rawptr < 6)
			return;
	}
	len = (rawbuf[4] << 8) | rawbuf[5];
	if (len < 9) {
		rawptr = 0;
		return;
	}
	if (rawptr < len + 6)
		return;
	p = 9 + rawbuf[8];
#if 0
	for (i = 0; i < len - p; i++) {
		if (!(i & 15))
			printf("\n%04x:", i);
		printf(" %02x", rawbuf[p + i]);
	}
	printf("\n");
#endif
	if (!dl_empty(vbi->clients))
		dvb_handle_pes_payload(vbi, rawbuf + p, len - p);
	rawptr -= len;
	if (rawptr)
		memmove(rawbuf, rawbuf + len, rawptr);
}

static int vbi_dvb_open(struct vbi *vbi, const char *vbi_name, const char *progname, int txtpid, int info)
{
	struct {
		u_int16_t pmtpid;
		u_int16_t txtpid;
		u_int16_t service_id;
		u_int8_t service_type;
		char service_provider_name[64];
		char service_name[64];
		u_int8_t txtlang[3];
		u_int8_t txttype;
		u_int8_t txtmagazine;
		u_int8_t txtpage;
	} progtbl[128], *progp;
	u_int8_t tbl[4096];
	int r;
	unsigned int i, j, k, l, progcnt = 0;
	struct dmx_pes_filter_params filterpar;

	/* open DVB demux device */
	if (!vbi_name)
		vbi_name = "/dev/dvb/adapter0/demux0";
	if ((vbi->fd = open(vbi_name, O_RDWR)) == -1) {
		error("cannot open demux device %s", vbi_name);
		return -1;
	}
	memset(progtbl, 0, sizeof(progtbl));
	if (txtpid >= 0x15 && txtpid < 0x1fff) {
		vbi->txtpid = txtpid;
		if (info) {
			printf("Using command line specified teletext PID 0x%x\n", vbi->txtpid);
		}
		goto txtpidfound;
	}

	/* parse PAT to enumerate services and to find the PMT PIDs */
	r = dvb_get_table(vbi->fd, 0, 0, tbl, sizeof(tbl));
	if (r == -1)
		goto outerr;
	if (r < 12) {
		error("PAT too short\n");
		goto outerr;
	}
	if (!(tbl[5] & 1)) {
		error("PAT not active (current_next_indicator == 0)");
		goto outerr;
	}
	if (tbl[6] != 0 || tbl[7] != 0) {
		error("PAT has multiple sections");
		goto outerr;
	}
	r -= 12; // header+CRC
	for (i = 0; i < (unsigned)r; i += 4) {
		if (progcnt >= sizeof(progtbl)/sizeof(progtbl[0])) {
			error("Program table overflow");
			goto outerr;
		}
		progtbl[progcnt].service_id = (tbl[8 + i] << 8) | tbl[9 + i];
		if (!progtbl[progcnt].service_id)  /* this is the NIT pointer */
			continue;
		progtbl[progcnt].pmtpid = ((tbl[10 + i] << 8) | tbl[11 + i]) & 0x1fff;
		progcnt++;
	}

	/* find the SDT to get the station names */
	r = dvb_get_table(vbi->fd, 0x11, 0x42, tbl, sizeof(tbl));
	if (r == -1)
		goto outerr;
	if (r < 15) {
		error("SDT too short\n");
		goto outerr;
	}
	if (!(tbl[5] & 1)) {
		error("SDT not active (current_next_indicator == 0)");
		goto outerr;
	}
	if (tbl[6] != 0 || tbl[7] != 0) {
		error("SDT has multiple sections");
		goto outerr;
	}
	r -= 4; // CRC
	i = 11;
	while (i < (unsigned)r - 1) {
		k = (tbl[i] << 8) | tbl[i+1]; /* service ID */
		progp = NULL;
		for (j = 0; j < progcnt; j++)
			if (progtbl[j].service_id == k) {
				progp = &progtbl[j];
				break;
			}
		j = i + 5;
		i = j + (((tbl[i+3] << 8) | tbl[i+4]) & 0x0fff);
		if (!progp) {
			error("SDT: service_id 0x%x not in PAT\n", k);
			continue;
		}
		while (j < i) {
			switch (tbl[j]) {
			case 0x48:  /* service descriptor */
				progp->service_type = tbl[j+2];
				k = j + 3;
				dvb_get_string(tbl + k + 1, tbl[k], progp->service_provider_name, sizeof(progp->service_provider_name));
				k += tbl[k] + 1;
				dvb_get_string(tbl + k + 1, tbl[k], progp->service_name, sizeof(progp->service_name));
				break;
			}
			j += 2 + tbl[j + 1];
		}
	}

	/* parse PMT's to find Teletext Services */
	for (l = 0; l < progcnt; l++) {
		progtbl[l].txtpid = 0x1fff;
		switch (progtbl[l].service_type) {
		case 0x01:	// digital television service
		case 0x03:	// Teletext service
		case 0x11:	// MPEG-2 HD digital television service
		case 0x16:	// H.264/AVC SD digital television service
		case 0x19:	// H.264/AVC HD digital television service
		case 0x1c:	// H.264/AVC frame compatible plano-stereoscopic HD digital television service
		case 0x1f:	// HEVC digital television service
		case 0x20:	// HEVC digital television service with HDR and/or a frame rate of 100 Hz, 120 000/1 001 Hz, or 120 Hz
			break;
		default:
			continue;	/* service is not digital TV */
		}
		if (progtbl[l].pmtpid < 0x15 ||        /* PMT PID sanity check */
		    progtbl[l].pmtpid >= 0x1fff)
			continue;
		r = dvb_get_table(vbi->fd, progtbl[l].pmtpid, 0x02, tbl, sizeof(tbl));
		if (r == -1)
			goto outerr;
		if (r < 16) {
			error("PMT pid 0x%x too short\n", progtbl[l].pmtpid);
			goto outerr;
		}
		if (!(tbl[5] & 1)) {
			error("PMT pid 0x%x not active (current_next_indicator == 0)", progtbl[l].pmtpid);
			goto outerr;
		}
		if (tbl[6] != 0 || tbl[7] != 0) {
			error("PMT pid 0x%x has multiple sections", progtbl[l].pmtpid);
			goto outerr;
		}
		r -= 4; // CRC
		i = 12 + (((tbl[10] << 8) | tbl[11]) & 0x0fff); /* skip program info section */
		while (i < (unsigned)r - 1) {
			j = i + 5;
			i = j + (((tbl[i + 3] << 8) | tbl[i + 4]) & 0x0fff);
			if (tbl[j - 5] != 0x06)   /* teletext streams have type 0x06 */
				continue;
			k = ((tbl[j - 4] << 8) | tbl[j - 3]) & 0x1fff;  /* elementary PID - save until we know if it's the teletext PID */
			while (j < i) {
				switch (tbl[j]) {
				case 0x56:  /* EBU teletext descriptor */
					progtbl[l].txtlang[0] = tbl[j + 2];
					progtbl[l].txtlang[1] = tbl[j + 3];
					progtbl[l].txtlang[2] = tbl[j + 4];
					progtbl[l].txttype = tbl[j + 5] >> 3;
					progtbl[l].txtmagazine = tbl[j + 5] & 7;
					progtbl[l].txtpage = tbl[j + 6];
					progtbl[l].txtpid = k;
					break;
				}
				j += 2 + tbl[j + 1];
			}
		}
	}

	if(info) {
		for (i = 0; i < progcnt; i++) {
			printf("Service ID 0x%04x Type 0x%02x Provider Name \"%s\" Name \"%s\"\n"
			       "  PMT PID 0x%04x TXT: PID 0x%04x lang %.3s type 0x%02x magazine %1u page %3u\n", 
			       progtbl[i].service_id, progtbl[i].service_type, progtbl[i].service_provider_name,
			       progtbl[i].service_name, progtbl[i].pmtpid, progtbl[i].txtpid, progtbl[i].txtlang,
			       progtbl[i].txttype, progtbl[i].txtmagazine, progtbl[i].txtpage);
		}
	}
	progp = NULL;
	if (progname) {
		j = strlen(progname);
		for (i = 0; i < progcnt; i++)
			if (!strncmp(progtbl[i].service_name, progname, j) && progtbl[i].txtpid != 0x1fff) {
				progp = &progtbl[i];
				break;
			}
	}
	if (progname && !progp) {
		j = strlen(progname);
		for (i = 0; i < progcnt; i++)
			if (!strncasecmp(progtbl[i].service_name, progname, j) && progtbl[i].txtpid != 0x1fff) {
				progp = &progtbl[i];
				break;
			}
	}
	if (!progp) {
		for (i = 0; i < progcnt; i++)
			if (progtbl[i].txtpid != 0x1fff) {
				progp = &progtbl[i];
				break;
			}
	}
	if (!progp) {
		error("No Teletext service found\n");
		goto outerr;
	}

	if(info) {
		printf("Using: Service ID 0x%04x Type 0x%02x Provider Name \"%s\" Name \"%s\"\n"
			"  PMT PID 0x%04x TXT: PID 0x%04x lang %.3s type 0x%02x magazine %1u page %3u\n", 
			progp->service_id, progp->service_type, progp->service_provider_name,
			progp->service_name, progp->pmtpid, progp->txtpid, progp->txtlang,
			progp->txttype, progp->txtmagazine, progp->txtpage);
	}
	
	vbi->txtpid = progp->txtpid;
 txtpidfound:
	rawbuf = malloc(rawbuf_size = 8192);
	if (!rawbuf)
		goto outerr;
	rawptr = 0;
#if 0
	close(vbi->fd);
	if ((vbi->fd = open(vbi_name, O_RDWR)) == -1) {
		error("cannot open demux device %s", vbi_name);
		return -1;
	}
#endif
	memset(&filterpar, 0, sizeof(filterpar));
	filterpar.pid = vbi->txtpid;
        filterpar.input = DMX_IN_FRONTEND;
        filterpar.output = DMX_OUT_TAP;
        filterpar.pes_type = DMX_PES_OTHER;
        filterpar.flags = DMX_IMMEDIATE_START;
        if (ioctl(vbi->fd, DMX_SET_PES_FILTER, &filterpar) < 0) {
                error("ioctl: DMX_SET_PES_FILTER %s (%u)", strerror(errno), errno);
                goto outerr;
        }
	return 0;

 outerr:
	close(vbi->fd);
	vbi->fd = -1;
	return -1;
}

struct vbi *open_null_vbi(struct cache *ca)
{
    static int inited = 0;
    struct vbi *vbi;

    if (not inited)
    	lang_init();
    inited = 1;

    vbi = malloc(sizeof(*vbi));
    if (!vbi)
    {
		error("out of memory");
		goto fail1;
    }

    vbi->fd = open("/dev/null", O_RDONLY);
	if (vbi->fd == -1)
	{
		error("cannot open null device");
		goto fail2;
	}

    vbi->txtpid = -1;

    vbi->bpb = 0;
    vbi->soc = 0;
    vbi->eoc = 0;
    vbi->bp8bl = 0;
    vbi->bp8bh = 0;

    vbi->bpl = 1;

    vbi->bufsize = 8;
    rawbuf_size = vbi->bufsize;
	rawbuf = malloc(rawbuf_size);
	if (!rawbuf)
		goto fail3;
	rawptr = 0;

    vbi->cache = ca;

    dl_init(vbi->clients);
    vbi->seq = 0;
    out_of_sync(vbi);
    vbi->ppage = vbi->rpage;

    // fdset_add_fd(fds, vbi->fd, vbi_handler, vbi);
    return vbi;

fail3:
    close(vbi->fd);
fail2:
    free(vbi);
fail1:
    return 0;
}

void send_errmsg(struct vbi *vbi, char *errmsg, ...)
{
	va_list args;

	if (errmsg == NULL || *errmsg == '\0')
		return;

	va_start(args, errmsg);
	char *buff = NULL;
	if (vasprintf(&buff, errmsg, args) < 0)
		buff = NULL;
	va_end(args);

	if(buff == NULL)
		out_of_mem(-1);

	vbi_send(vbi, EV_ERR, 0, 0, 0, buff);
}
