#
#	$Id: BBCNews.pm,v 1.48 2004/12/30 02:48:21 kevin Exp $
#
#	Author: Kevin Walsh <kevin@cursor.biz>
#
#	This is a complete rewrite of a similar module by Gordon Johnston
#	<gordonj@newswall.org.uk>.  Gordon's BbcNews.pm version 0.2.1 was
#	dated October 2002 (http://newswall.org.uk/~slimp3/news-ticker.html).
#
#	Copyright (c) 2003-2004 Cursor Software Limited.
#	Portions copyright (c) 2002 Gordon Johnston.
#	All rights reserved.
#
#	----------------------------------------------------------------------
#
#	BBC News plugin plug-in for the SLIMP3 server.
#
#	Requires the "news_mirror.pl" to update the news text file on
#	a regular basis unless you decide to configure live updates.
#	Note that live updates could stop the music on all players while the
#	update is in progress - depending upon your Internet connection
#	speed, of course.
#
#	The news_mirror.pl script should be configured to create/update the
#	bbcnews.txt file in the SLIMP3 server user's home directory, which
#	is the same directory as the .slimp3.pref file.  You don't need to
#	do this if you configure this module to perform live lookups.
#
#	Scroll through the news categories using the up/down buttons.
#	The 'add' (rec) key will re-read the news from the data source.
#	The news will be re-read every now and again so the 'add' key is
#	largely unnecessary.
#
#	----------------------------------------------------------------------
#
#	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., 59 Temple Place, Suite 330, Boston, MA
#	02111-1307 USA
#
package Plugins::BBCNews;
use File::Spec::Functions qw(catfile);
use Slim::Utils::Strings qw(string);
use strict;

#
#	the BBC News Ticker comes in 8 news categories along with the
#	'Last update time' pseudo-category
#
my @news_description = (
    'Last update time',	#0
    'World News',	#1
    'UK News',		#2
    'Sports News',	#3
    'Business News',	#4
    'Sci-Tech News',	#5
    'Weather',		#6
    'Travel News',	#7
    'Finance'		#8
);

#
#	specify the catagories you want and their display order
#	(see the @news_description array, below)
#
my @display_order = (0,2,1,5,4,8,3,6);

#
#	specify the file/inet data sources here
#
my @locations = (
    'http://tickers.bbc.co.uk/tickerdata/story2.dat',
    catfile(Slim::Utils::Prefs::preferencesPath(),'bbcnews.txt'),
);

#
#	end of configurable variables
#

use vars qw($VERSION);
$VERSION = substr(q$Revision: 1.48 $,10);

my %prefs = (
    'location' => {
	order => 0,
	default => 0,
	translate => 1,
	widget => {
	    options => {
		0 => 'LIVEDATA',
		1 => 'TEXTFILE',
	    },
	},
    },
    'checktime' => {
	order => 1,
	default => 60,
	widget => {
	    validate => \&Slim::Web::Setup::validateInt,
	    validateArgs => [1,1440,1,1440],
	},
    },
    'use_custom_logo' => {
	order => 2,
	default => 1,
	translate => 1,
	widget => {
	    options => {
		'1' => 'YES',
		'0' => 'NO',
	    },
	},
    },
    'use_custom_degrees' => {
	order => 3,
	default => 1,
	translate => 1,
	widget => {
	    options => {
		'1' => 'YES',
		'0' => 'NO',
	    },
	},
    },
);

my $last_check;
my $last_update;
my @news_list = ();
my %context;

my %functions = (
    'left' => sub {
	Slim::Buttons::Common::popModeRight(shift);
    },
    'right' => sub {
	Slim::Display::Animation::bumpRight(shift);
    },
    'up' => sub {
	my $client = shift;

	$context{$client} = Slim::Buttons::Common::scroll(
	    $client,
	    -1,
	    $#display_order + 1,
	    $context{$client} || 0,
	);
	$client->update();
    },
    'down' => sub {
	my $client = shift;

	$context{$client} = Slim::Buttons::Common::scroll(
	    $client,
	    1,
	    $#display_order + 1,
	    $context{$client} || 0,
	);
	$client->update();
    },
    'add' => sub {
	#
	#	refresh the news from the text file
	#
	#	note that the [add] key is labeled as [rec] on the Sony remotes
	#
	my $client = shift;

	Slim::Display::Animation::showBriefly(
	    $client,
	    get_name() . ' ' . string('PLUGIN_BBCNEWS_UPDATING')
	);
	$last_update = $last_check = undef;
    },
);

