#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <locale.h>

#include "vt.h"
#include "misc.h"
#include "fdset.h"
#include "vbi.h"
#include "lang.h"
#include "dllist.h"
#include "export.h"

int debug = 0;
static volatile int timed_out = 0;

struct req
{
    struct dl_node node[1];
    char *name;			// file name
    char *pgno_str;		// the pgno as given on the cmdline
    int pgno, subno;		// decoded pgno
    struct export *export;	// export data
    struct vt_page vtp[1];	// the capture page data
};



static void
usage(FILE *fp, int exitval)
{
    fprintf(fp, "\nUsage: %s [options] ppp[.ss]...\n", prgname);
    fprintf(fp,
	    "\n"
	    "  Valid options:\t\tDefault:\n"
	    "    --help\n"
	    "    --version\n"
	    "    -vbi <vbidev>\t\t/dev/vbi0\n"
	    "    -finetune <-4..4|auto>\t0\n"
	    //"    -oldbttv\t\t\t(for bttv <0.5.20)\n"
	    "    -charset latin-1/2/koi8-r/iso8859-7\tlatin-1\n"
	    "    -timeout <secs>\t\tnone\n"
	    "    -name <filename>\t\tttext-%%s.%%e\n"
	    "    -format <fmt[,options]>\tascii\n"
	    "    -format help\n"
	    "    -progname name\n"
	    "    -pid pid\n"
	    "\n"
	    "  ppp[.ss] stands for a page number and an\n"
	    "  optional subpage number (ie 123.4).  If\n"
	    "  the subpage number is omitted the first\n"
	    "  transmitted subpage is captured.\n"
	    "\n"
	    "\n"
	    "  To use the DVB interface, use\n"
	    "  -vbi /dev/dvb/adapter0/demux0\n"
	    "\n"
	    "  The -progname or -pid option specify\n"
	    "  the program or teletext PID if using\n"
	    "  the DVB interface.\n"
	);
    exit(exitval);
}

static void
exp_help(FILE *fp)
{
    struct export_module **ep;
    char **cp, c;

    fprintf(fp,
	    "\nSyntax: -format Name[,Options]\n"
	    "\n"
	    "    Name\tExt.\tOptions\n"
	    "    --------------------------------\n"
	);
    for (ep = modules; *ep; ep++)
    {
	fprintf(fp, "    %-7s\t.%-4s", (*ep)->fmt_name, (*ep)->extension);
	for (c = '\t', cp = (*ep)->options; cp && *cp; cp++, c = ',')
	    fprintf(fp, "%c%s", c, *cp);
	fprintf(fp, "\n");
    }
    fprintf(fp,
	    "\n"
	    "Common options: reveal,hide\n"
	    "\n"
	    "Example: -format ansi,reveal,bg=none\n"
	    "\n"
	);
    exit(0);
}


static void
sig_handler(int sig)
{
    if (debug)
	write(2, "*BREAK*\r\n", 9);
    timed_out = 1;
}


static int
arg_pgno(char *p, int *subno)
{
    char *end;
    int pgno;

    *subno = ANY_SUB;
    if (*p)
    {
	pgno = strtol(p, &end, 16);
	if ((*end == ':' || *end == '/' || *end == '.') && end[1])
	    *subno = strtol(end + 1, &end, 16);
	if (*end == 0)
	    if (pgno >= 0x100 && pgno <= 0x899)
		if (*subno == ANY_SUB || (*subno >= 0x00 && *subno <= 0x3f7f))
		    return pgno;
    }
    fatal("%s: invalid page number", p);
}


static int
option(int argc, char **argv, int *ind, char **arg)
{
    static struct { char *nam, *altnam; int arg; } opts[] = {
	{ "--help", "-h", 0 },
	{ "--version", "-v", 0 },
	{ "-copyright", "-©", 0 },
	{ "-debug", "--debug", 0 },
	{ "-vbi", "-dev", 1 },
	{ "-newbttv", "-new", 0 },
	{ "-oldbttv", "-old", 0 },
	{ "-finetune", "-f", 1 },
	{ "-charset", "-latin", 1 },
	{ "-format", "-fmt", 1 },
	{ "-name", "-o", 1 },
	{ "-timeout", "-t", 1 },
	{ "-progname", "-pn", 1 },
	{ "-pid", "--pid", 1 },
    };
    int i;

    if (*ind >= argc)
	return 0;

    *arg = argv[(*ind)++];
    for (i = 0; i < NELEM(opts); ++i)
	if (streq(*arg, opts[i].nam) || streq(*arg, opts[i].altnam))
	{
	    if (opts[i].arg)
		if (*ind < argc)
		    *arg = argv[(*ind)++];
		else
		    fatal("option %s requires an argument", *arg);
	    return i+1;
	}

    if (**arg == '-')
    {
	fatal("%s: invalid option", *arg);
	usage(stderr, 2);
    }

    return -1;
}



