#!/usr/bin/perl


##~#~~#~~~#~~~~#~~~~~#~~~~~~#~~~~~~~#~~~~~~~~#~~~~~~~~~#~~~~~~~~~~~~#
# Name: iradio                                                       #
# Description: Simple command line radio player with support of      #
#              LIRC. Supports only PVR TV cards based on IVTV driver.#
# Dependencies: ivtv-radio, v4l2-ctl, aplay                          #
# Author: Jiri Tyr                                                   #
# Last update: 28.09.2008                                            #
# License: GPL                                                       #
##~#~~#~~~#~~~~#~~~~~#~~~~~~#~~~~~~~#~~~~~~~~#~~~~~~~~~#~~~~~~~~~~~~#


use strict;
use warnings;
use Env qw(HOME);
use Getopt::Long;
use Lirc::Client;
use XML::Simple;


my $VERSION = '0.1.3';


# global variables
my ($device, $channel, $frequency, $tune, $lirc, $lircdev, $rcfile, $debug, $volume, $version, $enablexosd, $help);


my $config_path = $ENV{'HOME'}.'/.ivtv';
my $config_file = $config_path.'/iradio.xml';
my $system_config_file = '/etc/ivtv/iradio.xml';

# check if exists configuration
unless (-e $config_path) {
	mkdir $config_path, 0755;
}
if (not -e $config_file and -e $system_config_file) {
	$config_file = $system_config_file;
} elsif (not -e $config_file) {
	&message('E', 'No config file!');
	exit 1;
}

# read configuration
my $xml = XMLin(
	$config_file,
	'KeyAttr'	=> '-name',
	'ForceArray'	=> ['channel'],
);

# check if there are some channels
if (scalar @{$xml->{'channel-list'}->{'channel'}} == 0) {
	&message('E', 'No channel specified!');
	exit 1;
}

# load setting from XML file
my $xmls = $xml->{'setting'};
if (exists $xmls->{'device'} and length $xmls->{'device'} > 0) {
	$device = $xmls->{'device'};
}
if (exists $xmls->{'default-channel'} and length $xmls->{'default-channel'} > 0) {
	$channel = $xmls->{'default-channel'};
}
if (exists $xmls->{'lirc'} and length $xmls->{'lirc'} > 0) {
	$lirc = $xmls->{'lirc'};
}
if (exists $xmls->{'lircdev'} and length $xmls->{'lircdev'} > 0) {
	$lircdev = $xmls->{'lircdev'};
}
if (exists $xmls->{'rcfile'} and length $xmls->{'rcfile'} > 0) {
	$rcfile = $xmls->{'rcfile'};
}
if (exists $xmls->{'debug'} and length $xmls->{'debug'} > 0) {
	$debug = $xmls->{'debug'};
}
if (exists $xmls->{'volume'} and length $xmls->{'volume'} > 0) {
	$volume = $xmls->{'volume'};
}
if (exists $xmls->{'xosd'} and length $xmls->{'xosd'} > 0) {
	$enablexosd = $xmls->{'xosd'};
}


# get command line options
my $p = new Getopt::Long::Parser();
$p->configure('bundling');
$p->getoptions(
	'd|device=s'	=> \$device,
	'c|channel=s'	=> \$channel,
	'f|frequency=f'	=> \$frequency,
	't|tune'	=> \$tune,
	'V|volume=i'	=> \$volume,
	'l|lirc'	=> \$lirc,
	'i|lircdev=s'	=> \$lircdev,
	'r|rcfile=s'	=> \$rcfile,
	'x|xosd'	=> \$enablexosd,
	'e|debug=i'	=> \$debug,
	'v|version'	=> \$version,
	'h|help'	=> \$help
);


# define all channels
my @ch_array = @{$xml->{'channel-list'}->{'channel'}};


# set default values
$device ||= '/dev/video0';
$channel ||= $ch_array[0]->{'name'};
$lirc ||= 0;
$lircdev ||= '/dev/lircd';
$rcfile ||= $ENV{'HOME'}.'/.lircrc';
$debug ||= 0;
$volume ||= 58800;
$enablexosd ||= 0;