#
#	lines()
#	-------
#	Create and return the two-line display.  Also "fake" the IR time so
#	that the "screen saver" doesn't take over.
#
sub lines
{
    my $client = shift;
    my @lines;

    load_preferences();

    #
    #	"Fake" the last remote control keypress time so the "screen saver"
    #	doesn't take over
    #
    Slim::Hardware::IR::setLastIRTime(
	$client,
	Time::HiRes::time() + (Slim::Utils::Prefs::get("screensavertimeout") * 5),
    );

    #
    #	re-read the news every now and again
    #
    if (!$last_check || (time() - $last_check) > ($prefs{checktime}->{current} * 60)) {
	$last_check = time();
	my $mtime = (stat($locations[$prefs{location}->{current}]))[9] || 0;

	if ($locations[$prefs{location}->{current}] =~ m|^http://|i) {
	    #
	    #	update the news from the internet on a regular basis
	    #
	    update_news($client);
	}
	else {
	    #
	    #	update the news from the text file only if the file has been
	    #	modified since we last read it
	    #
	    if (!$last_update || $mtime > $last_update) {
		$last_update = $mtime;
		update_news($client) if $mtime;
	    }
	}
    }

    $context{$client} ||= 0;

    $lines[0] = (
	get_name() .
	' (' .
	(($context{$client} || 0) + 1) .
	' ' .
	string('OF') .
	' ' .
	($#display_order + 1) .
	') ' . 
	$news_description[$display_order[$context{$client}]]
    );

    if (exists($news_list[$display_order[$context{$client}]])) {
	$lines[1] = $news_list[$display_order[$context{$client}]];
    }
    else {
	$lines[1] = string('PLUGIN_BBCNEWS_NO_NEWS');
    }
    $lines[2] = $lines[3] = undef;
    @lines;
}	

#
#	update_news()
#	-------------
#	Overwrites the @news_list with data collected from either the
#	remote HTTP URI or a text file, depending upon what's configured
#	into the $prefs{location}->{current} preference.
#
sub update_news
{
    my $client = shift;
    my $divider = Slim::Hardware::VFD::symbol('rightarrow') x 2;
    my $cur_story = 0;
    my $cur_desc = '';
    my @text = ();

    if ($locations[$prefs{location}->{current}] =~ m|^http://|i) {
	#
	#	the location is a HTTP URI so open a socket to the remote
	#	webpage and collect the data
	#
	my $sock = Slim::Web::RemoteStream::openRemoteStream($locations[$prefs{location}->{current}],$client) or return undef;
	push(@text,$_) while (<$sock>);
	$sock->close();
    }
    else {
	#
	#	the location is not a HTTP URI so strip any leading
	#	protocol specification, such as "file://" from the location
	#	and treat the resulting string as a path to a text file
	#
	$locations[$prefs{location}->{current}] =~ s|^\w+://||i;
	open(FILE,$locations[$prefs{location}->{current}]) or return undef;
	push(@text,$_) while (<FILE>);
	close FILE;
    }

    @news_list = ();

    #
    #	parse the data gathered in one of the above collection operations
    #	to create the @news_list array
    #
    foreach (@text) {
	chomp;

	if (/^STORY (\d+)/) {
	    $cur_story = $1;
	    $cur_desc = uc($news_description[$cur_story]);
	}
	elsif (/^HEADLINE (Last update at .+)/i) {
	    $news_list[0] .= $1;
	}
	elsif (/^HEADLINE $cur_desc(\s+\d+\s)?/) {
	    undef;
	}
	elsif (/^HEADLINE (.+)/) {
	    $news_list[$cur_story] .= "$1 $divider ";
	}
    }

    #
    #	make some minor modifications to the "weather" string
    #	(use degrees-C and degrees-F characters as appropriate)
    #
    if ($prefs{use_custom_degrees}->{current}) {
	my $deg = Slim::Hardware::VFD::symbol('degrees');
	$news_list[6] =~ s/(\d)([CF])/$1$deg$2/ig;
    }
    else {
	sub degrees { uc(shift) eq 'C' ? chr(0x1A) : chr(0x1B) };
	$news_list[6] =~ s/(\d)([CF])/$1 . degrees($2)/eig;
    }

    #
    #	make some minor modifications to the "finance" string
    #	(put the +- change in parenthesis)
    #
    $news_list[8] =~ s/\s([+\-][\d.,]+)\s/ ($1) /g;

    #
    #	go through the @news_list removing unnecessary whitespace and
    #	appending a long gap to the end of the news item
    #
    foreach (@news_list) {
	s/\s\s+/ /g;
	s/\s+$divider\s+$/                              /;
    }
    undef;
}

#
#	load_custom_chars()
#	-------------------
#	Creates custom "B" and "C" characters which will be used to
#	create the BBC logo and the custom "degrees" symbol.
#
sub load_custom_chars
{
    if ($prefs{use_custom_logo}->{current}) {
	Slim::Hardware::VFD::setCustomChar('bbc-b',(
	    0b11111,
	    0b10011,
	    0b10101,
	    0b10011,
	    0b10101,
	    0b10011,
	    0b11111,
	    0,
	));
	Slim::Hardware::VFD::setCustomChar('bbc-c',(
	    0b11111,
	    0b11001,
	    0b10111,
	    0b10111,
	    0b10111,
	    0b11001,
	    0b11111,
	    0,
	));
    }
    
    if ($prefs{use_custom_degrees}->{current}) {
	Slim::Hardware::VFD::setCustomChar('degrees',(
	    0b01110,
	    0b10001,
	    0b10001,
	    0b01110,
	    0b00000,
	    0b00000,
	    0b00000,
	    0,
	));
    }
    undef;
}

#
#	get_name()
#	----------
#	Returns the localised PLUGIN_BBCNEWS_MODULE_NAME string and replaces
#	"BBC" with the BBC logo if $prefs{use_custom_logo}->{current} is true.
#
sub get_name
{
    my $name = string('PLUGIN_BBCNEWS_MODULE_NAME');

    if ($prefs{use_custom_logo}->{current}) {
	my $bbc = (Slim::Hardware::VFD::symbol('bbc-b') x 2) . Slim::Hardware::VFD::symbol('bbc-c');
	$name =~ s/BBC/$bbc/ig;
    }
    $name;
}

#
#	load_preferences()
#	------------------
#	Load the current preferences or set defaults
#
sub load_preferences
{
    while (my ($key,$val) = each %prefs) {
	my $name = __PACKAGE__;
	$name =~ s/^.*:://;
	$key = 'plugin_' . lc($name) . "_$key";

	if (Slim::Utils::Prefs::isDefined($key)) {
	    $val->{current} = Slim::Utils::Prefs::get($key);
	}
	else {
	    $val->{current} = $val->{default};
	    Slim::Utils::Prefs::set($key,$val->{default});
	}
    }
}

sub setupGroup
{
    my $name = __PACKAGE__;
    $name =~ s/^.*:://;
    $name = 'plugin_' . lc($name);

    load_preferences();

    my @order;
    push(@order,"${name}_$_") for (sort {$prefs{$a}->{order} <=> $prefs{$b}->{order}} keys %prefs);

    my $version = ' (' . string("${name}_VERSION") . ')';
    $version =~ s/%s/$VERSION/;
    $version =~ s/\s+\)/)/;

    my %group = (
	PrefOrder => \@order,
	GroupHead => string("${name}_MODULE_NAME") . $version,
	GroupDesc => string("${name}_MODULE_DESCRIPTION"),
	GroupLine => 1,
	GroupSub => 1,
	Suppress_PrefSub => 1,
	Suppress_PrefLine => 1,
    );

    my %widgets;
    while (my ($key,$val) = each %prefs) {
	$widgets{"${name}_$key"} = $val->{widget};

	if ($val->{translate} && exists($val->{widget}->{options})) {
	    $_ = string("${name}_$_") for (values %{$val->{widget}->{options}});
	    delete $val->{translate};
	}
    }

    return (\%group,\%widgets);
}