static void
event(struct dl_head *reqs, struct vt_event *ev)
{
    struct req *req, *nxt;

    switch (ev->type)
    {
	case EV_PAGE:	// new page
	{
	    struct vt_page *vtp = ev->p1;

	    for (req = PTR reqs->first; nxt = PTR req->node->next; req = nxt)
		if (req->pgno == vtp->pgno)
		    if (req->subno == ANY_SUB || req->subno == vtp->subno)
		    {
			if (debug)
			    printf("captured page %x.%02x\n", vtp->pgno,
								vtp->subno);
			*req->vtp = *vtp;
			dl_insert_last(reqs + 1, dl_remove(req->node));
			// the same page may be there in different formats.
			// so, don't break.
			//break;
		    }
	}
    }
}



int
main(int argc, char **argv)
{
    char *vbi_name = NULL;
    int fine_tune = 1; // auto = 999;
    int newbttv = -1;
    int timeout = 0;
    char *fname = "ttext-%s.%e";
    char *out_fmt = "ascii";
    struct export *fmt = 0;
    int opt, ind;
    char *arg;
    struct vbi *vbi;
    struct req *req;
    struct dl_head reqs[2];	// simple linear lists of requests & captures
    char *progname = NULL;
    int txtpid = -1;

    setlocale(LC_ALL, "");
    setprgname(argv[0]);

    fdset_init(fds);
    dl_init(reqs);	// the requests
    dl_init(reqs+1);	// the captured pages

    ind = 1;
    while (opt = option(argc, argv, &ind, &arg))
	switch (opt)
	{
	    case 1:	// help
		usage(stdout, 0);
		break;
	    case 2:	// version
		printf("AleVT Version "VERSION"\n");
		exit(0);
	    case 3:	// copyright
		printf("Copyright 2000 by E. Toernig, froese@gmx.de\n");
		exit(0);
	    case 4:	// debug
		debug++;
		break;
	    case 5:	// vbi
		vbi_name = arg;
		break;
	    case 6:	// newbttv
		newbttv = 1;
		break;
	    case 7:	// oldbttv
		newbttv = 0;
		break;
	    case 8:	// finetune
		if (streq(arg, "auto"))
		    fine_tune = 999;
		else
		    fine_tune = strtol(arg, 0, 10);
		break;
            case 9:    // charset
		if (streq(arg, "latin-1") || streq(arg, "1"))
		    latin1 = LATIN1;
		else if (streq(arg, "latin-2") || streq(arg, "2"))
		    latin1 = LATIN2;
		else if (streq(arg, "koi8-r") || streq(arg, "koi"))
		    latin1 = KOI8;
		else if (streq(arg, "iso8859-7") || streq(arg, "el"))
		    latin1 = GREEK;
		else
		    fatal("bad charset (not latin-1/2/koi8-r/iso8859-7)");
		break;
	    case 10:	// format
		if (streq(arg, "help") || streq(arg, "?") || streq(arg, "list"))
		    exp_help(stdout);
		out_fmt = arg;
		fmt = 0;
		break;
	    case 11:	// name
		fname = arg;
		break;
	    case 12:	// timeout
		timeout = strtol(arg, 0, 10);
		if (timeout < 1 || timeout > 999999)
		    fatal("bad timeout value", timeout);
		break;
  	    case 13:    // progname
		progname = arg;
		break;
  	    case 14:    // pid
		txtpid = strtoul(arg, NULL, 0);
		break;
	    case -1:	// non-option arg
		if (not fmt)
		    fmt = export_open(out_fmt);
		if (not fmt)
		    fatal("%s", export_errstr());
		if (not(req = malloc(sizeof(*req))))
		    out_of_mem(sizeof(*req));
		req->name = fname;
		req->pgno_str = arg;
		req->pgno = arg_pgno(arg, &req->subno);
		req->export = fmt;
		dl_insert_last(reqs, req->node);
		break;
	}

    if (dl_empty(reqs))
	fatal("no pages requested");

    // setup device
    if (not(vbi = vbi_open(vbi_name, 0, fine_tune, newbttv, progname, txtpid, 0)))
	fatal("cannot open %s", vbi_name);
    vbi_add_handler(vbi, event, reqs); // register event handler

    signal(SIGUSR1, sig_handler);
    signal(SIGALRM, sig_handler);
    if (timeout)
	alarm(timeout);

    // capture pages (moves requests from reqs[0] to reqs[1])
    while (not dl_empty(reqs) && not timed_out)
	if (fdset_select(fds, 30000) == 0)	// 30sec select time out
	{
	    error("no signal.");
	    break;
	}

    alarm(0);
    vbi_del_handler(vbi, event, reqs);
    vbi_close(vbi);
    
    if (not dl_empty(reqs))
	error("capture aborted.  some pages are missing.");

    for (req = PTR reqs[1].first; req->node->next; req = PTR req->node->next)
    {
	fname = export_mkname(req->export, req->name, req->vtp, req->pgno_str);
	if (not fname || export(req->export, req->vtp, fname))
	    error("error saving page %s: %s", req->pgno_str, export_errstr());
	if (fname)
	    free(fname);
    }

    exit(dl_empty(reqs) ? 0 : 1);
}
