/*
 * Copyright (c) 2011-2012 - Mauro Carvalho Chehab
 *
 * 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 version 2
 * of the License.
 *
 * 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
 * Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 *
 */

#include "libdvbv5/dvb-file.h"
#include "libdvbv5/dvb-dev.h"
#include <config.h>
#include <argp.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

#ifdef ENABLE_NLS
# define _(string) gettext(string)
# include "gettext.h"
# include <locale.h>
# include <langinfo.h>
# include <iconv.h>
#else
# define _(string) string
#endif

# define N_(string) string

#define PROGRAM_NAME	"dvb-fe-tool"

const char *argp_program_version = PROGRAM_NAME " version " V4L_UTILS_VERSION;
const char *argp_program_bug_address = "Mauro Carvalho Chehab <mchehab@kernel.org>";

static const char doc[] = N_(
	"\nA DVB frontend tool using API version 5\n"
	"\nOn the options below, the arguments are:\n"
	"  ADAPTER      - the dvb adapter to control\n"
	"  FRONTEND     - the dvb frontend to control\n"
	"  SERVER       - server address which is running the dvb5-daemon\n"
	"  PORT         - server port used by the dvb5-daemon\n");

static const struct argp_option options[] = {
	{"verbose",	'v',	0,		0,	N_("enables debug messages"), 0},
	{"adapter",	'a',	N_("ADAPTER"),	0,	N_("dvb adapter"), 0},
	{"frontend",	'f',	N_("FRONTEND"),	0,	N_("dvb frontend"), 0},
	{"set-delsys",	'd',	N_("PARAMS"),	0,	N_("set delivery system"), 0},
	{"femon",	'm',	0,		0,	N_("monitors frontend stats on an streaming frontend"), 0},
	{"acoustical",	'A',	0,		0,	N_("beeps if signal quality is good. Also enables femon mode. Please notice that console beep should be enabled on your wm."), 0},
#if 0 /* Currently not implemented */
	{"set",		's',	N_("PARAMS"),	0,	N_("set frontend"), 0},
#endif
	{"get",		'g',	0,		0,	N_("get frontend"), 0},
	{"server",	'H',	N_("SERVER"),	0, 	N_("dvbv5-daemon host IP address"), 0},
	{"tcp-port",	'T',	N_("PORT"),	0, 	N_("dvbv5-daemon host tcp port"), 0},
	{"device-mon",	'D',	0,		0,	N_("monitors device insert/removal"), 0},
	{"count",	'c',	N_("COUNT"),	0,	N_("samples to take (default 0 = infinite)"), 0},
	{"help",        '?',	0,		0,	N_("Give this help list"), -1},
	{"usage",	-3,	0,		0,	N_("Give a short usage message")},
	{"version",	'V',	0,		0,	N_("Print program version"), -1},
	{ 0, 0, 0, 0, 0, 0 }
};

static int adapter = 0;
static int frontend = 0;
static unsigned get = 0;
static char *set_params = NULL;
static char *server = NULL;
static unsigned port = 0;
static int verbose = 0;
static int delsys = 0;
static int femon = 0;
static int acoustical = 0;
static int timeout_flag = 0;
static int device_mon = 0;
static int count = 0;

static void do_timeout(int x)
{
	(void)x;

	if (timeout_flag == 0) {
		timeout_flag = 1;
		alarm(2);
		signal(SIGALRM, do_timeout);
	} else {
		/* something has gone wrong ... exit */
		exit(1);
	}
}

#define ERROR(x...)                                                     \
	do {                                                            \
		fprintf(stderr, _("ERROR: "));                             \
		fprintf(stderr, x);                                     \
		fprintf(stderr, "\n");                                 \
	} while (0)


static error_t parse_opt(int k, char *arg, struct argp_state *state)
{
	switch (k) {
	case 'a':
		adapter = atoi(arg);
		break;
	case 'f':
		frontend = atoi(arg);
		break;
	case 'd':
		delsys = dvb_parse_delsys(arg);
		if (delsys < 0)
			return ARGP_ERR_UNKNOWN;
		break;
	case 'm':
		femon++;
		break;
	case 'A':
		femon++;
		acoustical++;
		break;
#if 0
	case 's':
		set_params = arg;
		break;
#endif
	case 'g':
		get++;
		break;
	case 'D':
		device_mon++;
		break;
	case 'H':
		server = arg;
		break;
	case 'T':
		port = atoi(arg);
		break;
	case 'v':
		verbose	++;
		break;
	case 'c':
		count = atoi(arg);
		break;
	case '?':
		argp_state_help(state, state->out_stream,
				ARGP_HELP_SHORT_USAGE | ARGP_HELP_LONG
				| ARGP_HELP_DOC);
		fprintf(state->out_stream, _("\nReport bugs to %s.\n"), argp_program_bug_address);
		exit(0);
	case 'V':
		fprintf (state->out_stream, "%s\n", argp_program_version);
		exit(0);
	case -3:
		argp_state_help(state, state->out_stream, ARGP_HELP_USAGE);
		exit(0);
	default:
		return ARGP_ERR_UNKNOWN;
	}
	return 0;
}

static struct argp argp = {
	.options = options,
	.parser = parse_opt,
	.doc = doc,
};

static int print_frontend_stats(FILE *fd,
				struct dvb_v5_fe_parms *parms)
{
	char buf[512], *p;
	int rc, i, len, show, n_status_lines = 0;

	rc = dvb_fe_get_stats(parms);
	if (rc) {
		ERROR(_("dvb_fe_get_stats failed"));
		return -1;
	}