sub setMode
{
    my $client = shift;

    load_preferences();
    load_custom_chars();

    $client->lines(\&lines);
    $client->update();
}

sub getFunctions
{
    \%functions;
}

sub getDisplayName
{
    my $name = 'PLUGIN_BBCNEWS_MODULE_NAME';

    $::VERSION =~ /^(\d+)/;
    return ($1 >= 6) ? $name : string($name);
}

sub strings
{
    local $/ = undef;
    <DATA>;
}

1;

__DATA__

PLUGIN_BBCNEWS_MODULE_NAME
	EN	BBC News

PLUGIN_BBCNEWS_MODULE_DESCRIPTION
	EN	Regurlarly updated news feed from the BBC

PLUGIN_BBCNEWS_VERSION
	EN	Version %s

PLUGIN_BBCNEWS_UPDATING
	EN	updating...

PLUGIN_BBCNEWS_NO_NEWS
	EN	No news at this time.  Press ADD to retry

PLUGIN_BBCNEWS_YES
	EN	Yes

PLUGIN_BBCNEWS_NO
	EN	No

PLUGIN_BBCNEWS_LIVEDATA
	EN	Read data directly from tickers.bbc.co.uk

PLUGIN_BBCNEWS_TEXTFILE
	EN	Read data from a text file (bbcnews.txt)

SETUP_PLUGIN_BBCNEWS_CHECKTIME
	EN	Re-read the news every now and again (minutes)

SETUP_PLUGIN_BBCNEWS_USE_CUSTOM_LOGO
	EN	Use custom characters to display the BBC logo

SETUP_PLUGIN_BBCNEWS_USE_CUSTOM_DEGREES
	EN	Use a custom character to display the degrees symbol

SETUP_PLUGIN_BBCNEWS_LOCATION
	EN	News feed location