# show help
if ($help) {
	&help();
	exit 0;
}

# show version
if ($version) {
	print 'iradio version '.$VERSION."\n";
	exit 0;
}


# set volume
&message('I', 'Setting volume: '.$volume) if ($debug);
system 'v4l2-ctl --set-ctrl=volume='.$volume.' --device '.$device.' 2>&1 1>/dev/null';


my $freq;

# select frequency
if (defined $frequency) {
	$freq = $frequency;
} elsif (not defined $channel or length $channel == 0) {
	&message('E', 'Undefined channel!');
	exit 1;
} else {
	$freq = &getFreq($channel);
	if ($freq == -1) {
		&message('E', 'Wrong channel!');
		exit 1;
	}
}


# tune or run player
if (defined $tune) {
	&tuneFreq($freq);
} else {
	# tune to a specific frequence - fist time have to be used ivtv-radio; later can be used v4l2-ctl
	&message('I', 'First frequency tuning: freq='.$freq) if ($debug);

	my $cmd = 'ivtv-radio -f '.$freq;

	unless ($debug) {
		$cmd .= ' -c "aplay -f dat < %s 2>&1 1>/dev/null" 2>&1 1>/dev/null';
	}
	if ($lirc) {
		$cmd .= ' &';
	}

	&message(undef, '    * cmd='.$cmd) if ($debug);
	system $cmd;
}


# LIRC support
if ($lirc) {
	my $d = 0;
	if ($debug > 2) {
		$d = 1;
	}
	my $lirc = new Lirc::Client('iradio',
		{
			'rcfile' => $rcfile,
			'dev'    => $lircdev,
			'debug'  => $d,
			'mode'   => 'iradio',
			'fake'   => 0
		}
	);

	my $xosd;
	if ($enablexosd == 1) {
		# try to load xosd module
		eval 'use X::Osd';

		# if module loaded, define xosd
		if (length $@ == 0) {
			&message('I', 'There is XOSD support') if ($debug);

			$xosd = new X::Osd(1);
			$xosd->set_font($xmls->{'xosd-font'} || '-*-arial-*-r-*-*-30-*-*-*-*-*-*-*');
			$xosd->set_colour($xmls->{'xosd-color'} || 'Green');
			$xosd->set_timeout($xmls->{'xosd-timeout'} || 3);
			$xosd->set_pos($xmls->{'xosd-position'} || 1); # 0=top, 1=bottom, 2=middle
			$xosd->set_align($xmls->{'xosd-align'} || 0); # 0=left, 1=center, 2=right
			$xosd->set_horizontal_offset($xmls->{'xosd-horizontal-offset'} || 30);
			$xosd->set_vertical_offset($xmls->{'xosd-vertical-offset'} || 50);
			$xosd->set_shadow_colour($xmls->{'xosd-shadow-color'} || 'DimGray');
			$xosd->set_shadow_offset($xmls->{'xosd-shadow-offset'} || 2);

			$xosd->string(0, 'CH'.(&getChannelIndex($channel)+1).' - '.$channel);
		}
	}

	# read code
	my $code;
	do {
		$code = $lirc->next_code();
		&processCode($code, $xosd);
	} while(defined $code);
}


exit 0;


###################
### SUBPROGRAMS ###
###################

sub getFreq {
	my $chan = shift;

	my $ret = -1;

	&message('I', 'Searching freq for channel '.$chan) if ($debug);

	foreach my $ch (@ch_array) {
		if (lc $ch->{'name'} eq lc $chan) {
			$ret = $ch->{'freq'};
			&message(undef, '    * freq='.$ret) if ($debug);
			last;
		}
	}

	if ($ret == -1 and $debug) {
		&message(undef, '    * no freq found!') if ($debug);
	}

	return $ret;
}