	p = buf;
	len = sizeof(buf);
	dvb_fe_snprintf_stat(parms,  DTV_STATUS, NULL, 0, &p, &len, &show);

	for (i = 0; i < MAX_DTV_STATS; i++) {
		show = 1;

		dvb_fe_snprintf_stat(parms, DTV_QUALITY, _("Quality"),
				     i, &p, &len, &show);

		dvb_fe_snprintf_stat(parms, DTV_STAT_SIGNAL_STRENGTH, _("Signal"),
				     i, &p, &len, &show);

		dvb_fe_snprintf_stat(parms, DTV_STAT_CNR, _("C/N"),
				     i, &p, &len, &show);

		dvb_fe_snprintf_stat(parms, DTV_STAT_ERROR_BLOCK_COUNT, _("UCB"),
				     i,  &p, &len, &show);

		dvb_fe_snprintf_stat(parms, DTV_BER, _("postBER"),
				     i,  &p, &len, &show);

		dvb_fe_snprintf_stat(parms, DTV_PRE_BER, _("preBER"),
				     i,  &p, &len, &show);

		dvb_fe_snprintf_stat(parms, DTV_PER, _("PER"),
				     i,  &p, &len, &show);
		if (p != buf) {
			if (isatty(fileno(fd))) {
				enum dvb_quality qual;
				int color;

				qual = dvb_fe_retrieve_quality(parms, 0);

				switch (qual) {
				case DVB_QUAL_POOR:
					color = 31;
					break;
				case DVB_QUAL_OK:
					color = 36;
					break;
				case DVB_QUAL_GOOD:
					color = 32;
					break;
				case DVB_QUAL_UNKNOWN:
				default:
					color = 0;
					break;
				}
				fprintf(fd, "\033[%dm", color);
				/*
				 * It would be great to change the BELL
				 * tone depending on the quality. The legacy
				 * femon used to to that, but this doesn't
				 * work anymore with modern Linux distros.
				 *
				 * So, just print a bell if quality is good.
				 *
				 * The console audio should be enabled
				 * at the window manater for this to
				 * work.
				 */
				if (acoustical) {
					if (qual == DVB_QUAL_GOOD)
						fprintf(fd, "\a");
				}
			}

			if (n_status_lines)
				fprintf(fd, "\t%s\n", buf);
			else
				fprintf(fd, "%s\n", buf);

			n_status_lines++;

			p = buf;
			len = sizeof(buf);
		}
	}

	fflush(fd);

	return 0;
}

static void get_show_stats(struct dvb_v5_fe_parms *parms)
{
	int rc;

	signal(SIGTERM, do_timeout);
	signal(SIGINT, do_timeout);

	do {
		rc = dvb_fe_get_stats(parms);
		if (!rc)
			print_frontend_stats(stderr, parms);
		if (count > 0 && !--count)
			break;
		if (!timeout_flag)
			usleep(1000000);
	} while (!timeout_flag);
}

static const char * const event_type[] = {
	[DVB_DEV_ADD] = "added",
	[DVB_DEV_CHANGE] = "changed",
	[DVB_DEV_REMOVE] = "removed",
};

static int dev_change_monitor(char *sysname,
			       enum dvb_dev_change_type type, void *user_priv)
{
	if (type >= ARRAY_SIZE(event_type))
		printf("unknown event on device %s\n", sysname);
	else
		printf("device %s was %s\n", sysname, event_type[type]);
	free(sysname);

	return 0;
}

int main(int argc, char *argv[])
{
	struct dvb_device *dvb;
	struct dvb_dev_list *dvb_dev;
	struct dvb_v5_fe_parms *parms;
	int ret, fe_flags = O_RDWR;

#ifdef ENABLE_NLS
	setlocale (LC_ALL, "");
	bindtextdomain (PACKAGE, LOCALEDIR);
	textdomain (PACKAGE);
#endif

	if (argp_parse(&argp, argc, argv, ARGP_NO_HELP | ARGP_NO_EXIT, 0, 0)) {
		argp_help(&argp, stderr, ARGP_HELP_SHORT_USAGE, PROGRAM_NAME);
		return -1;
	}

	/*
	 * If called without any option, be verbose, to print the
	 * DVB frontend information.
	 */
	if (!get && !delsys && !set_params && !femon)
		verbose++;

	if (!delsys && !set_params)
		fe_flags = O_RDONLY;

	dvb = dvb_dev_alloc();
	if (!dvb)
		return -1;

	if (server && port) {
		printf(_("Connecting to %s:%d\n"), server, port);
		ret = dvb_dev_remote_init(dvb, server, port);
		if (ret < 0)
			return -1;
	}

	dvb_dev_set_log(dvb, verbose, NULL);
	if (device_mon) {
		dvb_dev_find(dvb, &dev_change_monitor, NULL);
		while (1) {
			usleep(1000000);
		}
	}
	dvb_dev_find(dvb, NULL, NULL);
	parms = dvb->fe_parms;

	dvb_dev = dvb_dev_seek_by_adapter(dvb, adapter, frontend,
					  DVB_DEVICE_FRONTEND);
	if (!dvb_dev)
		return -1;

	if (!dvb_dev_open(dvb, dvb_dev->sysname, fe_flags))
		return -1;

	if (delsys) {
		printf(_("Changing delivery system to: %s\n"),
			delivery_system_name[delsys]);
		dvb_set_sys(parms, delsys);
		goto ret;
	}

#if 0
	if (set_params)
		do_something();
#endif
	if (get) {
		dvb_fe_get_parms(parms);
		dvb_fe_prt_parms(parms);
	}

	if (femon)
		get_show_stats(parms);

ret:
	dvb_dev_free(dvb);

	return 0;
}