sub getChannelIndex {
	my $ch = shift;

	my $ret = -1;

	for(my $i=0; $i<scalar @ch_array; $i++) {
		if (lc $ch_array[$i]->{'name'} eq lc $ch) {
			$ret = $i;
			last;
		}
	}

	return $ret;
}


sub processCode {
	my $c = shift;
	my $xosd = shift;

	&message('I', 'Resolving code '.$c) if ($debug);

	if ($c eq 'QUIT') {
		&message('I', 'Quit application') if ($debug);
		system 'killall -9 aplay 2>/dev/null';
		exit 0;
	} elsif ($c =~ /^CH(\d+)$/) {
		my $cn = $1;
		&message('I', 'Tune CH'.$cn) if ($debug);
		$channel = $ch_array[$cn-1]->{'name'};
		&tuneFreq($ch_array[$cn-1]->{'freq'});

		if (defined $xosd) {
			$xosd->string(0, 'CH'.$cn.' - '.$channel);
		}
	} elsif ($c eq 'CH+') {
		&message('I', 'Tune next channel') if ($debug);
		&message(undef, '    * cur='.$channel) if ($debug);

		my $len = scalar @ch_array;
		my $index = &getChannelIndex($channel);

		if ($index == $len-1) {
			$channel = $ch_array[0]->{'name'};
		} else {
			$channel = $ch_array[$index+1]->{'name'};
		}

		&message(undef, '    * new='.$channel) if ($debug);
		&tuneFreq(&getFreq($channel));

		if (defined $xosd) {
			$xosd->string(0, 'CH'.(&getChannelIndex($channel)+1).' - '.$channel);
		}
	} elsif ($c eq 'CH-') {
		&message('I', 'Tune previous channel') if ($debug);
		&message(undef, '    * cur='.$channel) if ($debug);

		my $len = scalar @ch_array;
		my $index = &getChannelIndex($channel);

		if ($index == 0) {
			$channel = $ch_array[$len-1]->{'name'};
		} else {
			$channel = $ch_array[$index-1]->{'name'};
		}

		&message(undef, '    * new='.$channel) if ($debug);
		&tuneFreq(&getFreq($channel));

		if (defined $xosd) {
			$xosd->string(0, 'CH'.(&getChannelIndex($channel)+1).' - '.$channel);
		}
	} elsif ($c eq 'SHOW_CHANNEL') {
		&message('I', 'Show current channel name') if ($debug);

		if (defined $xosd) {
			$xosd->string(0, 'CH'.(&getChannelIndex($channel)+1).' - '.$channel);
		}
	}
}


sub tuneFreq {
	my $f = shift;

	&message('I', 'Tune frequency:') if ($debug);

	# tune frequency
	&message(undef, '    * set frequency: '.$f) if ($debug);
	system 'v4l2-ctl -f '.$f.' -d '.$device.' 2>&1 1>/dev/null && sleep 0.5';
}


sub message {
	my $flag = shift;
	my $text = shift;

	if (defined $flag) {
		$flag .= ': ';
	} else {
		$flag = '';
	}

	print STDERR $flag.$text."\n";
}


sub help {
	print <<END;
Using: iradio [OPTIONS]

OPTIONS:
  -d, --device=STR	video device (default /dev/video0)
  -c, --channel=STR     channel name
  -f, --frequency=FREQ	frequency in MHz
  -t, --tune            tune only
  -V, --volume=INT      volume level ([0-65535], default 58800)
  -l, --lirc            enable lirc support
  -i, --lircdev=FILE    lircd file (default /dev/lircd)
  -r, --rcfiles=FILE    lircrc file (default ~/.lircrc)
  -x, --xosd            enable xosd support ([0|1], default 0)
  -e, --debug=INT       print debug messages ([0-3], default 0)
  -v, --version         print version number
  -h, --help            print this help

Examples:
  iradio -s wrs
  iradio -s onefm -t

Report bugs to <jiri(dot)tyr(at)e-learning(dot)vslib(dot)cz>.
END
}
